blob: 9513e6879177940b95b0d22d8c2b37551faa620d [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/expr_node.h"
#include <map>
#include <type_traits>
#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.h"
#include "src/developer/debug/zxdb/expr/eval_context_impl.h"
#include "src/developer/debug/zxdb/expr/eval_test_support.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/mock_expr_node.h"
#include "src/developer/debug/zxdb/expr/pretty_type.h"
#include "src/developer/debug/zxdb/symbols/base_type.h"
#include "src/developer/debug/zxdb/symbols/code_block.h"
#include "src/developer/debug/zxdb/symbols/collection.h"
#include "src/developer/debug/zxdb/symbols/data_member.h"
#include "src/developer/debug/zxdb/symbols/mock_symbol_data_provider.h"
#include "src/developer/debug/zxdb/symbols/modified_type.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 ExprNodeTest : public TestWithLoop {};
// A PrettyType with a getter that returns a constant value.
class MockGetterPrettyType : public PrettyType {
public:
static const char kGetterName[];
static const char kMemberName[];
static const int kGetterValue;
static const int kMemberValue;
MockGetterPrettyType() : PrettyType({{kGetterName, "5"}}) {}
void Format(FormatNode* node, const FormatOptions& options,
const fxl::RefPtr<EvalContext>& context, fit::deferred_callback cb) override {}
EvalFunction GetMember(const std::string& member_name) const override {
if (member_name == kMemberName) {
return [](const fxl::RefPtr<EvalContext>&, const ExprValue& object_value, EvalCallback cb) {
cb(ExprValue(kMemberValue));
};
}
return EvalFunction();
}
};
const char MockGetterPrettyType::kGetterName[] = "get5";
const char MockGetterPrettyType::kMemberName[] = "member";
const int MockGetterPrettyType::kGetterValue = 5;
const int MockGetterPrettyType::kMemberValue = 42;
// A PrettyType with a dereference function that returns a constant value.
class MockDerefPrettyType : public PrettyType {
public:
MockDerefPrettyType(ExprValue val) : PrettyType(), val_(std::move(val)) {}
void Format(FormatNode* node, const FormatOptions& options,
const fxl::RefPtr<EvalContext>& context, fit::deferred_callback cb) override {}
EvalFunction GetDereferencer() const override {
return [val = val_](const fxl::RefPtr<EvalContext>&, ExprValue, EvalCallback cb) { cb(val); };
}
private:
ExprValue val_;
};
} // namespace
TEST_F(ExprNodeTest, EvalIdentifier) {
auto context = fxl::MakeRefCounted<MockEvalContext>();
ExprValue foo_expected(12);
context->AddVariable("foo", foo_expected);
// This identifier should be found synchronously and returned.
auto good_identifier = fxl::MakeRefCounted<IdentifierExprNode>("foo");
bool called = false;
ExprValue out_value;
good_identifier->Eval(context, [&called, &out_value](ErrOrValue value) {
called = true;
EXPECT_FALSE(value.has_error());
out_value = value.take_value();
});
// This should succeed synchronously.
EXPECT_TRUE(called);
EXPECT_EQ(foo_expected, out_value);
// This identifier should be not found.
auto bad_identifier = fxl::MakeRefCounted<IdentifierExprNode>("bar");
called = false;
bad_identifier->Eval(context, [&called](ErrOrValue value) {
called = true;
EXPECT_TRUE(value.has_error());
});
// It should fail synchronously.
EXPECT_TRUE(called);
}
// This test mocks at the SymbolDataProvider level because most of the dereference logic is in the
// EvalContextImpl.
TEST_F(ExprNodeTest, DereferenceReferencePointer) {
auto data_provider = fxl::MakeRefCounted<MockSymbolDataProvider>();
auto context = fxl::MakeRefCounted<EvalContextImpl>(fxl::WeakPtr<const ProcessSymbols>(),
data_provider, ExprLanguage::kC);
// Dereferencing should remove the const on the pointer but not the pointee.
auto base_type = fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeUnsigned, 4, "uint32_t");
auto const_base_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kConstType, base_type);
auto ptr_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, const_base_type);
auto const_ptr_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kConstType, ptr_type);
// The value being pointed to.
constexpr uint32_t kValue = 0x12345678;
constexpr uint64_t kAddress = 0x1020;
data_provider->AddMemory(kAddress, {0x78, 0x56, 0x34, 0x12});
// The pointer.
ExprValue ptr_value(const_ptr_type, {0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
// Execute the dereference.
auto deref_node =
fxl::MakeRefCounted<DereferenceExprNode>(fxl::MakeRefCounted<MockExprNode>(true, ptr_value));
bool called = false;
ExprValue out_value;
deref_node->Eval(context, [&called, &out_value](ErrOrValue value) {
called = true;
EXPECT_TRUE(value.ok());
out_value = value.take_value();
});
loop().RunUntilNoTasks();
EXPECT_TRUE(called);
// The type should be the const base type.
EXPECT_EQ(const_base_type.get(), out_value.type());
ASSERT_EQ(4u, out_value.data().size());
EXPECT_EQ(kValue, out_value.GetAs<uint32_t>());
// Now go backwards and get the address of the value.
auto addr_node =
fxl::MakeRefCounted<AddressOfExprNode>(fxl::MakeRefCounted<MockExprNode>(true, out_value));
called = false;
out_value = ExprValue();
addr_node->Eval(context, [&called, &out_value](ErrOrValue value) {
called = true;
EXPECT_TRUE(value.ok());
out_value = value.take_value();
});
// Taking the address should always complete synchronously.
EXPECT_TRUE(called);
// The value should be the address.
ASSERT_EQ(8u, out_value.data().size());
EXPECT_EQ(kAddress, out_value.GetAs<uint64_t>());
// The type should be a pointer modifier on the old type. The pointer modifier will be a
// dynamically created one so won't match the original we made above, but the underlying "const
// int" should still match.
const ModifiedType* out_mod_type = out_value.type()->AsModifiedType();
ASSERT_TRUE(out_mod_type);
EXPECT_EQ(DwarfTag::kPointerType, out_mod_type->tag());
EXPECT_EQ(const_base_type.get(), out_mod_type->modified().Get()->AsModifiedType());
EXPECT_EQ("const uint32_t*", out_mod_type->GetFullName());
}
TEST_F(ExprNodeTest, DereferenceErrors) {
auto data_provider = fxl::MakeRefCounted<MockSymbolDataProvider>();
auto context = fxl::MakeRefCounted<EvalContextImpl>(fxl::WeakPtr<const ProcessSymbols>(),
data_provider, ExprLanguage::kC);
auto base_type = MakeInt32Type();
auto ptr_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, base_type);
// Try to dereference an invalid address.
ExprValue bad_ptr_value(ptr_type, {0, 0, 0, 0, 0, 0, 0, 0});
auto bad_deref_node = fxl::MakeRefCounted<DereferenceExprNode>(
fxl::MakeRefCounted<MockExprNode>(true, bad_ptr_value));
bool called = false;
bad_deref_node->Eval(context, [&called](ErrOrValue value) {
called = true;
EXPECT_TRUE(value.has_error());
EXPECT_EQ("Invalid pointer 0x0", value.err().msg());
});
loop().RunUntilNoTasks();
EXPECT_TRUE(called);
// Try to take the address of the invalid expression above. The error should be forwarded.
auto addr_bad_deref_node = fxl::MakeRefCounted<AddressOfExprNode>(std::move(bad_deref_node));
called = false;
addr_bad_deref_node->Eval(context, [&called](ErrOrValue value) {
called = true;
EXPECT_TRUE(value.has_error());
EXPECT_EQ("Invalid pointer 0x0", value.err().msg());
});
loop().RunUntilNoTasks();
EXPECT_TRUE(called);
// Dereference an undefined value.
auto undef_node = fxl::MakeRefCounted<MockExprNode>(true, Err("Undefined"));
auto undef_deref_node = fxl::MakeRefCounted<DereferenceExprNode>(std::move(undef_node));
called = false;
undef_deref_node->Eval(context, [&called](ErrOrValue value) {
called = true;
EXPECT_TRUE(value.has_error());
EXPECT_EQ("Undefined", value.err().msg());
});
loop().RunUntilNoTasks();
EXPECT_TRUE(called);
}
// This also tests ExprNode::EvalFollowReferences() by making the index a reference type.
TEST_F(ExprNodeTest, ArrayAccess) {
// The base address of the array (of type uint32_t*).
auto uint32_type = fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeUnsigned, 4, "uint32_t");
auto uint32_ptr_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, uint32_type);
constexpr uint64_t kAddress = 0x12345678;
ExprValue pointer_value(uint32_ptr_type, {0x78, 0x56, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00});
auto pointer_node = fxl::MakeRefCounted<MockExprNode>(false, pointer_value);
// The index value (= 5) lives in memory as a 32-bit little-endian value.
constexpr uint64_t kRefAddress = 0x5000;
constexpr uint8_t kIndex = 5;
auto context = fxl::MakeRefCounted<MockEvalContext>();
context->data_provider()->AddMemory(kRefAddress, {kIndex, 0, 0, 0});
// The index expression is a reference to the index we saved above, and the
// reference data is the address.
auto uint32_ref_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kReferenceType, uint32_type);
auto index = fxl::MakeRefCounted<MockExprNode>(
false, ExprValue(uint32_ref_type, {0, 0x50, 0, 0, 0, 0, 0, 0}));
// The node to evaluate the access. Note the pointer are index nodes are moved here so the source
// reference is gone. This allows us to test that they stay in scope during an async call below.
auto access = fxl::MakeRefCounted<ArrayAccessExprNode>(std::move(pointer_node), std::move(index));
// We expect it to read @ kAddress[kIndex]. Insert a value there.
constexpr uint64_t kExpectedAddr = kAddress + 4 * kIndex;
constexpr uint32_t kExpectedValue = 0x11223344;
context->data_provider()->AddMemory(kExpectedAddr, {0x44, 0x33, 0x22, 0x11});
// Execute.
bool called = false;
ExprValue out_value;
access->Eval(context, [&called, &out_value](ErrOrValue value) {
called = true;
EXPECT_FALSE(value.has_error()) << value.err().msg();
out_value = value.take_value();
});
// The two parts of the expression were set as async above, so it should not have been called yet.
EXPECT_FALSE(called);
// Clear out references to the stuff being executed. It should not crash, the relevant data should
// remain alive.
context.reset();
access.reset();
// Should succeed asynchronously.
loop().RunUntilNoTasks();
EXPECT_TRUE(called);
// Should have found our data at the right place.
EXPECT_EQ(uint32_type.get(), out_value.type());
EXPECT_EQ(kExpectedValue, out_value.GetAs<uint32_t>());
EXPECT_EQ(kExpectedAddr, out_value.source().address());
}
// This is more of an integration smoke test for "." and "->". The details are tested in
// resolve_collection_unittest.cc.
TEST_F(ExprNodeTest, MemberAccess) {
auto context = fxl::MakeRefCounted<MockEvalContext>();
// Define a class.
auto int32_type = MakeInt32Type();
auto sc =
MakeCollectionType(DwarfTag::kStructureType, "Foo", {{"a", int32_type}, {"b", int32_type}});
// Set up a call to do "." synchronously.
auto struct_node =
fxl::MakeRefCounted<MockExprNode>(true, ExprValue(sc, {0x78, 0x56, 0x34, 0x12}));
auto access_node = fxl::MakeRefCounted<MemberAccessExprNode>(
struct_node, ExprToken(ExprTokenType::kDot, ".", 0), ParsedIdentifier("a"));
// Do the call.
bool called = false;
ExprValue out_value;
access_node->Eval(context, [&called, &out_value](ErrOrValue value) {
called = true;
EXPECT_FALSE(value.has_error());
out_value = value.take_value();
});
// Should have run synchronously.
EXPECT_TRUE(called);
EXPECT_EQ(0x12345678, out_value.GetAs<int32_t>());
// Test indirection: "foo->a".
auto foo_ptr_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, sc);
// Add memory in two chunks since the mock data provider can only respond with the addresses it's
// given.
constexpr uint64_t kAddress = 0x1000;
context->data_provider()->AddMemory(kAddress, {0x44, 0x33, 0x22, 0x11});
context->data_provider()->AddMemory(kAddress + 4, {0x88, 0x77, 0x66, 0x55});
// Make this one evaluate the left-hand-size asynchronously. This value references kAddress
// (little-endian).
auto struct_ptr_node = fxl::MakeRefCounted<MockExprNode>(
false, ExprValue(foo_ptr_type, {0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}));
auto access_ptr_node = fxl::MakeRefCounted<MemberAccessExprNode>(
struct_ptr_node, ExprToken(ExprTokenType::kArrow, "->", 0), ParsedIdentifier("b"));
// Do the call.
called = false;
out_value = ExprValue();
access_ptr_node->Eval(context, [&called, &out_value](ErrOrValue value) {
called = true;
EXPECT_TRUE(value.ok());
out_value = value.take_value();
});
// Should have run asynchronously.
EXPECT_FALSE(called);
loop().RunUntilNoTasks();
EXPECT_TRUE(called);
EXPECT_EQ(sizeof(int32_t), out_value.data().size());
EXPECT_EQ(0x55667788, out_value.GetAs<int32_t>());
}
// Tests that Rust references are autodereferenced by the . operator.
TEST_F(ExprNodeTest, RustMemberAccess) {
auto context = fxl::MakeRefCounted<MockEvalContext>();
auto unit = fxl::MakeRefCounted<CompileUnit>(fxl::WeakPtr<ModuleSymbols>(), DwarfLang::kRust,
"module.so");
// Define a class.
auto int32_type = MakeInt32Type();
auto sc =
MakeCollectionType(DwarfTag::kStructureType, "Foo", {{"a", int32_type}, {"b", int32_type}});
SymbolTestParentSetter sc_parent(sc, unit);
// Define a reference type.
auto foo_ptr_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, sc);
SymbolTestParentSetter foo_ptr_type_parent(foo_ptr_type, unit);
foo_ptr_type->set_assigned_name("&Foo");
// Add memory in two chunks since the mock data provider can only respond with the addresses it's
// given.
constexpr uint64_t kAddress = 0x1000;
context->data_provider()->AddMemory(kAddress, {0x44, 0x33, 0x22, 0x11});
context->data_provider()->AddMemory(kAddress + 4, {0x88, 0x77, 0x66, 0x55});
// Make this one evaluate the left-hand-size asynchronously. This value references kAddress
// (little-endian).
auto struct_ptr_node = fxl::MakeRefCounted<MockExprNode>(
false, ExprValue(foo_ptr_type, {0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}));
auto access_ptr_node = fxl::MakeRefCounted<MemberAccessExprNode>(
struct_ptr_node, ExprToken(ExprTokenType::kDot, ".", 0), ParsedIdentifier("b"));
// Do the call.
auto called = false;
auto out_value = ExprValue();
access_ptr_node->Eval(context, [&called, &out_value](ErrOrValue value) {
called = true;
EXPECT_TRUE(value.ok());
out_value = value.take_value();
});
// Should have run asynchronously.
EXPECT_FALSE(called);
loop().RunUntilNoTasks();
EXPECT_TRUE(called);
EXPECT_EQ(sizeof(int32_t), out_value.data().size());
EXPECT_EQ(0x55667788, out_value.GetAs<int32_t>());
}
// Tests dereferencing via "*" and "->" with a type that has a pretty type.
TEST_F(ExprNodeTest, PrettyDereference) {
auto context = fxl::MakeRefCounted<MockEvalContext>();
// Make a struct to return, it has one 32-bit value.
auto int32_type = MakeInt32Type();
auto struct_type =
MakeCollectionType(DwarfTag::kStructureType, "StructType", {{"a", int32_type}});
constexpr uint8_t kAValue = 42;
ExprValue struct_value(struct_type, {kAValue, 0, 0, 0}); // ReturnType.a = kAValue.
// Register the PrettyType that provides a getter. It always returns struct_value.
const char kTypeName[] = "MyType";
IdentifierGlob glob;
ASSERT_FALSE(glob.Init(kTypeName).has_error());
context->pretty_type_manager().Add(ExprLanguage::kC, glob,
std::make_unique<MockDerefPrettyType>(struct_value));
// Value of MyType to pass to the evaluator. The contents of this don't matter, only the type
// name will be matched.
auto my_type = MakeCollectionType(DwarfTag::kStructureType, kTypeName, {});
ExprValue my_value(my_type, {});
auto my_node = fxl::MakeRefCounted<MockExprNode>(true, my_value);
// Dereferencing MyType should yield the pretty type result |struct_type| above.
auto deref_node = fxl::MakeRefCounted<DereferenceExprNode>(my_node);
bool called = false;
deref_node->Eval(context, [&called, struct_value](ErrOrValue value) {
called = true;
EXPECT_TRUE(value.ok()) << value.err().msg();
// Should have returned the constant struct.
EXPECT_EQ(struct_value, value.value());
});
EXPECT_TRUE(called);
// Accessing "MyType->a" should use the PrettyType to dereference to the |struct_type| and then
// resolve the member "a" on it, giving kAValue as the result.
auto member_node = fxl::MakeRefCounted<MemberAccessExprNode>(
my_node, ExprToken(ExprTokenType::kArrow, "->", 0), ParsedIdentifier("a"));
called = false;
member_node->Eval(context, [&called, struct_value, kAValue](ErrOrValue value) {
called = true;
EXPECT_TRUE(value.ok()) << value.err().msg();
// Should have returned the constant struct.
EXPECT_EQ(kAValue, value.value().GetAs<int32_t>());
});
EXPECT_TRUE(called);
}
// The casting tests cover most casting-related functionality. This acts as a smoketest that it's
// hooked up, and specifically tests the tricky special-casing of casting references to references
// (which shouldn't expand the reference value).
TEST_F(ExprNodeTest, Cast) {
DerivedClassTestSetup d;
auto context = fxl::MakeRefCounted<MockEvalContext>();
// Base2& base2_ref_value = base2_value;
// static_cast<Derived&>(base2_ref_value); // <- cast_ref_ref_node
auto base2_ref_node = fxl::MakeRefCounted<MockExprNode>(true, d.base2_ref_value);
auto derived_ref_type_node = fxl::MakeRefCounted<TypeExprNode>(d.derived_ref_type);
auto cast_ref_ref_node = fxl::MakeRefCounted<CastExprNode>(
CastType::kStatic, std::move(derived_ref_type_node), std::move(base2_ref_node));
// Do the call.
bool called = false;
ExprValue out_value;
cast_ref_ref_node->Eval(context, [&called, &out_value](ErrOrValue value) {
called = true;
ASSERT_FALSE(value.has_error());
out_value = value.take_value();
});
// Should have run synchronously.
EXPECT_TRUE(called);
EXPECT_EQ(d.derived_ref_value, out_value);
// Now cast a ref to an object. This should dereference the object and find the base class inside
// of it.
// static_cast<Base2>(derived_ref_value)
auto derived_ref_node = fxl::MakeRefCounted<MockExprNode>(true, d.derived_ref_value);
auto base2_type_node = fxl::MakeRefCounted<TypeExprNode>(d.base2_type);
auto cast_node = fxl::MakeRefCounted<CastExprNode>(CastType::kStatic, std::move(base2_type_node),
std::move(derived_ref_node));
// Provide the memory for the derived type for when we dereference the ref.
context->data_provider()->AddMemory(d.kDerivedAddr, d.derived_value.data());
called = false;
out_value = ExprValue();
cast_node->Eval(context, [&called, &out_value](ErrOrValue value) {
called = true;
EXPECT_FALSE(value.has_error()) << value.err().msg();
out_value = value.take_value();
});
// Dereferencing will be an asynchronous memory request so it will not have completed yet.
EXPECT_FALSE(called);
loop().RunUntilNoTasks();
EXPECT_TRUE(called);
// Should have converted to the Base2 value.
EXPECT_EQ(d.base2_value, out_value);
}
// Tests integration with the PrettyType's member mechanism. A PrettyType provides a getter function
// that can evaluate a value on an object that looks like a member access.
TEST_F(ExprNodeTest, PrettyTypeMember) {
auto context = fxl::MakeRefCounted<MockEvalContext>();
// Register the PrettyType that provides a getter.
const char kTypeName[] = "MyType";
IdentifierGlob glob;
ASSERT_FALSE(glob.Init(kTypeName).has_error());
context->pretty_type_manager().Add(ExprLanguage::kC, glob,
std::make_unique<MockGetterPrettyType>());
// Object on left side of the ".".
auto type = fxl::MakeRefCounted<Collection>(DwarfTag::kStructureType, kTypeName);
type->set_byte_size(1); // Make it not zero size.
ExprValue value(type, {});
auto content = fxl::MakeRefCounted<MockExprNode>(true, value);
// Evaluate "value.<kMemberName>`
auto dot_access = fxl::MakeRefCounted<MemberAccessExprNode>(
content, ExprToken(ExprTokenType::kDot, ".", 0),
ParsedIdentifier(MockGetterPrettyType::kMemberName));
// Evaluate, everything is synchronously available.
bool called = false;
dot_access->Eval(context, [&called](ErrOrValue value) {
called = true;
EXPECT_FALSE(value.has_error()) << value.err().msg();
EXPECT_EQ(MockGetterPrettyType::kMemberValue, value.value().GetAs<int32_t>());
});
EXPECT_TRUE(called);
// Now try one with a pointer.
auto type_ptr = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, type);
constexpr uint64_t kAddress = 0x110000;
ExprValue pointer_value(type_ptr, {0x00, 0x00, 0x11, 0, 0, 0, 0, 0});
auto pointer = fxl::MakeRefCounted<MockExprNode>(true, pointer_value);
// Data pointed to by the pointer (object is one byte, doesn't matter what value).
context->data_provider()->AddMemory(kAddress, {0x00});
auto arrow_access = fxl::MakeRefCounted<MemberAccessExprNode>(
pointer, ExprToken(ExprTokenType::kArrow, "->", 0),
ParsedIdentifier(MockGetterPrettyType::kMemberName));
// Evaluate, requires a loop because fetching the pointer data is async.
called = false;
arrow_access->Eval(context, [&called](ErrOrValue value) {
called = true;
EXPECT_FALSE(value.has_error()) << value.err().msg();
EXPECT_EQ(MockGetterPrettyType::kMemberValue, value.value().GetAs<int>());
});
EXPECT_FALSE(called);
loop().RunUntilNoTasks();
EXPECT_TRUE(called);
// Try an non-pointer with the "->" operator.
auto invalid_arrow_access = fxl::MakeRefCounted<MemberAccessExprNode>(
content, ExprToken(ExprTokenType::kArrow, "->", 0),
ParsedIdentifier(MockGetterPrettyType::kMemberName));
called = false;
invalid_arrow_access->Eval(context, [&called](ErrOrValue value) {
called = true;
EXPECT_TRUE(value.has_error());
EXPECT_EQ("Attempting to dereference 'MyType' which is not a pointer.", value.err().msg());
});
EXPECT_TRUE(called); // This error is synchronous.
// Combine a custom dereferencer with a custom getter. So "needs_deref->getter()" where
// needs_deref's type provides a pretty dereference operator.
const char kDerefTypeName[] = "NeedsDeref";
IdentifierGlob deref_glob;
ASSERT_FALSE(deref_glob.Init(kDerefTypeName).has_error());
context->pretty_type_manager().Add(ExprLanguage::kC, deref_glob,
std::make_unique<MockDerefPrettyType>(value));
// This is the node that returns the NeedsDeref type. Its value is unimportant.
auto needs_deref_type = MakeCollectionType(DwarfTag::kStructureType, kDerefTypeName, {});
ExprValue needs_deref_value(needs_deref_type, {});
auto needs_deref_node = fxl::MakeRefCounted<MockExprNode>(true, needs_deref_value);
// Nodes that represent the call "needs_deref->get5()";
auto pretty_arrow_access = fxl::MakeRefCounted<MemberAccessExprNode>(
needs_deref_node, ExprToken(ExprTokenType::kArrow, "->", 0),
ParsedIdentifier(MockGetterPrettyType::kMemberName));
// This is synchronous since no pointers are actually dereferenced.
called = false;
pretty_arrow_access->Eval(context, [&called](ErrOrValue value) {
called = true;
EXPECT_FALSE(value.has_error()) << value.err().msg();
EXPECT_EQ(MockGetterPrettyType::kMemberValue, value.value().GetAs<int>());
});
EXPECT_TRUE(called);
}
// Tests integration with the PrettyType's getter mechanism. A PrettyType provides a getter function
// that can evaluate a value on an object that looks like a function call.
TEST_F(ExprNodeTest, PrettyTypeGetter) {
auto context = fxl::MakeRefCounted<MockEvalContext>();
// Register the PrettyType that provides a getter.
const char kTypeName[] = "MyType";
IdentifierGlob glob;
ASSERT_FALSE(glob.Init(kTypeName).has_error());
context->pretty_type_manager().Add(ExprLanguage::kC, glob,
std::make_unique<MockGetterPrettyType>());
// Object on left side of the ".".
auto type = fxl::MakeRefCounted<Collection>(DwarfTag::kStructureType, kTypeName);
type->set_byte_size(1); // Make it not zero size.
ExprValue value(type, {});
auto content = fxl::MakeRefCounted<MockExprNode>(true, value);
// Evaluate "value.<kGetterName>`
auto dot_access = fxl::MakeRefCounted<MemberAccessExprNode>(
content, ExprToken(ExprTokenType::kDot, ".", 0),
ParsedIdentifier(MockGetterPrettyType::kGetterName));
auto dot_call = fxl::MakeRefCounted<FunctionCallExprNode>(dot_access);
// Evaluate, everything is synchronously available.
bool called = false;
dot_call->Eval(context, [&called](ErrOrValue value) {
called = true;
EXPECT_FALSE(value.has_error()) << value.err().msg();
EXPECT_EQ(MockGetterPrettyType::kGetterValue, value.value().GetAs<int32_t>());
});
EXPECT_TRUE(called);
// Now try one with a pointer.
auto type_ptr = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, type);
constexpr uint64_t kAddress = 0x110000;
ExprValue pointer_value(type_ptr, {0x00, 0x00, 0x11, 0, 0, 0, 0, 0});
auto pointer = fxl::MakeRefCounted<MockExprNode>(true, pointer_value);
// Data pointed to by the pointer (object is one byte, doesn't matter what value).
context->data_provider()->AddMemory(kAddress, {0x00});
auto arrow_access = fxl::MakeRefCounted<MemberAccessExprNode>(
pointer, ExprToken(ExprTokenType::kArrow, "->", 0),
ParsedIdentifier(MockGetterPrettyType::kGetterName));
auto arrow_call = fxl::MakeRefCounted<FunctionCallExprNode>(arrow_access);
// Evaluate, requires a loop because fetching the pointer data is async.
called = false;
arrow_call->Eval(context, [&called](ErrOrValue value) {
called = true;
EXPECT_FALSE(value.has_error()) << value.err().msg();
EXPECT_EQ(MockGetterPrettyType::kGetterValue, value.value().GetAs<int>());
});
EXPECT_FALSE(called);
loop().RunUntilNoTasks();
EXPECT_TRUE(called);
// Try an non-pointer with the "->" operator.
auto invalid_arrow_access = fxl::MakeRefCounted<MemberAccessExprNode>(
content, ExprToken(ExprTokenType::kArrow, "->", 0),
ParsedIdentifier(MockGetterPrettyType::kGetterName));
auto invalid_arrow_call = fxl::MakeRefCounted<FunctionCallExprNode>(invalid_arrow_access);
called = false;
invalid_arrow_call->Eval(context, [&called](ErrOrValue value) {
called = true;
EXPECT_TRUE(value.has_error());
EXPECT_EQ("Attempting to dereference 'MyType' which is not a pointer.", value.err().msg());
});
EXPECT_TRUE(called); // This error is synchronous.
// Combine a custom dereferencer with a custom getter. So "needs_deref->getter()" where
// needs_deref's type provides a pretty dereference operator.
const char kDerefTypeName[] = "NeedsDeref";
IdentifierGlob deref_glob;
ASSERT_FALSE(deref_glob.Init(kDerefTypeName).has_error());
context->pretty_type_manager().Add(ExprLanguage::kC, deref_glob,
std::make_unique<MockDerefPrettyType>(value));
// This is the node that returns the NeedsDeref type. Its value is unimportant.
auto needs_deref_type = MakeCollectionType(DwarfTag::kStructureType, kDerefTypeName, {});
ExprValue needs_deref_value(needs_deref_type, {});
auto needs_deref_node = fxl::MakeRefCounted<MockExprNode>(true, needs_deref_value);
// Nodes that represent the call "needs_deref->get5()";
auto pretty_arrow_access = fxl::MakeRefCounted<MemberAccessExprNode>(
needs_deref_node, ExprToken(ExprTokenType::kArrow, "->", 0),
ParsedIdentifier(MockGetterPrettyType::kGetterName));
auto pretty_arrow_call = fxl::MakeRefCounted<FunctionCallExprNode>(pretty_arrow_access);
// This is synchronous since no pointers are actually dereferenced.
called = false;
pretty_arrow_call->Eval(context, [&called](ErrOrValue value) {
called = true;
EXPECT_FALSE(value.has_error()) << value.err().msg();
EXPECT_EQ(MockGetterPrettyType::kGetterValue, value.value().GetAs<int>());
});
EXPECT_TRUE(called);
}
TEST_F(ExprNodeTest, Sizeof) {
auto context = fxl::MakeRefCounted<MockEvalContext>();
// References on raw types should be stripped. Make a one-byte sized type and an 8-byte reference
// to it.
auto char_type = fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeSignedChar, 1, "char");
auto char_ref_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kReferenceType, char_type);
EXPECT_EQ(8u, char_ref_type->byte_size());
auto char_ref_type_node = fxl::MakeRefCounted<TypeExprNode>(char_ref_type);
auto sizeof_char_ref_type = fxl::MakeRefCounted<SizeofExprNode>(char_ref_type_node);
bool called = false;
sizeof_char_ref_type->Eval(context, [&called](ErrOrValue v) {
EXPECT_FALSE(v.has_error());
// Should have retrieved the size of the char, not the reference itself.
uint64_t sizeof_value = 0;
EXPECT_FALSE(v.value().PromoteTo64(&sizeof_value).has_error());
EXPECT_EQ(1u, sizeof_value);
called = true;
});
EXPECT_TRUE(called); // Make sure callback executed.
// Test sizeof() for an asynchronously-executed boolean value.
auto char_value_node = fxl::MakeRefCounted<MockExprNode>(false, ExprValue(true));
auto sizeof_char = fxl::MakeRefCounted<SizeofExprNode>(char_value_node);
called = false;
sizeof_char->Eval(context, [&called](ErrOrValue v) {
EXPECT_FALSE(v.has_error());
// Should have retrieved the size of the char.
uint64_t sizeof_value = 0;
EXPECT_FALSE(v.value().PromoteTo64(&sizeof_value).has_error());
EXPECT_EQ(1u, sizeof_value);
called = true;
});
loop().RunUntilNoTasks();
EXPECT_TRUE(called); // Make sure callback executed.
}
} // namespace zxdb