| // 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_ptr_ref.h" | |
| #include <gtest/gtest.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/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/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 ResolvePtrRefTest : public TestWithLoop {}; | |
| } // namespace | |
| TEST_F(ResolvePtrRefTest, NotPointer) { | |
| auto eval_context = fxl::MakeRefCounted<MockEvalContext>(); | |
| auto int32_type = MakeInt32Type(); | |
| ExprValue int32_value(int32_type, {0x00, 0x00, 0x00, 0x00}); | |
| bool called = false; | |
| ResolvePointer(eval_context, int32_value, [&called](ErrOrValue value) { | |
| called = true; | |
| EXPECT_TRUE(value.has_error()); | |
| EXPECT_EQ("Attempting to dereference 'int32_t' which is not a pointer.", value.err().msg()); | |
| }); | |
| // This should fail synchronously. | |
| EXPECT_TRUE(called); | |
| // Pointer with incorrectly sized data. | |
| auto int32_ptr_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, int32_type); | |
| ExprValue int32_ptr_value(int32_ptr_type, {0x00, 0x00, 0x00, 0x00}); | |
| called = false; | |
| ResolvePointer(eval_context, int32_ptr_value, [&called](ErrOrValue value) { | |
| called = true; | |
| EXPECT_TRUE(value.has_error()); | |
| EXPECT_EQ( | |
| "The value of type 'int32_t*' is the incorrect size (expecting 8, got " | |
| "4). Please file a bug.", | |
| value.err().msg()); | |
| }); | |
| // This should fail synchronously. | |
| EXPECT_TRUE(called); | |
| } | |
| TEST_F(ResolvePtrRefTest, InvalidMemory) { | |
| auto eval_context = fxl::MakeRefCounted<MockEvalContext>(); | |
| constexpr uint64_t kAddress = 0x10; | |
| auto int32_type = MakeInt32Type(); | |
| // This read will return no data. | |
| bool called = false; | |
| ResolvePointer(eval_context, kAddress, int32_type, [&called](ErrOrValue value) { | |
| called = true; | |
| EXPECT_TRUE(value.has_error()); | |
| EXPECT_EQ("Invalid pointer 0x10", value.err().msg()); | |
| debug_ipc::MessageLoop::Current()->QuitNow(); | |
| }); | |
| EXPECT_FALSE(called); | |
| loop().Run(); | |
| EXPECT_TRUE(called); | |
| // This read will return only 2 bytes (it requires 4). | |
| eval_context->data_provider()->AddMemory(kAddress, {0x00, 0x00}); | |
| called = false; | |
| ResolvePointer(eval_context, kAddress, int32_type, [&called](ErrOrValue value) { | |
| called = true; | |
| EXPECT_TRUE(value.has_error()); | |
| EXPECT_EQ("Invalid pointer 0x10", value.err().msg()); | |
| debug_ipc::MessageLoop::Current()->QuitNow(); | |
| }); | |
| EXPECT_FALSE(called); | |
| loop().Run(); | |
| EXPECT_TRUE(called); | |
| } | |
| // Tests EnsureResolveReference when the value is not a reference. | |
| TEST_F(ResolvePtrRefTest, NotRef) { | |
| auto eval_context = fxl::MakeRefCounted<MockEvalContext>(); | |
| auto int32_type = MakeInt32Type(); | |
| ExprValue value(123); | |
| bool called = false; | |
| ExprValue out_value; | |
| EnsureResolveReference(eval_context, value, [&called, &out_value](ErrOrValue result) { | |
| EXPECT_FALSE(result.has_error()); | |
| called = true; | |
| out_value = result.take_value(); | |
| }); | |
| // Should have run synchronously. | |
| EXPECT_TRUE(called); | |
| EXPECT_EQ(value, out_value); | |
| } | |
| // Tests EnsureResolveReference when the value is a const ref. The const should get ignored, the ref | |
| // should be stripped, and the pointed-to value should be the result. | |
| TEST_F(ResolvePtrRefTest, ConstRef) { | |
| auto eval_context = fxl::MakeRefCounted<MockEvalContext>(); | |
| // Add a 32-bit int at a given address. | |
| constexpr uint64_t kAddress = 0x300020; | |
| std::vector<uint8_t> int_value = {0x04, 0x03, 0x02, 0x01}; | |
| eval_context->data_provider()->AddMemory(kAddress, int_value); | |
| // Make "volatile const int32_t&". This tests modifies on both sides of the reference (volatile on | |
| // the outside, const on the inside). | |
| auto int32_type = MakeInt32Type(); | |
| auto const_int32_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kConstType, int32_type); | |
| auto const_int32_ref_type = | |
| fxl::MakeRefCounted<ModifiedType>(DwarfTag::kReferenceType, const_int32_type); | |
| auto volatile_const_int32_ref_type = | |
| fxl::MakeRefCounted<ModifiedType>(DwarfTag::kVolatileType, const_int32_ref_type); | |
| ExprValue value(volatile_const_int32_ref_type, {0x20, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00}); | |
| bool called = false; | |
| ExprValue out_value; | |
| EnsureResolveReference(eval_context, value, [&called, &out_value](ErrOrValue result) { | |
| EXPECT_FALSE(result.has_error()); | |
| called = true; | |
| out_value = result.take_value(); | |
| debug_ipc::MessageLoop::Current()->QuitNow(); | |
| }); | |
| // Should have run asynchronously. | |
| EXPECT_FALSE(called); | |
| loop().Run(); | |
| EXPECT_TRUE(called); | |
| // The resolved type should be "const int32_t" and have the input value. | |
| EXPECT_EQ(const_int32_type.get(), out_value.type()); | |
| EXPECT_EQ(int_value, out_value.data()); | |
| // Check the location of the result. | |
| EXPECT_EQ(ExprValueSource::Type::kMemory, out_value.source().type()); | |
| EXPECT_EQ(kAddress, out_value.source().address()); | |
| } | |
| TEST_F(ResolvePtrRefTest, GetPointedToType_Null) { | |
| auto eval_context = fxl::MakeRefCounted<MockEvalContext>(); | |
| fxl::RefPtr<Type> pointed_to; | |
| Err err = GetPointedToType(eval_context, nullptr, &pointed_to); | |
| EXPECT_TRUE(err.has_error()); | |
| EXPECT_EQ("No type information.", err.msg()); | |
| } | |
| TEST_F(ResolvePtrRefTest, GetPointedToType_NotPointer) { | |
| auto eval_context = fxl::MakeRefCounted<MockEvalContext>(); | |
| auto int32_type = fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeSigned, 4, "int32_t"); | |
| fxl::RefPtr<Type> pointed_to; | |
| Err err = GetPointedToType(eval_context, int32_type.get(), &pointed_to); | |
| EXPECT_TRUE(err.has_error()); | |
| EXPECT_EQ("Attempting to dereference 'int32_t' which is not a pointer.", err.msg()); | |
| } | |
| // A pointer to nothing is used as an encoding for void*. | |
| TEST_F(ResolvePtrRefTest, GetPointedToType_NoPointedToType) { | |
| auto eval_context = fxl::MakeRefCounted<MockEvalContext>(); | |
| // Pointer to nothing. | |
| auto ptr_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, LazySymbol()); | |
| fxl::RefPtr<Type> pointed_to; | |
| Err err = GetPointedToType(eval_context, ptr_type.get(), &pointed_to); | |
| EXPECT_TRUE(err.has_error()); | |
| EXPECT_EQ("Can not dereference a pointer to 'void'.", err.msg()); | |
| } | |
| TEST_F(ResolvePtrRefTest, GetPointedToType_Good) { | |
| auto eval_context = fxl::MakeRefCounted<MockEvalContext>(); | |
| auto int32_type = fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeSigned, 4, "int32_t"); | |
| auto ptr_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, int32_type); | |
| fxl::RefPtr<Type> pointed_to; | |
| Err err = GetPointedToType(eval_context, ptr_type.get(), &pointed_to); | |
| EXPECT_FALSE(err.has_error()) << err.msg(); | |
| EXPECT_EQ(int32_type.get(), pointed_to.get()); | |
| } | |
| // Tests that ResolvePointer and EnsureResolveReference automatically converts a reference to be a | |
| // derived class according to the setting value. | |
| TEST_F(ResolvePtrRefTest, Derived) { | |
| ProcessSymbolsTestSetup symbol_setup; | |
| MockModuleSymbols* mock_module_symbols = symbol_setup.InjectMockModule(); | |
| auto symbol_data_provider = fxl::MakeRefCounted<MockSymbolDataProvider>(); | |
| VirtualBaseTestSetup setup(symbol_data_provider.get(), mock_module_symbols); | |
| auto eval_context = fxl::MakeRefCounted<TestEvalContextImpl>( | |
| symbol_setup.process().GetWeakPtr(), symbol_data_provider, ExprLanguage::kC); | |
| ExprValue ref_value(setup.kBaseAddress, setup.base_class_ref); | |
| ExprValue ptr_value(setup.kBaseAddress, setup.base_class_ptr); | |
| // Reference: No promotion to derived classes. | |
| // ------------------------------------------- | |
| eval_context->set_should_promote_to_derived(false); | |
| ErrOrValue result(Err("Uncalled")); | |
| EnsureResolveReference(eval_context, ref_value, [&result](ErrOrValue r) { result = r; }); | |
| loop().RunUntilNoTasks(); | |
| // The result should be a "BaseClass" object. | |
| ASSERT_TRUE(result.ok()) << result.err().msg(); | |
| EXPECT_EQ("BaseClass", result.value().type()->GetFullName()); | |
| EXPECT_EQ(setup.base_class->byte_size(), result.value().data().size()); | |
| EXPECT_EQ(setup.kBaseAddress, result.value().source().address()); | |
| // Pointer: No promotion to derived classes. | |
| // ----------------------------------------- | |
| result = Err("Uncalled"); | |
| ResolvePointer(eval_context, ptr_value, [&result](ErrOrValue r) { result = r; }); | |
| loop().RunUntilNoTasks(); | |
| // The result should be a "BaseClass" object. | |
| ASSERT_TRUE(result.ok()) << result.err().msg(); | |
| EXPECT_EQ("BaseClass", result.value().type()->GetFullName()); | |
| EXPECT_EQ(setup.base_class->byte_size(), result.value().data().size()); | |
| EXPECT_EQ(setup.kBaseAddress, result.value().source().address()); | |
| // Reference: Auto promotion to derived classes. | |
| // --------------------------------------------- | |
| eval_context->set_should_promote_to_derived(true); | |
| result = Err("Uncalled"); | |
| EnsureResolveReference(eval_context, ref_value, [&result](ErrOrValue r) { result = r; }); | |
| loop().RunUntilNoTasks(); | |
| // The result should be a "DerivedClass" object. | |
| ASSERT_TRUE(result.ok()) << result.err().msg(); | |
| EXPECT_EQ("DerivedClass", result.value().type()->GetFullName()); | |
| EXPECT_EQ(setup.derived_class->byte_size(), result.value().data().size()); | |
| EXPECT_EQ(setup.kDerivedAddress, result.value().source().address()); | |
| // Pointer: Auto promotion to derived classes. | |
| // ------------------------------------------- | |
| eval_context->set_should_promote_to_derived(true); | |
| result = Err("Uncalled"); | |
| ResolvePointer(eval_context, ptr_value, [&result](ErrOrValue r) { result = r; }); | |
| loop().RunUntilNoTasks(); | |
| // The result should be a "DerivedClass" object. | |
| ASSERT_TRUE(result.ok()) << result.err().msg(); | |
| EXPECT_EQ("DerivedClass", result.value().type()->GetFullName()); | |
| EXPECT_EQ(setup.derived_class->byte_size(), result.value().data().size()); | |
| EXPECT_EQ(setup.kDerivedAddress, result.value().source().address()); | |
| } | |
| } // namespace zxdb |