https://github.com/Nerixyz created https://github.com/llvm/llvm-project/pull/156370
In native PDB, types of constant array members were not completed when completing a struct. This lead to an assertion failure later when trying to print a variable of the struct (`Shell/SymbolFile/PDB/udt-layout.test`, see #114906). The following shows the issue: ```cpp struct G { int foo = 1; }; struct H { G g[2]; }; H h; ``` Running `target variable h` would result in a request to complete `H`. This will run the `UdtRecordCompleter` over the struct fields. When it encounters a data member, [it tries to complete its type](https://github.com/llvm/llvm-project/blob/bf4486eb29c3009ddf68968b021c6fd98e3c2d52/lldb/source/Plugins/SymbolFile/NativePDB/UdtRecordCompleter.cpp#L260). This would previously only complete records. However, types of constant array members (in our case `G`) need to be completed as well, as they are required to compute the layout of the record. With this PR, `TypeSystemClang::RequireCompleteType` will also complete the types of constant array members. To hit the assertion, the layout needs to be requested (e.g. by getting a child member). That's why I used both `target variable` and `expression`. Unfortunately, this doesn't allow `Shell/SymbolFile/PDB/udt-layout.test` to run with the native plugin yet, because it checks for the order in which base classes are printed. The DIA plugin puts virtual bases first, while the native plugin puts them last (where they are in memory). >From 967d3453e997ed25c3548898d69a40d079d307b1 Mon Sep 17 00:00:00 2001 From: Nerixyz <nerix...@outlook.de> Date: Mon, 1 Sep 2025 21:02:33 +0200 Subject: [PATCH] [LLDB] Complete constant array member types in class members --- .../TypeSystem/Clang/TypeSystemClang.cpp | 15 ++- .../NativePDB/Inputs/incomplete-tag-type.cpp | 5 + .../NativePDB/incomplete-tag-type.cpp | 45 -------- .../NativePDB/incomplete-tag-type.test | 109 ++++++++++++++++++ 4 files changed, 126 insertions(+), 48 deletions(-) delete mode 100644 lldb/test/Shell/SymbolFile/NativePDB/incomplete-tag-type.cpp create mode 100644 lldb/test/Shell/SymbolFile/NativePDB/incomplete-tag-type.test diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp index 39aacdb58e694..038677f68b991 100644 --- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp +++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp @@ -9577,12 +9577,21 @@ TypeSystemClang::DeclContextGetTypeSystemClang(const CompilerDeclContext &dc) { } void TypeSystemClang::RequireCompleteType(CompilerType type) { + if (!type) + return; + + clang::QualType qual_type(ClangUtil::GetCanonicalQualType(type)); + if (qual_type.isNull()) + return; + // Technically, enums can be incomplete too, but we don't handle those as they // are emitted even under -flimit-debug-info. - if (!TypeSystemClang::IsCXXClassType(type)) - return; + bool is_constant_array = qual_type->isConstantArrayType(); + bool is_cxx_record = qual_type->getAsCXXRecordDecl() != nullptr; + if (is_constant_array || is_cxx_record) + type.GetCompleteType(); - if (type.GetCompleteType()) + if (!is_cxx_record) return; // No complete definition in this module. Mark the class as complete to diff --git a/lldb/test/Shell/SymbolFile/NativePDB/Inputs/incomplete-tag-type.cpp b/lldb/test/Shell/SymbolFile/NativePDB/Inputs/incomplete-tag-type.cpp index c930338905445..d08f49d1014ba 100644 --- a/lldb/test/Shell/SymbolFile/NativePDB/Inputs/incomplete-tag-type.cpp +++ b/lldb/test/Shell/SymbolFile/NativePDB/Inputs/incomplete-tag-type.cpp @@ -13,3 +13,8 @@ struct E { E(); }; E::E() = default; + +struct I { + I(); +}; +I::I() = default; diff --git a/lldb/test/Shell/SymbolFile/NativePDB/incomplete-tag-type.cpp b/lldb/test/Shell/SymbolFile/NativePDB/incomplete-tag-type.cpp deleted file mode 100644 index 7bc7e618667f7..0000000000000 --- a/lldb/test/Shell/SymbolFile/NativePDB/incomplete-tag-type.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// clang-format off -// REQUIRES: lld, x86 - -// RUN: %clang_cl --target=x86_64-windows-msvc -c /Fo%t1.obj -- %p/Inputs/incomplete-tag-type.cpp -// RUN: %clang_cl --target=x86_64-windows-msvc /O1 /Z7 -c /Fo%t2.obj -- %s -// RUN: lld-link /debug:full /nodefaultlib /entry:main %t1.obj %t2.obj /out:%t.exe /pdb:%t.pdb -// RUN: %lldb -f %t.exe -o \ -// RUN: "settings set interpreter.stop-command-source-on-error false" \ -// RUN: -o "expression b" -o "expression d" -o "expression static_e_ref" -o "exit" 2>&1 | FileCheck %s - -// CHECK: (lldb) expression b -// CHECK: (B) $0 = {} -// CHECK: (lldb) expression d -// CHECK: (D) $1 = {} -// CHECK: (lldb) expression static_e_ref -// CHECK: error:{{.*}}incomplete type 'E' where a complete type is required - -// Complete base class. -struct A { int x; A(); }; -struct B : A {}; -B b; - -// Complete data member. -struct C { - C(); -}; - -struct D { - C c; -}; -D d; - -// Incomplete static data member should return error. -struct E { - E(); -}; - -struct F { - static E static_e; -}; - -E F::static_e = E(); -E& static_e_ref = F::static_e; - -int main(){} diff --git a/lldb/test/Shell/SymbolFile/NativePDB/incomplete-tag-type.test b/lldb/test/Shell/SymbolFile/NativePDB/incomplete-tag-type.test new file mode 100644 index 0000000000000..f30866ccdd6f0 --- /dev/null +++ b/lldb/test/Shell/SymbolFile/NativePDB/incomplete-tag-type.test @@ -0,0 +1,109 @@ +# REQUIRES: lld, x86 + +# RUN: split-file %s %t + +# RUN: %clang_cl --target=x86_64-windows-msvc -c /Fo%t1.obj -- %p/Inputs/incomplete-tag-type.cpp +# RUN: %clang_cl --target=x86_64-windows-msvc /O1 /Z7 -c /Fo%t2.obj -- %t/main.cpp +# RUN: lld-link /debug:full /nodefaultlib /entry:main %t1.obj %t2.obj /out:%t.exe /pdb:%t.pdb + +# RUN: %lldb -f %t.exe -s %t/target-var.input 2>&1 | FileCheck %s --check-prefix=TARGET-VAR +# RUN: %lldb -f %t.exe -s %t/expr.input 2>&1 | FileCheck %s --check-prefix=EXPR + +#--- main.cpp + +// Complete base class. +struct A { int x; A(); }; +struct B : A {}; +B b; + +// Complete data member. +struct C { + C(); +}; + +struct D { + C c; +}; +D d; + +// Incomplete static data member should return error. +struct E { + E(); +}; + +struct F { + static E static_e; +}; + +E F::static_e = E(); +E& static_e_ref = F::static_e; + +struct G { + int foo = 1; +}; +struct H { + G g[2]; +}; +H h; + +struct I { + I(); +}; +struct J { + I i[2]; +}; +J j; + + +int main(){} + +#--- target-var.input + +target variable b +target variable d +target variable h +target variable j +target variable static_e_ref +exit + +#--- expr.input + +settings set interpreter.stop-command-source-on-error false +expression b +expression d +expression h +expression j +expression static_e_ref +exit + +# TARGET-VAR: (lldb) target variable b +# TARGET-VAR-NEXT: (B) b = (A = <incomplete type>) +# TARGET-VAR-NEXT: (lldb) target variable d +# TARGET-VAR-NEXT: (D) d = {} +# TARGET-VAR-NEXT: (lldb) target variable h +# TARGET-VAR-NEXT: (H) h = { +# TARGET-VAR-NEXT: g = { +# TARGET-VAR-NEXT: [0] = (foo = 1) +# TARGET-VAR-NEXT: [1] = (foo = 1) +# TARGET-VAR-NEXT: } +# TARGET-VAR-NEXT: } +# TARGET-VAR-NEXT: (lldb) target variable j +# TARGET-VAR-NEXT: (J) j = {} +# TARGET-VAR-NEXT: (lldb) target variable static_e_ref +# TARGET-VAR-NEXT: (E &) static_e_ref = 0x{{.*}} <incomplete type "E"> + +# EXPR: (lldb) expression b +# EXPR-NEXT: (B) $0 = {} +# EXPR-NEXT: (lldb) expression d +# EXPR-NEXT: (D) $1 = {} +# EXPR-NEXT: (lldb) expression h +# EXPR-NEXT: (H) $2 = { +# EXPR-NEXT: g = { +# EXPR-NEXT: [0] = (foo = 1) +# EXPR-NEXT: [1] = (foo = 1) +# EXPR-NEXT: } +# EXPR-NEXT: } +# EXPR-NEXT: (lldb) expression j +# EXPR-NEXT: (J) $3 = {} +# EXPR-NEXT: (lldb) expression static_e_ref +# EXPR: error:{{.*}}incomplete type 'E' where a complete type is required _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits