diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -192,6 +192,10 @@ these definitions were allowed. Note that such definitions are ODR violations if the header is included more than once. +- Clang now diagnoses if structs/unions with the same name are different in + different used modules. Behavior in C and Objective-C language modes now is + the same as in C++. + What's New in Clang |release|? ============================== Some of the major new features and improvements to Clang are listed diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -3998,6 +3998,7 @@ // to save some space. Use the provided accessors to access it. public: friend class DeclContext; + friend class ASTDeclReader; /// Enum that represents the different ways arguments are passed to and /// returned from function calls. This takes into account the target-specific /// and version-specific rules along with the rules determined by the @@ -4253,9 +4254,16 @@ /// nullptr is returned if no named data member exists. const FieldDecl *findFirstNamedDataMember() const; + /// Get precomputed ODRHash or add a new one. + unsigned getODRHash(); + private: /// Deserialize just the fields. void LoadFieldsFromExternalStorage() const; + + /// True if a valid hash is stored in ODRHash. + bool hasODRHash() const { return RecordDeclBits.ODRHash; } + void setODRHash(unsigned Hash) { RecordDeclBits.ODRHash = Hash; } }; class FileScopeAsmDecl : public Decl { diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h --- a/clang/include/clang/AST/DeclBase.h +++ b/clang/include/clang/AST/DeclBase.h @@ -1577,10 +1577,14 @@ /// Indicates whether this struct has had its field layout randomized. uint64_t IsRandomized : 1; + + /// True if a valid hash is stored in ODRHash. This should shave off some + /// extra storage and prevent CXXRecordDecl to store unused bits. + uint64_t ODRHash : 26; }; /// Number of non-inherited bits in RecordDeclBitfields. - enum { NumRecordDeclBits = 15 }; + enum { NumRecordDeclBits = 41 }; /// Stores the bits used by OMPDeclareReductionDecl. /// If modified NumOMPDeclareReductionDeclBits and the accessor diff --git a/clang/include/clang/AST/ODRDiagsEmitter.h b/clang/include/clang/AST/ODRDiagsEmitter.h --- a/clang/include/clang/AST/ODRDiagsEmitter.h +++ b/clang/include/clang/AST/ODRDiagsEmitter.h @@ -45,6 +45,12 @@ const CXXRecordDecl *SecondRecord, const struct CXXRecordDecl::DefinitionData *SecondDD) const; + /// Diagnose ODR mismatch between 2 RecordDecl that are not CXXRecordDecl. + /// + /// Returns true if found a mismatch and diagnosed it. + bool diagnoseMismatch(const RecordDecl *FirstRecord, + const RecordDecl *SecondRecord) const; + /// Diagnose ODR mismatch between 2 ObjCProtocolDecl. /// /// Returns true if found a mismatch and diagnosed it. diff --git a/clang/include/clang/AST/ODRHash.h b/clang/include/clang/AST/ODRHash.h --- a/clang/include/clang/AST/ODRHash.h +++ b/clang/include/clang/AST/ODRHash.h @@ -55,6 +55,10 @@ // more information than the AddDecl class. void AddCXXRecordDecl(const CXXRecordDecl *Record); + // Use this for ODR checking records in C/Objective-C between modules. This + // method compares more information than the AddDecl class. + void AddRecordDecl(const RecordDecl *Record); + // Use this for ODR checking functions between modules. This method compares // more information than the AddDecl class. SkipBody will process the // hash as if the function has no body. diff --git a/clang/include/clang/Serialization/ASTReader.h b/clang/include/clang/Serialization/ASTReader.h --- a/clang/include/clang/Serialization/ASTReader.h +++ b/clang/include/clang/Serialization/ASTReader.h @@ -1156,6 +1156,10 @@ llvm::SmallDenseMap, 2> PendingOdrMergeFailures; + /// C/ObjC record definitions in which we found an ODR violation. + llvm::SmallDenseMap, 2> + PendingRecordOdrMergeFailures; + /// Function definitions in which we found an ODR violation. llvm::SmallDenseMap, 2> PendingFunctionOdrMergeFailures; diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -4711,6 +4711,7 @@ setParamDestroyedInCallee(false); setArgPassingRestrictions(APK_CanPassInRegs); setIsRandomized(false); + setODRHash(0); } RecordDecl *RecordDecl::Create(const ASTContext &C, TagKind TK, DeclContext *DC, @@ -4885,6 +4886,19 @@ return nullptr; } +unsigned RecordDecl::getODRHash() { + if (hasODRHash()) + return RecordDeclBits.ODRHash; + + // Only calculate hash on first call of getODRHash per record. + ODRHash Hash; + Hash.AddRecordDecl(this); + // For RecordDecl the ODRHash is stored in the remaining 26 + // bit of RecordDeclBits, adjust the hash to accomodate. + setODRHash(Hash.CalculateHash() >> 6); + return RecordDeclBits.ODRHash; +} + //===----------------------------------------------------------------------===// // BlockDecl Implementation //===----------------------------------------------------------------------===// diff --git a/clang/lib/AST/ODRDiagsEmitter.cpp b/clang/lib/AST/ODRDiagsEmitter.cpp --- a/clang/lib/AST/ODRDiagsEmitter.cpp +++ b/clang/lib/AST/ODRDiagsEmitter.cpp @@ -1547,6 +1547,101 @@ return true; } +bool ODRDiagsEmitter::diagnoseMismatch(const RecordDecl *FirstRecord, + const RecordDecl *SecondRecord) const { + if (FirstRecord == SecondRecord) + return false; + + std::string FirstModule = getOwningModuleNameForDiagnostic(FirstRecord); + std::string SecondModule = getOwningModuleNameForDiagnostic(SecondRecord); + + auto PopulateHashes = [](DeclHashes &Hashes, const RecordDecl *Record, + const DeclContext *DC) { + for (const Decl *D : Record->decls()) { + if (!ODRHash::isSubDeclToBeProcessed(D, DC)) + continue; + Hashes.emplace_back(D, computeODRHash(D)); + } + }; + + DeclHashes FirstHashes; + DeclHashes SecondHashes; + const DeclContext *DC = FirstRecord; + PopulateHashes(FirstHashes, FirstRecord, DC); + PopulateHashes(SecondHashes, SecondRecord, DC); + + DiffResult DR = FindTypeDiffs(FirstHashes, SecondHashes); + ODRMismatchDecl FirstDiffType = DR.FirstDiffType; + ODRMismatchDecl SecondDiffType = DR.SecondDiffType; + const Decl *FirstDecl = DR.FirstDecl; + const Decl *SecondDecl = DR.SecondDecl; + + if (FirstDiffType == Other || SecondDiffType == Other) { + diagnoseSubMismatchUnexpected(DR, FirstRecord, FirstModule, SecondRecord, + SecondModule); + return true; + } + + if (FirstDiffType != SecondDiffType) { + diagnoseSubMismatchDifferentDeclKinds(DR, FirstRecord, FirstModule, + SecondRecord, SecondModule); + return true; + } + + assert(FirstDiffType == SecondDiffType); + switch (FirstDiffType) { + // Already handled. + case EndOfClass: + case Other: + // C++ only, invalid in this context. + case PublicSpecifer: + case PrivateSpecifer: + case ProtectedSpecifer: + case StaticAssert: + case CXXMethod: + case TypeAlias: + case Friend: + case FunctionTemplate: + // Cannot be contained by RecordDecl, invalid in this context. + case ObjCMethod: + case ObjCProperty: + llvm_unreachable("Invalid diff type"); + + case Field: { + if (diagnoseSubMismatchField(FirstRecord, FirstModule, SecondModule, + cast(FirstDecl), + cast(SecondDecl))) + return true; + break; + } + case TypeDef: { + if (diagnoseSubMismatchTypedef(FirstRecord, FirstModule, SecondModule, + cast(FirstDecl), + cast(SecondDecl), + /*IsTypeAlias=*/false)) + return true; + break; + } + case Var: { + if (diagnoseSubMismatchVar(FirstRecord, FirstModule, SecondModule, + cast(FirstDecl), + cast(SecondDecl))) + return true; + break; + } + } + + Diag(FirstDecl->getLocation(), + diag::err_module_odr_violation_mismatch_decl_unknown) + << FirstRecord << FirstModule.empty() << FirstModule << FirstDiffType + << FirstDecl->getSourceRange(); + Diag(SecondDecl->getLocation(), + diag::note_module_odr_violation_mismatch_decl_unknown) + << SecondModule.empty() << SecondModule << FirstDiffType + << SecondDecl->getSourceRange(); + return true; +} + bool ODRDiagsEmitter::diagnoseMismatch( const FunctionDecl *FirstFunction, const FunctionDecl *SecondFunction) const { diff --git a/clang/lib/AST/ODRHash.cpp b/clang/lib/AST/ODRHash.cpp --- a/clang/lib/AST/ODRHash.cpp +++ b/clang/lib/AST/ODRHash.cpp @@ -595,6 +595,24 @@ } } +void ODRHash::AddRecordDecl(const RecordDecl *Record) { + assert(!isa(Record) && + "For CXXRecordDecl should call AddCXXRecordDecl."); + AddDecl(Record); + + // Filter out sub-Decls which will not be processed in order to get an + // accurate count of Decl's. + llvm::SmallVector Decls; + for (Decl *SubDecl : Record->decls()) { + if (isSubDeclToBeProcessed(SubDecl, Record)) + Decls.push_back(SubDecl); + } + + ID.AddInteger(Decls.size()); + for (const Decl *SubDecl : Decls) + AddSubDecl(SubDecl); +} + void ODRHash::AddFunctionDecl(const FunctionDecl *Function, bool SkipBody) { assert(Function && "Expecting non-null pointer."); diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp --- a/clang/lib/Serialization/ASTReader.cpp +++ b/clang/lib/Serialization/ASTReader.cpp @@ -9504,6 +9504,7 @@ void ASTReader::diagnoseOdrViolations() { if (PendingOdrMergeFailures.empty() && PendingOdrMergeChecks.empty() && + PendingRecordOdrMergeFailures.empty() && PendingFunctionOdrMergeFailures.empty() && PendingEnumOdrMergeFailures.empty() && PendingObjCProtocolOdrMergeFailures.empty()) @@ -9528,6 +9529,15 @@ } } + // Trigger the import of the full definition of each record in C/ObjC. + auto RecordOdrMergeFailures = std::move(PendingRecordOdrMergeFailures); + PendingRecordOdrMergeFailures.clear(); + for (auto &Merge : RecordOdrMergeFailures) { + Merge.first->decls_begin(); + for (auto &D : Merge.second) + D->decls_begin(); + } + // Trigger the import of functions. auto FunctionOdrMergeFailures = std::move(PendingFunctionOdrMergeFailures); PendingFunctionOdrMergeFailures.clear(); @@ -9645,8 +9655,9 @@ } } - if (OdrMergeFailures.empty() && FunctionOdrMergeFailures.empty() && - EnumOdrMergeFailures.empty() && ObjCProtocolOdrMergeFailures.empty()) + if (OdrMergeFailures.empty() && RecordOdrMergeFailures.empty() && + FunctionOdrMergeFailures.empty() && EnumOdrMergeFailures.empty() && + ObjCProtocolOdrMergeFailures.empty()) return; // Ensure we don't accidentally recursively enter deserialization while @@ -9685,6 +9696,26 @@ } } + // Issue any pending ODR-failure diagnostics for RecordDecl in C/ObjC. Note + // that in C++ this is done as a part of CXXRecordDecl ODR checking. + for (auto &Merge : RecordOdrMergeFailures) { + // If we've already pointed out a specific problem with this class, don't + // bother issuing a general "something's different" diagnostic. + if (!DiagnosedOdrMergeFailures.insert(Merge.first).second) + continue; + + RecordDecl *FirstRecord = Merge.first; + bool Diagnosed = false; + for (auto *SecondRecord : Merge.second) { + if (DiagsEmitter.diagnoseMismatch(FirstRecord, SecondRecord)) { + Diagnosed = true; + break; + } + } + (void)Diagnosed; + assert(Diagnosed && "Unable to emit ODR diagnostic."); + } + // Issue ODR failures diagnostics for functions. for (auto &Merge : FunctionOdrMergeFailures) { FunctionDecl *FirstFunction = Merge.first; diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp --- a/clang/lib/Serialization/ASTReaderDecl.cpp +++ b/clang/lib/Serialization/ASTReaderDecl.cpp @@ -829,6 +829,7 @@ void ASTDeclReader::VisitRecordDecl(RecordDecl *RD) { VisitRecordDeclImpl(RD); + RD->setODRHash(Record.readInt()); // Maintain the invariant of a redeclaration chain containing only // a single definition. @@ -849,6 +850,8 @@ Reader.MergedDeclContexts.insert(std::make_pair(RD, OldDef)); RD->demoteThisDefinitionToDeclaration(); Reader.mergeDefinitionVisibility(OldDef, RD); + if (OldDef->getODRHash() != RD->getODRHash()) + Reader.PendingRecordOdrMergeFailures[OldDef].push_back(RD); } else { OldDef = RD; } diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp --- a/clang/lib/Serialization/ASTWriterDecl.cpp +++ b/clang/lib/Serialization/ASTWriterDecl.cpp @@ -491,6 +491,10 @@ Record.push_back(D->hasNonTrivialToPrimitiveCopyCUnion()); Record.push_back(D->isParamDestroyedInCallee()); Record.push_back(D->getArgPassingRestrictions()); + // Only compute this for C/Objective-C, in C++ this is computed as part + // of CXXRecordDecl. + if (!isa(D)) + Record.push_back(D->getODRHash()); if (D->getDeclContext() == D->getLexicalDeclContext() && !D->hasAttrs() && @@ -2127,6 +2131,8 @@ Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // getArgPassingRestrictions Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 2)); + // ODRHash + Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 26)); // DC Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // LexicalOffset diff --git a/clang/test/Modules/compare-record.c b/clang/test/Modules/compare-record.c new file mode 100644 --- /dev/null +++ b/clang/test/Modules/compare-record.c @@ -0,0 +1,418 @@ +// RUN: rm -rf %t +// RUN: split-file %s %t + +// Build first header file +// RUN: echo "#define FIRST" >> %t/include/first.h +// RUN: cat %t/test.c >> %t/include/first.h +// RUN: echo "#undef FIRST" >> %t/include/first.h + +// Build second header file +// RUN: echo "#define SECOND" >> %t/include/second.h +// RUN: cat %t/test.c >> %t/include/second.h +// RUN: echo "#undef SECOND" >> %t/include/second.h + +// Test that each header can compile +// RUN: %clang_cc1 -fsyntax-only -x objective-c %t/include/first.h -fblocks -fobjc-arc +// RUN: %clang_cc1 -fsyntax-only -x objective-c %t/include/second.h -fblocks -fobjc-arc + +// Run test +// RUN: %clang_cc1 -I%t/include -verify %t/test.c -fblocks -fobjc-arc \ +// RUN: -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/modules.cache + +// Run tests for nested structs +// DEFINE: %{filename} = test-nested-struct.c +// DEFINE: %{macro_flag} = -DCASE1=1 +// DEFINE: %{command} = %clang_cc1 -I%t/include -verify %t/%{filename} -fblocks -fobjc-arc \ +// DEFINE: -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/modules.cache \ +// DEFINE: %{macro_flag} -emit-llvm -o %t/%{filename}.bc +// RUN: %{command} +// REDEFINE: %{macro_flag} = -DCASE2=1 +// RUN: %{command} +// REDEFINE: %{macro_flag} = -DCASE3=1 +// RUN: %{command} + +// Test that we don't accept different structs and unions with the same name +// from multiple modules but detect mismatches and provide actionable +// diagnostic. + +//--- include/first-empty.h +//--- include/module.modulemap +module First { + module Empty { + header "first-empty.h" + } + module Hidden { + header "first.h" + header "first-nested-struct.h" + export * + } +} +module Second { + header "second.h" + header "second-nested-struct.h" + export * +} + +//--- test.c +#if !defined(FIRST) && !defined(SECOND) +# include "first-empty.h" +# include "second.h" +#endif + +#if defined(FIRST) +struct CompareForwardDeclaration1; +struct CompareForwardDeclaration2 {}; +#elif defined(SECOND) +struct CompareForwardDeclaration1 {}; +struct CompareForwardDeclaration2; +#else +struct CompareForwardDeclaration1 *compareForwardDeclaration1; +struct CompareForwardDeclaration2 *compareForwardDeclaration2; +#endif + +#if defined(FIRST) +struct CompareMatchingFields { + int matchingFieldName; +}; + +struct CompareFieldPresence1 { + int fieldPresence1; +}; +struct CompareFieldPresence2 {}; + +struct CompareFieldName { + int fieldNameA; +}; + +struct CompareFieldOrder { + int fieldOrderX; + int fieldOrderY; +}; +#elif defined(SECOND) +struct CompareMatchingFields { + int matchingFieldName; +}; + +struct CompareFieldPresence1 { +}; +struct CompareFieldPresence2 { + int fieldPresence2; +}; + +struct CompareFieldName { + int fieldNameB; +}; + +struct CompareFieldOrder { + int fieldOrderY; + int fieldOrderX; +}; +#else +struct CompareMatchingFields compareMatchingFields; +struct CompareFieldPresence1 compareFieldPresence1; +// expected-error@first.h:* {{'CompareFieldPresence1' has different definitions in different modules; first difference is definition in module 'First.Hidden' found field}} +// expected-note@second.h:* {{but in 'Second' found end of class}} +struct CompareFieldPresence2 compareFieldPresence2; +// expected-error@second.h:* {{'CompareFieldPresence2::fieldPresence2' from module 'Second' is not present in definition of 'struct CompareFieldPresence2' in module 'First.Hidden'}} +// expected-note@first.h:* {{definition has no member 'fieldPresence2'}} +struct CompareFieldName compareFieldName; +// expected-error@second.h:* {{'CompareFieldName::fieldNameB' from module 'Second' is not present in definition of 'struct CompareFieldName' in module 'First.Hidden'}} +// expected-note@first.h:* {{definition has no member 'fieldNameB'}} +struct CompareFieldOrder compareFieldOrder; +// expected-error@first.h:* {{'CompareFieldOrder' has different definitions in different modules; first difference is definition in module 'First.Hidden' found field 'fieldOrderX'}} +// expected-note@second.h:* {{but in 'Second' found field 'fieldOrderY'}} +#endif + +#if defined(FIRST) +struct CompareFieldType { + int fieldType; +}; + +typedef int FieldTypedefNameA; +struct CompareFieldTypedefName { + FieldTypedefNameA fieldTypedefName; +}; + +typedef int TypedefUnderlyingType; +struct CompareFieldTypeUnderlyingTypedef { + TypedefUnderlyingType fieldTypeUnderlyingTypedef; +}; + +typedef int TypedefFinal; +struct CompareFieldTypedefChain { + TypedefFinal fieldTypeTypedefChain; +}; +#elif defined(SECOND) +struct CompareFieldType { + float fieldType; +}; + +typedef int FieldTypedefNameB; +struct CompareFieldTypedefName { + FieldTypedefNameB fieldTypedefName; +}; + +struct CompareFieldTypeUnderlyingTypedef { + int fieldTypeUnderlyingTypedef; +}; + +typedef int TypedefIntermediate; +typedef TypedefIntermediate TypedefFinal; +struct CompareFieldTypedefChain { + TypedefFinal fieldTypeTypedefChain; +}; +#else +struct CompareFieldType compareFieldType; +// expected-error@second.h:* {{'CompareFieldType::fieldType' from module 'Second' is not present in definition of 'struct CompareFieldType' in module 'First.Hidden'}} +// expected-note@first.h:* {{declaration of 'fieldType' does not match}} +struct CompareFieldTypedefName compareFieldTypedefName; +// expected-error@first.h:* {{'CompareFieldTypedefName' has different definitions in different modules; first difference is definition in module 'First.Hidden' found field 'fieldTypedefName' with type 'FieldTypedefNameA' (aka 'int')}} +// expected-note@second.h:* {{but in 'Second' found field 'fieldTypedefName' with type 'FieldTypedefNameB' (aka 'int')}} +struct CompareFieldTypeUnderlyingTypedef compareFieldTypeUnderlyingTypedef; +// expected-error@first.h:* {{'CompareFieldTypeUnderlyingTypedef' has different definitions in different modules; first difference is definition in module 'First.Hidden' found field 'fieldTypeUnderlyingTypedef' with type 'TypedefUnderlyingType' (aka 'int')}} +// expected-note@second.h:* {{but in 'Second' found field 'fieldTypeUnderlyingTypedef' with type 'int'}} +struct CompareFieldTypedefChain compareFieldTypedefChain; +#endif + +#if defined(FIRST) +struct CompareMatchingBitfields { + unsigned matchingBitfields : 3; +}; + +struct CompareBitfieldPresence1 { + unsigned bitfieldPresence1 : 1; +}; +struct CompareBitfieldPresence2 { + unsigned bitfieldPresence2; +}; + +struct CompareBitfieldWidth { + unsigned bitfieldWidth : 2; +}; + +struct CompareBitfieldWidthExpression { + unsigned bitfieldWidthExpression : 1 + 1; +}; +#elif defined(SECOND) +struct CompareMatchingBitfields { + unsigned matchingBitfields : 3; +}; + +struct CompareBitfieldPresence1 { + unsigned bitfieldPresence1; +}; +struct CompareBitfieldPresence2 { + unsigned bitfieldPresence2 : 1; +}; + +struct CompareBitfieldWidth { + unsigned bitfieldWidth : 1; +}; + +struct CompareBitfieldWidthExpression { + unsigned bitfieldWidthExpression : 2; +}; +#else +struct CompareMatchingBitfields compareMatchingBitfields; +struct CompareBitfieldPresence1 compareBitfieldPresence1; +// expected-error@first.h:* {{'CompareBitfieldPresence1' has different definitions in different modules; first difference is definition in module 'First.Hidden' found bitfield 'bitfieldPresence1'}} +// expected-note@second.h:* {{but in 'Second' found non-bitfield 'bitfieldPresence1'}} +struct CompareBitfieldPresence2 compareBitfieldPresence2; +// expected-error@first.h:* {{'CompareBitfieldPresence2' has different definitions in different modules; first difference is definition in module 'First.Hidden' found non-bitfield 'bitfieldPresence2'}} +// expected-note@second.h:* {{but in 'Second' found bitfield 'bitfieldPresence2'}} +struct CompareBitfieldWidth compareBitfieldWidth; +// expected-error@first.h:* {{'CompareBitfieldWidth' has different definitions in different modules; first difference is definition in module 'First.Hidden' found bitfield 'bitfieldWidth' with one width expression}} +// expected-note@second.h:* {{but in 'Second' found bitfield 'bitfieldWidth' with different width expression}} +struct CompareBitfieldWidthExpression compareBitfieldWidthExpression; +// expected-error@first.h:* {{'CompareBitfieldWidthExpression' has different definitions in different modules; first difference is definition in module 'First.Hidden' found bitfield 'bitfieldWidthExpression' with one width expression}} +// expected-note@second.h:* {{but in 'Second' found bitfield 'bitfieldWidthExpression' with different width expressio}} +#endif + +#if defined(FIRST) +struct CompareMatchingArrayFields { + int matchingArrayField[7]; +}; + +struct CompareArrayLength { + int arrayLengthField[5]; +}; + +struct CompareArrayType { + int arrayTypeField[5]; +}; +#elif defined(SECOND) +struct CompareMatchingArrayFields { + int matchingArrayField[7]; +}; + +struct CompareArrayLength { + int arrayLengthField[7]; +}; + +struct CompareArrayType { + float arrayTypeField[5]; +}; +#else +struct CompareMatchingArrayFields compareMatchingArrayFields; +struct CompareArrayLength compareArrayLength; +// expected-error@second.h:* {{'CompareArrayLength::arrayLengthField' from module 'Second' is not present in definition of 'struct CompareArrayLength' in module 'First.Hidden'}} +// expected-note@first.h:* {{declaration of 'arrayLengthField' does not match}} +struct CompareArrayType compareArrayType; +// expected-error@second.h:* {{'CompareArrayType::arrayTypeField' from module 'Second' is not present in definition of 'struct CompareArrayType' in module 'First.Hidden'}} +// expected-note@first.h:* {{declaration of 'arrayTypeField' does not match}} +#endif + +#if defined(FIRST) +struct CompareFieldAsForwardDeclaration { + struct FieldForwardDeclaration *fieldForwardDeclaration; +}; + +enum FieldEnumA { kFieldEnumValue }; +struct CompareFieldAsEnum { + enum FieldEnumA fieldEnum; +}; + +struct FieldStructA {}; +struct CompareFieldAsStruct { + struct FieldStructA fieldStruct; +}; +#elif defined(SECOND) +struct FieldForwardDeclaration {}; +struct CompareFieldAsForwardDeclaration { + struct FieldForwardDeclaration *fieldForwardDeclaration; +}; + +enum FieldEnumB { kFieldEnumValue }; +struct CompareFieldAsEnum { + enum FieldEnumB fieldEnum; +}; + +struct FieldStructB {}; +struct CompareFieldAsStruct { + struct FieldStructB fieldStruct; +}; +#else +struct CompareFieldAsForwardDeclaration compareFieldAsForwardDeclaration; +struct CompareFieldAsEnum compareFieldAsEnum; +// expected-error@second.h:* {{'CompareFieldAsEnum::fieldEnum' from module 'Second' is not present in definition of 'struct CompareFieldAsEnum' in module 'First.Hidden'}} +// expected-note@first.h:* {{declaration of 'fieldEnum' does not match}} +struct CompareFieldAsStruct compareFieldAsStruct; +// expected-error@second.h:* {{'CompareFieldAsStruct::fieldStruct' from module 'Second' is not present in definition of 'struct CompareFieldAsStruct' in module 'First.Hidden'}} +// expected-note@first.h:* {{declaration of 'fieldStruct' does not match}} +#endif + +#if defined(FIRST) +union CompareMatchingUnionFields { + int matchingFieldA; + float matchingFieldB; +}; + +union CompareUnionFieldOrder { + int unionFieldOrderA; + float unionFieldOrderB; +}; + +union CompareUnionFieldType { + int unionFieldType; +}; +#elif defined(SECOND) +union CompareMatchingUnionFields { + int matchingFieldA; + float matchingFieldB; +}; + +union CompareUnionFieldOrder { + float unionFieldOrderB; + int unionFieldOrderA; +}; + +union CompareUnionFieldType { + unsigned int unionFieldType; +}; +#else +union CompareMatchingUnionFields compareMatchingUnionFields; +union CompareUnionFieldOrder compareUnionFieldOrder; +// expected-error@first.h:* {{'CompareUnionFieldOrder' has different definitions in different modules; first difference is definition in module 'First.Hidden' found field 'unionFieldOrderA'}} +// expected-note@second.h:* {{but in 'Second' found field 'unionFieldOrderB'}} +union CompareUnionFieldType compareUnionFieldType; +// expected-error@second.h:* {{'CompareUnionFieldType::unionFieldType' from module 'Second' is not present in definition of 'union CompareUnionFieldType' in module 'First.Hidden'}} +// expected-note@first.h:* {{declaration of 'unionFieldType' does not match}} +#endif + +// Test that we find and compare definitions even if they are not the first encountered declaration in a module. +#if defined(FIRST) +struct CompareDefinitionsRegardlessForwardDeclarations { + int definitionField; +}; +#elif defined(SECOND) +struct CompareDefinitionsRegardlessForwardDeclarations; +struct CompareDefinitionsRegardlessForwardDeclarations { + float definitionField; +}; +#else +struct CompareDefinitionsRegardlessForwardDeclarations compareDefinitions; +// expected-error@second.h:* {{'CompareDefinitionsRegardlessForwardDeclarations::definitionField' from module 'Second' is not present in definition of 'struct CompareDefinitionsRegardlessForwardDeclarations' in module 'First.Hidden'}} +// expected-note@first.h:* {{declaration of 'definitionField' does not match}} +#endif + +//--- include/first-nested-struct.h +struct CompareNestedStruct { + struct NestedLevel1 { + struct NestedLevel2 { + int a; + } y; + } x; +}; + +struct IndirectStruct { + int mismatchingField; +}; +struct DirectStruct { + struct IndirectStruct indirectField; +}; +struct CompareDifferentFieldInIndirectStruct { + struct DirectStruct directField; +}; +struct CompareIndirectStructPointer { + struct DirectStruct *directFieldPointer; +}; + +//--- include/second-nested-struct.h +struct CompareNestedStruct { + struct NestedLevel1 { + struct NestedLevel2 { + float b; + } y; + } x; +}; + +struct IndirectStruct { + float mismatchingField; +}; +struct DirectStruct { + struct IndirectStruct indirectField; +}; +struct CompareDifferentFieldInIndirectStruct { + struct DirectStruct directField; +}; +struct CompareIndirectStructPointer { + struct DirectStruct *directFieldPointer; +}; + +//--- test-nested-struct.c +#include "first-empty.h" +#include "second-nested-struct.h" + +#if defined(CASE1) +struct CompareNestedStruct compareNestedStruct; +// expected-error@second-nested-struct.h:* {{'NestedLevel2::b' from module 'Second' is not present in definition of 'struct NestedLevel2' in module 'First.Hidden'}} +// expected-note@first-nested-struct.h:* {{definition has no member 'b'}} +#elif defined(CASE2) +struct CompareDifferentFieldInIndirectStruct compareIndirectStruct; +// expected-error@second-nested-struct.h:* {{'IndirectStruct::mismatchingField' from module 'Second' is not present in definition of 'struct IndirectStruct' in module 'First.Hidden'}} +// expected-note@first-nested-struct.h:* {{declaration of 'mismatchingField' does not match}} +#elif defined(CASE3) +struct CompareIndirectStructPointer compareIndirectStructPointer; +// expected-error@second-nested-struct.h:* {{'IndirectStruct::mismatchingField' from module 'Second' is not present in definition of 'struct IndirectStruct' in module 'First.Hidden'}} +// expected-note@first-nested-struct.h:* {{declaration of 'mismatchingField' does not match}} +#endif