blob: 4b9e4663769ce69e962c855e9862bef57198df69 [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_array.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/pretty_type.h"
#include "src/developer/debug/zxdb/symbols/array_type.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/type_test_support.h"
namespace zxdb {
namespace {
class ResolveArrayTest : public TestWithLoop {};
// A PrettyType implementation that provides arracy access. This array access returns the index * 2
// as the array value.
class TestPrettyArray : public PrettyType {
public:
TestPrettyArray(){}
void Format(FormatNode* node, const FormatOptions& options,
const fxl::RefPtr<EvalContext>& context, fit::deferred_callback cb) override {}
EvalArrayFunction GetArrayAccess() const override {
return [](const fxl::RefPtr<EvalContext>&, const ExprValue& object_value, int64_t index,
EvalCallback cb) { cb(ExprValue(index * 2)); };
}
};
} // namespace
// Tests the static resolution case when the source is a vector register. The "source" of array
// elements in this case is tricky.
TEST_F(ResolveArrayTest, ResolveVectorRegister) {
auto eval_context = fxl::MakeRefCounted<MockEvalContext>();
// Array holds 4 uint32_t.
constexpr uint32_t kTypeSize = 4;
auto elt_type = fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeUnsigned, kTypeSize, "uint32_t");
auto array_type = fxl::MakeRefCounted<ArrayType>(elt_type, 4);
std::vector<uint8_t> array_bytes = {0, 0, 0, 0, // array[0] = 0
1, 0, 0, 0, // array[1] = 1
2, 0, 0, 0, // array[2] = 2
3, 0, 0, 0}; // array[3] = 3
constexpr debug::RegisterID register_id = debug::RegisterID::kX64_xmm3;
ExprValue value(array_type, array_bytes, ExprValueSource(register_id));
// Ask for all 4 values.
bool called = false;
ErrOrValueVector result((std::vector<ExprValue>()));
ResolveArray(eval_context, value, 0, 4, [&called, &result](ErrOrValueVector cb_result) {
called = true;
result = std::move(cb_result);
debug::MessageLoop::Current()->QuitNow();
});
loop().RunUntilNoTasks();
ASSERT_TRUE(called);
EXPECT_FALSE(result.has_error());
ASSERT_EQ(4u, result.value().size());
// Each element should be 32 bits wide and shifted 32 bits more than the previous.
EXPECT_EQ(0u, result.value()[0].GetAs<uint32_t>());
EXPECT_EQ(ExprValueSource(register_id, 32, 0), result.value()[0].source());
EXPECT_EQ(1u, result.value()[1].GetAs<uint32_t>());
EXPECT_EQ(ExprValueSource(register_id, 32, 32), result.value()[1].source());
EXPECT_EQ(2u, result.value()[2].GetAs<uint32_t>());
EXPECT_EQ(ExprValueSource(register_id, 32, 64), result.value()[2].source());
EXPECT_EQ(3u, result.value()[3].GetAs<uint32_t>());
EXPECT_EQ(ExprValueSource(register_id, 32, 96), result.value()[3].source());
}
// Resolves an array element with a pointer as the base.
TEST_F(ResolveArrayTest, ResolvePointer) {
auto eval_context = fxl::MakeRefCounted<MockEvalContext>();
// Request 3 elements from 1-4.
constexpr uint64_t kBaseAddress = 0x100000;
constexpr uint32_t kBeginIndex = 1;
constexpr uint32_t kEndIndex = 4;
// Array holds 3 uint16_t.
constexpr uint32_t kTypeSize = 2;
auto elt_type = fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeUnsigned, kTypeSize, "uint16_t");
auto ptr_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, elt_type);
// Create memory with two values 0x3344, 0x5566. Note that these are offset one value from the
// beginning of the array so the requested address of the kBeginIndex'th element matches this
// address.
constexpr uint64_t kBeginAddress = kBaseAddress + kBeginIndex * kTypeSize;
eval_context->data_provider()->AddMemory(kBeginAddress, {0x44, 0x33, 0x66, 0x55});
// Data in the value is the pointer to the beginning of the array.
ExprValue value(ptr_type, {0, 0, 0x10, 0, 0, 0, 0, 0});
bool called = false;
ErrOrValueVector result((std::vector<ExprValue>()));
ResolveArray(eval_context, value, kBeginIndex, kEndIndex,
[&called, &result](ErrOrValueVector cb_result) {
called = true;
result = std::move(cb_result);
debug::MessageLoop::Current()->QuitNow();
});
// Should be called async.
EXPECT_FALSE(called);
loop().Run();
EXPECT_TRUE(called);
// Should have returned two values (the overlap of the array and the requested range).
ASSERT_TRUE(result.ok());
ASSERT_EQ(2u, result.value().size());
EXPECT_EQ(elt_type.get(), result.value()[0].type());
EXPECT_EQ(0x3344, result.value()[0].GetAs<uint16_t>());
EXPECT_EQ(kBaseAddress + kTypeSize, result.value()[0].source().address());
EXPECT_EQ(elt_type.get(), result.value()[1].type());
EXPECT_EQ(0x5566, result.value()[1].GetAs<uint16_t>());
EXPECT_EQ(kBaseAddress + kTypeSize * 2, result.value()[1].source().address());
// Test the one-element variant.
called = false;
ErrOrValue single_result((ExprValue()));
ResolveArrayItem(eval_context, value, kBeginIndex, [&called, &single_result](ErrOrValue result) {
called = true;
single_result = std::move(result);
debug::MessageLoop::Current()->QuitNow();
});
// Should be called async.
EXPECT_FALSE(called);
loop().Run();
EXPECT_TRUE(called);
ASSERT_TRUE(single_result.ok());
EXPECT_EQ(elt_type.get(), single_result.value().type());
EXPECT_EQ(0x3344, single_result.value().GetAs<uint16_t>());
EXPECT_EQ(kBaseAddress + kTypeSize, single_result.value().source().address());
}
TEST_F(ResolveArrayTest, Invalid) {
auto eval_context = fxl::MakeRefCounted<MockEvalContext>();
// Resolving an array on an empty ExprValue.
bool called = false;
ResolveArrayItem(eval_context, ExprValue(), 1, [&called](ErrOrValue result) {
called = true;
EXPECT_TRUE(result.has_error());
EXPECT_EQ("Can't resolve an array access on type '<Unknown>'.", result.err().msg());
});
EXPECT_TRUE(called);
// Resolving an array on an integer type.
called = false;
ResolveArrayItem(eval_context, ExprValue(56), 1, [&called](ErrOrValue result) {
called = true;
EXPECT_TRUE(result.has_error());
EXPECT_EQ("Can't resolve an array access on type 'int32_t'.", result.err().msg());
});
EXPECT_TRUE(called);
}
// Tests a PrettyType's implementation of [].
TEST_F(ResolveArrayTest, PrettyArray) {
auto eval_context = fxl::MakeRefCounted<MockEvalContext>();
const char kMyTypeName[] = "MyType";
// Set up pretty array mock for "MyType".
IdentifierGlob mytype_glob;
ASSERT_FALSE(mytype_glob.Init(kMyTypeName).has_error());
eval_context->pretty_type_manager().Add(ExprLanguage::kC, mytype_glob,
std::make_unique<TestPrettyArray>());
auto my_type = MakeCollectionType(DwarfTag::kStructureType, kMyTypeName, {});
ExprValue my_value(my_type, {});
constexpr uint64_t kIndex = 55;
// Test the one-element variant.
bool called = false;
ErrOrValue result((ExprValue()));
ResolveArrayItem(eval_context, my_value, kIndex, [&called, &result](ErrOrValue value) {
called = true;
result = std::move(value);
debug::MessageLoop::Current()->QuitNow();
});
// The PrettyType executes synchronously so it should complete synchronouly.
EXPECT_TRUE(called);
ASSERT_TRUE(result.ok());
// Result should be twice the input.
EXPECT_EQ(kIndex * 2, result.value().GetAs<uint64_t>());
}
TEST_F(ResolveArrayTest, CoercePointerToArrayOfSize) {
auto eval_context = fxl::MakeRefCounted<MockEvalContext>();
constexpr uint64_t kBaseAddress = 0x100000;
// Array holds 3 uint16_t.
constexpr uint32_t kTypeSize = 2;
auto elt_type = fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeUnsigned, kTypeSize, "uint16_t");
auto ptr_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, elt_type);
// The full array in memory including 3 items. The values are 0x1122, 0x3344, 0x5566
std::vector<uint8_t> array_bytes = {0x22, 0x11, 0x44, 0x33, 0x66, 0x55};
eval_context->data_provider()->AddMemory(kBaseAddress, array_bytes);
ExprValue ptr_value(kBaseAddress, ptr_type);
// This callback is used for all examples below.
bool called = false;
ErrOrValue result((ExprValue()));
auto callback = [&called, &result](ErrOrValue value) {
called = true;
result = std::move(value);
debug::MessageLoop::Current()->QuitNow();
};
// Request an array of size 2. It should fetch the memory.
CoerceArraySize(eval_context, ptr_value, 2, callback);
loop().RunUntilNoTasks();
ASSERT_TRUE(called);
ASSERT_TRUE(result.ok());
ASSERT_EQ(kTypeSize * 2, result.value().data().size());
std::vector<uint8_t> two_value_expected(array_bytes.begin(), array_bytes.begin() + kTypeSize * 2);
EXPECT_EQ(two_value_expected, result.value().data().bytes());
EXPECT_EQ("uint16_t[2]", result.value().type()->GetFullName());
EXPECT_EQ(kBaseAddress, result.value().source().address());
// Size 0 should also work.
called = false;
CoerceArraySize(eval_context, ptr_value, 0, callback);
loop().RunUntilNoTasks();
ASSERT_TRUE(called);
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result.value().data().empty());
EXPECT_EQ("uint16_t[0]", result.value().type()->GetFullName());
EXPECT_EQ(kBaseAddress, result.value().source().address());
}
TEST_F(ResolveArrayTest, CoerceStaticArraySize) {
auto eval_context = fxl::MakeRefCounted<MockEvalContext>();
constexpr uint64_t kBaseAddress = 0x100000;
constexpr uint32_t kOriginalArraySize = 2;
// Array holds 3 uint16_t.
constexpr uint32_t kTypeSize = 2;
auto elt_type = fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeUnsigned, kTypeSize, "uint16_t");
auto array_type = fxl::MakeRefCounted<ArrayType>(elt_type, kOriginalArraySize);
// The full array in memory including 3 items. The values are 0x1122, 0x3344, 0x5566
std::vector<uint8_t> array_bytes = {0x22, 0x11, 0x44, 0x33, 0x66, 0x55};
eval_context->data_provider()->AddMemory(kBaseAddress, array_bytes);
ExprValue value(array_type, array_bytes, ExprValueSource(kBaseAddress));
// Source array with 2 elts and a temporary source (not referencing the memory added).
std::vector<uint8_t> two_elt_bytes(array_bytes.begin(),
array_bytes.begin() + (kOriginalArraySize * kTypeSize));
ExprValue temp_value(array_type, two_elt_bytes);
// This callback is used for all examples below.
bool called = false;
ErrOrValue result((ExprValue()));
auto callback = [&called, &result](ErrOrValue value) {
called = true;
result = std::move(value);
debug::MessageLoop::Current()->QuitNow();
};
// Shrinking the temporary value should succeed.
CoerceArraySize(eval_context, temp_value, 1, callback);
loop().RunUntilNoTasks();
ASSERT_TRUE(called);
ASSERT_TRUE(result.ok());
ASSERT_EQ(2u, result.value().data().size());
EXPECT_EQ(0x1122, result.value().GetAs<uint16_t>());
EXPECT_EQ("uint16_t[1]", result.value().type()->GetFullName());
// Expanding to the same size should work.
called = false;
CoerceArraySize(eval_context, temp_value, kOriginalArraySize, callback);
loop().RunUntilNoTasks();
ASSERT_TRUE(called);
ASSERT_TRUE(result.ok());
EXPECT_EQ(temp_value, result.value());
EXPECT_EQ("uint16_t[2]", result.value().type()->GetFullName());
// Expanding the temp value should fail (it references no memory).
called = false;
CoerceArraySize(eval_context, temp_value, kOriginalArraySize + 1, callback);
loop().RunUntilNoTasks();
ASSERT_TRUE(called);
ASSERT_FALSE(result.ok());
EXPECT_EQ("Can not expand array that is not in memory.", result.err().msg());
// Make an identical value that references the source memory.
ExprValue mem_value(array_type, two_elt_bytes, ExprValueSource(kBaseAddress));
// Expanding to the same size should work.
called = false;
CoerceArraySize(eval_context, mem_value, kOriginalArraySize + 1, callback);
loop().RunUntilNoTasks();
ASSERT_TRUE(called);
ASSERT_TRUE(result.ok());
EXPECT_EQ(array_bytes, result.value().data().bytes());
EXPECT_EQ("uint16_t[3]", result.value().type()->GetFullName());
EXPECT_EQ(kBaseAddress, result.value().source().address());
// Expanding beyond the valid memory should fail.
called = false;
CoerceArraySize(eval_context, mem_value, kOriginalArraySize + 2, callback);
loop().RunUntilNoTasks();
ASSERT_TRUE(called);
ASSERT_FALSE(result.ok());
EXPECT_EQ("Array memory not valid.", result.err().msg());
// Shrinking to empty should also work and it should preserve the source.
called = false;
CoerceArraySize(eval_context, mem_value, 0, callback);
loop().RunUntilNoTasks();
ASSERT_TRUE(called);
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result.value().data().empty());
EXPECT_EQ("uint16_t[0]", result.value().type()->GetFullName());
EXPECT_EQ(kBaseAddress, result.value().source().address());
}
} // namespace zxdb