summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/clang/Basic/DiagnosticSemaKinds.td4
-rw-r--r--include/clang/Sema/ExternalSemaSource.h6
-rw-r--r--include/clang/Sema/MultiplexExternalSemaSource.h4
-rw-r--r--include/clang/Sema/Sema.h8
-rw-r--r--include/clang/Serialization/ASTBitCodes.h6
-rw-r--r--include/clang/Serialization/ASTReader.h7
-rw-r--r--lib/Sema/MultiplexExternalSemaSource.cpp4
-rw-r--r--lib/Sema/Sema.cpp54
-rw-r--r--lib/Sema/SemaDecl.cpp32
-rw-r--r--lib/Sema/SemaExpr.cpp12
-rw-r--r--lib/Serialization/ASTReader.cpp21
-rw-r--r--lib/Serialization/ASTWriter.cpp18
-rw-r--r--test/Analysis/engine/replay-without-inlining.c2
-rw-r--r--test/SemaCXX/undefined-inline.cpp57
14 files changed, 169 insertions, 66 deletions
diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td
index fe47c3a9c8..b1f4fb62dc 100644
--- a/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3284,10 +3284,12 @@ def note_deleted_assign_field : Note<
"%select{copy|move}0 assignment operator of %1 is implicitly deleted "
"because field %2 is of %select{reference|const-qualified}4 type %3">;
-// This should eventually be an error.
+// These should be errors.
def warn_undefined_internal : Warning<
"%select{function|variable}0 %q1 has internal linkage but is not defined">,
InGroup<DiagGroup<"undefined-internal">>;
+def warn_undefined_inline : Warning<"inline function %q0 is not defined">,
+ InGroup<DiagGroup<"undefined-inline">>;
def note_used_here : Note<"used here">;
def warn_internal_in_extern_inline : ExtWarn<
diff --git a/include/clang/Sema/ExternalSemaSource.h b/include/clang/Sema/ExternalSemaSource.h
index 6693dc8683..cbce757ea7 100644
--- a/include/clang/Sema/ExternalSemaSource.h
+++ b/include/clang/Sema/ExternalSemaSource.h
@@ -67,9 +67,11 @@ public:
virtual void ReadKnownNamespaces(
SmallVectorImpl<NamespaceDecl *> &Namespaces);
- virtual void ReadUndefinedInternals(
+ /// \brief Load the set of used but not defined functions or variables with
+ /// internal linkage, or used but not defined internal functions.
+ virtual void ReadUndefinedButUsed(
llvm::DenseMap<NamedDecl*, SourceLocation> &Undefined);
-
+
/// \brief Do last resort, unqualified lookup on a LookupResult that
/// Sema cannot find.
///
diff --git a/include/clang/Sema/MultiplexExternalSemaSource.h b/include/clang/Sema/MultiplexExternalSemaSource.h
index 494ba656bb..32b7276c83 100644
--- a/include/clang/Sema/MultiplexExternalSemaSource.h
+++ b/include/clang/Sema/MultiplexExternalSemaSource.h
@@ -253,7 +253,9 @@ public:
/// which will be used during typo correction.
virtual void ReadKnownNamespaces(SmallVectorImpl<NamespaceDecl*> &Namespaces);
- virtual void ReadUndefinedInternals(
+ /// \brief Load the set of used but not defined functions or variables with
+ /// internal linkage, or used but not defined inline functions.
+ virtual void ReadUndefinedButUsed(
llvm::DenseMap<NamedDecl*, SourceLocation> &Undefined);
/// \brief Do last resort, unqualified lookup on a LookupResult that
diff --git a/include/clang/Sema/Sema.h b/include/clang/Sema/Sema.h
index d0d3ff937d..da80f0c8df 100644
--- a/include/clang/Sema/Sema.h
+++ b/include/clang/Sema/Sema.h
@@ -738,12 +738,12 @@ public:
// argument locations.
llvm::DenseMap<ParmVarDecl *, SourceLocation> UnparsedDefaultArgLocs;
- /// UndefinedInternals - all the used, undefined objects with
- /// internal linkage in this translation unit.
- llvm::DenseMap<NamedDecl *, SourceLocation> UndefinedInternals;
+ /// UndefinedInternals - all the used, undefined objects which require a
+ /// definition in this translation unit.
+ llvm::DenseMap<NamedDecl *, SourceLocation> UndefinedButUsed;
/// Obtain a sorted list of functions that are undefined but ODR-used.
- void getUndefinedInternals(
+ void getUndefinedButUsed(
llvm::SmallVectorImpl<std::pair<NamedDecl *, SourceLocation> > &Undefined);
typedef std::pair<ObjCMethodList, ObjCMethodList> GlobalMethods;
diff --git a/include/clang/Serialization/ASTBitCodes.h b/include/clang/Serialization/ASTBitCodes.h
index 166434201c..6f76367342 100644
--- a/include/clang/Serialization/ASTBitCodes.h
+++ b/include/clang/Serialization/ASTBitCodes.h
@@ -526,9 +526,9 @@ namespace clang {
/// being deserialized.
MACRO_UPDATES = 48,
- /// \brief Record code for undefined but used internal functions and
- /// variables.
- UNDEFINED_INTERNALS = 49
+ /// \brief Record code for undefined but used functions and variables that
+ /// need a definition in this TU.
+ UNDEFINED_BUT_USED = 49
};
/// \brief Record types used within a source manager block.
diff --git a/include/clang/Serialization/ASTReader.h b/include/clang/Serialization/ASTReader.h
index aa3633b4b3..d3f3466c33 100644
--- a/include/clang/Serialization/ASTReader.h
+++ b/include/clang/Serialization/ASTReader.h
@@ -677,8 +677,9 @@ private:
/// \brief A list of the namespaces we've seen.
SmallVector<uint64_t, 4> KnownNamespaces;
- /// \brief A list of undefined decls with internal linkage.
- SmallVector<uint64_t, 8> UndefinedInternals;
+ /// \brief A list of undefined decls with internal linkage followed by the
+ /// SourceLocation of a matching ODR-use.
+ SmallVector<uint64_t, 8> UndefinedButUsed;
/// \brief A list of modules that were imported by precompiled headers or
/// any other non-module AST file.
@@ -1520,7 +1521,7 @@ public:
virtual void ReadKnownNamespaces(
SmallVectorImpl<NamespaceDecl *> &Namespaces);
- virtual void ReadUndefinedInternals(
+ virtual void ReadUndefinedButUsed(
llvm::DenseMap<NamedDecl *, SourceLocation> &Undefined);
virtual void ReadTentativeDefinitions(
diff --git a/lib/Sema/MultiplexExternalSemaSource.cpp b/lib/Sema/MultiplexExternalSemaSource.cpp
index 609324d32d..ecce6d8e7b 100644
--- a/lib/Sema/MultiplexExternalSemaSource.cpp
+++ b/lib/Sema/MultiplexExternalSemaSource.cpp
@@ -201,10 +201,10 @@ void MultiplexExternalSemaSource::ReadKnownNamespaces(
Sources[i]->ReadKnownNamespaces(Namespaces);
}
-void MultiplexExternalSemaSource::ReadUndefinedInternals(
+void MultiplexExternalSemaSource::ReadUndefinedButUsed(
llvm::DenseMap<NamedDecl*, SourceLocation> &Undefined){
for(size_t i = 0; i < Sources.size(); ++i)
- Sources[i]->ReadUndefinedInternals(Undefined);
+ Sources[i]->ReadUndefinedButUsed(Undefined);
}
bool MultiplexExternalSemaSource::LookupUnqualified(LookupResult &R, Scope *S){
diff --git a/lib/Sema/Sema.cpp b/lib/Sema/Sema.cpp
index 76ea996ec5..5e5011b7f6 100644
--- a/lib/Sema/Sema.cpp
+++ b/lib/Sema/Sema.cpp
@@ -366,12 +366,16 @@ static bool ShouldRemoveFromUnused(Sema *SemaRef, const DeclaratorDecl *D) {
}
namespace {
- struct SortUndefinedInternal {
+ struct SortUndefinedButUsed {
const SourceManager &SM;
- explicit SortUndefinedInternal(SourceManager &SM) : SM(SM) {}
+ explicit SortUndefinedButUsed(SourceManager &SM) : SM(SM) {}
bool operator()(const std::pair<NamedDecl *, SourceLocation> &l,
const std::pair<NamedDecl *, SourceLocation> &r) const {
+ if (l.second.isValid() && !r.second.isValid())
+ return true;
+ if (!l.second.isValid() && r.second.isValid())
+ return false;
if (l.second != r.second)
return SM.isBeforeInTranslationUnit(l.second, r.second);
return SM.isBeforeInTranslationUnit(l.first->getLocation(),
@@ -381,55 +385,65 @@ namespace {
}
/// Obtains a sorted list of functions that are undefined but ODR-used.
-void Sema::getUndefinedInternals(
+void Sema::getUndefinedButUsed(
SmallVectorImpl<std::pair<NamedDecl *, SourceLocation> > &Undefined) {
for (llvm::DenseMap<NamedDecl *, SourceLocation>::iterator
- I = UndefinedInternals.begin(), E = UndefinedInternals.end();
+ I = UndefinedButUsed.begin(), E = UndefinedButUsed.end();
I != E; ++I) {
NamedDecl *ND = I->first;
// Ignore attributes that have become invalid.
if (ND->isInvalidDecl()) continue;
- // If we found out that the decl is external, don't warn.
- if (ND->getLinkage() == ExternalLinkage) continue;
-
// __attribute__((weakref)) is basically a definition.
if (ND->hasAttr<WeakRefAttr>()) continue;
if (FunctionDecl *FD = dyn_cast<FunctionDecl>(ND)) {
if (FD->isDefined())
continue;
+ if (FD->getLinkage() == ExternalLinkage &&
+ !FD->getMostRecentDecl()->isInlined())
+ continue;
} else {
if (cast<VarDecl>(ND)->hasDefinition() != VarDecl::DeclarationOnly)
continue;
+ if (ND->getLinkage() == ExternalLinkage)
+ continue;
}
Undefined.push_back(std::make_pair(ND, I->second));
}
- // Sort (in order of use site) so that we're not (as) dependent on
- // the iteration order through an llvm::DenseMap.
+ // Sort (in order of use site) so that we're not dependent on the iteration
+ // order through an llvm::DenseMap.
std::sort(Undefined.begin(), Undefined.end(),
- SortUndefinedInternal(Context.getSourceManager()));
+ SortUndefinedButUsed(Context.getSourceManager()));
}
-/// checkUndefinedInternals - Check for undefined objects with internal linkage.
-static void checkUndefinedInternals(Sema &S) {
- if (S.UndefinedInternals.empty()) return;
+/// checkUndefinedButUsed - Check for undefined objects with internal linkage
+/// or that are inline.
+static void checkUndefinedButUsed(Sema &S) {
+ if (S.UndefinedButUsed.empty()) return;
// Collect all the still-undefined entities with internal linkage.
SmallVector<std::pair<NamedDecl *, SourceLocation>, 16> Undefined;
- S.getUndefinedInternals(Undefined);
+ S.getUndefinedButUsed(Undefined);
if (Undefined.empty()) return;
for (SmallVectorImpl<std::pair<NamedDecl *, SourceLocation> >::iterator
I = Undefined.begin(), E = Undefined.end(); I != E; ++I) {
NamedDecl *ND = I->first;
- S.Diag(ND->getLocation(), diag::warn_undefined_internal)
- << isa<VarDecl>(ND) << ND;
- S.Diag(I->second, diag::note_used_here);
+ if (ND->getLinkage() != ExternalLinkage) {
+ S.Diag(ND->getLocation(), diag::warn_undefined_internal)
+ << isa<VarDecl>(ND) << ND;
+ } else {
+ assert(cast<FunctionDecl>(ND)->getMostRecentDecl()->isInlined() &&
+ "used object requires definition but isn't inline or internal?");
+ S.Diag(ND->getLocation(), diag::warn_undefined_inline) << ND;
+ }
+ if (I->second.isValid())
+ S.Diag(I->second, diag::note_used_here);
}
}
@@ -743,8 +757,8 @@ void Sema::ActOnEndOfTranslationUnit() {
}
if (ExternalSource)
- ExternalSource->ReadUndefinedInternals(UndefinedInternals);
- checkUndefinedInternals(*this);
+ ExternalSource->ReadUndefinedButUsed(UndefinedButUsed);
+ checkUndefinedButUsed(*this);
}
if (Diags.getDiagnosticLevel(diag::warn_unused_private_field,
@@ -1088,7 +1102,7 @@ void ExternalSemaSource::ReadKnownNamespaces(
SmallVectorImpl<NamespaceDecl *> &Namespaces) {
}
-void ExternalSemaSource::ReadUndefinedInternals(
+void ExternalSemaSource::ReadUndefinedButUsed(
llvm::DenseMap<NamedDecl *, SourceLocation> &Undefined) {
}
diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp
index 4404c6f9a0..0d549fcc18 100644
--- a/lib/Sema/SemaDecl.cpp
+++ b/lib/Sema/SemaDecl.cpp
@@ -2215,6 +2215,23 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, Decl *OldD, Scope *S) {
New->setType(QualType(NewType, 0));
NewQType = Context.getCanonicalType(New->getType());
}
+
+ // If this redeclaration makes the function inline, we may need to add it to
+ // UndefinedButUsed.
+ if (!Old->isInlined() && New->isInlined() &&
+ !New->hasAttr<GNUInlineAttr>() &&
+ (getLangOpts().CPlusPlus || !getLangOpts().GNUInline) &&
+ Old->isUsed(false) &&
+ !Old->isDefined() && !New->isThisDeclarationADefinition())
+ UndefinedButUsed.insert(std::make_pair(Old->getCanonicalDecl(),
+ SourceLocation()));
+
+ // If this redeclaration makes it newly gnu_inline, we don't want to warn
+ // about it.
+ if (New->hasAttr<GNUInlineAttr>() &&
+ Old->isInlined() && !Old->hasAttr<GNUInlineAttr>()) {
+ UndefinedButUsed.erase(Old->getCanonicalDecl());
+ }
if (getLangOpts().CPlusPlus) {
// (C++98 13.1p2):
@@ -8431,12 +8448,17 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body,
if (FD) {
FD->setBody(Body);
- // The only way to be included in UndefinedInternals is if there is an
- // ODR-use before the definition. Avoid the expensive map lookup if this
+ // The only way to be included in UndefinedButUsed is if there is an
+ // ODR use before the definition. Avoid the expensive map lookup if this
// is the first declaration.
- if (FD->getPreviousDecl() != 0 && FD->getPreviousDecl()->isUsed() &&
- FD->getLinkage() != ExternalLinkage)
- UndefinedInternals.erase(FD);
+ if (FD->getPreviousDecl() != 0 && FD->getPreviousDecl()->isUsed()) {
+ if (FD->getLinkage() != ExternalLinkage)
+ UndefinedButUsed.erase(FD);
+ else if (FD->isInlined() &&
+ (LangOpts.CPlusPlus || !LangOpts.GNUInline) &&
+ (!FD->getPreviousDecl()->hasAttr<GNUInlineAttr>()))
+ UndefinedButUsed.erase(FD);
+ }
// If the function implicitly returns zero (like 'main') or is naked,
// don't complain about missing return statements.
diff --git a/lib/Sema/SemaExpr.cpp b/lib/Sema/SemaExpr.cpp
index 87d75f630f..5dd75ced97 100644
--- a/lib/Sema/SemaExpr.cpp
+++ b/lib/Sema/SemaExpr.cpp
@@ -10506,9 +10506,13 @@ void Sema::MarkFunctionReferenced(SourceLocation Loc, FunctionDecl *Func) {
}
// Keep track of used but undefined functions.
- if (!Func->isDefined() && Func->getLinkage() != ExternalLinkage) {
- SourceLocation &old = UndefinedInternals[Func->getCanonicalDecl()];
- if (old.isInvalid()) old = Loc;
+ if (!Func->isDefined()) {
+ if (Func->getLinkage() != ExternalLinkage)
+ UndefinedButUsed.insert(std::make_pair(Func->getCanonicalDecl(), Loc));
+ else if (Func->getMostRecentDecl()->isInlined() &&
+ (LangOpts.CPlusPlus || !LangOpts.GNUInline) &&
+ !Func->getMostRecentDecl()->hasAttr<GNUInlineAttr>())
+ UndefinedButUsed.insert(std::make_pair(Func->getCanonicalDecl(), Loc));
}
// Normally the must current decl is marked used while processing the use and
@@ -11021,7 +11025,7 @@ static void MarkVarDeclODRUsed(Sema &SemaRef, VarDecl *Var,
if (Var->hasDefinition(SemaRef.Context) == VarDecl::DeclarationOnly &&
Var->getLinkage() != ExternalLinkage &&
!(Var->isStaticDataMember() && Var->hasInit())) {
- SourceLocation &old = SemaRef.UndefinedInternals[Var->getCanonicalDecl()];
+ SourceLocation &old = SemaRef.UndefinedButUsed[Var->getCanonicalDecl()];
if (old.isInvalid()) old = Loc;
}
diff --git a/lib/Serialization/ASTReader.cpp b/lib/Serialization/ASTReader.cpp
index d38b5308de..1524da9bd6 100644
--- a/lib/Serialization/ASTReader.cpp
+++ b/lib/Serialization/ASTReader.cpp
@@ -2462,19 +2462,19 @@ bool ASTReader::ReadASTBlock(ModuleFile &F) {
KnownNamespaces.push_back(getGlobalDeclID(F, Record[I]));
break;
- case UNDEFINED_INTERNALS:
- if (UndefinedInternals.size() % 2 != 0) {
- Error("Invalid existing UndefinedInternals");
+ case UNDEFINED_BUT_USED:
+ if (UndefinedButUsed.size() % 2 != 0) {
+ Error("Invalid existing UndefinedButUsed");
return true;
}
if (Record.size() % 2 != 0) {
- Error("invalid undefined internals record");
+ Error("invalid undefined-but-used record");
return true;
}
for (unsigned I = 0, N = Record.size(); I != N; /* in loop */) {
- UndefinedInternals.push_back(getGlobalDeclID(F, Record[I++]));
- UndefinedInternals.push_back(
+ UndefinedButUsed.push_back(getGlobalDeclID(F, Record[I++]));
+ UndefinedButUsed.push_back(
ReadSourceLocation(F, Record, I).getRawEncoding());
}
break;
@@ -5962,16 +5962,15 @@ void ASTReader::ReadKnownNamespaces(
}
}
-void ASTReader::ReadUndefinedInternals(
+void ASTReader::ReadUndefinedButUsed(
llvm::DenseMap<NamedDecl*, SourceLocation> &Undefined) {
- for (unsigned Idx = 0, N = UndefinedInternals.size(); Idx != N;) {
- NamedDecl *D = cast<NamedDecl>(GetDecl(UndefinedInternals[Idx++]));
+ for (unsigned Idx = 0, N = UndefinedButUsed.size(); Idx != N;) {
+ NamedDecl *D = cast<NamedDecl>(GetDecl(UndefinedButUsed[Idx++]));
SourceLocation Loc =
- SourceLocation::getFromRawEncoding(UndefinedInternals[Idx++]);
+ SourceLocation::getFromRawEncoding(UndefinedButUsed[Idx++]);
Undefined.insert(std::make_pair(D, Loc));
}
}
-
void ASTReader::ReadTentativeDefinitions(
SmallVectorImpl<VarDecl *> &TentativeDefs) {
diff --git a/lib/Serialization/ASTWriter.cpp b/lib/Serialization/ASTWriter.cpp
index 8120799c11..2d975466b5 100644
--- a/lib/Serialization/ASTWriter.cpp
+++ b/lib/Serialization/ASTWriter.cpp
@@ -824,7 +824,7 @@ void ASTWriter::WriteBlockInfoBlock() {
RECORD(OPENCL_EXTENSIONS);
RECORD(DELEGATING_CTORS);
RECORD(KNOWN_NAMESPACES);
- RECORD(UNDEFINED_INTERNALS);
+ RECORD(UNDEFINED_BUT_USED);
RECORD(MODULE_OFFSET_MAP);
RECORD(SOURCE_MANAGER_LINE_TABLE);
RECORD(OBJC_CATEGORIES_MAP);
@@ -3588,15 +3588,15 @@ void ASTWriter::WriteASTCore(Sema &SemaRef,
AddDeclRef(I->first, KnownNamespaces);
}
- // Build a record of all used, undefined objects with internal linkage.
- RecordData UndefinedInternals;
+ // Build a record of all used, undefined objects that require definitions.
+ RecordData UndefinedButUsed;
SmallVector<std::pair<NamedDecl *, SourceLocation>, 16> Undefined;
- SemaRef.getUndefinedInternals(Undefined);
+ SemaRef.getUndefinedButUsed(Undefined);
for (SmallVectorImpl<std::pair<NamedDecl *, SourceLocation> >::iterator
I = Undefined.begin(), E = Undefined.end(); I != E; ++I) {
- AddDeclRef(I->first, UndefinedInternals);
- AddSourceLocation(I->second, UndefinedInternals);
+ AddDeclRef(I->first, UndefinedButUsed);
+ AddSourceLocation(I->second, UndefinedButUsed);
}
// Write the control block
@@ -3814,9 +3814,9 @@ void ASTWriter::WriteASTCore(Sema &SemaRef,
if (!KnownNamespaces.empty())
Stream.EmitRecord(KNOWN_NAMESPACES, KnownNamespaces);
- // Write the undefined internal functions and variables.
- if (!UndefinedInternals.empty())
- Stream.EmitRecord(UNDEFINED_INTERNALS, UndefinedInternals);
+ // Write the undefined internal functions and variables, and inline functions.
+ if (!UndefinedButUsed.empty())
+ Stream.EmitRecord(UNDEFINED_BUT_USED, UndefinedButUsed);
// Write the visible updates to DeclContexts.
for (llvm::SmallPtrSet<const DeclContext *, 16>::iterator
diff --git a/test/Analysis/engine/replay-without-inlining.c b/test/Analysis/engine/replay-without-inlining.c
index 0602973169..14b2b819b9 100644
--- a/test/Analysis/engine/replay-without-inlining.c
+++ b/test/Analysis/engine/replay-without-inlining.c
@@ -16,7 +16,7 @@ typedef struct {
int cur;
int end;
} IB;
-inline unsigned long gl(IB *input);
+unsigned long gl(IB *input);
inline void gbs(IB *input, unsigned char *buf, int count);
void getB(IB *st, Hdr2 *usedtobeundef);
inline unsigned char gb(IB *input) {
diff --git a/test/SemaCXX/undefined-inline.cpp b/test/SemaCXX/undefined-inline.cpp
new file mode 100644
index 0000000000..ad719ae03a
--- /dev/null
+++ b/test/SemaCXX/undefined-inline.cpp
@@ -0,0 +1,57 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+// PR14993
+
+namespace test1 {
+ inline void f(); // expected-warning{{inline function 'test1::f' is not defined}}
+ void test() { f(); } // expected-note{{used here}}
+}
+
+namespace test2 {
+ inline int f();
+ void test() { (void)sizeof(f()); }
+}
+
+namespace test3 {
+ void f(); // expected-warning{{inline function 'test3::f' is not defined}}
+ inline void f();
+ void test() { f(); } // expected-note{{used here}}
+}
+
+namespace test4 {
+ inline void error_on_zero(int); // expected-warning{{inline function 'test4::error_on_zero' is not defined}}
+ inline void error_on_zero(char*) {}
+ void test() { error_on_zero(0); } // expected-note{{used here}}
+}
+
+namespace test5 {
+ struct X { void f(); };
+ void test(X &x) { x.f(); }
+}
+
+namespace test6 {
+ struct X { inline void f(); }; // expected-warning{{inline function 'test6::X::f' is not defined}}
+ void test(X &x) { x.f(); } // expected-note{{used here}}
+}
+
+namespace test7 {
+ void f(); // expected-warning{{inline function 'test7::f' is not defined}}
+ void test() { f(); } // no used-here note.
+ inline void f();
+}
+
+namespace test8 {
+ inline void foo() __attribute__((gnu_inline));
+ void test() { foo(); }
+}
+
+namespace test9 {
+ void foo();
+ void test() { foo(); }
+ inline void foo() __attribute__((gnu_inline));
+}
+
+namespace test10 {
+ inline void foo();
+ void test() { foo(); }
+ inline void foo() __attribute__((gnu_inline));
+}