blob: 6514ca597c6adef97cbd59d8c4d0eef77e4e6ba0 [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 <map>
#include <type_traits>
#include "garnet/bin/zxdb/common/err.h"
#include "garnet/bin/zxdb/common/test_with_loop.h"
#include "garnet/bin/zxdb/expr/expr_eval_context.h"
#include "garnet/bin/zxdb/expr/expr_node.h"
#include "garnet/bin/zxdb/expr/expr_value.h"
#include "garnet/bin/zxdb/expr/mock_expr_eval_context.h"
#include "garnet/bin/zxdb/expr/mock_expr_node.h"
#include "garnet/bin/zxdb/expr/symbol_eval_context.h"
#include "garnet/bin/zxdb/expr/symbol_variable_resolver.h"
#include "garnet/bin/zxdb/symbols/base_type.h"
#include "garnet/bin/zxdb/symbols/code_block.h"
#include "garnet/bin/zxdb/symbols/collection.h"
#include "garnet/bin/zxdb/symbols/data_member.h"
#include "garnet/bin/zxdb/symbols/mock_symbol_data_provider.h"
#include "garnet/bin/zxdb/symbols/modified_type.h"
#include "garnet/bin/zxdb/symbols/type_test_support.h"
#include "garnet/lib/debug_ipc/helper/platform_message_loop.h"
#include "gtest/gtest.h"
namespace zxdb {
namespace {
class ExprNodeTest : public TestWithLoop {};
} // namespace
TEST_F(ExprNodeTest, EvalIdentifier) {
auto context = fxl::MakeRefCounted<MockExprEvalContext>();
ExprValue foo_expected(12);
context->AddVariable("foo", foo_expected);
// This identifier should be found synchronously and returned.
auto good_identifier = fxl::MakeRefCounted<IdentifierExprNode>(
ExprToken(ExprToken::Type::kName, "foo", 0));
bool called = false;
Err out_err;
ExprValue out_value;
good_identifier->Eval(context, [&called, &out_err, &out_value](
const Err& err, ExprValue value) {
called = true;
out_err = err;
out_value = value;
});
// This should succeed synchronously.
EXPECT_TRUE(called);
EXPECT_FALSE(out_err.has_error());
EXPECT_EQ(foo_expected, out_value);
// This identifier should be not found.
auto bad_identifier = fxl::MakeRefCounted<IdentifierExprNode>(
ExprToken(ExprToken::Type::kName, "bar", 0));
called = false;
out_value = ExprValue();
bad_identifier->Eval(context, [&called, &out_err, &out_value](
const Err& err, ExprValue value) {
called = true;
out_err = err;
out_value = ExprValue(); // value;
});
// It should fail synchronously.
EXPECT_TRUE(called);
EXPECT_TRUE(out_err.has_error());
EXPECT_EQ(ExprValue(), out_value);
}
template <typename T>
void DoUnaryMinusTest(T in) {
auto context = fxl::MakeRefCounted<MockExprEvalContext>();
ExprValue foo_expected(in);
context->AddVariable("foo", foo_expected);
auto identifier = fxl::MakeRefCounted<IdentifierExprNode>(
ExprToken(ExprToken::kName, "foo", 0));
// Validate the value by itself. This also has the effect of checking the
// ExprValue type-specific constructor.
bool called = false;
Err out_err;
ExprValue out_value;
identifier->Eval(context, [&called, &out_err, &out_value](const Err& err,
ExprValue value) {
called = true;
out_err = err;
out_value = value;
});
EXPECT_TRUE(called);
EXPECT_EQ(sizeof(T), out_value.data().size());
called = false;
out_err = Err();
out_value = ExprValue();
// Apply a unary '-' to that value.
auto unary = fxl::MakeRefCounted<UnaryOpExprNode>(
ExprToken(ExprToken::kMinus, "-", 0), std::move(identifier));
unary->Eval(context,
[&called, &out_err, &out_value](const Err& err, ExprValue value) {
called = true;
out_err = err;
out_value = value;
});
// This checked that the type conversions have followed C rules. This is
// the expected value (int/unsigned unchanged, everything smaller than an int
// is promoted to an int, everything larger remains unchanged).
auto expected = -in;
// The type of the output should be the same as the input for unary '-'.
EXPECT_TRUE(called);
EXPECT_FALSE(out_err.has_error()) << out_err.msg();
EXPECT_EQ(sizeof(expected), out_value.data().size());
if (std::is_unsigned<decltype(expected)>::value) {
EXPECT_EQ(BaseType::kBaseTypeUnsigned, out_value.GetBaseType());
} else {
EXPECT_EQ(BaseType::kBaseTypeSigned, out_value.GetBaseType());
}
EXPECT_EQ(expected, out_value.GetAs<decltype(expected)>());
}
template <typename T>
void DoUnaryMinusTypeTest() {
DoUnaryMinusTest<T>(0);
DoUnaryMinusTest<T>(std::numeric_limits<T>::max());
DoUnaryMinusTest<T>(std::numeric_limits<T>::lowest());
}
TEST_F(ExprNodeTest, UnaryMinus) {
// Test the limits of all built-in types.
DoUnaryMinusTypeTest<int8_t>();
DoUnaryMinusTypeTest<uint8_t>();
DoUnaryMinusTypeTest<int16_t>();
DoUnaryMinusTypeTest<uint16_t>();
DoUnaryMinusTypeTest<int32_t>();
DoUnaryMinusTypeTest<uint32_t>();
DoUnaryMinusTypeTest<int64_t>();
DoUnaryMinusTypeTest<uint64_t>();
// Try an unsupported value (a 3-byte signed). This should throw an error and
// compute an empty value.
auto context = fxl::MakeRefCounted<MockExprEvalContext>();
ExprValue expected(
fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeUnsigned, 3, "uint24_t"),
{0, 0, 0});
context->AddVariable("foo", expected);
auto identifier = fxl::MakeRefCounted<IdentifierExprNode>(
ExprToken(ExprToken::kName, "foo", 0));
auto unary = fxl::MakeRefCounted<UnaryOpExprNode>(
ExprToken(ExprToken::kMinus, "-", 0), std::move(identifier));
bool called = false;
Err out_err;
ExprValue out_value;
unary->Eval(context,
[&called, &out_err, &out_value](const Err& err, ExprValue value) {
called = true;
out_err = err;
out_value = value;
});
EXPECT_TRUE(called);
EXPECT_TRUE(out_err.has_error());
EXPECT_EQ("Negation for this value is not supported.", out_err.msg());
EXPECT_EQ(ExprValue(), out_value);
}
// This test mocks at the SymbolDataProvider level because most of the
// dereference logic is in the SymbolEvalContext.
TEST_F(ExprNodeTest, DereferenceReferencePointer) {
auto data_provider = fxl::MakeRefCounted<MockSymbolDataProvider>();
auto context = fxl::MakeRefCounted<SymbolEvalContext>(
fxl::WeakPtr<const ProcessSymbols>(),
SymbolContext::ForRelativeAddresses(), data_provider,
nullptr);
// 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>(
Symbol::kTagConstType, LazySymbol(base_type));
auto ptr_type = fxl::MakeRefCounted<ModifiedType>(
Symbol::kTagPointerType, LazySymbol(const_base_type));
auto const_ptr_type = fxl::MakeRefCounted<ModifiedType>(Symbol::kTagConstType,
LazySymbol(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;
Err out_err;
ExprValue out_value;
deref_node->Eval(context, [&called, &out_err, &out_value](const Err& err,
ExprValue value) {
called = true;
out_err = err;
out_value = value;
debug_ipc::MessageLoop::Current()->QuitNow();
});
// Should complete asynchronously.
EXPECT_FALSE(called);
loop().Run();
EXPECT_TRUE(called);
EXPECT_FALSE(out_err.has_error()) << out_err.msg();
// 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_err = Err();
out_value = ExprValue();
addr_node->Eval(context, [&called, &out_err, &out_value](const Err& err,
ExprValue value) {
called = true;
out_err = err;
out_value = value;
});
// Taking the address should always complete synchronously.
EXPECT_TRUE(called);
EXPECT_FALSE(out_err.has_error()) << out_err.msg();
// 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(Symbol::kTagPointerType, 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());
// Try to dereference an invalid address.
ExprValue bad_ptr_value(const_ptr_type, {0, 0, 0, 0, 0, 0, 0, 0});
auto bad_deref_node = fxl::MakeRefCounted<DereferenceExprNode>(
fxl::MakeRefCounted<MockExprNode>(true, bad_ptr_value));
called = false;
out_err = Err();
out_value = ExprValue();
bad_deref_node->Eval(context, [&called, &out_err, &out_value](
const Err& err, ExprValue value) {
called = true;
out_err = err;
out_value = value;
debug_ipc::MessageLoop::Current()->QuitNow();
});
// Should complete asynchronously.
EXPECT_FALSE(called);
loop().Run();
EXPECT_TRUE(called);
EXPECT_TRUE(out_err.has_error());
EXPECT_EQ("Invalid pointer 0x0", out_err.msg());
}
// 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>(
Symbol::kTagPointerType, LazySymbol(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<MockExprEvalContext>();
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>(
Symbol::kTagReferenceType, LazySymbol(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;
Err out_err;
ExprValue out_value;
access->Eval(context, [&called, &out_err, &out_value](const Err& err,
ExprValue value) {
called = true;
out_err = err;
out_value = value;
debug_ipc::MessageLoop::Current()->QuitNow();
});
// 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();
loop().Run();
// Should have succeeded asynchronously.
EXPECT_TRUE(called);
EXPECT_FALSE(out_err.has_error()) << out_err.msg();
// 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<MockExprEvalContext>();
// Define a class.
auto int32_type = MakeInt32Type();
auto sc = MakeCollectionType(Symbol::kTagStructureType, "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(ExprToken::Type::kDot, ".", 0),
Identifier(ExprToken(ExprToken::Type::kName, "a", 0)));
// Do the call.
bool called = false;
Err out_err;
ExprValue out_value;
access_node->Eval(context, [&called, &out_err, &out_value](const Err& err,
ExprValue value) {
called = true;
out_err = err;
out_value = value;
debug_ipc::MessageLoop::Current()->QuitNow();
});
// Should have run synchronously.
EXPECT_TRUE(called);
EXPECT_FALSE(out_err.has_error());
EXPECT_EQ(0x12345678, out_value.GetAs<int32_t>());
// Test indirection: "foo->a".
auto foo_ptr_type = fxl::MakeRefCounted<ModifiedType>(Symbol::kTagPointerType,
LazySymbol(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(ExprToken::Type::kArrow, "->", 0),
Identifier(ExprToken(ExprToken::Type::kName, "b", 0)));
// Do the call.
called = false;
out_err = Err();
out_value = ExprValue();
access_ptr_node->Eval(context, [&called, &out_err, &out_value](
const Err& err, ExprValue value) {
called = true;
out_err = err;
out_value = value;
debug_ipc::MessageLoop::Current()->QuitNow();
});
// Should have run asynchronously.
EXPECT_FALSE(called);
loop().Run();
EXPECT_TRUE(called);
EXPECT_FALSE(out_err.has_error()) << out_err.msg();
EXPECT_EQ(sizeof(int32_t), out_value.data().size());
EXPECT_EQ(0x55667788, out_value.GetAs<int32_t>());
}
} // namespace zxdb