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,37 @@ 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 a complete. When the debugger constructs types, we must + /// have enough information to reconstruct a type in a language specific AST + /// internally. When we need a complete type, such as when a class A inherits + /// from class B, we a full definition for class B so that we can construct a + /// valid AST representation of class A. If we have a variable that is an + /// instance of a class, we must also have the full definition of the class in + /// order to display the variable to the user. Some 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 we require a full + /// definition for a type but we can't find ony, we must forcefully complete + /// some types in order to be able to create the needed AST types. The + /// debugger will do everything it can to try and search for a complete type + /// when displaying information to the user, but when this fails, this API can + /// be used to detect when this happens so the debugger user interface can try + /// and convey to the user that this type is incomplete when it should have + /// been complete. When this function returns true, a class will be empty and + /// it will contain with no base classes, no member functions and no member + /// variables. Calls to the SBType::IsTypeComplete() function will return true + /// for types that return true to this function because the class was + /// forcefully completed (so it appears to be complete). If this returns + /// false, the debug information had a valid definition and the type didn't + /// need to be forcefully completed + 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); } -