Skip to content

Commit 03bcfb4

Browse files
committed
[lldb] Don't recursively load types of static member variables in the DWARF AST parser
When LLDB's DWARF parser is parsing the member DIEs of a struct/class it currently fully resolves the types of static member variables in a class before adding the respective `VarDecl` to the record. For record types fully resolving the type will also parse the member DIEs of the respective class. The other way of resolving is just 'forward' resolving the type which will try to load only the minimum amount of information about the type (for records that would only be the name/kind of the type). Usually we always resolve types on-demand so it's rarely useful to speculatively fully resolve them on the first use. This patch changes makes that we only 'forward' resolve the types of static members. This solves the fact that LLDB unnecessarily loads debug information to parse the type if it's maybe not needed later and it also avoids a crash where the parsed type might in turn reference the surrounding class that is currently being parsed. The new test case demonstrates the crash that might happen. The crash happens with the following steps: 1. We parse class `ToLayout` and it's members. 2. We parse the static class member and fully resolve its type (`DependsOnParam2<ToLayout>`). 3. That type has a non-static class member `DependsOnParam1<ToLayout>` for which LLDB will try to calculate the size. 4. The layout (and size)`DependsOnParam1<ToLayout>` turns depends on the `ToLayout` size/layout. 5. Clang will calculate the record layout/size for `ToLayout` even though we are currently parsing it and it's missing it's non-static member. The created is missing the offset for the yet unparsed non-static member. If we later try to get the offset we end up hitting different asserts. Most common is the one in `TypeSystemClang::DumpValue` where it checks that the record layout has offsets for the current FieldDecl. ``` assert(field_idx < record_layout.getFieldCount()); ``` Fixed rdar://67910011 Reviewed By: shafik Differential Revision: https://reviews.llvm.org/D100180 (cherry picked from commit 34c697c)
1 parent 5ac1e20 commit 03bcfb4

File tree

6 files changed

+61
-1
lines changed

6 files changed

+61
-1
lines changed

lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2533,7 +2533,7 @@ void DWARFASTParserClang::ParseSingleMember(
25332533
if (accessibility == eAccessNone)
25342534
accessibility = eAccessPublic;
25352535
TypeSystemClang::AddVariableToRecordType(
2536-
class_clang_type, name, var_type->GetLayoutCompilerType(),
2536+
class_clang_type, name, var_type->GetForwardCompilerType(),
25372537
accessibility);
25382538
}
25392539
return;

lldb/test/API/functionalities/lazy-loading/TestLazyLoading.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def setUp(self):
4040
class_in_namespace_decl = [class_decl_kind, "ClassInNamespace"]
4141
class_we_enter_decl = [class_decl_kind, "ClassWeEnter"]
4242
class_member_decl = [struct_decl_kind, "ClassMember"]
43+
class_static_member_decl = [struct_decl_kind, "StaticClassMember"]
4344
unused_class_member_decl = [struct_decl_kind, "UnusedClassMember"]
4445
unused_class_member_ptr_decl = [struct_decl_kind, "UnusedClassMemberPtr"]
4546

@@ -56,6 +57,7 @@ def assert_no_decls_loaded(self):
5657
self.assert_decl_not_loaded(self.other_struct_decl)
5758
self.assert_decl_not_loaded(self.class_in_namespace_decl)
5859
self.assert_decl_not_loaded(self.class_member_decl)
60+
self.assert_decl_not_loaded(self.class_static_member_decl)
5961
self.assert_decl_not_loaded(self.unused_class_member_decl)
6062

6163
def get_ast_dump(self):
@@ -228,6 +230,8 @@ def test_class_function_access_member(self):
228230
self.assert_decl_not_completed(self.unused_class_member_ptr_decl)
229231
# We loaded the member we used.
230232
self.assert_decl_loaded(self.class_member_decl)
233+
# We didn't load the type of the unused static member.
234+
self.assert_decl_not_completed(self.class_static_member_decl)
231235

232236
# This should not have loaded anything else.
233237
self.assert_decl_not_loaded(self.other_struct_decl)

lldb/test/API/functionalities/lazy-loading/main.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ struct OtherStruct {
2323
// Class loading declarations.
2424

2525
struct ClassMember { int i; };
26+
struct StaticClassMember { int i; };
2627
struct UnusedClassMember { int i; };
2728
struct UnusedClassMemberPtr { int i; };
2829

@@ -34,12 +35,14 @@ class ClassWeEnter {
3435
public:
3536
int dummy; // Prevent bug where LLDB always completes first member.
3637
ClassMember member;
38+
static StaticClassMember static_member;
3739
UnusedClassMember unused_member;
3840
UnusedClassMemberPtr *unused_member_ptr;
3941
int enteredFunction() {
4042
return member.i; // Location: class function
4143
}
4244
};
45+
StaticClassMember ClassWeEnter::static_member;
4346
};
4447

4548
//----------------------------------------------------------------------------//
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
CXX_SOURCES := main.cpp
2+
3+
include Makefile.rules
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import lldb
2+
from lldbsuite.test.decorators import *
3+
from lldbsuite.test.lldbtest import *
4+
from lldbsuite.test import lldbutil
5+
6+
class TestCase(TestBase):
7+
8+
mydir = TestBase.compute_mydir(__file__)
9+
10+
@no_debug_info_test
11+
def test(self):
12+
"""
13+
This tests a static member with a type which size depends on the
14+
surrounding class. LLDB should *not* try to generate the record layout
15+
for those types while parsing the members from debug info.
16+
"""
17+
self.build()
18+
self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
19+
20+
# Force the record layout for 'ToLayout' to be generated by printing
21+
# a value of it's type.
22+
self.expect("target variable test_var")
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// This class just serves as an indirection between LLDB and Clang. LLDB might
2+
// be tempted to check the member type of DependsOnParam2 for whether it's
3+
// in some 'currently-loading' state before trying to produce the record layout.
4+
// By inheriting from ToLayout this will make LLDB just check if
5+
// DependsOnParam1 is currently being loaded (which it's not) but it will
6+
template <typename ToLayoutParam> struct DependsOnParam1 : ToLayoutParam {};
7+
// This class forces the memory layout of it's type parameter to be created.
8+
template <typename ToLayoutParam> struct DependsOnParam2 {
9+
DependsOnParam1<ToLayoutParam> m;
10+
};
11+
12+
// This is the class that LLDB has to generate the record layout for.
13+
struct ToLayout {
14+
// A static member variable which memory layout depends on the surrounding
15+
// class. This comes first so that if we accidentially generate the layout
16+
// for static member types we end up recursively going back to 'ToLayout'
17+
// before 'some_member' has been loaded.
18+
static DependsOnParam2<ToLayout> a_static_member;
19+
// Some dummy member variable. This is only there so that Clang can detect
20+
// that the record layout is inconsistent (i.e., the number of fields in the
21+
// layout doesn't fit to the fields in the declaration).
22+
int some_member;
23+
};
24+
DependsOnParam2<ToLayout> ToLayout::a_static_member;
25+
26+
ToLayout test_var;
27+
28+
int main() { return test_var.some_member; }

0 commit comments

Comments
 (0)