Skip to content

Commit 3bf96b0

Browse files
committed
[lldb] Disable minimal import mode for RecordDecls that back FieldDecls
Clang adds a Decl in two phases to a DeclContext. First it adds it invisible and then it makes it visible (which will add it to the lookup data structures). It's important that we can't do lookups into the DeclContext we are currently adding the Decl to during this process as once the Decl has been added, any lookup will automatically build a new lookup map and add the added Decl to it. The second step would then add the Decl a second time to the lookup which will lead to weird errors later one. I made adding a Decl twice to a lookup an assertion error in D84827. In the first step Clang also does some computations on the added Decl if it's for example a FieldDecl that is added to a RecordDecl. One of these computations is checking if the FieldDecl is of a record type and the record type has a deleted constexpr destructor which will delete the constexpr destructor of the record that got the FieldDecl. This can lead to a bug with the way we implement MinimalImport in LLDB and the following code: ``` struct Outer { typedef int HookToOuter; struct NestedClass { HookToOuter RefToOuter; } NestedClassMember; // We are adding this. }; ``` 1. We just imported `Outer` minimally so far. 2. We are now asked to add `NestedClassMember` as a FieldDecl. 3. We import `NestedClass` minimally. 4. We add `NestedClassMember` and clang does a lookup for a constexpr dtor in `NestedClass`. `NestedClassMember` hasn't been added to the lookup. 5. The lookup into `NestedClass` will now load the members of `NestedClass`. 6. We try to import the type of `RefToOuter` which will try to import the `HookToOuter` typedef. 7. We import the typedef and while importing we check for conflicts in `Outer` via a lookup. 8. The lookup into `Outer` will cause the invisible `NestedClassMember` to be added to the lookup. 9. We continue normally until we get back to the `addDecl` call in step 2. 10. We now add `NestedClassMember` to the lookup even though we already did that in step 8. The fix here is disabling the minimal import for RecordTypes from FieldDecls. We actually already did this, but so far we only force the definition of the type to be imported *after* we imported the FieldDecl. This just moves that code *before* we import the FieldDecl so prevent the issue above. Reviewed By: shafik, aprantl Differential Revision: https://reviews.llvm.org/D102993
1 parent 8b656b8 commit 3bf96b0

File tree

5 files changed

+74
-10
lines changed

5 files changed

+74
-10
lines changed

lldb/source/Plugins/ExpressionParser/Clang/ClangASTImporter.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,37 @@ ClangASTImporter::ASTImporterDelegate::ImportImpl(Decl *From) {
888888
LLDB_LOG(log, "[ClangASTImporter] Complete definition not found");
889889
}
890890

891+
// Disable the minimal import for fields that have record types. There is
892+
// no point in minimally importing the record behind their type as Clang
893+
// will anyway request their definition when the FieldDecl is added to the
894+
// RecordDecl (as Clang will query the FieldDecl's type for things such
895+
// as a deleted constexpr destructor).
896+
// By importing the type ahead of time we avoid some corner cases where
897+
// the FieldDecl's record is importing in the middle of Clang's
898+
// `DeclContext::addDecl` logic.
899+
if (clang::FieldDecl *fd = dyn_cast<FieldDecl>(From)) {
900+
// This is only necessary because we do the 'minimal import'. Remove this
901+
// once LLDB stopped using that mode.
902+
assert(isMinimalImport() && "Only necessary for minimal import");
903+
QualType field_type = fd->getType();
904+
if (field_type->isRecordType()) {
905+
// First get the underlying record and minimally import it.
906+
clang::TagDecl *record_decl = field_type->getAsTagDecl();
907+
llvm::Expected<Decl *> imported = Import(record_decl);
908+
if (!imported)
909+
return imported.takeError();
910+
// Check how/if the import got redirected to a different AST. Now
911+
// import the definition of what was actually imported. If there is no
912+
// origin then that means the record was imported by just picking a
913+
// compatible type in the target AST (in which case there is no more
914+
// importing to do).
915+
if (clang::Decl *origin = m_master.GetDeclOrigin(*imported).decl) {
916+
if (llvm::Error def_err = ImportDefinition(record_decl))
917+
return std::move(def_err);
918+
}
919+
}
920+
}
921+
891922
return ASTImporter::ImportImpl(From);
892923
}
893924

lldb/source/Plugins/ExpressionParser/Clang/ClangASTSource.cpp

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -479,10 +479,7 @@ void ClangASTSource::FindExternalLexicalDecls(
479479
decl->getDeclKindName(), ast_dump);
480480
}
481481

482-
Decl *copied_decl = CopyDecl(decl);
483-
484-
if (!copied_decl)
485-
continue;
482+
CopyDecl(decl);
486483

487484
// FIXME: We should add the copied decl to the 'decls' list. This would
488485
// add the copied Decl into the DeclContext and make sure that we
@@ -492,12 +489,6 @@ void ClangASTSource::FindExternalLexicalDecls(
492489
// lookup issues later on.
493490
// We can't just add them for now as the ASTImporter already added the
494491
// decl into the DeclContext and this would add it twice.
495-
496-
if (FieldDecl *copied_field = dyn_cast<FieldDecl>(copied_decl)) {
497-
QualType copied_field_type = copied_field->getType();
498-
499-
m_ast_importer_sp->RequireCompleteType(copied_field_type);
500-
}
501492
} else {
502493
SkippedDecls = true;
503494
}
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
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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+
def test(self):
11+
self.build()
12+
self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
13+
test_var = self.expect_expr("test_var", result_type="In")
14+
nested_member = test_var.GetChildMemberWithName('NestedClassMember')
15+
self.assertEqual("Outer::NestedClass",
16+
nested_member.GetType().GetName())
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
struct Outer {
2+
typedef int HookToOuter;
3+
// When importing this type, we have to import all of it before adding it
4+
// via the FieldDecl to 'Outer'. If we don't do this, then Clang will
5+
// while adding 'NestedClassMember' ask for the full type of 'NestedClass'
6+
// which then will start pulling in the 'RefToOuter' member. That member
7+
// will import the typedef above and add it to 'Outer'. And adding a
8+
// Decl to a DeclContext that is currently already in the process of adding
9+
// another Decl will cause an inconsistent lookup.
10+
struct NestedClass {
11+
HookToOuter RefToOuter;
12+
int SomeMember;
13+
} NestedClassMember;
14+
};
15+
16+
// We query the members of base classes of a type by doing a lookup via Clang.
17+
// As this tests is trying to find a borked lookup, we need a base class here
18+
// to make our 'GetChildMemberWithName' use the Clang lookup.
19+
struct In : Outer {};
20+
21+
In test_var;
22+
23+
int main() { return test_var.NestedClassMember.SomeMember; }

0 commit comments

Comments
 (0)