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,27 @@ } } + const ClangASTMetadata *md = m_master.GetDeclMetadata(From); + auto *td = dyn_cast(From); + if (td && md && md->IsForcefullyCompleted()) { + if (auto *proxy = llvm::dyn_cast( + getToContext().getExternalSource())) { + if (TagDecl *complete = proxy->GetOriginalSource().FindCompleteType(td)) { + ImporterDelegateSP delegate_sp = + m_master.GetDelegate(&getToContext(), &complete->getASTContext()); + + Expected imported = delegate_sp->ImportImpl(complete); + if (imported) { + RegisterImportedDecl(From, *imported); + m_decls_to_ignore.insert(*imported); + m_master.CompleteTagDeclWithOrigin(cast(*imported), + complete); + } + return imported; + } + } + } + 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,12 @@ bool HasObjectPtr() const { return m_has_object_ptr; } + 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 +99,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/ExpressionParser/Clang/ClangASTSource.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangASTSource.h --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangASTSource.h +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangASTSource.h @@ -213,7 +213,16 @@ /// Clang AST contexts like to own their AST sources, so this is a state- /// free proxy object. class ClangASTSourceProxy : public clang::ExternalASTSource { + /// LLVM RTTI support. + static char ID; + public: + /// LLVM RTTI support. + bool isA(const void *ClassID) const override { return ClassID == &ID; } + static bool classof(const clang::ExternalASTSource *s) { + return s->isA(&ID); + } + ClangASTSourceProxy(ClangASTSource &original) : m_original(original) {} bool FindExternalVisibleDeclsByName(const clang::DeclContext *DC, @@ -251,6 +260,8 @@ return m_original.StartTranslationUnit(Consumer); } + ClangASTSource &GetOriginalSource() { return m_original; } + private: ClangASTSource &m_original; }; @@ -356,6 +367,8 @@ /// ExternalASTSource. TypeSystemClang *GetTypeSystem() const { return m_clang_ast_context; } + clang::TagDecl *FindCompleteType(const clang::TagDecl *decl); + protected: bool FindObjCMethodDeclsWithOrigin( NameSearchContext &context, diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangASTSource.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangASTSource.cpp --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangASTSource.cpp +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangASTSource.cpp @@ -50,6 +50,8 @@ }; } +char ClangASTSource::ClangASTSourceProxy::ID; + ClangASTSource::ClangASTSource( const lldb::TargetSP &target, const std::shared_ptr &importer) @@ -185,127 +187,125 @@ return (name_decls.size() != 0); } -void ClangASTSource::CompleteType(TagDecl *tag_decl) { +TagDecl *ClangASTSource::FindCompleteType(const TagDecl *decl) { Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS)); - if (log) { - LLDB_LOG(log, - " CompleteTagDecl on (ASTContext*){0} Completing " - "(TagDecl*){1} named {2}", - m_clang_ast_context->getDisplayName(), tag_decl, - tag_decl->getName()); - - LLDB_LOG(log, " CTD Before:\n{0}", ClangUtil::DumpDecl(tag_decl)); - } - - auto iter = m_active_lexical_decls.find(tag_decl); - if (iter != m_active_lexical_decls.end()) - return; - m_active_lexical_decls.insert(tag_decl); - ScopedLexicalDeclEraser eraser(m_active_lexical_decls, tag_decl); + if (const NamespaceDecl *namespace_context = + dyn_cast(decl->getDeclContext())) { + ClangASTImporter::NamespaceMapSP namespace_map = + m_ast_importer_sp->GetNamespaceMap(namespace_context); - if (!m_ast_importer_sp->CompleteTagDecl(tag_decl)) { - // We couldn't complete the type. Maybe there's a definition somewhere - // else that can be completed. + LLDB_LOGV(log, " CTD Inspecting namespace map{0} ({1} entries)", + namespace_map.get(), namespace_map->size()); - LLDB_LOG(log, " CTD Type could not be completed in the module in " - "which it was first found."); + if (!namespace_map) + return nullptr; - bool found = false; + for (const ClangASTImporter::NamespaceMapItem &item : *namespace_map) { + LLDB_LOG(log, " CTD Searching namespace {0} in module {1}", + item.second.GetName(), item.first->GetFileSpec().GetFilename()); - DeclContext *decl_ctx = tag_decl->getDeclContext(); + TypeList types; - if (const NamespaceDecl *namespace_context = - dyn_cast(decl_ctx)) { - ClangASTImporter::NamespaceMapSP namespace_map = - m_ast_importer_sp->GetNamespaceMap(namespace_context); + ConstString name(decl->getName()); - LLDB_LOGV(log, " CTD Inspecting namespace map{0} ({1} entries)", - namespace_map.get(), namespace_map->size()); + item.first->FindTypesInNamespace(name, item.second, UINT32_MAX, types); - if (!namespace_map) - return; + for (uint32_t ti = 0, te = types.GetSize(); ti != te; ++ti) { + lldb::TypeSP type = types.GetTypeAtIndex(ti); - for (ClangASTImporter::NamespaceMap::iterator i = namespace_map->begin(), - e = namespace_map->end(); - i != e && !found; ++i) { - LLDB_LOG(log, " CTD Searching namespace {0} in module {1}", - i->second.GetName(), i->first->GetFileSpec().GetFilename()); + if (!type) + continue; - TypeList types; + CompilerType clang_type(type->GetFullCompilerType()); - ConstString name(tag_decl->getName().str().c_str()); + if (!ClangUtil::IsClangType(clang_type)) + continue; - i->first->FindTypesInNamespace(name, i->second, UINT32_MAX, types); + const TagType *tag_type = + ClangUtil::GetQualType(clang_type)->getAs(); - for (uint32_t ti = 0, te = types.GetSize(); ti != te && !found; ++ti) { - lldb::TypeSP type = types.GetTypeAtIndex(ti); + if (!tag_type) + continue; - if (!type) - continue; + TagDecl *candidate_tag_decl = + const_cast(tag_type->getDecl()); - CompilerType clang_type(type->GetFullCompilerType()); + if (TypeSystemClang::GetCompleteDecl( + &candidate_tag_decl->getASTContext(), candidate_tag_decl)) + return candidate_tag_decl; + } + } + } else { + TypeList types; - if (!ClangUtil::IsClangType(clang_type)) - continue; + ConstString name(decl->getName()); - const TagType *tag_type = - ClangUtil::GetQualType(clang_type)->getAs(); + const ModuleList &module_list = m_target->GetImages(); - if (!tag_type) - continue; + bool exact_match = false; + llvm::DenseSet searched_symbol_files; + module_list.FindTypes(nullptr, name, exact_match, UINT32_MAX, + searched_symbol_files, types); - TagDecl *candidate_tag_decl = - const_cast(tag_type->getDecl()); + for (uint32_t ti = 0, te = types.GetSize(); ti != te; ++ti) { + lldb::TypeSP type = types.GetTypeAtIndex(ti); - if (m_ast_importer_sp->CompleteTagDeclWithOrigin(tag_decl, - candidate_tag_decl)) - found = true; - } - } - } else { - TypeList types; + if (!type) + continue; - ConstString name(tag_decl->getName().str().c_str()); + CompilerType clang_type(type->GetFullCompilerType()); - const ModuleList &module_list = m_target->GetImages(); + if (!ClangUtil::IsClangType(clang_type)) + continue; - bool exact_match = false; - llvm::DenseSet searched_symbol_files; - module_list.FindTypes(nullptr, name, exact_match, UINT32_MAX, - searched_symbol_files, types); + const TagType *tag_type = + ClangUtil::GetQualType(clang_type)->getAs(); - for (uint32_t ti = 0, te = types.GetSize(); ti != te && !found; ++ti) { - lldb::TypeSP type = types.GetTypeAtIndex(ti); + if (!tag_type) + continue; - if (!type) - continue; + TagDecl *candidate_tag_decl = const_cast(tag_type->getDecl()); - CompilerType clang_type(type->GetFullCompilerType()); + // We have found a type by basename and we need to make sure the decl + // contexts are the same before we can try to complete this type with + // another + if (!TypeSystemClang::DeclsAreEquivalent(const_cast(decl), + candidate_tag_decl)) + continue; - if (!ClangUtil::IsClangType(clang_type)) - continue; + if (TypeSystemClang::GetCompleteDecl(&candidate_tag_decl->getASTContext(), + candidate_tag_decl)) + return candidate_tag_decl; + } + } + return nullptr; +} - const TagType *tag_type = - ClangUtil::GetQualType(clang_type)->getAs(); +void ClangASTSource::CompleteType(TagDecl *tag_decl) { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS)); - if (!tag_type) - continue; + if (log) { + LLDB_LOG(log, + " CompleteTagDecl on (ASTContext*){0} Completing " + "(TagDecl*){1} named {2}", + m_clang_ast_context->getDisplayName(), tag_decl, + tag_decl->getName()); - TagDecl *candidate_tag_decl = - const_cast(tag_type->getDecl()); + LLDB_LOG(log, " CTD Before:\n{0}", ClangUtil::DumpDecl(tag_decl)); + } - // We have found a type by basename and we need to make sure the decl - // contexts are the same before we can try to complete this type with - // another - if (!TypeSystemClang::DeclsAreEquivalent(tag_decl, candidate_tag_decl)) - continue; + auto iter = m_active_lexical_decls.find(tag_decl); + if (iter != m_active_lexical_decls.end()) + return; + m_active_lexical_decls.insert(tag_decl); + ScopedLexicalDeclEraser eraser(m_active_lexical_decls, tag_decl); - if (m_ast_importer_sp->CompleteTagDeclWithOrigin(tag_decl, - candidate_tag_decl)) - found = true; - } - } + if (!m_ast_importer_sp->CompleteTagDecl(tag_decl)) { + // We couldn't complete the type. Maybe there's a definition somewhere + // else that can be completed. + if (TagDecl *alternate = FindCompleteType(tag_decl)) + m_ast_importer_sp->CompleteTagDeclWithOrigin(tag_decl, alternate); } LLDB_LOG(log, " [CTD] After:\n{0}", ClangUtil::DumpDecl(tag_decl)); diff --git a/lldb/source/Plugins/ExpressionParser/Clang/CxxModuleHandler.cpp b/lldb/source/Plugins/ExpressionParser/Clang/CxxModuleHandler.cpp --- a/lldb/source/Plugins/ExpressionParser/Clang/CxxModuleHandler.cpp +++ b/lldb/source/Plugins/ExpressionParser/Clang/CxxModuleHandler.cpp @@ -7,6 +7,8 @@ //===----------------------------------------------------------------------===// #include "Plugins/ExpressionParser/Clang/CxxModuleHandler.h" +#include "Plugins/ExpressionParser/Clang/ClangASTImporter.h" +#include "Plugins/ExpressionParser/Clang/ClangASTMetadata.h" #include "Plugins/TypeSystem/Clang/TypeSystemClang.h" #include "lldb/Utility/Log.h" 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 := -flimit-debug-info +LD_EXTRAS := -L. -lone -ltwo +CXX_SOURCES := main.cpp + +ONE_CXXFLAGS := -flimit-debug-info +ifdef STRIP_ONE + ONE_CXXFLAGS += -g0 +endif + +TWO_CXXFLAGS := -flimit-debug-info +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,85 @@ +""" +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")) + the_type = exe.FindFirstType(name) + self.assertTrue(the_type) + self.trace("the_type: %s"%the_type) + field_names = map(lambda f: f.GetName(), the_type.get_fields_array()) + self.assertEquals(list(field_names), ["member"]) + + 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") + + 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") + + 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") + + 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,15 @@ +#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,4 @@ +#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;