[ODRHash] Support ODR violation detection in functions.

Extend the hashing to functions, which allows detection of function definition
mismatches across modules.

llvm-svn: 320230
This commit is contained in:
Richard Trieu 2017-12-09 01:29:40 +00:00
parent a249c4f513
commit e81caeb314
10 changed files with 456 additions and 49 deletions

View File

@ -1760,6 +1760,11 @@ protected:
unsigned IsCopyDeductionCandidate : 1;
private:
/// Store the ODRHash after first calculation.
unsigned HasODRHash : 1;
unsigned ODRHash;
/// \brief End part of this FunctionDecl's source range.
///
/// We could compute the full range in getSourceRange(). However, when we're
@ -1842,8 +1847,9 @@ protected:
IsExplicitlyDefaulted(false), HasImplicitReturnZero(false),
IsLateTemplateParsed(false), IsConstexpr(isConstexprSpecified),
InstantiationIsPending(false), UsesSEHTry(false), HasSkippedBody(false),
WillHaveBody(false), IsCopyDeductionCandidate(false),
EndRangeLoc(NameInfo.getEndLoc()), DNLoc(NameInfo.getInfo()) {}
WillHaveBody(false), IsCopyDeductionCandidate(false), HasODRHash(false),
ODRHash(0), EndRangeLoc(NameInfo.getEndLoc()),
DNLoc(NameInfo.getInfo()) {}
using redeclarable_base = Redeclarable<FunctionDecl>;
@ -2443,6 +2449,10 @@ public:
/// returns 0.
unsigned getMemoryFunctionKind() const;
/// \brief Returns ODRHash of the function. This value is calculated and
/// stored on first call, then the stored value returned on the other calls.
unsigned getODRHash();
// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
static bool classofKind(Kind K) {

View File

@ -53,6 +53,10 @@ public:
// more information than the AddDecl class.
void AddCXXRecordDecl(const CXXRecordDecl *Record);
// Use this for ODR checking functions between modules. This method compares
// more information than the AddDecl class.
void AddFunctionDecl(const FunctionDecl *Function);
// Process SubDecls of the main Decl. This method calls the DeclVisitor
// while AddDecl does not.
void AddSubDecl(const Decl *D);

View File

@ -270,6 +270,29 @@ def note_module_odr_violation_mismatch_decl_diff : Note<"but in '%0' found "
"friend function %2|"
"}1">;
def err_module_odr_violation_function : Error<
"%q0 has different definitions in different modules; "
"%select{definition in module '%2'|defined here}1 "
"first difference is "
"%select{"
"return type is %4|"
"%ordinal4 parameter with name %5|"
"%ordinal4 parameter with type %5%select{| decayed from %7}6|"
"%ordinal4 parameter with%select{out|}5 a default argument|"
"%ordinal4 parameter with a default argument|"
"function body"
"}3">;
def note_module_odr_violation_function : Note<"but in '%0' found "
"%select{"
"different return type %2|"
"%ordinal2 parameter with name %3|"
"%ordinal2 parameter with type %3%select{| decayed from %5}4|"
"%ordinal2 parameter with%select{out|}3 a default argument|"
"%ordinal2 parameter with a different default argument|"
"a different body"
"}1">;
def err_module_odr_violation_mismatch_decl_unknown : Error<
"%q0 %select{with definition in module '%2'|defined here}1 has different "
"definitions in different modules; first difference is this "

View File

@ -1092,6 +1092,10 @@ private:
llvm::SmallDenseMap<CXXRecordDecl *, llvm::SmallVector<DataPointers, 2>, 2>
PendingOdrMergeFailures;
/// \brief Function definitions in which we found an ODR violation.
llvm::SmallDenseMap<FunctionDecl *, llvm::SmallVector<FunctionDecl *, 2>, 2>
PendingFunctionOdrMergeFailures;
/// \brief DeclContexts in which we have diagnosed an ODR violation.
llvm::SmallPtrSet<DeclContext*, 2> DiagnosedOdrMergeFailures;

View File

@ -26,6 +26,7 @@
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/ExternalASTSource.h"
#include "clang/AST/ODRHash.h"
#include "clang/AST/PrettyPrinter.h"
#include "clang/AST/Redeclarable.h"
#include "clang/AST/Stmt.h"
@ -3601,6 +3602,25 @@ unsigned FunctionDecl::getMemoryFunctionKind() const {
return 0;
}
unsigned FunctionDecl::getODRHash() {
if (HasODRHash)
return ODRHash;
if (FunctionDecl *Definition = getDefinition()) {
if (Definition != this) {
HasODRHash = true;
ODRHash = Definition->getODRHash();
return ODRHash;
}
}
class ODRHash Hash;
Hash.AddFunctionDecl(this);
HasODRHash = true;
ODRHash = Hash.CalculateHash();
return ODRHash;
}
//===----------------------------------------------------------------------===//
// FieldDecl Implementation
//===----------------------------------------------------------------------===//

View File

@ -466,6 +466,36 @@ void ODRHash::AddCXXRecordDecl(const CXXRecordDecl *Record) {
}
}
void ODRHash::AddFunctionDecl(const FunctionDecl *Function) {
assert(Function && "Expecting non-null pointer.");
// Skip hashing these kinds of function.
if (Function->isImplicit()) return;
if (Function->isDefaulted()) return;
if (Function->isDeleted()) return;
if (!Function->hasBody()) return;
if (!Function->getBody()) return;
// Skip functions that are specializations or in specialization context.
const DeclContext *DC = Function;
while (DC) {
if (isa<ClassTemplateSpecializationDecl>(DC)) return;
if (auto *F = dyn_cast<FunctionDecl>(DC))
if (F->isFunctionTemplateSpecialization()) return;
DC = DC->getParent();
}
AddDecl(Function);
AddQualType(Function->getReturnType());
ID.AddInteger(Function->param_size());
for (auto Param : Function->parameters())
AddSubDecl(Param);
AddStmt(Function->getBody());
}
void ODRHash::AddDecl(const Decl *D) {
assert(D && "Expecting non-null pointer.");
auto Result = DeclMap.insert(std::make_pair(D, DeclMap.size()));

View File

@ -9235,8 +9235,16 @@ void ASTReader::finishPendingActions() {
const FunctionDecl *Defn = nullptr;
if (!getContext().getLangOpts().Modules || !FD->hasBody(Defn)) {
FD->setLazyBody(PB->second);
} else
mergeDefinitionVisibility(const_cast<FunctionDecl*>(Defn), FD);
} else {
auto *NonConstDefn = const_cast<FunctionDecl*>(Defn);
mergeDefinitionVisibility(NonConstDefn, FD);
if (!FD->isLateTemplateParsed() &&
!NonConstDefn->isLateTemplateParsed() &&
FD->getODRHash() != NonConstDefn->getODRHash()) {
PendingFunctionOdrMergeFailures[FD].push_back(NonConstDefn);
}
}
continue;
}
@ -9253,7 +9261,8 @@ void ASTReader::finishPendingActions() {
}
void ASTReader::diagnoseOdrViolations() {
if (PendingOdrMergeFailures.empty() && PendingOdrMergeChecks.empty())
if (PendingOdrMergeFailures.empty() && PendingOdrMergeChecks.empty() &&
PendingFunctionOdrMergeFailures.empty())
return;
// Trigger the import of the full definition of each class that had any
@ -9275,6 +9284,20 @@ void ASTReader::diagnoseOdrViolations() {
}
}
// Trigger the import of functions.
auto FunctionOdrMergeFailures = std::move(PendingFunctionOdrMergeFailures);
PendingFunctionOdrMergeFailures.clear();
for (auto &Merge : FunctionOdrMergeFailures) {
Merge.first->buildLookup();
Merge.first->decls_begin();
Merge.first->getBody();
for (auto &FD : Merge.second) {
FD->buildLookup();
FD->decls_begin();
FD->getBody();
}
}
// For each declaration from a merged context, check that the canonical
// definition of that context also contains a declaration of the same
// entity.
@ -9357,13 +9380,35 @@ void ASTReader::diagnoseOdrViolations() {
}
}
if (OdrMergeFailures.empty())
if (OdrMergeFailures.empty() && FunctionOdrMergeFailures.empty())
return;
// Ensure we don't accidentally recursively enter deserialization while
// we're producing our diagnostics.
Deserializing RecursionGuard(this);
// Common code for hashing helpers.
ODRHash Hash;
auto ComputeQualTypeODRHash = [&Hash](QualType Ty) {
Hash.clear();
Hash.AddQualType(Ty);
return Hash.CalculateHash();
};
auto ComputeODRHash = [&Hash](const Stmt *S) {
assert(S);
Hash.clear();
Hash.AddStmt(S);
return Hash.CalculateHash();
};
auto ComputeSubDeclODRHash = [&Hash](const Decl *D) {
assert(D);
Hash.clear();
Hash.AddSubDecl(D);
return Hash.CalculateHash();
};
// Issue any pending ODR-failure diagnostics.
for (auto &Merge : OdrMergeFailures) {
// If we've already pointed out a specific problem with this class, don't
@ -9411,13 +9456,6 @@ void ASTReader::diagnoseOdrViolations() {
<< SecondModule << Range << DiffType;
};
ODRHash Hash;
auto ComputeQualTypeODRHash = [&Hash](QualType Ty) {
Hash.clear();
Hash.AddQualType(Ty);
return Hash.CalculateHash();
};
unsigned FirstNumBases = FirstDD->NumBases;
unsigned FirstNumVBases = FirstDD->NumVBases;
unsigned SecondNumBases = SecondDD->NumBases;
@ -9520,14 +9558,12 @@ void ASTReader::diagnoseOdrViolations() {
if (FirstTemplate && SecondTemplate) {
DeclHashes FirstTemplateHashes;
DeclHashes SecondTemplateHashes;
ODRHash Hash;
auto PopulateTemplateParameterHashs =
[&Hash](DeclHashes &Hashes, const ClassTemplateDecl *TD) {
[&ComputeSubDeclODRHash](DeclHashes &Hashes,
const ClassTemplateDecl *TD) {
for (auto *D : TD->getTemplateParameters()->asArray()) {
Hash.clear();
Hash.AddSubDecl(D);
Hashes.emplace_back(D, Hash.CalculateHash());
Hashes.emplace_back(D, ComputeSubDeclODRHash(D));
}
};
@ -9696,18 +9732,15 @@ void ASTReader::diagnoseOdrViolations() {
DeclHashes FirstHashes;
DeclHashes SecondHashes;
ODRHash Hash;
auto PopulateHashes = [&Hash, FirstRecord](DeclHashes &Hashes,
CXXRecordDecl *Record) {
auto PopulateHashes = [&ComputeSubDeclODRHash, FirstRecord](
DeclHashes &Hashes, CXXRecordDecl *Record) {
for (auto *D : Record->decls()) {
// Due to decl merging, the first CXXRecordDecl is the parent of
// Decls in both records.
if (!ODRHash::isWhitelistedDecl(D, FirstRecord))
continue;
Hash.clear();
Hash.AddSubDecl(D);
Hashes.emplace_back(D, Hash.CalculateHash());
Hashes.emplace_back(D, ComputeSubDeclODRHash(D));
}
};
PopulateHashes(FirstHashes, FirstRecord);
@ -9901,19 +9934,6 @@ void ASTReader::diagnoseOdrViolations() {
<< SecondModule << Range << DiffType;
};
auto ComputeODRHash = [&Hash](const Stmt* S) {
assert(S);
Hash.clear();
Hash.AddStmt(S);
return Hash.CalculateHash();
};
auto ComputeQualTypeODRHash = [&Hash](QualType Ty) {
Hash.clear();
Hash.AddQualType(Ty);
return Hash.CalculateHash();
};
switch (FirstDiffType) {
case Other:
case EndOfClass:
@ -10488,6 +10508,160 @@ void ASTReader::diagnoseOdrViolations() {
<< Merge.first;
}
}
// Issue ODR failures diagnostics for functions.
for (auto &Merge : FunctionOdrMergeFailures) {
enum ODRFunctionDifference {
ReturnType,
ParameterName,
ParameterType,
ParameterSingleDefaultArgument,
ParameterDifferentDefaultArgument,
FunctionBody,
};
FunctionDecl *FirstFunction = Merge.first;
std::string FirstModule = getOwningModuleNameForDiagnostic(FirstFunction);
bool Diagnosed = false;
for (auto &SecondFunction : Merge.second) {
if (FirstFunction == SecondFunction)
continue;
std::string SecondModule =
getOwningModuleNameForDiagnostic(SecondFunction);
auto ODRDiagError = [FirstFunction, &FirstModule,
this](SourceLocation Loc, SourceRange Range,
ODRFunctionDifference DiffType) {
return Diag(Loc, diag::err_module_odr_violation_function)
<< FirstFunction << FirstModule.empty() << FirstModule << Range
<< DiffType;
};
auto ODRDiagNote = [&SecondModule, this](SourceLocation Loc,
SourceRange Range,
ODRFunctionDifference DiffType) {
return Diag(Loc, diag::note_module_odr_violation_function)
<< SecondModule << Range << DiffType;
};
if (ComputeQualTypeODRHash(FirstFunction->getReturnType()) !=
ComputeQualTypeODRHash(SecondFunction->getReturnType())) {
ODRDiagError(FirstFunction->getReturnTypeSourceRange().getBegin(),
FirstFunction->getReturnTypeSourceRange(), ReturnType)
<< FirstFunction->getReturnType();
ODRDiagNote(SecondFunction->getReturnTypeSourceRange().getBegin(),
SecondFunction->getReturnTypeSourceRange(), ReturnType)
<< SecondFunction->getReturnType();
Diagnosed = true;
break;
}
assert(FirstFunction->param_size() == SecondFunction->param_size() &&
"Merged functions with different number of parameters");
auto ParamSize = FirstFunction->param_size();
bool ParameterMismatch = false;
for (unsigned I = 0; I < ParamSize; ++I) {
auto *FirstParam = FirstFunction->getParamDecl(I);
auto *SecondParam = SecondFunction->getParamDecl(I);
assert(getContext().hasSameType(FirstParam->getType(),
SecondParam->getType()) &&
"Merged function has different parameter types.");
if (FirstParam->getDeclName() != SecondParam->getDeclName()) {
ODRDiagError(FirstParam->getLocation(), FirstParam->getSourceRange(),
ParameterName)
<< I + 1 << FirstParam->getDeclName();
ODRDiagNote(SecondParam->getLocation(), SecondParam->getSourceRange(),
ParameterName)
<< I + 1 << SecondParam->getDeclName();
ParameterMismatch = true;
break;
};
QualType FirstParamType = FirstParam->getType();
QualType SecondParamType = SecondParam->getType();
if (FirstParamType != SecondParamType &&
ComputeQualTypeODRHash(FirstParamType) !=
ComputeQualTypeODRHash(SecondParamType)) {
if (const DecayedType *ParamDecayedType =
FirstParamType->getAs<DecayedType>()) {
ODRDiagError(FirstParam->getLocation(),
FirstParam->getSourceRange(), ParameterType)
<< (I + 1) << FirstParamType << true
<< ParamDecayedType->getOriginalType();
} else {
ODRDiagError(FirstParam->getLocation(),
FirstParam->getSourceRange(), ParameterType)
<< (I + 1) << FirstParamType << false;
}
if (const DecayedType *ParamDecayedType =
SecondParamType->getAs<DecayedType>()) {
ODRDiagNote(SecondParam->getLocation(),
SecondParam->getSourceRange(), ParameterType)
<< (I + 1) << SecondParamType << true
<< ParamDecayedType->getOriginalType();
} else {
ODRDiagNote(SecondParam->getLocation(),
SecondParam->getSourceRange(), ParameterType)
<< (I + 1) << SecondParamType << false;
}
ParameterMismatch = true;
break;
}
const Expr *FirstInit = FirstParam->getInit();
const Expr *SecondInit = SecondParam->getInit();
if ((FirstInit == nullptr) != (SecondInit == nullptr)) {
ODRDiagError(FirstParam->getLocation(), FirstParam->getSourceRange(),
ParameterSingleDefaultArgument)
<< (I + 1) << (FirstInit == nullptr)
<< (FirstInit ? FirstInit->getSourceRange() : SourceRange());
ODRDiagNote(SecondParam->getLocation(), SecondParam->getSourceRange(),
ParameterSingleDefaultArgument)
<< (I + 1) << (SecondInit == nullptr)
<< (SecondInit ? SecondInit->getSourceRange() : SourceRange());
ParameterMismatch = true;
break;
}
if (FirstInit && SecondInit &&
ComputeODRHash(FirstInit) != ComputeODRHash(SecondInit)) {
ODRDiagError(FirstParam->getLocation(), FirstParam->getSourceRange(),
ParameterDifferentDefaultArgument)
<< (I + 1) << FirstInit->getSourceRange();
ODRDiagNote(SecondParam->getLocation(), SecondParam->getSourceRange(),
ParameterDifferentDefaultArgument)
<< (I + 1) << SecondInit->getSourceRange();
ParameterMismatch = true;
break;
}
assert(ComputeSubDeclODRHash(FirstParam) ==
ComputeSubDeclODRHash(SecondParam) &&
"Undiagnosed parameter difference.");
}
if (ParameterMismatch) {
Diagnosed = true;
break;
}
// If no error has been generated before now, assume the problem is in
// the body and generate a message.
ODRDiagError(FirstFunction->getLocation(),
FirstFunction->getSourceRange(), FunctionBody);
ODRDiagNote(SecondFunction->getLocation(),
SecondFunction->getSourceRange(), FunctionBody);
Diagnosed = true;
break;
}
assert(Diagnosed && "Unable to emit ODR diagnostic.");
}
}
void ASTReader::StartedDeserializing() {

View File

@ -796,6 +796,9 @@ void ASTDeclReader::VisitFunctionDecl(FunctionDecl *FD) {
FD->setCachedLinkage(Linkage(Record.readInt()));
FD->EndRangeLoc = ReadSourceLocation();
FD->ODRHash = Record.readInt();
FD->HasODRHash = true;
switch ((FunctionDecl::TemplatedKind)Record.readInt()) {
case FunctionDecl::TK_NonTemplate:
mergeRedeclarable(FD, Redecl);

View File

@ -538,6 +538,8 @@ void ASTDeclWriter::VisitFunctionDecl(FunctionDecl *D) {
Record.push_back(D->getLinkageInternal());
Record.AddSourceLocation(D->getLocEnd());
Record.push_back(D->getODRHash());
Record.push_back(D->getTemplatedKind());
switch (D->getTemplatedKind()) {
case FunctionDecl::TK_NonTemplate:
@ -2074,6 +2076,7 @@ void ASTWriter::WriteDeclAbbrevs() {
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // LateParsed
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 3)); // Linkage
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // LocEnd
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 32)); // ODRHash
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 3)); // TemplateKind
// This Array slurps the rest of the record. Fortunately we want to encode
// (nearly) all the remaining (variable number of) fields in the same way.

View File

@ -557,11 +557,11 @@ S10 s10;
#if defined(FIRST)
struct S11 {
void A(int x) {}
void A(int x);
};
#elif defined(SECOND)
struct S11 {
void A(int y) {}
void A(int y);
};
#else
S11 s11;
@ -571,11 +571,11 @@ S11 s11;
#if defined(FIRST)
struct S12 {
void A(int x) {}
void A(int x);
};
#elif defined(SECOND)
struct S12 {
void A(int x = 1) {}
void A(int x = 1);
};
#else
S12 s12;
@ -585,11 +585,11 @@ S12 s12;
#if defined(FIRST)
struct S13 {
void A(int x = 1 + 0) {}
void A(int x = 1 + 0);
};
#elif defined(SECOND)
struct S13 {
void A(int x = 1) {}
void A(int x = 1);
};
#else
S13 s13;
@ -599,11 +599,11 @@ S13 s13;
#if defined(FIRST)
struct S14 {
void A(int x[2]) {}
void A(int x[2]);
};
#elif defined(SECOND)
struct S14 {
void A(int x[3]) {}
void A(int x[3]);
};
#else
S14 s14;
@ -2751,14 +2751,14 @@ namespace DefaultArguments {
template <typename T>
struct S {
struct R {
void foo(T x = 0) {}
void foo(T x = 0);
};
};
#elif defined(SECOND)
template <typename T>
struct S {
struct R {
void foo(T x = 1) {}
void foo(T x = 1);
};
};
#else
@ -2771,13 +2771,13 @@ void run() {
#if defined(FIRST)
template <typename alpha> struct Bravo {
void charlie(bool delta = false) {}
void charlie(bool delta = false);
};
typedef Bravo<char> echo;
echo foxtrot;
#elif defined(SECOND)
template <typename alpha> struct Bravo {
void charlie(bool delta = (false)) {}
void charlie(bool delta = (false));
};
typedef Bravo<char> echo;
echo foxtrot;
@ -2788,6 +2788,142 @@ Bravo<char> golf;
#endif
} // namespace DefaultArguments
namespace FunctionDecl {
#if defined(FIRST)
struct S1 {};
S1 s1a;
#elif defined(SECOND)
struct S1 {};
#else
S1 s1;
#endif
#if defined(FIRST)
struct S2 {
S2() = default;
};
S2 s2a = S2();
#elif defined(SECOND)
struct S2 {
S2() = default;
};
#else
S2 s2;
#endif
#if defined(FIRST)
struct S3 {
S3() = delete;
};
S3* s3c;
#elif defined(SECOND)
struct S3 {
S3() = delete;
};
#else
S3* s3;
#endif
#if defined(FIRST) || defined(SECOND)
int F1(int x, float y = 2.7) { return 1; }
#else
int I1 = F1(1);
#endif
#if defined(FIRST)
int F2() { return 1; }
#elif defined(SECOND)
double F2() { return 1; }
#else
int I2 = F2();
// expected-error@-1 {{call to 'F2' is ambiguous}}
// expected-note@first.h:* {{candidate function}}
// expected-note@second.h:* {{candidate function}}
#endif
#if defined(FIRST)
int F3(float) { return 1; }
#elif defined(SECOND)
int F3(double) { return 1; }
#else
int I3 = F3(1);
// expected-error@-1 {{call to 'F3' is ambiguous}}
// expected-note@first.h:* {{candidate function}}
// expected-note@second.h:* {{candidate function}}
#endif
#if defined(FIRST)
int F4(int x) { return 1; }
#elif defined(SECOND)
int F4(int y) { return 1; }
#else
int I4 = F4(1);
// expected-error@second.h:* {{'FunctionDecl::F4' has different definitions in different modules; definition in module 'SecondModule' first difference is 1st parameter with name 'y'}}
// expected-note@first.h:* {{but in 'FirstModule' found 1st parameter with name 'x'}}
#endif
#if defined(FIRST)
int F5(int x) { return 1; }
#elif defined(SECOND)
int F5(int x = 1) { return 1; }
#else
int I5 = F6(1);
// expected-error@second.h:* {{'FunctionDecl::F5' has different definitions in different modules; definition in module 'SecondModule' first difference is 1st parameter without a default argument}}
// expected-note@first.h:* {{but in 'FirstModule' found 1st parameter with a default argument}}
#endif
#if defined(FIRST)
int F6(int x = 2) { return 1; }
#elif defined(SECOND)
int F6(int x = 1) { return 1; }
#else
int I6 = F6(1);
// expected-error@second.h:* {{'FunctionDecl::F6' has different definitions in different modules; definition in module 'SecondModule' first difference is 1st parameter with a default argument}}
// expected-note@first.h:* {{but in 'FirstModule' found 1st parameter with a different default argument}}
#endif
using I = int;
#if defined(FIRST)
I F7() { return 0; }
#elif defined(SECOND)
int F7() { return 0; }
#else
int I7 = F7();
// expected-error@second.h:* {{'FunctionDecl::F7' has different definitions in different modules; definition in module 'SecondModule' first difference is return type is 'int'}}
// expected-note@first.h:* {{but in 'FirstModule' found different return type 'FunctionDecl::I' (aka 'int')}}
#endif
#if defined(FIRST)
int F8(int) { return 0; }
#elif defined(SECOND)
int F8(I) { return 0; }
#else
int I8 = F8(1);
// expected-error@second.h:* {{'FunctionDecl::F8' has different definitions in different modules; definition in module 'SecondModule' first difference is 1st parameter with type 'FunctionDecl::I' (aka 'int')}}
// expected-note@first.h:* {{but in 'FirstModule' found 1st parameter with type 'int'}}
#endif
#if defined(FIRST)
int F9(int[1]) { return 0; }
#elif defined(SECOND)
int F9(int[2]) { return 0; }
#else
int I9 = F9(nullptr);
// expected-error@second.h:* {{'FunctionDecl::F9' has different definitions in different modules; definition in module 'SecondModule' first difference is 1st parameter with type 'int *' decayed from 'int [2]'}}
// expected-note@first.h:* {{but in 'FirstModule' found 1st parameter with type 'int *' decayed from 'int [1]'}}
#endif
#if defined(FIRST)
int F10() { return 1; }
#elif defined(SECOND)
int F10() { return 2; }
#else
int I10 = F10();
#endif
// expected-error@second.h:* {{'FunctionDecl::F10' has different definitions in different modules; definition in module 'SecondModule' first difference is function body}}
// expected-note@first.h:* {{but in 'FirstModule' found a different body}}
} // namespace FunctionDecl
// Keep macros contained to one file.
#ifdef FIRST
#undef FIRST