diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangASTImporter.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangASTImporter.cpp --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangASTImporter.cpp +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangASTImporter.cpp @@ -18,6 +18,7 @@ #include "Plugins/ExpressionParser/Clang/ClangASTImporter.h" #include "Plugins/ExpressionParser/Clang/ClangASTMetadata.h" +#include "Plugins/ExpressionParser/Clang/ClangASTSource.h" #include "Plugins/ExpressionParser/Clang/ClangExternalASTSourceCallbacks.h" #include "Plugins/ExpressionParser/Clang/ClangUtil.h" #include "Plugins/TypeSystem/Clang/TypeSystemClang.h" @@ -876,6 +877,33 @@ } } + // If we have a forcefully completed type, try to find an actual definition + // for it in other modules. + const ClangASTMetadata *md = m_master.GetDeclMetadata(From); + auto *td = dyn_cast(From); + if (td && md && md->IsForcefullyCompleted()) { + Log *log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS); + LLDB_LOG(log, + "[ClangASTImporter] Searching for a complete definition of {0} in " + "other modules", + td->getName()); + Expected dc_or_err = ImportContext(td->getDeclContext()); + if (!dc_or_err) + return dc_or_err.takeError(); + Expected dn_or_err = Import(td->getDeclName()); + if (!dn_or_err) + return dn_or_err.takeError(); + DeclContext *dc = *dc_or_err; + DeclContext::lookup_result lr = dc->lookup(*dn_or_err); + if (lr.size()) { + clang::Decl *lookup_found = lr.front(); + RegisterImportedDecl(From, lookup_found); + m_decls_to_ignore.insert(lookup_found); + return lookup_found; + } else + LLDB_LOG(log, "[ClangASTImporter] Complete definition not found"); + } + return ASTImporter::ImportImpl(From); } diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangASTMetadata.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangASTMetadata.h --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangASTMetadata.h +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangASTMetadata.h @@ -19,7 +19,8 @@ public: ClangASTMetadata() : m_user_id(0), m_union_is_user_id(false), m_union_is_isa_ptr(false), - m_has_object_ptr(false), m_is_self(false), m_is_dynamic_cxx(true) {} + m_has_object_ptr(false), m_is_self(false), m_is_dynamic_cxx(true), + m_is_forcefully_completed(false) {} bool GetIsDynamicCXXType() const { return m_is_dynamic_cxx; } @@ -83,6 +84,15 @@ bool HasObjectPtr() const { return m_has_object_ptr; } + /// A type is "forcefully completed" if it was declared complete to satisfy an + /// AST invariant (e.g. base classes must be complete types), but in fact we + /// were not able to find a actual definition for it. + bool IsForcefullyCompleted() const { return m_is_forcefully_completed; } + + void SetIsForcefullyCompleted(bool value = true) { + m_is_forcefully_completed = true; + } + void Dump(Stream *s); private: @@ -92,7 +102,7 @@ }; bool m_union_is_user_id : 1, m_union_is_isa_ptr : 1, m_has_object_ptr : 1, - m_is_self : 1, m_is_dynamic_cxx : 1; + m_is_self : 1, m_is_dynamic_cxx : 1, m_is_forcefully_completed : 1; }; } // namespace lldb_private diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp @@ -2061,26 +2061,19 @@ CompilerType base_class_type = m_ast.GetType(type_source_info->getType()); if (!base_class_type.GetCompleteType()) { - auto module = dwarf->GetObjectFile()->GetModule(); - module->ReportError(":: Class '%s' has a base class '%s' which " - "does not have a complete definition.", - die.GetName(), - base_class_type.GetTypeName().GetCString()); - if (die.GetCU()->GetProducer() == eProducerClang) - module->ReportError(":: Try compiling the source file with " - "-fstandalone-debug."); - - // We have no choice other than to pretend that the base class - // is complete. If we don't do this, clang will crash when we - // call setBases() inside of - // "clang_type.TransferBaseClasses()" below. Since we - // provide layout assistance, all ivars in this class and other - // classes will be fine, this is the best we can do short of - // crashing. + // We mark the class as complete to allow the TransferBaseClasses + // call to succeed. But we make a note of the fact that this class + // is not _really_ complete so we can later search for a definition + // in a different module. if (TypeSystemClang::StartTagDeclarationDefinition( base_class_type)) { TypeSystemClang::CompleteTagDeclarationDefinition( base_class_type); + const auto *td = TypeSystemClang::GetQualType( + base_class_type.GetOpaqueQualType()) + .getTypePtr() + ->getAsTagDecl(); + m_ast.GetMetadata(td)->SetIsForcefullyCompleted(); } } } diff --git a/lldb/test/API/functionalities/limit-debug-info/Makefile b/lldb/test/API/functionalities/limit-debug-info/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/limit-debug-info/Makefile @@ -0,0 +1,27 @@ +CFLAGS_EXTRAS = $(LIMIT_DEBUG_INFO_FLAGS) +LD_EXTRAS = -L. -lone -ltwo +CXX_SOURCES = main.cpp + +ONE_CXXFLAGS = $(LIMIT_DEBUG_INFO_FLAGS) +ifdef STRIP_ONE + ONE_CXXFLAGS += -g0 +endif + +TWO_CXXFLAGS = $(LIMIT_DEBUG_INFO_FLAGS) +ifdef STRIP_TWO + TWO_CXXFLAGS += -g0 +endif + +include Makefile.rules + +a.out: libone libtwo + +libone: + $(MAKE) -f $(MAKEFILE_RULES) \ + DYLIB_ONLY=YES DYLIB_CXX_SOURCES=one.cpp DYLIB_NAME=one \ + CFLAGS_EXTRAS="$(ONE_CXXFLAGS)" + +libtwo: libone + $(MAKE) -f $(MAKEFILE_RULES) \ + DYLIB_ONLY=YES DYLIB_CXX_SOURCES=two.cpp DYLIB_NAME=two \ + CFLAGS_EXTRAS="$(TWO_CXXFLAGS)" LD_EXTRAS="-L. -lone" diff --git a/lldb/test/API/functionalities/limit-debug-info/TestLimitDebugInfo.py b/lldb/test/API/functionalities/limit-debug-info/TestLimitDebugInfo.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/limit-debug-info/TestLimitDebugInfo.py @@ -0,0 +1,90 @@ +""" +Test completing types using information from other shared libraries. +""" + +import os +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class LimitDebugInfoTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + def _check_type(self, target, name): + exe = target.FindModule(lldb.SBFileSpec("a.out")) + type_ = exe.FindFirstType(name) + self.trace("type_: %s"%type_) + self.assertTrue(type_) + base = type_.GetDirectBaseClassAtIndex(0).GetType() + self.trace("base:%s"%base) + self.assertTrue(base) + self.assertEquals(base.GetNumberOfFields(), 0) + + def _check_debug_info_is_limited(self, target): + # Without other shared libraries we should only see the member declared + # in the derived class. This serves as a sanity check that we are truly + # building with limited debug info. + self._check_type(target, "InheritsFromOne") + self._check_type(target, "InheritsFromTwo") + + @skipIf(bugnumber="pr46284", debug_info="gmodules") + def test_one_and_two_debug(self): + self.build() + target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + + self._check_debug_info_is_limited(target) + + self.registerSharedLibrariesWithTarget(target, ["one", "two"]) + + # But when other shared libraries are loaded, we should be able to see + # all members. + self.expect_expr("inherits_from_one.member", result_value="47") + self.expect_expr("inherits_from_one.one", result_value="142") + + self.expect_expr("inherits_from_two.member", result_value="47") + self.expect_expr("inherits_from_two.one", result_value="142") + self.expect_expr("inherits_from_two.two", result_value="242") + + @skipIf(bugnumber="pr46284", debug_info="gmodules") + def test_two_debug(self): + self.build(dictionary=dict(STRIP_ONE="1")) + target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + + self._check_debug_info_is_limited(target) + + self.registerSharedLibrariesWithTarget(target, ["one", "two"]) + + # This time, we should only see the members from the second library. + self.expect_expr("inherits_from_one.member", result_value="47") + self.expect("expr inherits_from_one.one", error=True, + substrs=["no member named 'one' in 'InheritsFromOne'"]) + + self.expect_expr("inherits_from_two.member", result_value="47") + self.expect("expr inherits_from_two.one", error=True, + substrs=["no member named 'one' in 'InheritsFromTwo'"]) + self.expect_expr("inherits_from_two.two", result_value="242") + + @skipIf(bugnumber="pr46284", debug_info="gmodules") + def test_one_debug(self): + self.build(dictionary=dict(STRIP_TWO="1")) + target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + + self._check_debug_info_is_limited(target) + + self.registerSharedLibrariesWithTarget(target, ["one", "two"]) + + # In this case we should only see the members from the second library. + # Note that we cannot see inherits_from_two.one because without debug + # info for "Two", we cannot determine that it in fact inherits from + # "One". + self.expect_expr("inherits_from_one.member", result_value="47") + self.expect_expr("inherits_from_one.one", result_value="142") + + self.expect_expr("inherits_from_two.member", result_value="47") + self.expect("expr inherits_from_two.one", error=True, + substrs=["no member named 'one' in 'InheritsFromTwo'"]) + self.expect("expr inherits_from_two.two", error=True, + substrs=["no member named 'two' in 'InheritsFromTwo'"]) diff --git a/lldb/test/API/functionalities/limit-debug-info/foo.cpp b/lldb/test/API/functionalities/limit-debug-info/foo.cpp new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/limit-debug-info/foo.cpp @@ -0,0 +1,6 @@ +struct A { + int a = 47; + virtual ~A(); +}; + +A::~A() = default; diff --git a/lldb/test/API/functionalities/limit-debug-info/main.cpp b/lldb/test/API/functionalities/limit-debug-info/main.cpp new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/limit-debug-info/main.cpp @@ -0,0 +1,13 @@ +#include "onetwo.h" + +struct InheritsFromOne : One { + constexpr InheritsFromOne() = default; + int member = 47; +} inherits_from_one; + +struct InheritsFromTwo : Two { + constexpr InheritsFromTwo() = default; + int member = 47; +} inherits_from_two; + +int main() { return 0; } diff --git a/lldb/test/API/functionalities/limit-debug-info/one.cpp b/lldb/test/API/functionalities/limit-debug-info/one.cpp new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/limit-debug-info/one.cpp @@ -0,0 +1,3 @@ +#include "onetwo.h" + +One::~One() = default; diff --git a/lldb/test/API/functionalities/limit-debug-info/onetwo.h b/lldb/test/API/functionalities/limit-debug-info/onetwo.h new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/limit-debug-info/onetwo.h @@ -0,0 +1,11 @@ +struct One { + int one = 142; + constexpr One() = default; + virtual ~One(); +}; + +struct Two : One { + int two = 242; + constexpr Two() = default; + ~Two() override; +}; diff --git a/lldb/test/API/functionalities/limit-debug-info/two.cpp b/lldb/test/API/functionalities/limit-debug-info/two.cpp new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/limit-debug-info/two.cpp @@ -0,0 +1,3 @@ +#include "onetwo.h" + +Two::~Two() = default;