bruno created this revision.
bruno added reviewers: rsmith, arphaman, vsapsai, martong.
Herald added subscribers: cfe-commits, ributzka, dexonsmith, jkorous, rnkovacs.
Herald added a reviewer: jdoerfert.
Herald added a project: clang.

Take `struct Z {...}` defined differently and imported from both modules
X and Y. While in C/Objective-C mode, clang used to pick one of the
definitions and ignore the other even though they are not structurally
equivalent. Hook up a mechanism to check for it and reject such
conflicts. Instead of silently compiling, clang now emits:

In module 'Y' imported from t.m:2:
./y.h:2:8: error: type 'struct Z' has incompatible definitions in different 
translation units
struct Z {

  ^

./y.h:3:10: note: field 'm' has type 'double' here

  double m;
         ^

./x.h:3:7: note: field 'm' has type 'int' here

  int m;
      ^

rdar://problem/56764293


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D71734

Files:
  clang/include/clang/AST/ASTStructuralEquivalence.h
  clang/include/clang/Serialization/ASTReader.h
  clang/lib/AST/ASTStructuralEquivalence.cpp
  clang/lib/Serialization/ASTReader.cpp
  clang/lib/Serialization/ASTReaderDecl.cpp
  clang/test/Modules/Inputs/merge-in-c/tag-types/module.modulemap
  clang/test/Modules/Inputs/merge-in-c/tag-types/x.h
  clang/test/Modules/Inputs/merge-in-c/tag-types/y.h
  clang/test/Modules/merge-tag-types.c

Index: clang/test/Modules/merge-tag-types.c
===================================================================
--- /dev/null
+++ clang/test/Modules/merge-tag-types.c
@@ -0,0 +1,14 @@
+// RUN: rm -rf %t
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/cache -x objective-c -I%S/Inputs/merge-in-c/tag-types -verify %s
+
+@import Y;
+@import X;
+
+void foo() {
+  struct Z z;
+  z.m = 2.0;
+}
+
+// expected-error@y.h:2 {{type 'struct Z' has incompatible definitions in different translation units}}
+// expected-note@y.h:3 {{field 'm' has type 'double' here}}
+// expected-note@x.h:3 {{field 'm' has type 'int' here}}
Index: clang/test/Modules/Inputs/merge-in-c/tag-types/y.h
===================================================================
--- /dev/null
+++ clang/test/Modules/Inputs/merge-in-c/tag-types/y.h
@@ -0,0 +1,4 @@
+
+struct Z {
+  double m;
+};
Index: clang/test/Modules/Inputs/merge-in-c/tag-types/x.h
===================================================================
--- /dev/null
+++ clang/test/Modules/Inputs/merge-in-c/tag-types/x.h
@@ -0,0 +1,4 @@
+
+struct Z {
+  int m;
+};
Index: clang/test/Modules/Inputs/merge-in-c/tag-types/module.modulemap
===================================================================
--- /dev/null
+++ clang/test/Modules/Inputs/merge-in-c/tag-types/module.modulemap
@@ -0,0 +1,10 @@
+
+module X {
+  header "x.h"
+  export *
+}
+
+module Y {
+  header "y.h"
+  export *
+}
Index: clang/lib/Serialization/ASTReaderDecl.cpp
===================================================================
--- clang/lib/Serialization/ASTReaderDecl.cpp
+++ clang/lib/Serialization/ASTReaderDecl.cpp
@@ -14,6 +14,7 @@
 #include "ASTCommon.h"
 #include "ASTReaderInternals.h"
 #include "clang/AST/ASTContext.h"
+#include "clang/AST/ASTStructuralEquivalence.h"
 #include "clang/AST/Attr.h"
 #include "clang/AST/AttrIterator.h"
 #include "clang/AST/Decl.h"
@@ -723,8 +724,22 @@
     llvm_unreachable("unexpected tag info kind");
   }
 
-  if (!isa<CXXRecordDecl>(TD))
-    mergeRedeclarable(TD, Redecl);
+  if (isa<CXXRecordDecl>(TD))
+    return Redecl;
+
+  mergeRedeclarable(TD, Redecl);
+  // Handle merging in C/Objective-C
+  if (!Reader.getContext().getLangOpts().CPlusPlus) {
+    auto *Canon = TD->getCanonicalDecl();
+    if (TD == Canon)
+      return Redecl;
+
+    // Only check decls loaded from different external sources and
+    // delay the actual checking using PendingStructuralMismatches.
+    if (TD->getGlobalID() != Canon->getGlobalID())
+      Reader.PendingStructuralMismatches[Canon].push_back(TD);
+  }
+
   return Redecl;
 }
 
Index: clang/lib/Serialization/ASTReader.cpp
===================================================================
--- clang/lib/Serialization/ASTReader.cpp
+++ clang/lib/Serialization/ASTReader.cpp
@@ -10,14 +10,14 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include "clang/Serialization/ASTRecordReader.h"
 #include "ASTCommon.h"
 #include "ASTReaderInternals.h"
-#include "clang/AST/AbstractTypeReader.h"
 #include "clang/AST/ASTConsumer.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/ASTMutationListener.h"
+#include "clang/AST/ASTStructuralEquivalence.h"
 #include "clang/AST/ASTUnresolvedSet.h"
+#include "clang/AST/AbstractTypeReader.h"
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclBase.h"
 #include "clang/AST/DeclCXX.h"
@@ -30,8 +30,8 @@
 #include "clang/AST/ExprCXX.h"
 #include "clang/AST/ExternalASTSource.h"
 #include "clang/AST/NestedNameSpecifier.h"
-#include "clang/AST/OpenMPClause.h"
 #include "clang/AST/ODRHash.h"
