diff --git a/lldb/bindings/interface/SBType.i b/lldb/bindings/interface/SBType.i --- a/lldb/bindings/interface/SBType.i +++ b/lldb/bindings/interface/SBType.i @@ -867,6 +867,9 @@ bool IsTypeComplete (); + bool + IsTypeForcefullyCompleted (); + %feature("docstring", "Returns the `TypeFlags` values for this type. diff --git a/lldb/include/lldb/API/SBType.h b/lldb/include/lldb/API/SBType.h --- a/lldb/include/lldb/API/SBType.h +++ b/lldb/include/lldb/API/SBType.h @@ -205,6 +205,29 @@ bool IsTypeComplete(); + /// Return true if this type should have been complete in the debug + /// information but it the full definition was omitted. + /// + /// Returns true for types that were incomplete in the debug information but + /// should have been complete. When the debugger constructs types, we must + /// have enough information to reconstruct a type. When we need a complete + /// type, such as when a class A inherits from class B, we need a full + /// definition for class B so that we can construct class A. Another instance + /// is we have a variable that is an instance of a type (not just a pointer or + /// reference) we must also have the full definition of the class in order to + /// display the variable to the user. Compiler options (-flimit-debug-info) + /// can cause the compiler to not emit full debug information for some types + /// in order to reduce debug information size. The assumption is the full + /// definition of this type will be available in some of the debug information + /// in a debug session, possibly even in another executable or shared + /// library's debug information. If a full definition for a type was required + /// but we can't find a full definition, this function will return true. Type + /// objects may return true to SBType::IsTypeComplete() as the types were + /// internally made complete to allow the type that depends on this type to be + /// created, but this function indicates that debug information should have + /// had a complete definition for the type, but we were not able to find one. + bool IsTypeForcefullyCompleted(); + uint32_t GetTypeFlags(); bool GetDescription(lldb::SBStream &description, diff --git a/lldb/include/lldb/Symbol/CompilerType.h b/lldb/include/lldb/Symbol/CompilerType.h --- a/lldb/include/lldb/Symbol/CompilerType.h +++ b/lldb/include/lldb/Symbol/CompilerType.h @@ -152,6 +152,8 @@ bool GetCompleteType() const; /// \} + bool IsForcefullyCompleted() const; + /// AST related queries. /// \{ size_t GetPointerByteSize() const; diff --git a/lldb/include/lldb/Symbol/TypeSystem.h b/lldb/include/lldb/Symbol/TypeSystem.h --- a/lldb/include/lldb/Symbol/TypeSystem.h +++ b/lldb/include/lldb/Symbol/TypeSystem.h @@ -201,6 +201,10 @@ virtual bool GetCompleteType(lldb::opaque_compiler_type_t type) = 0; + virtual bool IsForcefullyCompleted(lldb::opaque_compiler_type_t type) { + return false; + } + // AST related queries virtual uint32_t GetPointerByteSize() = 0; diff --git a/lldb/source/API/SBType.cpp b/lldb/source/API/SBType.cpp --- a/lldb/source/API/SBType.cpp +++ b/lldb/source/API/SBType.cpp @@ -582,6 +582,13 @@ return eTemplateArgumentKindNull; } +bool SBType::IsTypeForcefullyCompleted() { + LLDB_INSTRUMENT_VA(this); + if (IsValid()) + return m_opaque_sp->GetCompilerType(false).IsForcefullyCompleted(); + return false; +} + SBTypeList::SBTypeList() : m_opaque_up(new TypeListImpl()) { LLDB_INSTRUMENT_VA(this); } diff --git a/lldb/source/Core/ValueObject.cpp b/lldb/source/Core/ValueObject.cpp --- a/lldb/source/Core/ValueObject.cpp +++ b/lldb/source/Core/ValueObject.cpp @@ -594,6 +594,14 @@ const TypeSummaryOptions &options) { destination.clear(); + // If we have a forcefully completed type, don't try and show a summary from + // a valid summary string or function because the type is not complete and + // no member variables or member functions will be available. + if (GetCompilerType().IsForcefullyCompleted()) { + destination = ""; + return true; + } + // ideally we would like to bail out if passing NULL, but if we do so we end // up not providing the summary for function pointers anymore if (/*summary_ptr == NULL ||*/ m_flags.m_is_getting_summary) diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h --- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h +++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h @@ -303,8 +303,16 @@ static clang::AccessSpecifier UnifyAccessSpecifiers(clang::AccessSpecifier lhs, clang::AccessSpecifier rhs); - static uint32_t GetNumBaseClasses(const clang::CXXRecordDecl *cxx_record_decl, - bool omit_empty_base_classes); + uint32_t GetNumBaseClasses(const clang::CXXRecordDecl *cxx_record_decl, + bool omit_empty_base_classes); + + uint32_t GetIndexForRecordChild(const clang::RecordDecl *record_decl, + clang::NamedDecl *canonical_decl, + bool omit_empty_base_classes); + + uint32_t GetIndexForRecordBase(const clang::RecordDecl *record_decl, + const clang::CXXBaseSpecifier *base_spec, + bool omit_empty_base_classes); /// Synthesize a clang::Module and return its ID or a default-constructed ID. OptionalClangModuleID GetOrCreateClangModule(llvm::StringRef name, @@ -374,7 +382,9 @@ bool FieldIsBitfield(clang::FieldDecl *field, uint32_t &bitfield_bit_size); - static bool RecordHasFields(const clang::RecordDecl *record_decl); + bool RecordHasFields(const clang::RecordDecl *record_decl); + + bool BaseSpecifierIsEmpty(const clang::CXXBaseSpecifier *b); CompilerType CreateObjCClass(llvm::StringRef name, clang::DeclContext *decl_ctx, @@ -641,6 +651,8 @@ bool GetCompleteType(lldb::opaque_compiler_type_t type) override; + bool IsForcefullyCompleted(lldb::opaque_compiler_type_t type) override; + // Accessors ConstString GetTypeName(lldb::opaque_compiler_type_t type, diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp --- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp +++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp @@ -1803,6 +1803,17 @@ return true; } } + + // We always want forcefully completed types to show up so we can print a + // message in the summary that indicates that the type is incomplete. + // This will help users know when they are running into issues with + // -flimit-debug-info instead of just seeing nothing if this is a base class + // (since we were hiding empty base classes), or nothing when you turn open + // an valiable whose type was incomplete. + ClangASTMetadata *meta_data = GetMetadata(record_decl); + if (meta_data && meta_data->IsForcefullyCompleted()) + return true; + return false; } @@ -1830,7 +1841,7 @@ return GetType(ast.getObjCInterfaceType(decl)); } -static inline bool BaseSpecifierIsEmpty(const CXXBaseSpecifier *b) { +bool TypeSystemClang::BaseSpecifierIsEmpty(const CXXBaseSpecifier *b) { return !TypeSystemClang::RecordHasFields(b->getType()->getAsCXXRecordDecl()); } @@ -6593,9 +6604,10 @@ return CompilerType(); } -static uint32_t GetIndexForRecordBase(const clang::RecordDecl *record_decl, - const clang::CXXBaseSpecifier *base_spec, - bool omit_empty_base_classes) { +uint32_t TypeSystemClang::GetIndexForRecordBase( + const clang::RecordDecl *record_decl, + const clang::CXXBaseSpecifier *base_spec, + bool omit_empty_base_classes) { uint32_t child_idx = 0; const clang::CXXRecordDecl *cxx_record_decl = @@ -6620,9 +6632,9 @@ return UINT32_MAX; } -static uint32_t GetIndexForRecordChild(const clang::RecordDecl *record_decl, - clang::NamedDecl *canonical_decl, - bool omit_empty_base_classes) { +uint32_t TypeSystemClang::GetIndexForRecordChild( + const clang::RecordDecl *record_decl, clang::NamedDecl *canonical_decl, + bool omit_empty_base_classes) { uint32_t child_idx = TypeSystemClang::GetNumBaseClasses( llvm::dyn_cast(record_decl), omit_empty_base_classes); @@ -9988,3 +10000,19 @@ m_isolated_asts[feature] = std::move(new_ast); return *m_isolated_asts[feature]; } + +bool TypeSystemClang::IsForcefullyCompleted(lldb::opaque_compiler_type_t type) { + if (type) { + clang::QualType qual_type(GetQualType(type)); + const clang::RecordType *record_type = + llvm::dyn_cast(qual_type.getTypePtr()); + if (record_type) { + const clang::RecordDecl *record_decl = record_type->getDecl(); + assert(record_decl); + ClangASTMetadata *metadata = GetMetadata(record_decl); + if (metadata) + return metadata->IsForcefullyCompleted(); + } + } + return false; +} diff --git a/lldb/source/Symbol/CompilerType.cpp b/lldb/source/Symbol/CompilerType.cpp --- a/lldb/source/Symbol/CompilerType.cpp +++ b/lldb/source/Symbol/CompilerType.cpp @@ -86,6 +86,12 @@ return false; } +bool CompilerType::IsForcefullyCompleted() const { + if (IsValid()) + return m_type_system->IsForcefullyCompleted(m_type); + return false; +} + bool CompilerType::IsConst() const { if (IsValid()) return m_type_system->IsConst(m_type); diff --git a/lldb/test/API/functionalities/limit-debug-info/TestLimitDebugInfo.py b/lldb/test/API/functionalities/limit-debug-info/TestLimitDebugInfo.py --- a/lldb/test/API/functionalities/limit-debug-info/TestLimitDebugInfo.py +++ b/lldb/test/API/functionalities/limit-debug-info/TestLimitDebugInfo.py @@ -16,10 +16,14 @@ type_ = exe.FindFirstType(name) self.trace("type_: %s"%type_) self.assertTrue(type_) + self.assertTrue(type_.IsTypeComplete()) + self.assertFalse(type_.IsTypeForcefullyCompleted()) base = type_.GetDirectBaseClassAtIndex(0).GetType() self.trace("base:%s"%base) self.assertTrue(base) self.assertEquals(base.GetNumberOfFields(), 0) + self.assertTrue(base.IsTypeComplete()) + self.assertTrue(base.IsTypeForcefullyCompleted()) def _check_debug_info_is_limited(self, target): # Without other shared libraries we should only see the member declared @@ -28,6 +32,100 @@ self._check_type(target, "InheritsFromOne") self._check_type(target, "InheritsFromTwo") + def _check_incomplete_frame_variable_output(self): + # Check that the display of the "frame variable" output identifies the + # incomplete types. Currently the expression parser will find the real + # definition for a type when running an expression for any forcefully + # completed types, but "frame variable" won't. I hope to fix this with + # a follow up patch, but if we don't find the actual definition we + # should clearly show this to the user by showing which types were + # incomplete. So this will test verifies the expected output for such + # types. We also need to verify the standard "frame variable" output + # which will inline all of the members on one line, versus the full + # output from "frame variable --raw" and a few other options. + # self.expect("frame variable two_as_member", error=True, + # substrs=["no member named 'one' in 'InheritsFromOne'"]) + + command_expect_pairs = [ + # Test standard "frame variable" output for types to make sure + # "" shows up where we expect it to + ["var two_as_member", [ + "(TwoAsMember) ::two_as_member = (two = , member = 47)"] + ], + ["var inherits_from_one", [ + "(InheritsFromOne) ::inherits_from_one = (One = , member = 47)"] + ], + ["var inherits_from_two", [ + "(InheritsFromTwo) ::inherits_from_two = (Two = , member = 47)"] + ], + ["var one_as_member", [ + "(OneAsMember) ::one_as_member = (one = , member = 47)"] + ], + ["var two_as_member", [ + "(TwoAsMember) ::two_as_member = (two = , member = 47)"] + ], + ["var array_of_one", [ + "(array::One[3]) ::array_of_one = ([0] = , [1] = , [2] = )"] + ], + ["var array_of_two", [ + "(array::Two[3]) ::array_of_two = ([0] = , [1] = , [2] = )"] + ], + ["var shadowed_one", [ + "(ShadowedOne) ::shadowed_one = (func_shadow::One = , member = 47)"] + ], + + # Now test "frame variable --show-types output" which has multi-line + # output and should not always show classes that were forcefully + # completed to the user to let them know they have a type that should + # have been complete but wasn't. + ["var --show-types inherits_from_one", [ + "(InheritsFromOne) ::inherits_from_one = {", + " (One) One = {}", + " (int) member = 47", + "}"] + ], + ["var --show-types inherits_from_two", [ + "(InheritsFromTwo) ::inherits_from_two = {", + " (Two) Two = {}", + " (int) member = 47", + "}"] + ], + ["var --show-types one_as_member", [ + "(OneAsMember) ::one_as_member = {", + " (member::One) one = {}", + " (int) member = 47", + "}"] + ], + ["var --show-types two_as_member", [ + "(TwoAsMember) ::two_as_member = {", + " (member::Two) two = {}", + " (int) member = 47", + "}"] + ], + ["var --show-types array_of_one", [ + "(array::One[3]) ::array_of_one = {", + " (array::One) [0] = {}", + " (array::One) [1] = {}", + " (array::One) [2] = {}", + "}"] + ], + ["var --show-types array_of_two", [ + "(array::Two[3]) ::array_of_two = {", + " (array::Two) [0] = {}", + " (array::Two) [1] = {}", + " (array::Two) [2] = {}", + "}"] + ], + ["var --show-types shadowed_one", [ + "(ShadowedOne) ::shadowed_one = {", + " (func_shadow::One) func_shadow::One = {}", + " (int) member = 47", + "}"] + ], + ] + for command, expect_items in command_expect_pairs: + self.expect(command, substrs=expect_items) + @skipIf(bugnumber="pr46284", debug_info="gmodules") @skipIfWindows # Clang emits type info even with -flimit-debug-info # Requires DW_CC_pass_by_* attributes from Clang 7 to correctly call @@ -40,7 +138,7 @@ self._check_debug_info_is_limited(target) lldbutil.run_to_name_breakpoint(self, "main", - extra_images=["one", "two"]) + extra_images=["one", "two"]) # But when other shared libraries are loaded, we should be able to see # all members. @@ -67,6 +165,8 @@ self.expect_expr("shadowed_one.member", result_value="47") self.expect_expr("shadowed_one.one", result_value="142") + self._check_incomplete_frame_variable_output() + @skipIf(bugnumber="pr46284", debug_info="gmodules") @skipIfWindows # Clang emits type info even with -flimit-debug-info # Requires DW_CC_pass_by_* attributes from Clang 7 to correctly call @@ -110,6 +210,8 @@ substrs=["calling 'one' with incomplete return type 'result::One'"]) self.expect_expr("get_two().member", result_value="224") + self._check_incomplete_frame_variable_output() + @skipIf(bugnumber="pr46284", debug_info="gmodules") @skipIfWindows # Clang emits type info even with -flimit-debug-info # Requires DW_CC_pass_by_* attributes from Clang 7 to correctly call @@ -155,3 +257,5 @@ substrs=["calling 'get_two' with incomplete return type 'result::Two'"]) self.expect("expr get_two().member", error=True, substrs=["calling 'get_two' with incomplete return type 'result::Two'"]) + + self._check_incomplete_frame_variable_output() diff --git a/lldb/unittests/Symbol/TestTypeSystemClang.cpp b/lldb/unittests/Symbol/TestTypeSystemClang.cpp --- a/lldb/unittests/Symbol/TestTypeSystemClang.cpp +++ b/lldb/unittests/Symbol/TestTypeSystemClang.cpp @@ -394,7 +394,7 @@ RecordDecl *empty_base_decl = TypeSystemClang::GetAsRecordDecl(empty_base); EXPECT_NE(nullptr, empty_base_decl); - EXPECT_FALSE(TypeSystemClang::RecordHasFields(empty_base_decl)); + EXPECT_FALSE(m_ast->RecordHasFields(empty_base_decl)); // Test that a record with direct fields returns true CompilerType non_empty_base = m_ast->CreateRecordType( @@ -408,7 +408,7 @@ TypeSystemClang::GetAsRecordDecl(non_empty_base); EXPECT_NE(nullptr, non_empty_base_decl); EXPECT_NE(nullptr, non_empty_base_field_decl); - EXPECT_TRUE(TypeSystemClang::RecordHasFields(non_empty_base_decl)); + EXPECT_TRUE(m_ast->RecordHasFields(non_empty_base_decl)); std::vector> bases; @@ -429,10 +429,9 @@ m_ast->GetAsCXXRecordDecl(empty_derived.GetOpaqueQualType()); RecordDecl *empty_derived_non_empty_base_decl = TypeSystemClang::GetAsRecordDecl(empty_derived); - EXPECT_EQ(1u, TypeSystemClang::GetNumBaseClasses( + EXPECT_EQ(1u, m_ast->GetNumBaseClasses( empty_derived_non_empty_base_cxx_decl, false)); - EXPECT_TRUE( - TypeSystemClang::RecordHasFields(empty_derived_non_empty_base_decl)); + EXPECT_TRUE(m_ast->RecordHasFields(empty_derived_non_empty_base_decl)); // Test that a record with no direct fields, but fields in a virtual base // returns true @@ -452,10 +451,10 @@ m_ast->GetAsCXXRecordDecl(empty_derived2.GetOpaqueQualType()); RecordDecl *empty_derived_non_empty_vbase_decl = TypeSystemClang::GetAsRecordDecl(empty_derived2); - EXPECT_EQ(1u, TypeSystemClang::GetNumBaseClasses( + EXPECT_EQ(1u, m_ast->GetNumBaseClasses( empty_derived_non_empty_vbase_cxx_decl, false)); EXPECT_TRUE( - TypeSystemClang::RecordHasFields(empty_derived_non_empty_vbase_decl)); + m_ast->RecordHasFields(empty_derived_non_empty_vbase_decl)); } TEST_F(TestTypeSystemClang, TemplateArguments) { @@ -969,4 +968,3 @@ ModuleSP module = t.GetExeModule(); EXPECT_EQ(module.get(), nullptr); } -