| // 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 |