[ODRHash] Hash `RecordDecl` and diagnose discovered mismatches.
authorVolodymyr Sapsai <vsapsai@apple.com>
Fri, 2 Dec 2022 02:39:23 +0000 (18:39 -0800)
committerVolodymyr Sapsai <vsapsai@apple.com>
Thu, 19 Jan 2023 21:57:48 +0000 (15:57 -0600)
When two modules contain struct/union with the same name, check the
definitions are equivalent and diagnose if they are not. This is similar
to `CXXRecordDecl` where we already discover and diagnose mismatches.

rdar://problem/56764293

Differential Revision: https://reviews.llvm.org/D71734

13 files changed:
clang/docs/ReleaseNotes.rst
clang/include/clang/AST/Decl.h
clang/include/clang/AST/DeclBase.h
clang/include/clang/AST/ODRDiagsEmitter.h
clang/include/clang/AST/ODRHash.h
clang/include/clang/Serialization/ASTReader.h
clang/lib/AST/Decl.cpp
clang/lib/AST/ODRDiagsEmitter.cpp
clang/lib/AST/ODRHash.cpp
clang/lib/Serialization/ASTReader.cpp
clang/lib/Serialization/ASTReaderDecl.cpp
clang/lib/Serialization/ASTWriterDecl.cpp
clang/test/Modules/compare-record.c [new file with mode: 0644]

index 541f8e1..9f9fdc5 100644 (file)
@@ -192,6 +192,10 @@ code bases.
   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
index 7322d4b..863f6ac 100644 (file)
@@ -3998,6 +3998,7 @@ class RecordDecl : public TagDecl {
   // 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 @@ public:
   /// 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 {
index 8a5f755..6134fdd 100644 (file)
@@ -1577,10 +1577,14 @@ class DeclContext {
 
     /// 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
index d3b7f5e..00a681b 100644 (file)
@@ -45,6 +45,12 @@ public:
                    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.
index 1ab2001..a489bb7 100644 (file)
@@ -55,6 +55,10 @@ public:
   // 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.
index cbac6b7..5eb4834 100644 (file)
@@ -1156,6 +1156,10 @@ private:
   llvm::SmallDenseMap<CXXRecordDecl *, llvm::SmallVector<DataPointers, 2>, 2>
       PendingOdrMergeFailures;
 
+  /// C/ObjC record definitions in which we found an ODR violation.
+  llvm::SmallDenseMap<RecordDecl *, llvm::SmallVector<RecordDecl *, 2>, 2>
+      PendingRecordOdrMergeFailures;
+
   /// Function definitions in which we found an ODR violation.
   llvm::SmallDenseMap<FunctionDecl *, llvm::SmallVector<FunctionDecl *, 2>, 2>
       PendingFunctionOdrMergeFailures;
index 49040f5..e60cc28 100644 (file)
@@ -4711,6 +4711,7 @@ RecordDecl::RecordDecl(Kind DK, TagKind TK, const ASTContext &C,
   setParamDestroyedInCallee(false);
   setArgPassingRestrictions(APK_CanPassInRegs);
   setIsRandomized(false);
+  setODRHash(0);
 }
 
 RecordDecl *RecordDecl::Create(const ASTContext &C, TagKind TK, DeclContext *DC,
@@ -4885,6 +4886,19 @@ const FieldDecl *RecordDecl::findFirstNamedDataMember() const {
   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
 //===----------------------------------------------------------------------===//
index 6e0f122..b5888fc 100644 (file)
@@ -1547,6 +1547,101 @@ bool ODRDiagsEmitter::diagnoseMismatch(
   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<FieldDecl>(FirstDecl),
+                                 cast<FieldDecl>(SecondDecl)))
+      return true;
+    break;
+  }
+  case TypeDef: {
+    if (diagnoseSubMismatchTypedef(FirstRecord, FirstModule, SecondModule,
+                                   cast<TypedefNameDecl>(FirstDecl),
+                                   cast<TypedefNameDecl>(SecondDecl),
+                                   /*IsTypeAlias=*/false))
+      return true;
+    break;
+  }
+  case Var: {
+    if (diagnoseSubMismatchVar(FirstRecord, FirstModule, SecondModule,
+                               cast<VarDecl>(FirstDecl),
+                               cast<VarDecl>(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 {
index 1a24bb2..6912f67 100644 (file)
@@ -595,6 +595,24 @@ void ODRHash::AddCXXRecordDecl(const CXXRecordDecl *Record) {
   }
 }
 
+void ODRHash::AddRecordDecl(const RecordDecl *Record) {
+  assert(!isa<CXXRecordDecl>(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<const Decl *, 16> 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.");
index 2dbd4dd..77f29f6 100644 (file)
@@ -9504,6 +9504,7 @@ void ASTReader::finishPendingActions() {
 
 void ASTReader::diagnoseOdrViolations() {
   if (PendingOdrMergeFailures.empty() && PendingOdrMergeChecks.empty() &&
+      PendingRecordOdrMergeFailures.empty() &&
       PendingFunctionOdrMergeFailures.empty() &&
       PendingEnumOdrMergeFailures.empty() &&
       PendingObjCProtocolOdrMergeFailures.empty())
@@ -9528,6 +9529,15 @@ void ASTReader::diagnoseOdrViolations() {
     }
   }
 
+  // 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 @@ void ASTReader::diagnoseOdrViolations() {
     }
   }
 
-  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 @@ void ASTReader::diagnoseOdrViolations() {
     }
   }
 
+  // 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;
index 9908064..6c7198d 100644 (file)
@@ -829,6 +829,7 @@ ASTDeclReader::VisitRecordDeclImpl(RecordDecl *RD) {
 
 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 @@ void ASTDeclReader::VisitRecordDecl(RecordDecl *RD) {
       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;
     }
index ca59dd6..f7daa50 100644 (file)
@@ -491,6 +491,10 @@ void ASTDeclWriter::VisitRecordDecl(RecordDecl *D) {
   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<CXXRecordDecl>(D))
+    Record.push_back(D->getODRHash());
 
   if (D->getDeclContext() == D->getLexicalDeclContext() &&
       !D->hasAttrs() &&
@@ -2127,6 +2131,8 @@ void ASTWriter::WriteDeclAbbrevs() {
   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 (file)
index 0000000..23dbe81
--- /dev/null
@@ -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