| // 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_std_string.h" |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/developer/debug/shared/message_loop.h" |
| #include "src/developer/debug/zxdb/common/tagged_data_builder.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/mock_eval_context.h" |
| #include "src/developer/debug/zxdb/symbols/type_test_support.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| // Memory address/length of the default string. This is relatively long so it's guaranteed to |
| // exceed the C++ short string optimization length. |
| constexpr uint64_t kStringAddress = 0x99887766; |
| constexpr uint64_t kStringLen = 69; // Not including null. |
| |
| class PrettyStringTest : public TestWithLoop { |
| public: |
| PrettyStringTest() { |
| context_ = fxl::MakeRefCounted<MockEvalContext>(); |
| |
| const char kStringData[] = |
| "Now is the time for all good men to come to the aid of their country."; |
| context_->data_provider()->AddMemory( |
| kStringAddress, std::vector<uint8_t>(std::begin(kStringData), std::end(kStringData))); |
| } |
| |
| // Calls the given getter, promotes the result to 64-bit and expects that it's equal to the given |
| // value. |
| void ExpectGetterReturns(const ExprValue& input, const std::string& getter_name, |
| uint64_t expected) { |
| PrettyStdString pretty; |
| |
| auto getter = pretty.GetGetter(getter_name); |
| ASSERT_TRUE(getter); |
| |
| bool should_quit = false; |
| bool called = false; |
| getter(context(), input, [&called, &should_quit, expected](ErrOrValue v) { |
| called = true; |
| |
| ASSERT_TRUE(v.ok()) << v.err().msg(); |
| |
| uint64_t actual = 0; |
| Err err = v.value().PromoteTo64(&actual); |
| ASSERT_TRUE(err.ok()); |
| EXPECT_EQ(expected, actual); |
| |
| if (should_quit) |
| debug::MessageLoop::Current()->QuitNow(); |
| }); |
| if (!called) { |
| should_quit = true; |
| debug::MessageLoop::Current()->Run(); |
| } |
| EXPECT_TRUE(called); |
| } |
| |
| // Calls the given getter and expects an error is returned. |
| void ExpectGetterErr(const ExprValue& input, const std::string& getter_name, |
| const std::string& expected_err_msg) { |
| PrettyStdString pretty; |
| |
| auto getter = pretty.GetGetter(getter_name); |
| ASSERT_TRUE(getter); |
| |
| bool should_quit = false; |
| bool called = false; |
| getter(context(), input, [&called, &should_quit, expected_err_msg](ErrOrValue v) { |
| called = true; |
| |
| ASSERT_FALSE(v.ok()); |
| EXPECT_EQ(v.err().msg(), expected_err_msg); |
| |
| if (should_quit) |
| debug::MessageLoop::Current()->QuitNow(); |
| }); |
| if (!called) { |
| should_quit = true; |
| debug::MessageLoop::Current()->Run(); |
| } |
| EXPECT_TRUE(called); |
| } |
| |
| fxl::RefPtr<MockEvalContext> context() { return context_; } |
| |
| private: |
| fxl::RefPtr<MockEvalContext> context_; |
| }; |
| |
| } // namespace |
| |
| TEST_F(PrettyStringTest, StdStringShort) { |
| // Encodes 'a'-'m' in the "short" form of a std::string. |
| uint8_t kShortMem[24] = { |
| 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, // Inline bytes... |
| 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x00, 0x00, 0x00, // ... |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, // ..., last byte is size. |
| }; |
| |
| ExprValue short_value(fxl::RefPtr<Type>(), |
| std::vector<uint8_t>(std::begin(kShortMem), std::end(kShortMem))); |
| FormatNode node("value", short_value); |
| |
| PrettyStdString pretty; |
| |
| // Expect synchronous completion for the inline value case. |
| bool completed = false; |
| pretty.Format(&node, FormatOptions(), context(), |
| fit::defer_callback([&completed]() { completed = true; })); |
| EXPECT_TRUE(completed); |
| |
| EXPECT_EQ("\"abcdefghijklm\"", node.description()); |
| |
| ExpectGetterReturns(short_value, "size", 13); |
| ExpectGetterReturns(short_value, "length", 13); |
| ExpectGetterReturns(short_value, "capacity", 22); // 24 bytes - size - null. |
| ExpectGetterReturns(short_value, "empty", 0); |
| } |
| |
| // Test that a std::string buffer that's mostly optimized out can still be foprmatted. |
| TEST_F(PrettyStringTest, StdStringShortMaximallyOptimized) { |
| // Minimal std::string has only a 0 byte as the last byte, indicating no size. |
| TaggedDataBuilder builder; |
| builder.AppendUnknown(23); |
| builder.Append({0}); |
| |
| ExprValue short_value(fxl::RefPtr<Type>(), builder.TakeData()); |
| FormatNode node("value", short_value); |
| |
| PrettyStdString pretty; |
| |
| // Expect synchronous completion for the inline value case. |
| bool completed = false; |
| pretty.Format(&node, FormatOptions(), context(), |
| fit::defer_callback([&completed]() { completed = true; })); |
| EXPECT_TRUE(completed); |
| |
| EXPECT_EQ("\"\"", node.description()); |
| |
| ExpectGetterReturns(short_value, "size", 0); |
| ExpectGetterReturns(short_value, "length", 0); |
| ExpectGetterReturns(short_value, "capacity", 22); // 24 bytes - size - null. |
| ExpectGetterReturns(short_value, "empty", 1); |
| } |
| |
| // Tests values missing in optimized data. |
| TEST_F(PrettyStringTest, StdStringShortTooOptimized) { |
| // Entirely optimized-out buffer. |
| TaggedDataBuilder builder; |
| builder.AppendUnknown(24); |
| |
| ExprValue opt_out_value(fxl::RefPtr<Type>(), builder.TakeData()); |
| FormatNode node("value", opt_out_value); |
| |
| PrettyStdString pretty; |
| |
| // Expect synchronous completion for the inline value case. |
| bool completed = false; |
| pretty.Format(&node, FormatOptions(), context(), |
| fit::defer_callback([&completed]() { completed = true; })); |
| EXPECT_TRUE(completed); |
| |
| EXPECT_EQ("", node.description()); |
| EXPECT_TRUE(node.err().has_error()); |
| EXPECT_EQ(ErrType::kOptimizedOut, node.err().type()); |
| |
| ExpectGetterErr(opt_out_value, "size", "optimized out"); |
| ExpectGetterErr(opt_out_value, "length", "optimized out"); |
| ExpectGetterErr(opt_out_value, "capacity", "optimized out"); |
| ExpectGetterErr(opt_out_value, "empty", "optimized out"); |
| } |
| |
| // Tests a string with no bytes but a source location. This string data encodes the "long" format |
| // which is in turn another pointer. |
| TEST_F(PrettyStringTest, StdStringLong) { |
| // The std::string object representation. |
| constexpr uint64_t kObjectAddress = 0x12345678; |
| uint8_t kLongMem[24] = { |
| // clang-format off |
| 0x66, 0x77, 0x88, 0x99, 0x00, 0x00, 0x00, 0x00, // Address = kStringAddress. |
| kStringLen, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Length = kStringLen |
| 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // Capacity (last bit is "long" flag). |
| // clang-format on |
| }; |
| context()->data_provider()->AddMemory( |
| kObjectAddress, std::vector<uint8_t>(std::begin(kLongMem), std::end(kLongMem))); |
| |
| // The std::string object. This has no data in it because the real type isn't known, but it does |
| // have a valid source address. |
| ExprValue value(fxl::RefPtr<Type>(), {}, ExprValueSource(kObjectAddress)); |
| |
| FormatNode node("value", value); |
| |
| PrettyStdString pretty; |
| |
| bool completed = false; |
| pretty.Format(&node, FormatOptions(), context(), |
| fit::defer_callback([&completed, loop = &loop()]() { |
| completed = true; |
| loop->QuitNow(); |
| })); |
| EXPECT_FALSE(completed); // Should be async. |
| loop().Run(); |
| EXPECT_TRUE(completed); |
| |
| EXPECT_EQ("\"Now is the time for all good men to come to the aid of their country.\"", |
| node.description()); |
| |
| ExpectGetterReturns(value, "size", kStringLen); |
| ExpectGetterReturns(value, "length", kStringLen); |
| ExpectGetterReturns(value, "capacity", 0x50); |
| ExpectGetterReturns(value, "empty", 0); |
| } |
| |
| } // namespace zxdb |