llvmorg-github-actions[bot] wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-debuginfo

Author: Clayton Knittel (ClaytonKnittel)

<details>
<summary>Changes</summary>

[[class.mem]](https://timsong-cpp.github.io/cppwp/n3337/class.mem#<!-- -->19) 
states:

&gt; "If a standard-layout union contains two or more standard-layout structs 
that share a common initial sequence, and if the standard-layout union object 
currently contains one of these standard-layout structs, it is permitted to 
inspect the common initial part of any of them. Two standard-layout structs 
share a common initial sequence if corresponding members have layout-compatible 
types and either neither member is a bit-field or both are bit-fields with the 
same width for a sequence of one or more initial members."

This makes it possible to obtain a reference to a type which was never 
constructed, which violates the assumption made by constructor homing that all 
types that may require debug info must be constructed.

This change takes the conservative approach of always emitting full debug info 
for standard-layout types which appear as a member of a standard-layout union 
somewhere in the TU. It could be made more aggressive by only emitting full 
debug info if there is another type in the union that shares a "common initial 
sequence", but that would add complexity to this exclusion logic and likely 
isn't worth it.

---
Full diff: https://github.com/llvm/llvm-project/pull/224439.diff


5 Files Affected:

- (modified) clang/lib/CodeGen/CGDebugInfo.cpp (+55) 
- (modified) clang/test/DebugInfo/CXX/limited-ctor.cpp (+41-1) 
- (added) clang/unittests/CodeGen/CGDebugInfoTest.cpp (+420) 
- (modified) clang/unittests/CodeGen/CMakeLists.txt (+1) 
- (modified) clang/unittests/CodeGen/TestCompiler.h (+8-3) 


``````````diff
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp 
b/clang/lib/CodeGen/CGDebugInfo.cpp
index 02864621d60a3..4d5c70b87f802 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -3357,6 +3357,46 @@ llvm::DIType *CGDebugInfo::GetPreferredNameType(const 
CXXRecordDecl *RD,
   return getOrCreateType(PNA->getTypedefType(), Unit);
 }
 
+static void completeStandardLayoutUnionType(CGDebugInfo &DebugInfo,
+                                            QualType QT);
+
+static void completeStandardLayoutUnionMembers(CGDebugInfo &DebugInfo,
+                                               const CXXRecordDecl *RD) {
+  for (const CXXBaseSpecifier &BS : RD->bases())
+    completeStandardLayoutUnionType(DebugInfo, BS.getType());
+
+  for (const FieldDecl *FD : RD->fields()) {
+    // Invalid declarations are skipped when determining the field layout of
+    // unions. This will of course cause a compiler error, but skip these
+    // fields anyway to avoid triggering the `isStandardLayout()` assertion in
+    // `completeStandardLayoutUnionType`.
+    if (FD->isInvalidDecl())
+      continue;
+    completeStandardLayoutUnionType(DebugInfo,
+                                    FD->getType()
+                                        ->getBaseElementTypeUnsafe()
+                                        ->getCanonicalTypeUnqualified());
+  }
+}
+
+static void completeStandardLayoutUnionType(CGDebugInfo &DebugInfo,
+                                            QualType QT) {
+  const auto *RT = QT->getAs<RecordType>();
+  if (!RT)
+    return;
+
+  auto *CRD = dyn_cast<CXXRecordDecl>(RT->getDecl()->getDefinitionOrSelf());
+  if (!CRD || !CRD->hasDefinition())
+    return;
+
+  // We checked at the root that this is a standard-layout type, which
+  // requires all its members / base types to be standard-layout.
+  assert(CRD->isStandardLayout());
+
+  DebugInfo.completeClassData(CRD);
+  completeStandardLayoutUnionMembers(DebugInfo, CRD);
+}
+
 std::pair<llvm::DIType *, llvm::DIType *>
 CGDebugInfo::CreateTypeDefinition(const RecordType *Ty) {
   RecordDecl *RD = Ty->getDecl()->getDefinitionOrSelf();
@@ -3414,6 +3454,21 @@ CGDebugInfo::CreateTypeDefinition(const RecordType *Ty) {
 
   RegionMap[RD].reset(FwdDecl);
 
+  if (DebugKind == llvm::codegenoptions::DebugInfoConstructor) {
+    // For standard-layout unions, recursively emit full debug info for all
+    // user-defined types (and their bases/fields) in the union. Per the C++
+    // spec, "it is permitted to inspect the common initial part of any of" the
+    // "common initial sequence" of distinct types in a standard-layout union.
+    // This exception to strict aliasing enables producing a reference to a
+    // type without ever having constructed that type, breaking the assumption
+    // made by constructor homing that all interesting types we'd want debug
+    // info for must have been constructed.
+    //
+    // See: https://wg21.link/class.mem#general-30
+    if (CXXDecl && CXXDecl->isUnion() && CXXDecl->isStandardLayout())
+      completeStandardLayoutUnionMembers(*this, CXXDecl);
+  }
+
   if (CGM.getCodeGenOpts().getDebuggerTuning() == llvm::DebuggerKind::LLDB)
     if (auto *PrefDI = GetPreferredNameType(CXXDecl, DefUnit))
       return {FwdDecl, PrefDI};
diff --git a/clang/test/DebugInfo/CXX/limited-ctor.cpp 
b/clang/test/DebugInfo/CXX/limited-ctor.cpp
index e820c0703df4f..fe6ea1c6d4d56 100644
--- a/clang/test/DebugInfo/CXX/limited-ctor.cpp
+++ b/clang/test/DebugInfo/CXX/limited-ctor.cpp
@@ -53,7 +53,7 @@ struct DeclaredConstexpr {
 template <class A, class B> struct Aliased {
   A first;
   B second;
-  constexpr Aliased(const A &a, const B &b) : first(a), second(b) {}
+  Aliased(const A &a, const B &b) : first(a), second(b) {}
 };
 union AliasedSlot {
   Aliased<const int, int> value;
@@ -66,6 +66,24 @@ int ReadAliasedSlot() {
   return TestAliasedSlot.value.first;
 }
 
+// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: 
"ConstexprAliased<int, int>"{{.*}}DIFlagTypePassByValue
+// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: 
"ConstexprAliased<const int, int>"{{.*}}DIFlagTypePassByValue
+template <class A, class B> struct ConstexprAliased {
+  A first;
+  B second;
+  constexpr ConstexprAliased(const A &a, const B &b) : first(a), second(b) {}
+};
+union ConstexprAliasedSlot {
+  ConstexprAliased<const int, int> value;
+  ConstexprAliased<int, int> mutable_value;
+  ConstexprAliasedSlot() {}
+  ~ConstexprAliasedSlot() {}
+} TestConstexprAliasedSlot;
+int ReadConstexprAliasedSlot() {
+  TestConstexprAliasedSlot.mutable_value = ConstexprAliased<int, int>(1, 2);
+  return TestConstexprAliasedSlot.value.first;
+}
+
 // Defined out-of-line constexpr constructor should emit full debug info.
 // CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: 
"OutOfLineConstexpr"{{.*}}DIFlagTypePassByValue
 struct OutOfLineConstexpr {
@@ -167,6 +185,28 @@ constexpr 
DelegatingConstexprOutOfLine::DelegatingConstexprOutOfLine()
     : DelegatingConstexprOutOfLine(42) {}
 constexpr DelegatingConstexprOutOfLine::DelegatingConstexprOutOfLine(int) {}
 
+// Test that all types and their bases/fields in a standard-layout union are
+// emitted with full debug info.
+// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: 
"NestedBase"{{.*}}DIFlagTypePassByValue
+// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: 
"NestedField"{{.*}}DIFlagTypePassByValue
+// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: 
"NestedOuter"{{.*}}DIFlagTypePassByValue
+struct NestedBase {
+  NestedBase();
+};
+struct NestedField {
+  int b;
+  NestedField();
+};
+struct NestedOuter : NestedBase {
+  NestedField f;
+  NestedOuter();
+};
+union NestedUnion {
+  NestedOuter out;
+  int raw;
+};
+void TestNestedUnion(NestedUnion) {}
+
 // Test for trivial constructor.
 // CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: 
"F"{{.*}}DIFlagTypePassByValue
 struct F {
diff --git a/clang/unittests/CodeGen/CGDebugInfoTest.cpp 
b/clang/unittests/CodeGen/CGDebugInfoTest.cpp
new file mode 100644
index 0000000000000..a6e4ddebde8ff
--- /dev/null
+++ b/clang/unittests/CodeGen/CGDebugInfoTest.cpp
@@ -0,0 +1,420 @@
+//=== unittests/CodeGen/CGDebugInfoTest.cpp - CGDebugInfo tests 
-----------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "TestCompiler.h"
+#include "clang/Basic/CodeGenOptions.h"
+#include "clang/Basic/LangOptions.h"
+#include "llvm/IR/DebugInfo.h"
+#include "llvm/IR/DebugInfoMetadata.h"
+#include "llvm/Support/Casting.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+
+namespace {
+
+class StandardLayoutUnionDebugInfoTest : public ::testing::Test {
+protected:
+  TestCompiler Compiler;
+  DebugInfoFinder Finder;
+
+  static clang::CodeGenOptions getCodeGenOpts() {
+    clang::CodeGenOptions CGOpts;
+    CGOpts.setDebugInfo(llvm::codegenoptions::DebugInfoConstructor);
+    return CGOpts;
+  }
+
+  static clang::LangOptions getLangOpts() {
+    clang::LangOptions LO;
+    LO.CPlusPlus = LO.CPlusPlus11 = 1;
+    return LO;
+  }
+
+  StandardLayoutUnionDebugInfoTest()
+      : Compiler(getLangOpts(), getCodeGenOpts()) {}
+
+  void compile(const char *Code) {
+    Compiler.init(Code);
+    Finder.reset();
+    Finder.processModule(*Compiler.compileModule());
+  }
+
+  const DICompositeType *findCompositeType(StringRef Name) const {
+    for (DIType *T : Finder.types()) {
+      if (T->getName() != Name)
+        continue;
+      while (auto *DT = dyn_cast_or_null<DIDerivedType>(T))
+        T = DT->getBaseType();
+      return dyn_cast_or_null<DICompositeType>(T);
+    }
+    return nullptr;
+  }
+
+  bool isCompleteType(StringRef Name) const {
+    const auto *CT = findCompositeType(Name);
+    return CT && !CT->isForwardDecl();
+  }
+
+  bool isForwardDecl(StringRef Name) const {
+    const auto *CT = findCompositeType(Name);
+    return CT && CT->isForwardDecl();
+  }
+};
+
+// Sanity test that structs without a visible constructor definition will emit
+// forward declarations of their debug info. The rest of the tests in this file
+// rely on this behavior.
+TEST_F(StandardLayoutUnionDebugInfoTest, StandaloneStruct) {
+  compile(R"cc(
+    struct StandaloneSL {
+      int x;
+      StandaloneSL(int);
+    };
+    void f(StandaloneSL) {}
+  )cc");
+
+  EXPECT_TRUE(isForwardDecl("StandaloneSL"));
+}
+
+TEST_F(StandardLayoutUnionDebugInfoTest, NonStandardLayoutStruct) {
+  compile(R"cc(
+    struct NonSLBase {
+      int x;
+    };
+    struct NonSL : NonSLBase {
+      int y;
+      NonSL(int);
+    };
+    void f(NonSL) {}
+  )cc");
+
+  EXPECT_TRUE(isForwardDecl("NonSL"));
+}
+
+TEST_F(StandardLayoutUnionDebugInfoTest, StandardLayoutUnion) {
+  compile(R"cc(
+    struct SLInUnion {
+      int x;
+      SLInUnion(int);
+    };
+
+    union SLUnion {
+      SLInUnion u;
+    };
+    void f(SLUnion) {}
+  )cc");
+
+  EXPECT_TRUE(isCompleteType("SLInUnion"));
+}
+
+TEST_F(StandardLayoutUnionDebugInfoTest, RecurseFieldTypes) {
+  compile(R"cc(
+    struct SLMember {
+      int x;
+      SLMember(int);
+    };
+
+    struct SLInUnion {
+      SLMember x;
+      SLInUnion(int);
+    };
+
+    union SLUnion {
+      SLInUnion u;
+    };
+    void f(SLUnion) {}
+  )cc");
+
+  EXPECT_TRUE(isCompleteType("SLInUnion"));
+  EXPECT_TRUE(isCompleteType("SLMember"));
+}
+
+TEST_F(StandardLayoutUnionDebugInfoTest, TemplatedMember) {
+  compile(R"cc(
+    template <typename T>
+    struct TemplatedSL {
+      T x;
+      TemplatedSL(T);
+    };
+
+    union TemplatedUnion {
+      TemplatedSL<int> a;
+      TemplatedSL<float> b;
+    };
+    void f(TemplatedUnion) {}
+  )cc");
+
+  EXPECT_TRUE(isCompleteType("TemplatedSL<int>"));
+  EXPECT_TRUE(isCompleteType("TemplatedSL<float>"));
+}
+
+TEST_F(StandardLayoutUnionDebugInfoTest, NonStandardLayoutUnion) {
+  compile(R"cc(
+    struct NonSLBase {
+      int x;
+    };
+    struct NonSL : NonSLBase {
+      int x;
+      NonSL(int);
+    };
+
+    struct SL {
+      int x;
+      SL(int);
+    };
+
+    union NonSLUnion {
+      SL s;
+      NonSL n;
+    };
+    void f(NonSLUnion) {}
+  )cc");
+
+  EXPECT_TRUE(isForwardDecl("SL"));
+  EXPECT_TRUE(isForwardDecl("NonSL"));
+}
+
+TEST_F(StandardLayoutUnionDebugInfoTest, NestedStruct) {
+  compile(R"cc(
+    union NestedUnion {
+      struct NestedSL {
+        int a;
+        NestedSL(int);
+      } n;
+    };
+    void f(NestedUnion) {}
+  )cc");
+
+  EXPECT_TRUE(isCompleteType("NestedSL"));
+}
+
+TEST_F(StandardLayoutUnionDebugInfoTest, Array) {
+  compile(R"cc(
+    struct SLInArray {
+      int x;
+      SLInArray(int);
+    };
+    struct SLInMultiArray {
+      int y;
+      SLInMultiArray(int);
+    };
+    union ArrayUnion {
+      SLInArray arr[3];
+      SLInMultiArray multi_arr[2][4];
+      int raw;
+    };
+    void f(ArrayUnion) {}
+  )cc");
+
+  EXPECT_TRUE(isCompleteType("SLInArray"));
+  EXPECT_TRUE(isCompleteType("SLInMultiArray"));
+}
+
+TEST_F(StandardLayoutUnionDebugInfoTest, CVQualified) {
+  compile(R"cc(
+    struct SLConst {
+      int x;
+      SLConst(int);
+    };
+    struct SLVolatile {
+      int y;
+      SLVolatile(int);
+    };
+    union CVUnion {
+      const SLConst c;
+      volatile SLVolatile v;
+      int raw;
+    };
+    void f(CVUnion) {}
+  )cc");
+
+  EXPECT_TRUE(isCompleteType("SLConst"));
+  EXPECT_TRUE(isCompleteType("SLVolatile"));
+}
+
+TEST_F(StandardLayoutUnionDebugInfoTest, GenericUnionTemplate) {
+  compile(R"cc(
+    template <typename T>
+    union GenericUnion {
+      T val;
+      int raw;
+    };
+    struct SLInGenericUnion {
+      int x;
+      SLInGenericUnion(int);
+    };
+    void f(GenericUnion<SLInGenericUnion>) {}
+  )cc");
+
+  EXPECT_TRUE(isCompleteType("SLInGenericUnion"));
+}
+
+TEST_F(StandardLayoutUnionDebugInfoTest, AnonymousUnion) {
+  compile(R"cc(
+    struct SLInAnonUnion {
+      int x;
+      SLInAnonUnion(int);
+    };
+    struct EnclosingStruct {
+      union {
+        SLInAnonUnion a;
+        int b;
+      };
+    };
+    void f(EnclosingStruct) {}
+  )cc");
+
+  EXPECT_TRUE(isCompleteType("SLInAnonUnion"));
+}
+
+TEST_F(StandardLayoutUnionDebugInfoTest, Inheritance) {
+  compile(R"cc(
+    struct EmptyBase {
+      EmptyBase(int);
+    };
+    struct SLDerived : EmptyBase {
+      int x;
+      SLDerived(int);
+    };
+    union DerivedUnion {
+      SLDerived d;
+      int raw;
+    };
+    void f(DerivedUnion) {}
+  )cc");
+
+  EXPECT_TRUE(isCompleteType("SLDerived"));
+  EXPECT_TRUE(isCompleteType("EmptyBase"));
+}
+
+TEST_F(StandardLayoutUnionDebugInfoTest, TypedefInheritance) {
+  compile(R"cc(
+    typedef struct EmptyBase {
+      EmptyBase(int);
+    } EmptyBaseAlias;
+    struct SLDerived : EmptyBaseAlias {
+      int y;
+      SLDerived(int);
+    };
+    union DerivedUnion {
+      SLDerived d;
+      int raw;
+    };
+    void f(DerivedUnion) {}
+  )cc");
+
+  EXPECT_TRUE(isCompleteType("SLDerived"));
+  EXPECT_TRUE(isCompleteType("EmptyBase"));
+}
+
+TEST_F(StandardLayoutUnionDebugInfoTest, MultipleInheritance) {
+  compile(R"cc(
+    struct EmptyBase1 {
+      EmptyBase1(int);
+    };
+    struct EmptyBase2 {
+      EmptyBase2(int);
+    };
+    struct SLDerived : EmptyBase1, EmptyBase2 {
+      int x;
+      SLDerived(int);
+    };
+    union DerivedUnion {
+      SLDerived d;
+      int raw;
+    };
+    void f(DerivedUnion) {}
+  )cc");
+
+  EXPECT_TRUE(isCompleteType("SLDerived"));
+  EXPECT_TRUE(isCompleteType("EmptyBase1"));
+  EXPECT_TRUE(isCompleteType("EmptyBase2"));
+}
+
+TEST_F(StandardLayoutUnionDebugInfoTest, InheritanceNonEmptyBase) {
+  compile(R"cc(
+    struct SLBase {
+      int y;
+      SLBase(int);
+    };
+    struct EmptyDerived : SLBase {
+      EmptyDerived(int);
+    };
+    union DerivedUnion {
+      EmptyDerived d;
+      int raw;
+    };
+    void f(DerivedUnion) {}
+  )cc");
+
+  EXPECT_TRUE(isCompleteType("EmptyDerived"));
+  EXPECT_TRUE(isCompleteType("SLBase"));
+}
+
+TEST_F(StandardLayoutUnionDebugInfoTest, TypedefInheritanceNonEmptyBase) {
+  compile(R"cc(
+    typedef struct SLBase {
+      int y;
+      SLBase(int);
+    } SLBaseAlias;
+    struct EmptyDerived : SLBaseAlias {
+      EmptyDerived(int);
+    };
+    union DerivedUnion {
+      EmptyDerived d;
+      int raw;
+    };
+    void f(DerivedUnion) {}
+  )cc");
+
+  EXPECT_TRUE(isCompleteType("EmptyDerived"));
+  EXPECT_TRUE(isCompleteType("SLBase"));
+}
+
+TEST_F(StandardLayoutUnionDebugInfoTest, RecurseInheritance) {
+  compile(R"cc(
+    struct SLBaseMember {
+      int x;
+      SLBaseMember(int);
+    };
+    struct SLBase {
+      SLBaseMember y;
+      SLBase(int);
+    };
+    struct EmptyDerived : SLBase {
+      EmptyDerived(int);
+    };
+    union DerivedUnion {
+      EmptyDerived d;
+      int raw;
+    };
+    void f(DerivedUnion) {}
+  )cc");
+
+  EXPECT_TRUE(isCompleteType("EmptyDerived"));
+  EXPECT_TRUE(isCompleteType("SLBase"));
+  EXPECT_TRUE(isCompleteType("SLBaseMember"));
+}
+
+TEST_F(StandardLayoutUnionDebugInfoTest, IgnorePointerMembers) {
+  compile(R"cc(
+    struct SLPointerInUnion {
+      int x;
+      SLPointerInUnion(int);
+    };
+
+    union SLUnion {
+      SLPointerInUnion* u;
+    };
+    void f(SLUnion) {}
+  )cc");
+
+  EXPECT_TRUE(isForwardDecl("SLPointerInUnion"));
+}
+
+} // namespace
diff --git a/clang/unittests/CodeGen/CMakeLists.txt 
b/clang/unittests/CodeGen/CMakeLists.txt
index d4efb2230a054..72bc0de6db67b 100644
--- a/clang/unittests/CodeGen/CMakeLists.txt
+++ b/clang/unittests/CodeGen/CMakeLists.txt
@@ -1,5 +1,6 @@
 add_clang_unittest(ClangCodeGenTests
   BufferSourceTest.cpp
+  CGDebugInfoTest.cpp
   CodeGenExternalTest.cpp
   DemangleTrapReasonInDebugInfo.cpp
   TBAAMetadataTest.cpp
diff --git a/clang/unittests/CodeGen/TestCompiler.h 
b/clang/unittests/CodeGen/TestCompiler.h
index 3ba839979b867..9ed3d1b565ef8 100644
--- a/clang/unittests/CodeGen/TestCompiler.h
+++ b/clang/unittests/CodeGen/TestCompiler.h
@@ -78,10 +78,15 @@ struct TestCompiler {
         llvm::MemoryBuffer::getMemBuffer(TestProgram), clang::SrcMgr::C_User));
   }
 
-  const BasicBlock *compile() {
+  llvm::Module *compileModule() {
     clang::ParseAST(compiler.getSema(), false, false);
-    M =
-      
static_cast<clang::CodeGenerator&>(compiler.getASTConsumer()).GetModule();
+    M = static_cast<clang::CodeGenerator &>(compiler.getASTConsumer())
+            .GetModule();
+    return M;
+  }
+
+  const BasicBlock *compile() {
+    compileModule();
 
     // Do not expect more than one function definition.
     auto FuncPtr = M->begin();

``````````

</details>


https://github.com/llvm/llvm-project/pull/224439
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to