Author: Clayton Knittel Date: 2026-09-23T13:59:15-07:00 New Revision: 1ff88ee1b0710bab455f1da22f7c7f7b36389cda
URL: https://github.com/llvm/llvm-project/commit/1ff88ee1b0710bab455f1da22f7c7f7b36389cda DIFF: https://github.com/llvm/llvm-project/commit/1ff88ee1b0710bab455f1da22f7c7f7b36389cda.diff LOG: Always emit complete debug info for types which appear as a member of a standard-layout union. (#224439) [[class.mem]](https://timsong-cpp.github.io/cppwp/n3337/class.mem#19) states: > "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. --------- Signed-off-by: Clayton Knittel <[email protected]> Added: Modified: clang/lib/CodeGen/CGDebugInfo.cpp clang/test/DebugInfo/CXX/limited-ctor.cpp Removed: ################################################################################ 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..1f1cd2abd50c2 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,242 @@ constexpr DelegatingConstexprOutOfLine::DelegatingConstexprOutOfLine() : DelegatingConstexprOutOfLine(42) {} constexpr DelegatingConstexprOutOfLine::DelegatingConstexprOutOfLine(int) {} +// Test that a standard layout type in a union emits full debug info. +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "SLInUnion"{{.*}}DIFlagTypePassByValue +struct SLInUnion { + int x; + SLInUnion(int); +}; + +union SLUnion { + SLInUnion u; +}; +void TestSLUnion(SLUnion) {} + +// 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: "ParentSLBase"{{.*}}DIFlagTypePassByValue +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "ChildSL"{{.*}}DIFlagTypePassByValue +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "ParentSL"{{.*}}DIFlagTypePassByValue +struct ParentSLBase{ + ParentSLBase(); +}; +struct ChildSL { + int b; + ChildSL(); +}; +struct ParentSL : ParentSLBase { + ChildSL f; + ParentSL(); +}; +union FollowMembers { + ParentSL a; + int b; +}; +void TestFollowMembers(FollowMembers) {} + +// Test that a template has its debug info emitted when in a standard-layout +// union. +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "TemplatedSL<int>"{{.*}}DIFlagTypePassByValue +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "TemplatedSL<float>"{{.*}}DIFlagTypePassByValue +template <typename T> +struct TemplatedSL { + T x; + TemplatedSL(T); +}; + +union TemplatedUnion { + TemplatedSL<int> a; + TemplatedSL<float> b; +}; +void TestTemplatedUnion(TemplatedUnion) {} + +// Test that a standard layout type in a non-standard-layout union does not +// emit full debug info. +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "SLInNonSLUnion"{{.*}}flags: DIFlagFwdDecl +struct NonSLBase { + int x; +}; +struct NonSL : NonSLBase { + int x; + NonSL(int); +}; + +struct SLInNonSLUnion { + int x; + SLInNonSLUnion(int); +}; + +union NonSLUnion { + SLInNonSLUnion s; + NonSL n; +}; +void TestNonSLUnion(NonSLUnion) {} + +// Test that a type nested in a standard-layout union follows the same rules +// and emits full debug info. +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "NestedSL"{{.*}}DIFlagTypePassByValue +union NestedUnion { + struct NestedSL { + int a; + NestedSL(int); + } n; +}; +void TestNestedUnion(NestedUnion) {} + +// Test that recursive type completion happens through arrays. +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "SLInArray"{{.*}}DIFlagTypePassByValue +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "SLInMultiArray"{{.*}}DIFlagTypePassByValue +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 TestArrayUnion(ArrayUnion) {} + +// Test that recursive type completion ignores cv-qualifiers. +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "SLConst"{{.*}}DIFlagTypePassByValue +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "SLVolatile"{{.*}}DIFlagTypePassByValue +struct SLConst { + int x; + SLConst(int); +}; +struct SLVolatile { + int y; + SLVolatile(int); +}; +union CVUnion { + const SLConst c; + volatile SLVolatile v; + int raw; +}; +void TestCVUnion(CVUnion) {} + +// Test that recursive type completion happens through templated fields. +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "SLInGenericUnion"{{.*}}DIFlagTypePassByValue +template <typename T> +union GenericUnion { + T val; + int raw; +}; +struct SLInGenericUnion { + int x; + SLInGenericUnion(int); +}; +void TestGenericUnion(GenericUnion<SLInGenericUnion>) {} + +// Test that recursive type completion happens for anonymous standard-layout +// unions. +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "SLInAnonUnion"{{.*}}DIFlagTypePassByValue +struct SLInAnonUnion { + int x; + SLInAnonUnion(int); +}; +struct EnclosingStruct { + union { + SLInAnonUnion a; + int b; + } u; +}; +void TestEnclosingStruct(EnclosingStruct) {} + +// Test that recursive type completion follows inheritence of typedefs. +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "SLDerived"{{.*}}DIFlagTypePassByValue +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "EmptyBase"{{.*}}DIFlagTypePassByValue +typedef struct EmptyBase { + EmptyBase(int); +} EmptyBaseAlias; +struct SLDerived : EmptyBaseAlias { + int y; + SLDerived(int); +}; +union TypedefDerivedUnion { + SLDerived d; + int raw; +}; +void TestTypedefDerivedUnion(TypedefDerivedUnion) {} + +// Test that recursive type completion follows multiple inheritence. +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "SLMultipleDerived"{{.*}}DIFlagTypePassByValue +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "EmptyBase1"{{.*}}DIFlagTypePassByValue +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "EmptyBase2"{{.*}}DIFlagTypePassByValue +struct EmptyBase1 { + EmptyBase1(int); +}; +struct EmptyBase2 { + EmptyBase2(int); +}; +struct SLMultipleDerived : EmptyBase1, EmptyBase2 { + int x; + SLMultipleDerived(int); +}; +union MultipleDerivedUnion { + SLMultipleDerived d; + int raw; +}; +void TestMultipleDerivedUnion(MultipleDerivedUnion) {} + +// Test that recursive type completion follows inheritence of non-empty bases. +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "EmptyDerived"{{.*}}DIFlagTypePassByValue +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "SLBase"{{.*}}DIFlagTypePassByValue +struct SLBase { + int y; + SLBase(int); +}; +struct EmptyDerived : SLBase { + EmptyDerived(int); +}; +union EmptyDerivedUnion { + EmptyDerived d; + int raw; +}; +void TestEmptyDerivedUnion(EmptyDerivedUnion) {} + +// Test that recursive type completion does not follow types through pointers. +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "SLPointerInUnion"{{.*}}flags: DIFlagFwdDecl +struct SLPointerInUnion { + int x; + SLPointerInUnion(int); +}; + +union SLUnionPointer { + SLPointerInUnion *u; +}; +void TestSLUnionPointer(SLUnionPointer) {} + +// Test that recursive type completion does not follow through pointer or +// reference members. +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "SLPointerAndReferenceMembers"{{.*}}DIFlagTypePassByValue +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "SLPointerMember"{{.*}}flags: DIFlagFwdDecl +// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "SLReferenceMember"{{.*}}flags: DIFlagFwdDecl +struct SLPointerMember { + int x; + SLPointerMember(int); +}; + +struct SLReferenceMember { + int x; + SLReferenceMember(int); +}; + +struct SLPointerAndReferenceMembers { + SLPointerMember *a; + SLReferenceMember &b; +}; + +union SLPointerAndReferenceMembersUnion { + SLPointerAndReferenceMembers a; +}; +void TestSLPointerAndReferenceMembers(SLPointerAndReferenceMembersUnion) {} + // Test for trivial constructor. // CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "F"{{.*}}DIFlagTypePassByValue struct F { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
