| // Copyright 2019 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/pretty_type_manager.h" |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/developer/debug/shared/message_loop.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/format_node.h" |
| #include "src/developer/debug/zxdb/expr/format_options.h" |
| #include "src/developer/debug/zxdb/expr/format_test_support.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/modified_type.h" |
| #include "src/developer/debug/zxdb/symbols/namespace.h" |
| #include "src/developer/debug/zxdb/symbols/symbol_test_parent_setter.h" |
| #include "src/developer/debug/zxdb/symbols/template_parameter.h" |
| #include "src/developer/debug/zxdb/symbols/type_test_support.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| class PrettyTypeManagerTest : public TestWithLoop {}; |
| |
| } // namespace |
| |
| TEST_F(PrettyTypeManagerTest, StdVector) { |
| auto context = fxl::MakeRefCounted<MockEvalContext>(); |
| |
| // Array data. |
| constexpr uint64_t kAddress = 0x221100; |
| context->data_provider()->AddMemory(kAddress, { |
| 1, 0, 0, 0, // [0] = 1 |
| 99, 0, 0, 0 // [1] = 99 |
| }); |
| |
| auto int32_type = MakeInt32Type(); |
| auto uint64_type = MakeUint64Type(); |
| auto int32_ptr_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, int32_type); |
| auto allocator_type = |
| MakeCollectionType(DwarfTag::kClassType, "std::__2::allocator<int32_t>", {}); |
| |
| // Put the type in the correct namespace. This is important so the identifier for the type name |
| // comes out with the correct parsing. |
| auto std_namespace = fxl::MakeRefCounted<Namespace>("std"); |
| auto v2_namespace = fxl::MakeRefCounted<Namespace>("__2"); |
| SymbolTestParentSetter v2_namespace_parent(v2_namespace, std_namespace); |
| |
| // The capacity is actually a compressed_pair. |
| auto cap_pair = MakeCollectionType(DwarfTag::kStructureType, "compresed_pair", |
| {{"__value_", int32_ptr_type}}); |
| |
| auto vector_type = MakeCollectionType( |
| DwarfTag::kClassType, "vector<int32_t, std::__2::allocator<int32_t> >", |
| {{"__begin_", int32_ptr_type}, {"__end_", int32_ptr_type}, {"__end_cap_", cap_pair}}); |
| SymbolTestParentSetter vector_type_parent(vector_type, v2_namespace); |
| |
| auto int32_param = fxl::MakeRefCounted<TemplateParameter>("T", int32_type, false); |
| auto allocator_param = fxl::MakeRefCounted<TemplateParameter>("allocator", allocator_type, false); |
| vector_type->set_template_params({LazySymbol(int32_param), LazySymbol(allocator_param)}); |
| |
| ExprValue vec_value(vector_type, { |
| 0x00, 0x11, 0x22, 0, 0, 0, 0, 0, // __begin_ |
| 0x08, 0x11, 0x22, 0, 0, 0, 0, 0, // __end_ = __begin_ + 8 |
| 0x10, 0x11, 0x22, 0, 0, 0, 0, 0, // __end_cap_ = __begin+16 |
| }); |
| |
| PrettyTypeManager manager; |
| PrettyType* pretty_vector = manager.GetForType(vector_type.get()); |
| ASSERT_TRUE(pretty_vector); |
| |
| FormatNode node("value", vec_value); |
| |
| bool called = false; |
| pretty_vector->Format( |
| &node, FormatOptions(), context, |
| fit::defer_callback([&called, loop = &loop()]() { called = true, loop->QuitNow(); })); |
| ASSERT_FALSE(called); // Should be async. |
| loop().Run(); |
| |
| ASSERT_EQ(2u, node.children().size()); |
| EXPECT_EQ(1, node.children()[0]->value().GetAs<int32_t>()); |
| EXPECT_EQ(99, node.children()[1]->value().GetAs<int32_t>()); |
| |
| // Test array access for vector: vec_value[1] == 99 |
| auto array_access = pretty_vector->GetArrayAccess(); |
| ASSERT_TRUE(array_access); |
| called = false; |
| array_access(context, vec_value, 1, [&called, loop = &loop()](ErrOrValue result) { |
| called = true; |
| EXPECT_FALSE(result.has_error()) << result.err().msg(); |
| EXPECT_EQ(99, result.value().GetAs<int32_t>()); |
| loop->QuitNow(); |
| }); |
| EXPECT_FALSE(called); // Should be async (requires memory fetch). |
| loop().Run(); |
| |
| // Test size and capacity getter. |
| auto size_getter = pretty_vector->GetGetter("size"); |
| ASSERT_TRUE(size_getter); |
| called = false; |
| size_getter(context, vec_value, [&called](ErrOrValue value) { |
| called = true; |
| EXPECT_TRUE(value.ok()); |
| EXPECT_EQ(2, value.value().GetAs<int64_t>()); |
| }); |
| EXPECT_TRUE(called); // Should by synchronous. |
| |
| auto capacity_getter = pretty_vector->GetGetter("capacity"); |
| ASSERT_TRUE(capacity_getter); |
| called = false; |
| capacity_getter(context, vec_value, [&called](ErrOrValue value) { |
| called = true; |
| EXPECT_TRUE(value.ok()); |
| EXPECT_EQ(4, value.value().GetAs<int64_t>()); |
| }); |
| EXPECT_TRUE(called); // Should by synchronous. |
| |
| // Invalid getter. |
| EXPECT_FALSE(pretty_vector->GetGetter("does_not_exist")); |
| |
| // Test vector<bool>. Currently this is unimplemented which generates some errors. The important |
| // thing is that this doesn't match the normal vector printer. When vector<bool> is implemented |
| // this expected result will change. |
| // |
| // This matches the member names of vector<bool> but the types aren't necessarily correct. |
| auto vector_bool_type = |
| MakeCollectionType(DwarfTag::kClassType, "vector<bool, std::__2::allocator<bool> >", |
| {{"__begin_", int32_ptr_type}, |
| {"__size_", uint64_type}, |
| {"__cap_alloc_", int32_type}, |
| {"__bits_per_word", int32_type}}); |
| SymbolTestParentSetter vector_bool_type_parent(vector_bool_type, v2_namespace); |
| |
| ExprValue vec_bool_value(vector_bool_type, { |
| 0x00, 0x11, 0x22, 0, 0, 0, 0, 0, // __begin_ |
| 9, 0, 0, 0, 0, 0, 0, 0, // __size_ |
| 0x16, 0, 0, 0, // __cap_alloc_ |
| 64, 0, 0, 0, // __bits_per_word |
| }); |
| |
| PrettyType* pretty_vector_bool = manager.GetForType(vector_bool_type.get()); |
| ASSERT_TRUE(pretty_vector_bool); |
| |
| FormatNode bool_node("value", vec_bool_value); |
| |
| called = false; |
| pretty_vector_bool->Format( |
| &bool_node, FormatOptions(), context, |
| fit::defer_callback([&called, loop = &loop()]() { called = true, loop->QuitNow(); })); |
| ASSERT_TRUE(called); // Current error case is sync. |
| |
| EXPECT_EQ( |
| "MockEvalContext::GetVariableValue 'vector_bool_printer_not_implemented_yet' not found.", |
| bool_node.err().msg()); |
| |
| // Since this is an error, it should have no children. |
| ASSERT_EQ(0u, bool_node.children().size()); |
| } |
| |
| TEST_F(PrettyTypeManagerTest, RustStringSlice) { |
| constexpr uint64_t kStringAddress = 0x99887766; |
| constexpr uint64_t kStringLen = 69; // Not including null. |
| |
| const char kStringData[] = |
| "Now is the time for all good men to come to the aid of their country."; |
| auto context = fxl::MakeRefCounted<MockEvalContext>(); |
| context->data_provider()->AddMemory( |
| kStringAddress, std::vector<uint8_t>(std::begin(kStringData), std::end(kStringData))); |
| |
| // The str object representation is just a pointer and a length. |
| uint8_t kRustObject[16] = { |
| 0x66, 0x77, 0x88, 0x99, 0x00, 0x00, 0x00, 0x00, // Address = kStringAddress. |
| kStringLen, 0, 0, 0, 0, 0, 0, 0 // Length = kStringLen. |
| }; |
| |
| auto str_type = |
| MakeCollectionType(DwarfTag::kStructureType, "&str", |
| {{"data_ptr", MakeRustCharPointerType()}, {"length", MakeUint64Type()}}); |
| SymbolTestParentSetter str_type_parent(str_type, MakeRustUnit()); |
| |
| ExprValue value(str_type, std::vector<uint8_t>(std::begin(kRustObject), std::end(kRustObject))); |
| FormatNode node("value", value); |
| |
| PrettyTypeManager manager; |
| PrettyType* pretty = manager.GetForType(str_type.get()); |
| ASSERT_TRUE(pretty); |
| |
| bool completed = false; |
| pretty->Format(&node, FormatOptions(), context, |
| fit::defer_callback([&completed, loop = &loop()]() { completed = true; })); |
| EXPECT_FALSE(completed); // Should be async. |
| loop().RunUntilNoTasks(); |
| EXPECT_TRUE(completed); |
| |
| EXPECT_EQ("\"Now is the time for all good men to come to the aid of their country.\"", |
| node.description()); |
| } |
| |
| TEST_F(PrettyTypeManagerTest, RustStringObject) { |
| constexpr uint64_t kStringAddress = 0x99887766; |
| constexpr uint64_t kStringLen = 69; // Not including null. |
| |
| const char kStringData[] = |
| "Now is the time for all good men to come to the aid of their country."; |
| auto context = fxl::MakeRefCounted<MockEvalContext>(); |
| context->data_provider()->AddMemory( |
| kStringAddress, std::vector<uint8_t>(std::begin(kStringData), std::end(kStringData))); |
| context->set_language(ExprLanguage::kRust); |
| |
| // The String object representation is a Vec object containing bytes. |
| uint8_t kRustObject[24] = { |
| 0x66, 0x77, 0x88, 0x99, 0x00, 0x00, 0x00, 0x00, // Address = kStringAddress. |
| kStringLen, 0, 0, 0, 0, 0, 0, 0, // Length = kStringLen. |
| kStringLen, 0, 0, 0, 0, 0, 0, 0 // Capacity = kStringLen. |
| }; |
| |
| auto alloc_namespace = fxl::MakeRefCounted<Namespace>("alloc"); |
| auto string_namespace = fxl::MakeRefCounted<Namespace>("string"); |
| auto vec_namespace = fxl::MakeRefCounted<Namespace>("vec"); |
| SymbolTestParentSetter string_ns_parent(string_namespace, alloc_namespace); |
| SymbolTestParentSetter vec_ns_parent(vec_namespace, alloc_namespace); |
| SymbolTestParentSetter alloc_ns_parent(alloc_namespace, MakeRustUnit()); |
| auto vec_type = MakeCollectionType( |
| DwarfTag::kStructureType, "Vec<*>", |
| {{"buf", MakeCollectionType( |
| DwarfTag::kStructureType, "Buffer", |
| {{"ptr", MakeCollectionType(DwarfTag::kStructureType, "Pointer", |
| {{"pointer", MakeRustCharPointerType()}})}})}, |
| {"len", MakeUint64Type()}, |
| {"cap", MakeUint64Type()}}); |
| SymbolTestParentSetter vec_type_parent(vec_type, vec_namespace); |
| auto str_type = MakeCollectionType(DwarfTag::kStructureType, "String", {{"vec", vec_type}}); |
| SymbolTestParentSetter str_type_parent(str_type, string_namespace); |
| |
| ExprValue value(str_type, std::vector<uint8_t>(std::begin(kRustObject), std::end(kRustObject))); |
| FormatNode node("value", value); |
| |
| PrettyTypeManager manager; |
| PrettyType* pretty = manager.GetForType(str_type.get()); |
| ASSERT_TRUE(pretty); |
| |
| bool completed = false; |
| pretty->Format(&node, FormatOptions(), context, |
| fit::defer_callback([&completed, loop = &loop()]() { completed = true; })); |
| EXPECT_FALSE(completed); // Should be async. |
| loop().RunUntilNoTasks(); |
| EXPECT_TRUE(completed); |
| |
| EXPECT_EQ("\"Now is the time for all good men to come to the aid of their country.\"", |
| node.description()) |
| << node.err().msg(); |
| } |
| |
| TEST_F(PrettyTypeManagerTest, ZxStatusT) { |
| auto context = fxl::MakeRefCounted<MockEvalContext>(); |
| |
| // Types in the global namespace named "zx_status_t" of the right size should get the enum name |
| // expanded (Zircon special-case). |
| auto int32_type = MakeInt32Type(); |
| auto status_t_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kTypedef, int32_type); |
| status_t_type->set_assigned_name("zx_status_t"); |
| |
| ExprValue status_ok(status_t_type, {0, 0, 0, 0}); |
| FormatOptions opts; |
| EXPECT_EQ(" = zx_status_t, 0 (ZX_OK)\n", GetDebugTreeForValue(context, status_ok, opts)); |
| |
| // -15 = ZX_ERR_BUFFER_TOO_SMALL |
| ExprValue status_too_small(status_t_type, {0xf1, 0xff, 0xff, 0xff}); |
| EXPECT_EQ(" = zx_status_t, -15 (ZX_ERR_BUFFER_TOO_SMALL)\n", |
| GetDebugTreeForValue(context, status_too_small, opts)); |
| |
| // Invalid negative number. |
| ExprValue status_invalid(status_t_type, {0xf0, 0xd8, 0xff, 0xff}); |
| EXPECT_EQ(" = zx_status_t, -10000 (<unknown>)\n", |
| GetDebugTreeForValue(context, status_invalid, opts)); |
| |
| // Positive values. |
| ExprValue status_one(status_t_type, {1, 0, 0, 0}); |
| EXPECT_EQ(" = zx_status_t, 1 (<unknown>)\n", GetDebugTreeForValue(context, status_one, opts)); |
| |
| // Hex formatting should be applied if requested. |
| opts.num_format = FormatOptions::NumFormat::kHex; |
| EXPECT_EQ(" = zx_status_t, 0xfffffff1 (ZX_ERR_BUFFER_TOO_SMALL)\n", |
| GetDebugTreeForValue(context, status_too_small, opts)); |
| |
| // Const types. |
| auto const_status_t_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kConstType, status_t_type); |
| ExprValue const_status_ok(const_status_t_type, {0, 0, 0, 0}); |
| EXPECT_EQ(" = zx_status_t const, 0x0 (ZX_OK)\n", |
| GetDebugTreeForValue(context, const_status_ok, opts)); |
| } |
| |
| } // namespace zxdb |