blob: 91cb02546bac63883c2dfa86e1b6557d1a7acc17 [file] [log] [blame]
// 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