| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/developer/debug/zxdb/expr/resolve_collection.h" |
| |
| #include <gtest/gtest.h> |
| |
| #include "llvm/BinaryFormat/Dwarf.h" |
| #include "src/developer/debug/shared/platform_message_loop.h" |
| #include "src/developer/debug/zxdb/common/err.h" |
| #include "src/developer/debug/zxdb/common/test_with_loop.h" |
| #include "src/developer/debug/zxdb/expr/abi_null.h" |
| #include "src/developer/debug/zxdb/expr/expr_parser.h" |
| #include "src/developer/debug/zxdb/expr/expr_value.h" |
| #include "src/developer/debug/zxdb/expr/mock_eval_context.h" |
| #include "src/developer/debug/zxdb/expr/test_eval_context_impl.h" |
| #include "src/developer/debug/zxdb/expr/virtual_base_test_setup.h" |
| #include "src/developer/debug/zxdb/symbols/base_type.h" |
| #include "src/developer/debug/zxdb/symbols/collection.h" |
| #include "src/developer/debug/zxdb/symbols/data_member.h" |
| #include "src/developer/debug/zxdb/symbols/identifier.h" |
| #include "src/developer/debug/zxdb/symbols/index_test_support.h" |
| #include "src/developer/debug/zxdb/symbols/inheritance_path.h" |
| #include "src/developer/debug/zxdb/symbols/inherited_from.h" |
| #include "src/developer/debug/zxdb/symbols/modified_type.h" |
| #include "src/developer/debug/zxdb/symbols/process_symbols_test_setup.h" |
| #include "src/developer/debug/zxdb/symbols/symbol_test_parent_setter.h" |
| #include "src/developer/debug/zxdb/symbols/type_test_support.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| class ResolveCollectionTest : public TestWithLoop { |
| public: |
| ResolveCollectionTest() : module_symbol_context_(ProcessSymbolsTestSetup::kDefaultLoadAddress) {} |
| |
| void SetUp() override { |
| TestWithLoop::SetUp(); |
| |
| module_symbols_ = process_setup_.InjectMockModule(); |
| index_root_ = &module_symbols_->index().root(); // Root of the index for module 1. |
| |
| data_provider_ = fxl::MakeRefCounted<MockSymbolDataProvider>(); |
| |
| // With the mock symbol system above, we make a real EvalContext that uses it. |
| eval_context_ = fxl::MakeRefCounted<TestEvalContextImpl>(std::make_shared<AbiNull>(), |
| process_setup_.process().GetWeakPtr(), |
| data_provider_, ExprLanguage::kC); |
| } |
| void TearDown() override { |
| index_root_ = nullptr; |
| data_provider_.reset(); |
| eval_context_.reset(); |
| module_symbols_ = nullptr; |
| |
| TestWithLoop::TearDown(); |
| } |
| |
| protected: |
| ProcessSymbolsTestSetup process_setup_; |
| |
| // Injected module. |
| MockModuleSymbols* module_symbols_; // Owned by process_setup_. |
| SymbolContext module_symbol_context_; |
| IndexNode* index_root_ = nullptr; |
| |
| fxl::RefPtr<MockSymbolDataProvider> data_provider_; |
| fxl::RefPtr<TestEvalContextImpl> eval_context_; |
| }; |
| |
| // Defines a class with two member types "a" and "b". It puts the definitions of "a" and "b' members |
| // into the two out params. |
| fxl::RefPtr<Collection> GetTestClassType(const DataMember** member_a, const DataMember** member_b) { |
| auto int32_type = MakeInt32Type(); |
| auto sc = |
| MakeCollectionType(DwarfTag::kStructureType, "Foo", {{"a", int32_type}, {"b", int32_type}}); |
| |
| *member_a = sc->data_members()[0].Get()->As<DataMember>(); |
| *member_b = sc->data_members()[1].Get()->As<DataMember>(); |
| return sc; |
| } |
| |
| // Helper function that calls ResolveMember with an identifier with the containing value. |
| ErrOrValue ResolveMemberFromString(const fxl::RefPtr<EvalContext>& eval_context, |
| const ExprValue& base, const std::string& name) { |
| ParsedIdentifier ident; |
| Err err = ExprParser::ParseIdentifier(name, &ident); |
| if (err.has_error()) |
| return err; |
| |
| return ResolveNonstaticMember(eval_context, base, ident); |
| } |
| |
| } // namespace |
| |
| TEST_F(ResolveCollectionTest, GoodMemberAccess) { |
| const DataMember* a_data; |
| const DataMember* b_data; |
| auto sc = GetTestClassType(&a_data, &b_data); |
| |
| // Make this const volatile to add extra layers. |
| auto vol_sc = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kVolatileType, sc); |
| auto const_vol_sc = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kConstType, vol_sc); |
| |
| // This struct has the values 1 and 2 in it. |
| constexpr uint64_t kBaseAddr = 0x11000; |
| ExprValue base(const_vol_sc, {0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00}, |
| ExprValueSource(kBaseAddr)); |
| |
| // Resolve A. |
| ErrOrValue out = ResolveNonstaticMember(eval_context_, base, FoundMember(sc.get(), a_data)); |
| ASSERT_TRUE(out.ok()) << out.err().msg(); |
| EXPECT_EQ("int32_t", out.value().type()->GetAssignedName()); |
| EXPECT_EQ(4u, out.value().data().size()); |
| EXPECT_EQ(1, out.value().GetAs<int32_t>()); |
| EXPECT_EQ(kBaseAddr, out.value().source().address()); |
| |
| // Resolve A by name. |
| ErrOrValue out_by_name = ResolveMemberFromString(eval_context_, base, "a"); |
| ASSERT_TRUE(out_by_name.ok()); |
| EXPECT_EQ(out.value(), out_by_name.value()); |
| |
| // Resolve B. |
| out = ResolveNonstaticMember(eval_context_, base, FoundMember(sc.get(), b_data)); |
| ASSERT_TRUE(out.ok()) << out.err().msg(); |
| EXPECT_EQ("int32_t", out.value().type()->GetAssignedName()); |
| EXPECT_EQ(4u, out.value().data().size()); |
| EXPECT_EQ(2, out.value().GetAs<int32_t>()); |
| EXPECT_EQ(kBaseAddr + 4, out.value().source().address()); |
| |
| // Resolve B by name. |
| out_by_name = ResolveMemberFromString(eval_context_, base, "b"); |
| ASSERT_TRUE(out_by_name.ok()); |
| EXPECT_EQ(out.value(), out_by_name.value()); |
| } |
| |
| // Tests that "a->b" can be resolved when the type of "a" is a forward definition. This requires |
| // looking up the symbol in the index to find its definition. |
| TEST_F(ResolveCollectionTest, ForwardDefinitionPtr) { |
| // Forward-declared type. |
| const char kMyStructName[] = "MyStruct"; |
| auto forward_decl = fxl::MakeRefCounted<Collection>(DwarfTag::kStructureType); |
| forward_decl->set_assigned_name(kMyStructName); |
| forward_decl->set_is_declaration(true); |
| |
| // Pointer to the forward declared type. |
| auto forward_decl_ptr = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, forward_decl); |
| |
| // Make a definition for the type and index it. It has one 32-bit data member. |
| auto int32_type = MakeInt32Type(); |
| auto def = MakeCollectionType(DwarfTag::kStructureType, kMyStructName, {{"a", int32_type}}); |
| TestIndexedSymbol indexed_def(module_symbols_, index_root_, kMyStructName, def); |
| |
| // Define the data for the object. It has a 32-bit little-endian value. |
| const uint64_t kObjectAddr = 0x12345678; |
| const uint8_t kIntValue = 42; |
| data_provider_->AddMemory(kObjectAddr, {kIntValue, 0, 0, 0}); |
| |
| // This pointer value references the memory above and its type is the forward declaration which |
| // does not define the members. |
| ExprValue ptr_value(forward_decl_ptr, {0x78, 0x56, 0x34, 0x12, 0, 0, 0, 0}); |
| |
| ParsedIdentifier a_ident; |
| Err err = ExprParser::ParseIdentifier("a", &a_ident); |
| ASSERT_FALSE(err.has_error()); |
| |
| // Resolve by name on an object with the type referencing the forward declaration. |
| bool called = false; |
| ErrOrValue out((ExprValue())); |
| ResolveMemberByPointer(eval_context_, ptr_value, a_ident, |
| [&called, &out](ErrOrValue value, const FoundMember&) { |
| called = true; |
| out = std::move(value); |
| }); |
| |
| // Requesting the memory for the pointer is async. |
| EXPECT_FALSE(called); |
| loop().RunUntilNoTasks(); |
| EXPECT_TRUE(called); |
| ASSERT_FALSE(out.has_error()) << err.msg(); |
| |
| // Should have resolved to the int32. |
| ASSERT_EQ(int32_type.get(), out.value().type()); |
| EXPECT_EQ(kIntValue, out.value().GetAs<int32_t>()); |
| } |
| |
| // Tests that a member type can be a forward definition and we can still find the size to extract it |
| // properly. This happens for std::string which is an extern template. The full definition is |
| // included only in libc++ even though the full definition is known at the time a struct including |
| // it is compiled. |
| TEST_F(ResolveCollectionTest, ForwardDefMember) { |
| // Forward-declared type. |
| const char kFwdDeclaredName[] = "FwdDeclared"; |
| auto forward_decl = fxl::MakeRefCounted<Collection>(DwarfTag::kStructureType); |
| forward_decl->set_assigned_name(kFwdDeclaredName); |
| forward_decl->set_is_declaration(true); |
| EXPECT_EQ(0u, forward_decl->byte_size()); // Forward-decls don't have sizes. |
| |
| // Real definition of the type in the index. |
| auto int32_type = MakeInt32Type(); |
| auto def = MakeCollectionType(DwarfTag::kStructureType, kFwdDeclaredName, {{"a", int32_type}}); |
| TestIndexedSymbol indexed_def(module_symbols_, index_root_, kFwdDeclaredName, def); |
| |
| // Struct that contains a reference to the forward-declared type as a member. |
| const char kMemberName[] = "a"; |
| auto containing = |
| MakeCollectionType(DwarfTag::kStructureType, "Containing", {{kMemberName, forward_decl}}); |
| containing->set_byte_size(def->byte_size()); |
| ExprValue containing_value(containing, {1, 0, 0, 0}); |
| |
| // Now resolve the member. |
| auto result = |
| ResolveNonstaticMember(eval_context_, containing_value, ParsedIdentifier(kMemberName)); |
| ASSERT_TRUE(result.ok()); |
| |
| // The result should be the right size which it should have picked up from the index, but the |
| // actual type should be the forward declaration (in this case, it might be more convenient if |
| // the return value was the definition since it's equivalent, but in practice there might by |
| // typedefs or C-V qualifiers so we always need to return the type specified in the struct |
| // definition. |
| EXPECT_EQ(def->byte_size(), result.value().data().size()); |
| EXPECT_EQ(forward_decl.get(), result.value().type()); |
| } |
| |
| // Tests that a base type can be a forward definition and we can still find members on it. In most |
| // cases base classes will be concrete, but sometimes the toolchain seems to optimize things and |
| // makes them declarations only, with one full definition for the full program. |
| TEST_F(ResolveCollectionTest, ForwardDefBase) { |
| // Full definition of the base type. |
| const char kBaseName[] = "BaseName"; |
| auto int32_type = MakeInt32Type(); |
| auto base_type = MakeCollectionType(DwarfTag::kStructureType, kBaseName, {{"a", int32_type}}); |
| base_type->set_assigned_name(kBaseName); |
| |
| // The base type needs to be indexed for the forward-declaration to be resolved. |
| TestIndexedSymbol base_indexed(module_symbols_, &module_symbols_->index().root(), kBaseName, |
| base_type); |
| |
| // Forward-declaration of the base type. |
| auto forward_decl = fxl::MakeRefCounted<Collection>(DwarfTag::kStructureType); |
| forward_decl->set_assigned_name(kBaseName); |
| forward_decl->set_is_declaration(true); |
| |
| // Derived class. |
| auto derived = MakeCollectionTypeWithOffset(DwarfTag::kStructureType, "Derived", 4, {}); |
| |
| // Inheritance record referencing the forward-declaration. |
| uint32_t base_offset = 0; // Offset in derived of base. |
| auto inherited = fxl::MakeRefCounted<InheritedFrom>(forward_decl, base_offset); |
| // auto inherited = fxl::MakeRefCounted<InheritedFrom>(base_type, base_offset); |
| derived->set_inherited_from({LazySymbol(inherited)}); |
| |
| ExprValue derived_value(derived, {42, 0, 0, 0}); |
| |
| bool called = false; |
| ResolveMember(eval_context_, derived_value, ParsedIdentifier(ParsedIdentifierComponent("a")), |
| [&called](ErrOrValue result) { |
| called = true; |
| |
| EXPECT_FALSE(result.has_error()) << result.err().msg(); |
| EXPECT_EQ(42, result.value().GetAs<int32_t>()); |
| }); |
| EXPECT_TRUE(called); |
| } |
| |
| TEST_F(ResolveCollectionTest, ExternStaticMember) { |
| // This test doesn't do an end-to-end resolution of the EvalContextImpl resolving extern variables |
| // since that requires a lot of setup and is tested by the EvalContextImpl unit tests. Instead |
| // this test only tests the resolve_collection code and validates that the extern variable was |
| // detected and the right EvalContext function was called. |
| const char kName[] = "member_name"; |
| |
| // External data member. |
| auto extern_member = fxl::MakeRefCounted<DataMember>(kName, MakeInt32Type(), 0); |
| extern_member->set_is_external(true); |
| |
| // Collection with the member. |
| auto collection = fxl::MakeRefCounted<Collection>(DwarfTag::kClassType); |
| SymbolTestParentSetter extern_member_parent(extern_member, collection); |
| |
| collection->set_assigned_name("Collection"); |
| collection->set_data_members({LazySymbol(extern_member)}); |
| |
| // The collection needs no storage since the member is static. |
| ExprValue collection_value(collection, {}); |
| |
| auto mock_eval_context = fxl::MakeRefCounted<MockEvalContext>(); |
| ExprValue expected(42); |
| mock_eval_context->AddVariable(extern_member.get(), expected); |
| |
| bool called = false; |
| ResolveMember(mock_eval_context, collection_value, ParsedIdentifier(kName), |
| [&called, expected](ErrOrValue result) { |
| called = true; |
| |
| EXPECT_FALSE(result.has_error()); |
| EXPECT_EQ(expected, result.value()); |
| }); |
| EXPECT_TRUE(called); |
| } |
| |
| // Tests that "foo->bar" works where "foo" is a pointer to a base class, and "bar" is a member of |
| // class virtually derived from "foo". When EvalContext::ShouldPromoteToDerived() is set, the |
| // pointer should be automatically up-casted when we know the types (this requires a vtable). |
| TEST_F(ResolveCollectionTest, ResolveVirtualDerivedMemberByPtr) { |
| VirtualBaseTestSetup setup(data_provider_.get(), module_symbols_); |
| |
| ExprValue ptr_value(setup.kBaseAddress, setup.base_class_ptr); |
| ParsedIdentifier member_name(setup.kDerivedIName); // Name of member on derived class. |
| |
| // Test with no promotion to derived classes. |
| eval_context_->set_should_promote_to_derived(false); |
| ErrOrValue result(Err("Uncalled")); |
| ResolveMemberByPointer(eval_context_, ptr_value, member_name, |
| [&result](ErrOrValue r, const FoundMember&) { result = r; }); |
| loop().RunUntilNoTasks(); |
| |
| // Member should not have been found since promotion was disabled. |
| ASSERT_FALSE(result.ok()); |
| EXPECT_EQ("No member 'derived_i' in struct 'BaseClass'.", result.err().msg()); |
| |
| // Now try with promotion enabled. |
| eval_context_->set_should_promote_to_derived(true); |
| result = Err("Uncalled"); |
| ResolveMemberByPointer(eval_context_, ptr_value, member_name, |
| [&result](ErrOrValue r, const FoundMember&) { result = r; }); |
| loop().RunUntilNoTasks(); |
| |
| // Member should have been found and resolved. |
| ASSERT_TRUE(result.ok()) << result.err().msg(); |
| EXPECT_EQ(setup.kDerivedI, result.value().GetAs<uint32_t>()); |
| } |
| |
| TEST_F(ResolveCollectionTest, BadMemberArgs) { |
| const DataMember* a_data; |
| const DataMember* b_data; |
| auto sc = GetTestClassType(&a_data, &b_data); |
| |
| // Test null base class pointer. |
| ErrOrValue out = |
| ResolveNonstaticMember(eval_context_, ExprValue(), FoundMember(sc.get(), a_data)); |
| ASSERT_TRUE(out.has_error()); |
| EXPECT_EQ("Can't resolve data member on non-struct/class value.", out.err().msg()); |
| |
| constexpr uint64_t kBaseAddr = 0x11000; |
| ExprValue base(sc, {0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00}, ExprValueSource(kBaseAddr)); |
| |
| // Null data member pointer. |
| out = ResolveNonstaticMember(eval_context_, base, FoundMember()); |
| EXPECT_TRUE(out.has_error()); |
| EXPECT_EQ("Invalid data member for struct 'Foo'.", out.err().msg()); |
| } |
| |
| TEST_F(ResolveCollectionTest, BadMemberAccess) { |
| const DataMember* a_data; |
| const DataMember* b_data; |
| auto sc = GetTestClassType(&a_data, &b_data); |
| |
| constexpr uint64_t kBaseAddr = 0x11000; |
| ExprValue base(sc, {0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00}, ExprValueSource(kBaseAddr)); |
| |
| // Lookup by name that doesn't exist. |
| ErrOrValue out = ResolveMemberFromString(eval_context_, base, "c"); |
| ASSERT_TRUE(out.has_error()); |
| EXPECT_EQ("No member 'c' in struct 'Foo'.", out.err().msg()); |
| |
| // Lookup by a DataMember that references outside of the struct (in this case, by one byte). |
| auto bad_member = fxl::MakeRefCounted<DataMember>(); |
| bad_member->set_assigned_name("c"); |
| bad_member->set_type(MakeInt32Type()); |
| bad_member->set_member_location(5); |
| |
| out = ResolveNonstaticMember(eval_context_, base, FoundMember(sc.get(), bad_member.get())); |
| ASSERT_TRUE(out.has_error()); |
| EXPECT_EQ("Invalid data offset 5 in object of size 8.", out.err().msg()); |
| } |
| |
| // Tests foo.bar where bar is in a base class of foo's type. |
| TEST_F(ResolveCollectionTest, BaseClass) { |
| const DataMember* a_data; |
| const DataMember* b_data; |
| auto base = GetTestClassType(&a_data, &b_data); |
| |
| auto derived = fxl::MakeRefCounted<Collection>(DwarfTag::kClassType); |
| |
| uint32_t base_offset = 4; // Offset in derived of base. |
| auto inherited = fxl::MakeRefCounted<InheritedFrom>(base, base_offset); |
| derived->set_inherited_from({LazySymbol(inherited)}); |
| |
| // This struct has the values 1 and 2 in it, offset by 4 bytes (the offset within "derived" of |
| // "base"). |
| constexpr uint64_t kBaseAddr = 0x11000; |
| ExprValue value(derived, {0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00}, |
| ExprValueSource(kBaseAddr)); |
| |
| // Resolve B by name. |
| ErrOrValue out = ResolveMemberFromString(eval_context_, value, "b"); |
| ASSERT_TRUE(out.ok()) << out.err().msg(); |
| EXPECT_EQ("int32_t", out.value().type()->GetAssignedName()); |
| EXPECT_EQ(4u, out.value().data().size()); |
| EXPECT_EQ(2, out.value().GetAs<int32_t>()); |
| |
| // Offset of B in "derived". |
| EXPECT_EQ(kBaseAddr + base_offset + 4, out.value().source().address()); |
| |
| // Test extracting the base class from the derived one. |
| ErrOrValue base_value = ResolveInherited(eval_context_, value, inherited.get()); |
| ASSERT_TRUE(base_value.ok()); |
| |
| ExprValue expected_base(base, {0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00}, |
| ExprValueSource(kBaseAddr + base_offset)); |
| EXPECT_EQ(expected_base, base_value.value()); |
| |
| // Test the other variant of ResolveInherited. |
| base_value = ResolveInherited(eval_context_, value, base, base_offset); |
| ASSERT_TRUE(base_value.ok()); |
| EXPECT_EQ(expected_base, base_value.value()); |
| } |
| |
| // Like BaseClass but using virtual inheritance. This data was copied from a test program. |
| // |
| // This test does its own setup instead of using the VirtualInheritanceTestSetup. That helper could |
| // be used which would save some setup here. They're different partially because they were written |
| // at different times and express slightly different hierarchy. But they're also different because |
| // this version encodes how Clang represents virtual inheritance expressions, and the |
| // VirtualBaseTestSetup does GCC's style which is slightly different. There is value in testing |
| // both versions. |
| TEST_F(ResolveCollectionTest, VirtualInheritance) { |
| // This is the vtable information (from the ELF file). |
| constexpr uint64_t kVtableAddress = 0x70f355000; |
| const std::vector<uint8_t> vtable_data{ |
| 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // +0x00: Offset of base from derived. |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // +0x08: ? |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // +0x10: ? |
| 0xf0, 0x11, 0x15, 0x0f, 0x07, 0x00, 0x00, 0x00, // +0x18: Derived Vtable (ptr to virt fn). |
| 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // +0x20: Offset of derived from base. |
| 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // +0x28: ? |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // +0x30: ? |
| 0x10, 0x12, 0x15, 0x0f, 0x07, 0x00, 0x00, 0x00, // +0x38: Base vtable (I think). |
| }; |
| data_provider_->AddMemory(kVtableAddress, vtable_data); |
| |
| // Derived class data (on the heap). |
| constexpr TargetPointer kDerivedAddress = 0x9aaa319ba8; |
| const std::vector<uint8_t> derived_data{ |
| 0x18, 0x50, 0x35, 0x0f, 0x07, 0x00, 0x00, 0x00, // Derived vtable. |
| 0x63, 0x00, 0x00, 0x00, // Derived object data (uint32_t) 99. |
| 0xaa, 0xaa, 0xaa, 0xaa, // Padding. |
| 0x38, 0x50, 0x35, 0x0f, 0x07, 0x00, 0x00, 0x00, // Base vtable. |
| 0x2a, 0x00, 0x00, 0x00, // Base object data (uint32_t) 42. |
| 0xaa, 0xaa, 0xaa, 0xaa}; // Padding. |
| data_provider_->AddMemory(kDerivedAddress, derived_data); |
| |
| // Base class data is inside the derived data above. |
| constexpr TargetPointer kBaseOffset = 0x10; |
| constexpr TargetPointer kBaseAddress = kDerivedAddress + kBaseOffset; |
| constexpr size_t kBaseSize = 12; // Not counting the padding. |
| |
| // Clang uses "vtbl_ptr_type*" as the type for the vtable pointers at the beginning of a virtual |
| // class. Clang defines the vtable as being pointers to functions "int()", so a pointer to a table |
| // is a pointer to that. For simplicity, we define it as uint64_t instead of "int()". |
| auto vtbl_entry_type = MakeUint64Type(); // Pointer to function "int()" in real life. |
| auto vtbl_ptr_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, vtbl_entry_type); |
| auto vtbl_ptr_type_ptr = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, vtbl_ptr_type); |
| |
| // Base type definition. |
| auto int32_type = MakeInt32Type(); |
| auto base_type = |
| MakeCollectionType(DwarfTag::kClassType, "MyBase", |
| {{"_vptr$MyBase", vtbl_ptr_type_ptr}, {"base_i", int32_type}}); |
| |
| // Derived type definition. |
| auto derived_type = |
| MakeCollectionType(DwarfTag::kClassType, "MyDerived", |
| {{"_vptr$MyDerived", vtbl_ptr_type_ptr}, {"derived_i", int32_type}}); |
| |
| // Inheritance information. The derived class' address will be placed at the top of the stack to |
| // run this. The computation steps are: |
| // |
| // 1. derived = Pointer to derived class. |
| // 2. (*derived) = Dereference derived vtable = 0x70f355018 |
| // 3. - 0x18 = Compute address of offset of base inside of derived. |
| // 4. Dereference = Offset of base inside of derived = 0x10 |
| // 5. derived + 0x10 = Location of base. |
| std::vector<uint8_t> base_loc_expr{ |
| llvm::dwarf::DW_OP_dup, llvm::dwarf::DW_OP_deref, llvm::dwarf::DW_OP_constu, 0x18, |
| llvm::dwarf::DW_OP_minus, llvm::dwarf::DW_OP_deref, llvm::dwarf::DW_OP_plus}; |
| auto inherited = fxl::MakeRefCounted<InheritedFrom>(base_type, DwarfExpr(base_loc_expr)); |
| derived_type->set_inherited_from({LazySymbol(inherited)}); |
| |
| ExprValue derived(derived_type, derived_data, ExprValueSource(kDerivedAddress)); |
| InheritancePath path(derived_type, inherited, base_type); |
| |
| // First try the pointer version which should fixup the pointer data. |
| bool called = false; |
| ResolveInheritedPtr(eval_context_, kDerivedAddress, path, |
| [&called, kBaseAddress](ErrOr<TargetPointer> result) { |
| called = true; |
| EXPECT_TRUE(result.ok()) << result.err().msg(); |
| EXPECT_EQ(kBaseAddress, result.value()); |
| }); |
| loop().RunUntilNoTasks(); |
| EXPECT_TRUE(called); |
| |
| // Now try the ResolveInherited version that takes a full object. |
| called = false; |
| ErrOrValue result(Err("Uncalled")); |
| ResolveInherited(eval_context_, derived, path, [&called, &result](ErrOrValue r) { |
| called = true; |
| result = r; |
| }); |
| ASSERT_FALSE(called); |
| loop().RunUntilNoTasks(); |
| ASSERT_TRUE(called); |
| |
| ASSERT_TRUE(result.ok()) << result.err().msg(); |
| ExprValue base = result.value(); |
| |
| // Should be located at the expected place in memory. |
| EXPECT_EQ(ExprValueSource::Type::kMemory, base.source().type()); |
| EXPECT_EQ(kBaseAddress, base.source().address()); |
| |
| EXPECT_EQ(base_type.get(), base.type()); |
| std::vector<uint8_t> expected_base_data(derived_data.begin() + kBaseOffset, |
| derived_data.begin() + kBaseOffset + kBaseSize); |
| EXPECT_EQ(expected_base_data, base.data().bytes()); |
| |
| // Add a level of non-virtual inheritance. This will put the base class ("MyDerived") at a 4-byte |
| // offset inside of the "MySuperDerived" class. |
| auto super_derived_type = |
| MakeCollectionType(DwarfTag::kClassType, "MySuperDerived", {{"super_derived_i", int32_type}}); |
| auto derived_inherited = |
| fxl::MakeRefCounted<InheritedFrom>(derived_type, super_derived_type->byte_size()); |
| super_derived_type->set_inherited_from({LazySymbol(derived_type)}); |
| super_derived_type->set_byte_size(super_derived_type->byte_size() + derived_type->byte_size()); |
| |
| // Prepend the 32-bit integer to the derived data from before. |
| std::vector<uint8_t> super_derived_data = {42, 0, 0, 0}; |
| super_derived_data.insert(super_derived_data.end(), derived_data.begin(), derived_data.end()); |
| constexpr TargetPointer kSuperDerivedAddress = kDerivedAddress - 4; |
| |
| // Prepend our item to the inheritance path for querying. |
| InheritancePath super_derived_path = path; |
| super_derived_path.path().front().from = derived_inherited; |
| super_derived_path.path().insert(super_derived_path.path().begin(), |
| InheritancePath::Step(super_derived_type)); |
| |
| // Resolve super_derived -> derived -> base path. |
| ExprValue super_derived(super_derived_type, super_derived_data, |
| ExprValueSource(kSuperDerivedAddress)); |
| called = false; |
| result = Err("Uncalled"); |
| ResolveInherited(eval_context_, super_derived, super_derived_path, |
| [&called, &result](ErrOrValue r) { |
| called = true; |
| result = r; |
| }); |
| loop().RunUntilNoTasks(); |
| ASSERT_TRUE(called); |
| |
| // Validate the result as above. |
| base = result.value(); |
| EXPECT_EQ(ExprValueSource::Type::kMemory, base.source().type()); |
| EXPECT_EQ(kBaseAddress, base.source().address()); |
| EXPECT_EQ(base_type.get(), base.type()); |
| EXPECT_EQ(expected_base_data, base.data().bytes()); |
| } |
| |
| // Tests resolving data members with "const values" given in the symbols. |
| TEST_F(ResolveCollectionTest, ConstValue) { |
| // Regular member. |
| auto int32_type = MakeInt32Type(); |
| auto int_member = fxl::MakeRefCounted<DataMember>("a", int32_type, 0); |
| |
| // Const member. This one doesn't have the external flag set. That is normally the case since |
| // for these to be inlined as members they have to be static which will set the is_external() |
| // flag. But the spec doesn't require that so we need to handle ConstValue members based only |
| // on the presence of the ConstValue attribute. |
| const char kConstMemberName[] = "kMember"; |
| auto const_int32_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kConstType, int32_type); |
| auto const_member = fxl::MakeRefCounted<DataMember>(kConstMemberName, const_int32_type, 0); |
| uint8_t kConstValue = 42; |
| const_member->set_const_value(ConstValue({kConstValue, 0, 0, 0})); |
| |
| // Check an extern member. This is a different code path. |
| const char kExternConstMemberName[] = "kExternMember"; |
| auto extern_const_member = |
| fxl::MakeRefCounted<DataMember>(kExternConstMemberName, const_int32_type, 0); |
| uint8_t kExternConstValue = 99; |
| extern_const_member->set_const_value(ConstValue({kExternConstValue, 0, 0, 0})); |
| extern_const_member->set_is_external(true); |
| |
| auto collection = fxl::MakeRefCounted<Collection>(DwarfTag::kStructureType, "MyStruct"); |
| collection->set_data_members( |
| std::vector<LazySymbol>{int_member, const_member, extern_const_member}); |
| |
| // The collection holds only the non-const-value integer. |
| collection->set_byte_size(4); |
| ExprValue coll_value(collection, {0xff, 0xff, 0xff, 0xff}); |
| |
| // Const one. |
| ErrOrValue result = |
| ResolveNonstaticMember(eval_context_, coll_value, ParsedIdentifier(kConstMemberName)); |
| ASSERT_TRUE(result.ok()); |
| EXPECT_EQ(const_int32_type.get(), result.value().type()); |
| EXPECT_EQ(kConstValue, result.value().GetAs<int32_t>()); |
| EXPECT_EQ(ExprValueSource::Type::kConstant, result.value().source().type()); |
| |
| // Extern const one. Have to use the non-nonstatic call to fully test the static codepath. |
| bool called = false; |
| ResolveMember(eval_context_, coll_value, ParsedIdentifier(kExternConstMemberName), |
| [&called, &result](ErrOrValue in_result) { |
| called = true; |
| result = std::move(in_result); |
| }); |
| loop().RunUntilNoTasks(); |
| EXPECT_TRUE(called); |
| |
| ASSERT_TRUE(result.ok()); |
| EXPECT_EQ(const_int32_type.get(), result.value().type()); |
| EXPECT_EQ(kExternConstValue, result.value().GetAs<int32_t>()); |
| EXPECT_EQ(ExprValueSource::Type::kConstant, result.value().source().type()); |
| } |
| |
| } // namespace zxdb |