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 @@ -203,6 +203,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 @@ -202,6 +202,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 @@ -490,7 +490,12 @@ if (!IsValid()) return false; - return m_opaque_sp->GetCompilerType(false).IsCompleteType(); + CompilerType compiler_type = m_opaque_sp->GetCompilerType(false); + // Only return true if we have a complete type and it wasn't forcefully + // completed. + if (compiler_type.IsCompleteType()) + return !compiler_type.IsForcefullyCompleted(); + return false; } uint32_t SBType::GetTypeFlags() { 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 @@ -1802,6 +1802,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; } @@ -1829,7 +1840,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()); } @@ -6608,9 +6619,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 = @@ -6635,9 +6647,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); @@ -10034,3 +10046,19 @@ m_isolated_asts.insert({feature, new_ast_sp}); return *new_ast_sp; } + +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 @@ -94,6 +94,12 @@ return false; } +bool CompilerType::IsForcefullyCompleted() const { + if (IsValid()) + return m_type_system->IsForcefullyCompleted(m_type); + return false; +} + bool CompilerType::IsConst() const { if (IsValid()) if (auto type_system_sp = GetTypeSystem()) 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,12 @@ type_ = exe.FindFirstType(name) self.trace("type_: %s"%type_) self.assertTrue(type_) + self.assertTrue(type_.IsTypeComplete()) base = type_.GetDirectBaseClassAtIndex(0).GetType() self.trace("base:%s"%base) self.assertTrue(base) self.assertEquals(base.GetNumberOfFields(), 0) + self.assertFalse(base.IsTypeComplete()) def _check_debug_info_is_limited(self, target): # Without other shared libraries we should only see the member declared @@ -28,6 +30,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 +136,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 +163,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 +208,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 +255,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 @@ -405,7 +405,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( @@ -419,7 +419,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; @@ -440,10 +440,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 @@ -463,10 +462,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) { @@ -980,4 +979,3 @@ ModuleSP module = t.GetExeModule(); EXPECT_EQ(module.get(), nullptr); } -