+#include "clang/AST/OpenMPClause.h"
 #include "clang/AST/RawCommentList.h"
 #include "clang/AST/TemplateBase.h"
 #include "clang/AST/TemplateName.h"
@@ -75,6 +75,7 @@
 #include "clang/Sema/Weak.h"
 #include "clang/Serialization/ASTBitCodes.h"
 #include "clang/Serialization/ASTDeserializationListener.h"
+#include "clang/Serialization/ASTRecordReader.h"
 #include "clang/Serialization/ContinuousRangeMap.h"
 #include "clang/Serialization/GlobalModuleIndex.h"
 #include "clang/Serialization/InMemoryModuleCache.h"
@@ -9263,6 +9264,30 @@
   PendingMergedDefinitionsToDeduplicate.clear();
 }
 
+void ASTReader::diagnoseStructuralMismatches() {
+  if (PendingStructuralMismatches.empty())
+    return;
+
+  // Updates while doing structural equivalence checks may in turn find and
+  // diagnose other failures, so take ownership of it first.
+  auto StructuralMismatches = std::move(PendingStructuralMismatches);
+  PendingStructuralMismatches.clear();
+
+  for (auto &Mismatches : StructuralMismatches) {
+    auto *Canon = Mismatches.first;
+    for (auto *D : Mismatches.second) {
+      llvm::DenseSet<std::pair<Decl *, Decl *>> NonEquivalentDecls;
+
+      StructuralEquivalenceContext Ctx(
+          D->getASTContext(), Canon->getASTContext(), NonEquivalentDecls,
+          StructuralEquivalenceKind::Default, false /*StrictTypeSpelling*/,
+          true /*Complain*/, true /*ErrorOnTagTypeMismatch*/,
+          false /*UseCanonicalDecls*/);
+      (void)Ctx.IsEquivalent(D, Canon);
+    }
+  }
+}
+
 void ASTReader::diagnoseOdrViolations() {
   if (PendingOdrMergeFailures.empty() && PendingOdrMergeChecks.empty() &&
       PendingFunctionOdrMergeFailures.empty() &&
@@ -11381,6 +11406,7 @@
       ReadTimer->stopTimer();
 
     diagnoseOdrViolations();
+    diagnoseStructuralMismatches();
 
     // We are not in recursive loading, so it's safe to pass the "interesting"
     // decls to the consumer.
Index: clang/lib/AST/ASTStructuralEquivalence.cpp
===================================================================
--- clang/lib/AST/ASTStructuralEquivalence.cpp
+++ clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -1574,8 +1574,10 @@
                                      Decl *D1, Decl *D2) {
   // FIXME: Check for known structural equivalences via a callback of some sort.
 
-  D1 = D1->getCanonicalDecl();
-  D2 = D2->getCanonicalDecl();
+  if (Context.UseCanonicalDecls) {
+    D1 = D1->getCanonicalDecl();
+    D2 = D2->getCanonicalDecl();
+  }
   std::pair<Decl *, Decl *> P{D1, D2};
 
   // Check whether we already know that these two declarations are not
Index: clang/include/clang/Serialization/ASTReader.h
===================================================================
--- clang/include/clang/Serialization/ASTReader.h
+++ clang/include/clang/Serialization/ASTReader.h
@@ -1084,6 +1084,10 @@
   /// been completed.
   std::deque<PendingDeclContextInfo> PendingDeclContextInfos;
 
+  /// C/ObjC definitions in which the structural equivalence check fails
+  llvm::SmallDenseMap<NamedDecl *, llvm::SmallVector<NamedDecl *, 2>, 2>
+      PendingStructuralMismatches;
+
   /// The set of NamedDecls that have been loaded, but are members of a
   /// context that has been merged into another context where the corresponding
   /// declaration is either missing or has not yet been loaded.
@@ -1412,6 +1416,7 @@
 
   void finishPendingActions();
   void diagnoseOdrViolations();
+  void diagnoseStructuralMismatches();
 
   void pushExternalDeclIntoScope(NamedDecl *D, DeclarationName Name);
 
Index: clang/include/clang/AST/ASTStructuralEquivalence.h
===================================================================
--- clang/include/clang/AST/ASTStructuralEquivalence.h
+++ clang/include/clang/AST/ASTStructuralEquivalence.h
@@ -69,15 +69,20 @@
   /// \c true if the last diagnostic came from ToCtx.
   bool LastDiagFromC2 = false;
 
+  /// \c true if canonical types should be used for each Decl before
+  /// comparing for structural equivalnce
+  bool UseCanonicalDecls;
+
   StructuralEquivalenceContext(
       ASTContext &FromCtx, ASTContext &ToCtx,
       llvm::DenseSet<std::pair<Decl *, Decl *>> &NonEquivalentDecls,
-      StructuralEquivalenceKind EqKind,
-      bool StrictTypeSpelling = false, bool Complain = true,
-      bool ErrorOnTagTypeMismatch = false)
+      StructuralEquivalenceKind EqKind, bool StrictTypeSpelling = false,
+      bool Complain = true, bool ErrorOnTagTypeMismatch = false,
+      bool UseCanonicalDecls = true)
       : FromCtx(FromCtx), ToCtx(ToCtx), NonEquivalentDecls(NonEquivalentDecls),
         EqKind(EqKind), StrictTypeSpelling(StrictTypeSpelling),
-        ErrorOnTagTypeMismatch(ErrorOnTagTypeMismatch), Complain(Complain) {}
+        ErrorOnTagTypeMismatch(ErrorOnTagTypeMismatch), Complain(Complain),
+        UseCanonicalDecls(UseCanonicalDecls) {}
 
   DiagnosticBuilder Diag1(SourceLocation Loc, unsigned DiagID);
   DiagnosticBuilder Diag2(SourceLocation Loc, unsigned DiagID);
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to