blob: 6d33c30dc7c019b2528a08fadbbf8202741e4859 [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 "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/eval_context_impl.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/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/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/type_test_support.h"
namespace zxdb {
namespace {
class ResolveCollectionTest : public TestWithLoop {};
// 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()->AsDataMember();
*member_b = sc->data_members()[1].Get()->AsDataMember();
return sc;
}
// Helper function that calls ResolveMember with an identifier with the containing value.
Err ResolveMemberFromString(fxl::RefPtr<EvalContext> eval_context, const ExprValue& base,
const std::string& name, ExprValue* out) {
ParsedIdentifier ident;
Err err = ExprParser::ParseIdentifier(name, &ident);
if (err.has_error())
return err;
return ResolveMember(eval_context, base, ident, out);
}
} // namespace
TEST_F(ResolveCollectionTest, GoodMemberAccess) {
auto eval_context = fxl::MakeRefCounted<MockEvalContext>();
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, LazySymbol(sc));
auto const_vol_sc = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kConstType, LazySymbol(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.
ExprValue out;
Err err = ResolveMember(eval_context, base, a_data, &out);
EXPECT_FALSE(err.has_error()) << err.msg();
EXPECT_EQ("int32_t", out.type()->GetAssignedName());
EXPECT_EQ(4u, out.data().size());
EXPECT_EQ(1, out.GetAs<int32_t>());
EXPECT_EQ(kBaseAddr, out.source().address());
// Resolve A by name.
ExprValue out_by_name;
err = ResolveMemberFromString(eval_context, base, "a", &out_by_name);
EXPECT_EQ(out, out_by_name);
// Resolve B.
out = ExprValue();
err = ResolveMember(eval_context, base, b_data, &out);
EXPECT_FALSE(err.has_error()) << err.msg();
EXPECT_EQ("int32_t", out.type()->GetAssignedName());
EXPECT_EQ(4u, out.data().size());
EXPECT_EQ(2, out.GetAs<int32_t>());
EXPECT_EQ(kBaseAddr + 4, out.source().address());
// Resolve B by name.
out_by_name = ExprValue();
err = ResolveMemberFromString(eval_context, base, "b", &out_by_name);
EXPECT_EQ(out, out_by_name);
}
// Tests that "a->b" can be resolved when the type of "a" is a foward definition. This requires
// looking up the symbol in the index to find its definition.
TEST_F(ResolveCollectionTest, ForwardDefinitionPtr) {
// Need a bunch of symbol stuff to have the index.
ProcessSymbolsTestSetup setup;
auto mod_ref = std::make_unique<MockModuleSymbols>("mod.so");
MockModuleSymbols* mod = mod_ref.get(); // Save for later.
constexpr uint64_t kLoadAddress = 0x1000000;
SymbolContext symbol_context(kLoadAddress);
setup.InjectModule("mod1", "1234", kLoadAddress, std::move(mod_ref));
auto& root = mod->index().root(); // Root of the index for module 1.
auto provider = fxl::MakeRefCounted<MockSymbolDataProvider>();
// With the mock symbol system above, we make a real EvalContext that uses it.
auto context = fxl::MakeRefCounted<EvalContextImpl>(setup.process().GetWeakPtr(), symbol_context,
provider, fxl::RefPtr<CodeBlock>());
// 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, LazySymbol(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(mod, &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;
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;
Err out_err;
ExprValue out_value;
ResolveMemberByPointer(
context, ptr_value, a_ident,
[&called, &out_err, &out_value](const Err& err, fxl::RefPtr<DataMember>, ExprValue value) {
called = true;
out_err = err;
out_value = value;
debug_ipc::MessageLoop::Current()->QuitNow();
});
// Requesting the memory for the pointer is async.
EXPECT_FALSE(out_err.has_error()) << err.msg();
EXPECT_FALSE(called);
loop().PostTask(FROM_HERE, [loop = &loop()]() { loop->QuitNow(); });
loop().Run();
EXPECT_TRUE(called);
EXPECT_FALSE(out_err.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>());
}
TEST_F(ResolveCollectionTest, BadMemberArgs) {
auto eval_context = fxl::MakeRefCounted<MockEvalContext>();
const DataMember* a_data;
const DataMember* b_data;
auto sc = GetTestClassType(&a_data, &b_data);
// Test null base class pointer.
ExprValue out;
Err err = ResolveMember(eval_context, ExprValue(), a_data, &out);
EXPECT_TRUE(err.has_error());
EXPECT_EQ("Can't resolve data member on non-struct/class value.", 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 = ExprValue();
err = ResolveMember(eval_context, base, nullptr, &out);
EXPECT_TRUE(err.has_error());
EXPECT_EQ("Invalid data member for struct 'Foo'.", err.msg());
}
TEST_F(ResolveCollectionTest, BadMemberAccess) {
auto eval_context = fxl::MakeRefCounted<MockEvalContext>();
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.
ExprValue out;
Err err = ResolveMemberFromString(eval_context, base, "c", &out);
EXPECT_TRUE(err.has_error());
EXPECT_EQ("No member 'c' in struct 'Foo'.", 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(LazySymbol(MakeInt32Type()));
bad_member->set_member_location(5);
out = ExprValue();
err = ResolveMember(eval_context, base, bad_member.get(), &out);
EXPECT_TRUE(err.has_error());
EXPECT_EQ("Invalid data member for struct 'Foo'.", err.msg());
}
// Tests foo.bar where bar is in a derived class of foo's type.
TEST_F(ResolveCollectionTest, DerivedClass) {
auto eval_context = fxl::MakeRefCounted<MockEvalContext>();
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>(LazySymbol(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.
ExprValue out;
Err err = ResolveMemberFromString(eval_context, value, "b", &out);
EXPECT_FALSE(err.has_error()) << err.msg();
EXPECT_EQ("int32_t", out.type()->GetAssignedName());
EXPECT_EQ(4u, out.data().size());
EXPECT_EQ(2, out.GetAs<int32_t>());
// Offset of B in "derived".
EXPECT_EQ(kBaseAddr + base_offset + 4, out.source().address());
// Test extracting the base class from the derived one.
ExprValue base_value;
err = ResolveInherited(value, inherited.get(), &base_value);
EXPECT_FALSE(err.has_error());
ExprValue expected_base(base, {0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00},
ExprValueSource(kBaseAddr + base_offset));
EXPECT_EQ(expected_base, base_value);
// Test the other variant of ResolveInherited.
base_value = ExprValue();
err = ResolveInherited(value, base, base_offset, &base_value);
EXPECT_FALSE(err.has_error());
EXPECT_EQ(expected_base, base_value);
}
} // namespace zxdb