diff --git a/lldb/unittests/Expression/CMakeLists.txt b/lldb/unittests/Expression/CMakeLists.txt index 2600557b6b376..0e0b002500eb4 100644 --- a/lldb/unittests/Expression/CMakeLists.txt +++ b/lldb/unittests/Expression/CMakeLists.txt @@ -10,6 +10,7 @@ add_lldb_unittest(ExpressionTests DWARFExpressionTest.cpp CppModuleConfigurationTest.cpp ExpressionTest.cpp + ValueMatcher.cpp LINK_COMPONENTS Support diff --git a/lldb/unittests/Expression/DWARFExpressionTest.cpp b/lldb/unittests/Expression/DWARFExpressionTest.cpp index 9d11060becfae..8c5568d9e4e65 100644 --- a/lldb/unittests/Expression/DWARFExpressionTest.cpp +++ b/lldb/unittests/Expression/DWARFExpressionTest.cpp @@ -5,8 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// - #include "lldb/Expression/DWARFExpression.h" +#include "ValueMatcher.h" #ifdef ARCH_AARCH64 #include "Plugins/ABI/AArch64/ABISysV_arm64.h" #endif @@ -135,40 +135,18 @@ class MockRegisterContext : public RegisterContext { }; } // namespace -static llvm::Expected Evaluate(llvm::ArrayRef expr, - lldb::ModuleSP module_sp = {}, - DWARFUnit *unit = nullptr, - ExecutionContext *exe_ctx = nullptr, - RegisterContext *reg_ctx = nullptr) { +static llvm::Expected Evaluate(llvm::ArrayRef expr, + lldb::ModuleSP module_sp = {}, + DWARFUnit *unit = nullptr, + ExecutionContext *exe_ctx = nullptr, + RegisterContext *reg_ctx = nullptr) { DataExtractor extractor(expr.data(), expr.size(), lldb::eByteOrderLittle, /*addr_size*/ 4); - llvm::Expected result = DWARFExpression::Evaluate( - exe_ctx, reg_ctx, module_sp, extractor, unit, lldb::eRegisterKindLLDB, - /*initial_value_ptr=*/nullptr, - /*object_address_ptr=*/nullptr); - if (!result) - return result.takeError(); - - switch (result->GetValueType()) { - case Value::ValueType::Scalar: - return result->GetScalar(); - case Value::ValueType::LoadAddress: - return LLDB_INVALID_ADDRESS; - case Value::ValueType::HostAddress: { - // Convert small buffers to scalars to simplify the tests. - DataBufferHeap &buf = result->GetBuffer(); - if (buf.GetByteSize() <= 8) { - uint64_t val = 0; - memcpy(&val, buf.GetBytes(), buf.GetByteSize()); - return Scalar(llvm::APInt(buf.GetByteSize() * 8, val, false)); - } - } - [[fallthrough]]; - default: - break; - } - return llvm::createStringError("unsupported value type"); + return DWARFExpression::Evaluate(exe_ctx, reg_ctx, module_sp, extractor, unit, + lldb::eRegisterKindLLDB, + /*initial_value_ptr=*/nullptr, + /*object_address_ptr=*/nullptr); } class DWARFExpressionTester : public YAMLModuleTester { @@ -177,18 +155,11 @@ class DWARFExpressionTester : public YAMLModuleTester { : YAMLModuleTester(yaml_data, cu_index) {} using YAMLModuleTester::YAMLModuleTester; - llvm::Expected Eval(llvm::ArrayRef expr) { + llvm::Expected Eval(llvm::ArrayRef expr) { return ::Evaluate(expr, m_module_sp, m_dwarf_unit); } }; -/// Unfortunately Scalar's operator==() is really picky. -static Scalar GetScalar(unsigned bits, uint64_t value, bool sign) { - Scalar scalar(value); - scalar.TruncOrExtendTo(bits, sign); - return scalar; -} - /// This is needed for the tests that use a mock process. class DWARFExpressionMockProcessTest : public ::testing::Test { public: @@ -255,48 +226,48 @@ class MockTarget : public Target { TEST(DWARFExpression, DW_OP_pick) { EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit1, DW_OP_lit0, DW_OP_pick, 0}), - llvm::HasValue(0)); + ExpectScalar(0)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit1, DW_OP_lit0, DW_OP_pick, 1}), - llvm::HasValue(1)); + ExpectScalar(1)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit1, DW_OP_lit0, DW_OP_pick, 2}), llvm::Failed()); } TEST(DWARFExpression, DW_OP_const) { // Extend to address size. - EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const1u, 0x88}), llvm::HasValue(0x88)); + EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const1u, 0x88}), ExpectScalar(0x88)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const1s, 0x88}), - llvm::HasValue(0xffffff88)); + ExpectScalar(0xffffff88)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const2u, 0x47, 0x88}), - llvm::HasValue(0x8847)); + ExpectScalar(0x8847)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const2s, 0x47, 0x88}), - llvm::HasValue(0xffff8847)); + ExpectScalar(0xffff8847)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const4u, 0x44, 0x42, 0x47, 0x88}), - llvm::HasValue(0x88474244)); + ExpectScalar(0x88474244)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const4s, 0x44, 0x42, 0x47, 0x88}), - llvm::HasValue(0x88474244)); + ExpectScalar(0x88474244)); // Truncate to address size. EXPECT_THAT_EXPECTED( Evaluate({DW_OP_const8u, 0x00, 0x11, 0x22, 0x33, 0x44, 0x42, 0x47, 0x88}), - llvm::HasValue(0x33221100)); + ExpectScalar(0x33221100)); EXPECT_THAT_EXPECTED( Evaluate({DW_OP_const8s, 0x00, 0x11, 0x22, 0x33, 0x44, 0x42, 0x47, 0x88}), - llvm::HasValue(0x33221100)); + ExpectScalar(0x33221100)); // Don't truncate to address size for compatibility with clang (pr48087). EXPECT_THAT_EXPECTED( Evaluate({DW_OP_constu, 0x81, 0x82, 0x84, 0x88, 0x90, 0xa0, 0x40}), - llvm::HasValue(0x01010101010101)); + ExpectScalar(0x01010101010101)); EXPECT_THAT_EXPECTED( Evaluate({DW_OP_consts, 0x81, 0x82, 0x84, 0x88, 0x90, 0xa0, 0x40}), - llvm::HasValue(0xffff010101010101)); + ExpectScalar(0xffff010101010101)); } TEST(DWARFExpression, DW_OP_skip) { EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const1u, 0x42, DW_OP_skip, 0x02, 0x00, DW_OP_const1u, 0xff}), - llvm::HasValue(0x42)); + ExpectScalar(0x42)); } TEST(DWARFExpression, DW_OP_bra) { @@ -309,7 +280,7 @@ TEST(DWARFExpression, DW_OP_bra) { DW_OP_const1u, 0xff, // push 0xff }), // clang-format on - llvm::HasValue(0x42)); + ExpectScalar(0x42)); EXPECT_THAT_ERROR(Evaluate({DW_OP_bra, 0x01, 0x00}).takeError(), llvm::Failed()); @@ -414,42 +385,42 @@ TEST(DWARFExpression, DW_OP_convert) { EXPECT_THAT_EXPECTED( t.Eval({DW_OP_const4u, 0x11, 0x22, 0x33, 0x44, // DW_OP_convert, offs_uint32_t, DW_OP_stack_value}), - llvm::HasValue(GetScalar(64, 0x44332211, not_signed))); + ExpectScalar(64, 0x44332211, not_signed)); // Zero-extend to 64 bits. EXPECT_THAT_EXPECTED( t.Eval({DW_OP_const4u, 0x11, 0x22, 0x33, 0x44, // DW_OP_convert, offs_uint64_t, DW_OP_stack_value}), - llvm::HasValue(GetScalar(64, 0x44332211, not_signed))); + ExpectScalar(64, 0x44332211, not_signed)); // Sign-extend to 64 bits. EXPECT_THAT_EXPECTED( t.Eval({DW_OP_const4s, 0xcc, 0xdd, 0xee, 0xff, // DW_OP_convert, offs_sint64_t, DW_OP_stack_value}), - llvm::HasValue(GetScalar(64, 0xffffffffffeeddcc, is_signed))); + ExpectScalar(64, 0xffffffffffeeddcc, is_signed)); // Sign-extend, then truncate. EXPECT_THAT_EXPECTED( t.Eval({DW_OP_const4s, 0xcc, 0xdd, 0xee, 0xff, // DW_OP_convert, offs_sint64_t, // DW_OP_convert, offs_uint32_t, DW_OP_stack_value}), - llvm::HasValue(GetScalar(32, 0xffeeddcc, not_signed))); + ExpectScalar(32, 0xffeeddcc, not_signed)); // Truncate to default unspecified (pointer-sized) type. EXPECT_THAT_EXPECTED(t.Eval({DW_OP_const4s, 0xcc, 0xdd, 0xee, 0xff, // DW_OP_convert, offs_sint64_t, // DW_OP_convert, 0x00, DW_OP_stack_value}), - llvm::HasValue(GetScalar(32, 0xffeeddcc, not_signed))); + ExpectScalar(32, 0xffeeddcc, not_signed)); // Truncate to 8 bits. EXPECT_THAT_EXPECTED(t.Eval({DW_OP_const4s, 'A', 'B', 'C', 'D', DW_OP_convert, offs_uchar, DW_OP_stack_value}), - llvm::HasValue(GetScalar(8, 'A', not_signed))); + ExpectScalar(8, 'A', not_signed)); // Also truncate to 8 bits. EXPECT_THAT_EXPECTED(t.Eval({DW_OP_const4s, 'A', 'B', 'C', 'D', DW_OP_convert, offs_schar, DW_OP_stack_value}), - llvm::HasValue(GetScalar(8, 'A', is_signed))); + ExpectScalar(8, 'A', is_signed)); // // Errors. @@ -479,33 +450,21 @@ TEST(DWARFExpression, DW_OP_stack_value) { TEST(DWARFExpression, DW_OP_piece) { EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const2u, 0x11, 0x22, DW_OP_piece, 2, DW_OP_const2u, 0x33, 0x44, DW_OP_piece, 2}), - llvm::HasValue(GetScalar(32, 0x44332211, true))); + ExpectHostAddress({0x11, 0x22, 0x33, 0x44})); EXPECT_THAT_EXPECTED( Evaluate({DW_OP_piece, 1, DW_OP_const1u, 0xff, DW_OP_piece, 1}), // Note that the "00" should really be "undef", but we can't // represent that yet. - llvm::HasValue(GetScalar(16, 0xff00, true))); -} - -TEST(DWARFExpression, DW_OP_piece_host_address) { - static const uint8_t expr_data[] = {DW_OP_lit2, DW_OP_stack_value, - DW_OP_piece, 40}; - llvm::ArrayRef expr(expr_data, sizeof(expr_data)); - DataExtractor extractor(expr.data(), expr.size(), lldb::eByteOrderLittle, 4); + ExpectHostAddress({0x00, 0xff})); // This tests if ap_int is extended to the right width. // expect 40*8 = 320 bits size. - llvm::Expected result = - DWARFExpression::Evaluate(nullptr, nullptr, nullptr, extractor, nullptr, - lldb::eRegisterKindDWARF, nullptr, nullptr); - ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); - ASSERT_EQ(result->GetValueType(), Value::ValueType::HostAddress); - ASSERT_EQ(result->GetBuffer().GetByteSize(), 40ul); - const uint8_t *data = result->GetBuffer().GetBytes(); - ASSERT_EQ(data[0], 2); - for (int i = 1; i < 40; i++) { - ASSERT_EQ(data[i], 0); - } + std::vector expected_host_buffer(40, 0); + expected_host_buffer[0] = 2; + + EXPECT_THAT_EXPECTED( + Evaluate({{DW_OP_lit2, DW_OP_stack_value, DW_OP_piece, 40}}), + ExpectHostAddress(expected_host_buffer)); } TEST(DWARFExpression, DW_OP_implicit_value) { @@ -513,7 +472,7 @@ TEST(DWARFExpression, DW_OP_implicit_value) { EXPECT_THAT_EXPECTED( Evaluate({DW_OP_implicit_value, bytes, 0x11, 0x22, 0x33, 0x44}), - llvm::HasValue(GetScalar(8 * bytes, 0x44332211, true))); + ExpectHostAddress({0x11, 0x22, 0x33, 0x44})); } TEST(DWARFExpression, DW_OP_unknown) { @@ -548,20 +507,13 @@ TEST_F(DWARFExpressionMockProcessTest, DW_OP_deref) { // Implicit location: *0x4. EXPECT_THAT_EXPECTED( Evaluate({DW_OP_lit4, DW_OP_deref, DW_OP_stack_value}, {}, {}, &exe_ctx), - llvm::HasValue(GetScalar(32, 0x07060504, false))); + ExpectScalar(32, 0x07060504, false)); // Memory location: *(*0x4). - // Evaluate returns LLDB_INVALID_ADDRESS for all load addresses. EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit4, DW_OP_deref}, {}, {}, &exe_ctx), - llvm::HasValue(Scalar(LLDB_INVALID_ADDRESS))); + ExpectLoadAddress(0x07060504)); // Memory location: *0x4. - // Evaluate returns LLDB_INVALID_ADDRESS for all load addresses. EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit4}, {}, {}, &exe_ctx), - llvm::HasValue(Scalar(4))); - // Implicit location: *0x4. - // Evaluate returns LLDB_INVALID_ADDRESS for all load addresses. - EXPECT_THAT_EXPECTED( - Evaluate({DW_OP_lit4, DW_OP_deref, DW_OP_stack_value}, {}, {}, &exe_ctx), - llvm::HasValue(GetScalar(32, 0x07060504, false))); + ExpectScalar(Scalar(4))); } TEST_F(DWARFExpressionMockProcessTest, WASM_DW_OP_addr) { @@ -581,18 +533,9 @@ TEST_F(DWARFExpressionMockProcessTest, WASM_DW_OP_addr) { ExecutionContext exe_ctx(target_sp, false); // DW_OP_addr takes a single operand of address size width: - uint8_t expr[] = {DW_OP_addr, 0x40, 0x0, 0x0, 0x0}; - DataExtractor extractor(expr, sizeof(expr), lldb::eByteOrderLittle, - /*addr_size*/ 4); - - llvm::Expected result = DWARFExpression::Evaluate( - &exe_ctx, /*reg_ctx*/ nullptr, /*module_sp*/ {}, extractor, - /*unit*/ nullptr, lldb::eRegisterKindLLDB, - /*initial_value_ptr*/ nullptr, - /*object_address_ptr*/ nullptr); - - ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); - ASSERT_EQ(result->GetValueType(), Value::ValueType::LoadAddress); + EXPECT_THAT_EXPECTED( + Evaluate({DW_OP_addr, 0x40, 0x0, 0x0, 0x0}, {}, {}, &exe_ctx), + ExpectLoadAddress(0x40)); } TEST_F(DWARFExpressionMockProcessTest, WASM_DW_OP_addr_index) { @@ -676,15 +619,11 @@ TEST_F(DWARFExpressionMockProcessTest, WASM_DW_OP_addr_index) { DWARFExpression expr(extractor); llvm::Expected result = evaluate(expr); - ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); - ASSERT_EQ(result->GetValueType(), Value::ValueType::LoadAddress); - ASSERT_EQ(result->GetScalar().UInt(), 0x5678u); + EXPECT_THAT_EXPECTED(result, ExpectLoadAddress(0x5678u)); ASSERT_TRUE(expr.Update_DW_OP_addr(dwarf_cu, 0xdeadbeef)); result = evaluate(expr); - ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); - ASSERT_EQ(result->GetValueType(), Value::ValueType::LoadAddress); - ASSERT_EQ(result->GetScalar().UInt(), 0xdeadbeefu); + EXPECT_THAT_EXPECTED(result, ExpectLoadAddress(0xdeadbeefu)); } class CustomSymbolFileDWARF : public SymbolFileDWARF { @@ -778,11 +717,12 @@ static auto testExpressionVendorExtensions(lldb::ModuleSP module_sp, RegisterContext *reg_ctx) { // Test that expression extensions can be evaluated, for example // DW_OP_WASM_location which is not currently handled by DWARFExpression: - EXPECT_THAT_EXPECTED(Evaluate({DW_OP_WASM_location, 0x03, // WASM_GLOBAL:0x03 - 0x04, 0x00, 0x00, // index:u32 - 0x00, DW_OP_stack_value}, - module_sp, &dwarf_unit, nullptr, reg_ctx), - llvm::HasValue(GetScalar(32, 42, false))); + EXPECT_THAT_EXPECTED( + Evaluate({DW_OP_WASM_location, 0x03, // WASM_GLOBAL:0x03 + 0x04, 0x00, 0x00, // index:u32 + 0x00, DW_OP_stack_value}, + module_sp, &dwarf_unit, nullptr, reg_ctx), + ExpectScalar(32, 42, false, Value::ContextType::RegisterInfo)); // Test that searches for opcodes work in the presence of extensions: uint8_t expr[] = {DW_OP_WASM_location, 0x03, 0x04, 0x00, 0x00, 0x00, @@ -1148,17 +1088,8 @@ TEST_F(DWARFExpressionMockProcessTest, DW_OP_piece_file_addr) { uint8_t expr[] = {DW_OP_addr, 0x40, 0x0, 0x0, 0x0, DW_OP_piece, 1, DW_OP_addr, 0x50, 0x0, 0x0, 0x0, DW_OP_piece, 1}; - DataExtractor extractor(expr, sizeof(expr), lldb::eByteOrderLittle, - /*addr_size=*/4); - llvm::Expected result = DWARFExpression::Evaluate( - &exe_ctx, /*reg_ctx=*/nullptr, /*module_sp=*/{}, extractor, - /*unit=*/nullptr, lldb::eRegisterKindLLDB, - /*initial_value_ptr=*/nullptr, - /*object_address_ptr=*/nullptr); - - ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); - ASSERT_EQ(result->GetValueType(), Value::ValueType::HostAddress); - ASSERT_THAT(result->GetBuffer().GetData(), ElementsAre(0x11, 0x22)); + EXPECT_THAT_EXPECTED(Evaluate(expr, {}, {}, &exe_ctx), + ExpectHostAddress({0x11, 0x22})); } /// A Process whose `ReadMemory` override queries a DenseMap. @@ -1228,28 +1159,15 @@ TEST_F(DWARFExpressionMockProcessTestWithAArch, DW_op_deref_no_ptr_fixing) { process_sp->GetThreadList().AddThread(thread); auto evaluate_expr = [&](auto &expr_data) { - DataExtractor extractor(expr_data, sizeof(expr_data), - lldb::eByteOrderLittle, - /*addr_size*/ 8); - DWARFExpression expr(extractor); - ExecutionContext exe_ctx(process_sp); - llvm::Expected result = DWARFExpression::Evaluate( - &exe_ctx, reg_ctx_sp.get(), /*module_sp*/ nullptr, extractor, - /*unit*/ nullptr, lldb::eRegisterKindLLDB, - /*initial_value_ptr=*/nullptr, - /*object_address_ptr=*/nullptr); - return result; + return Evaluate(expr_data, {}, {}, &exe_ctx, reg_ctx_sp.get()); }; uint8_t expr_reg[] = {DW_OP_breg22, 0}; llvm::Expected result_reg = evaluate_expr(expr_reg); - ASSERT_THAT_EXPECTED(result_reg, llvm::Succeeded()); - ASSERT_EQ(result_reg->GetValueType(), Value::ValueType::LoadAddress); - ASSERT_EQ(result_reg->GetScalar().ULongLong(), addr); + EXPECT_THAT_EXPECTED(result_reg, ExpectLoadAddress(addr)); uint8_t expr_deref[] = {DW_OP_breg22, 0, DW_OP_deref}; llvm::Expected result_deref = evaluate_expr(expr_deref); - ASSERT_THAT_EXPECTED(result_deref, llvm::Succeeded()); - ASSERT_EQ(result_deref->GetScalar().ULongLong(), expected_value); + EXPECT_THAT_EXPECTED(result_deref, ExpectLoadAddress(expected_value)); } diff --git a/lldb/unittests/Expression/ValueMatcher.cpp b/lldb/unittests/Expression/ValueMatcher.cpp new file mode 100644 index 0000000000000..ee7ccaebabd64 --- /dev/null +++ b/lldb/unittests/Expression/ValueMatcher.cpp @@ -0,0 +1,205 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "ValueMatcher.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/InterleavedRange.h" +#include "llvm/Support/raw_os_ostream.h" +#include "llvm/Support/raw_ostream.h" + +using namespace lldb_private; + +static void FormatValueDetails(llvm::raw_ostream &os, + Value::ValueType value_type, + Value::ContextType context_type, + const Scalar &scalar, + llvm::ArrayRef buffer_data) { + os << "Value("; + os << "value_type=" << Value::GetValueTypeAsCString(value_type); + os << ", context_type=" << Value::GetContextTypeAsCString(context_type); + + if (value_type == Value::ValueType::HostAddress) { + auto bytes_to_print = buffer_data.take_front(16); + os << ", buffer=["; + llvm::interleave( + bytes_to_print, + [&](uint8_t byte) { + os << llvm::format("%02x", static_cast(byte)); + }, + [&]() { os << " "; }); + if (buffer_data.size() > 16) + os << " ..."; + os << "] (" << buffer_data.size() << " bytes)"; + } else { + os << ", value=" << scalar; + } + os << ")"; +} + +void lldb_private::PrintTo(const Value &val, std::ostream *os) { + if (!os) + return; + + llvm::raw_os_ostream raw_os(*os); + FormatValueDetails(raw_os, val.GetValueType(), val.GetContextType(), + val.GetScalar(), val.GetBuffer().GetData()); +} + +bool ValueMatcher::MatchAndExplain(const Value &val, + std::ostream *stream) const { + if (stream) { + llvm::raw_os_ostream os(*stream); + return MatchAndExplainImpl(val, os); + } + + llvm::raw_null_ostream os; + return MatchAndExplainImpl(val, os); +} + +// Match the provided value and explain any mismatches using +// the raw_ostream. We use the llvm::raw_ostream here to simplify the formatting +// of Scalar values which already know how to print themselves to that stream. +bool ValueMatcher::MatchAndExplainImpl(const Value &val, + llvm::raw_ostream &os) const { + if (val.GetValueType() != m_value_type) { + os << "value_type mismatch: expected " + << Value::GetValueTypeAsCString(m_value_type) << ", got " + << Value::GetValueTypeAsCString(val.GetValueType()) << " "; + return false; + } + + if (val.GetContextType() != m_context_type) { + os << "context_type mismatch: expected " + << Value::GetContextTypeAsCString(m_context_type) << ", got " + << Value::GetContextTypeAsCString(val.GetContextType()) << " "; + return false; + } + + if (m_value_type == Value::ValueType::HostAddress) { + const DataBufferHeap &buffer = val.GetBuffer(); + const size_t buffer_size = buffer.GetByteSize(); + if (buffer_size != m_expected_bytes.size()) { + os << "buffer size mismatch: expected " << m_expected_bytes.size() + << ", got " << buffer_size << " "; + return false; + } + + const uint8_t *data = buffer.GetBytes(); + for (size_t i = 0; i < buffer_size; ++i) { + if (data[i] != m_expected_bytes[i]) { + os << "byte mismatch at index " << i << ": expected " + << llvm::format("0x%02x", static_cast(m_expected_bytes[i])) + << ", got " << llvm::format("0x%02x", static_cast(data[i])) + << " "; + return false; + } + } + } else { + // For Scalar, FileAddress, and LoadAddress compare m_value. + const Scalar &actual_scalar = val.GetScalar(); + if (actual_scalar != m_expected_scalar) { + os << "scalar value mismatch: expected " << m_expected_scalar << ", got " + << actual_scalar; + return false; + } + } + + return true; +} + +void ValueMatcher::DescribeTo(std::ostream *os) const { + if (!os) + return; + llvm::raw_os_ostream raw_os(*os); + FormatValueDetails(raw_os, m_value_type, m_context_type, m_expected_scalar, + m_expected_bytes); +} + +void ValueMatcher::DescribeNegationTo(std::ostream *os) const { + if (!os) + return; + *os << "value does not match"; +} + +testing::Matcher +lldb_private::MatchScalarValue(Value::ValueType value_type, + const Scalar &expected_scalar, + Value::ContextType context_type) { + return ValueMatcher(value_type, expected_scalar, context_type); +} + +testing::Matcher +lldb_private::MatchHostValue(Value::ValueType value_type, + const std::vector &expected_bytes, + Value::ContextType context_type) { + return ValueMatcher(value_type, expected_bytes, context_type); +} + +testing::Matcher +lldb_private::IsScalar(const Scalar &expected_scalar, + Value::ContextType context_type) { + return MatchScalarValue(Value::ValueType::Scalar, expected_scalar, + context_type); +} + +testing::Matcher +lldb_private::IsLoadAddress(const Scalar &expected_address, + Value::ContextType context_type) { + return MatchScalarValue(Value::ValueType::LoadAddress, expected_address, + context_type); +} + +testing::Matcher +lldb_private::IsFileAddress(const Scalar &expected_address, + Value::ContextType context_type) { + return MatchScalarValue(Value::ValueType::FileAddress, expected_address, + context_type); +} + +testing::Matcher +lldb_private::IsHostValue(const std::vector &expected_bytes, + Value::ContextType context_type) { + return MatchHostValue(Value::ValueType::HostAddress, expected_bytes, + context_type); +} + +Scalar lldb_private::GetScalar(unsigned bits, uint64_t value, bool sign) { + Scalar scalar(value); + scalar.TruncOrExtendTo(bits, sign); + return scalar; +} + +llvm::detail::ValueMatchesPoly> +lldb_private::ExpectScalar(const Scalar &expected_scalar, + Value::ContextType context_type) { + return llvm::HasValue(IsScalar(expected_scalar, context_type)); +} + +llvm::detail::ValueMatchesPoly> +lldb_private::ExpectScalar(unsigned bits, uint64_t value, bool sign, + Value::ContextType context_type) { + return ExpectScalar(GetScalar(bits, value, sign), context_type); +} + +llvm::detail::ValueMatchesPoly> +lldb_private::ExpectLoadAddress(const Scalar &expected_address, + Value::ContextType context_type) { + return llvm::HasValue(IsLoadAddress(expected_address, context_type)); +} + +llvm::detail::ValueMatchesPoly> +lldb_private::ExpectFileAddress(const Scalar &expected_address, + Value::ContextType context_type) { + return llvm::HasValue(IsFileAddress(expected_address, context_type)); +} + +llvm::detail::ValueMatchesPoly> +lldb_private::ExpectHostAddress(const std::vector &expected_bytes, + Value::ContextType context_type) { + return llvm::HasValue(IsHostValue(expected_bytes, context_type)); +} diff --git a/lldb/unittests/Expression/ValueMatcher.h b/lldb/unittests/Expression/ValueMatcher.h new file mode 100644 index 0000000000000..3ca7b15e1d3c8 --- /dev/null +++ b/lldb/unittests/Expression/ValueMatcher.h @@ -0,0 +1,155 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// \file +/// This file contains the definition of the ValueMatcher class which is a used +/// to match lldb_private::Value in gtest assert/expect macros. It also contains +/// several helper functions to create matchers for common Value types. +/// +/// The ValueMatcher class was created using the gtest guide found here: +// https://google.github.io/googletest/gmock_cook_book.html#writing-new-monomorphic-matchers +//===----------------------------------------------------------------------===// + +#ifndef LLDB_UNITTESTS_EXPRESSION_VALUEMATCHER_H +#define LLDB_UNITTESTS_EXPRESSION_VALUEMATCHER_H + +#include "lldb/Core/Value.h" +#include "lldb/Utility/Scalar.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Testing/Support/Error.h" +#include "gtest/gtest.h" +#include +#include + +namespace lldb_private { + +/// Custom printer for Value objects to make test failures more readable. +void PrintTo(const Value &val, std::ostream *os); + +/// Custom matcher for Value. +/// +/// It matches against an expected value_type, and context_type. +/// For HostAddress value types it will match the expected contents of +/// the host buffer. For other value types it matches against an expected +/// scalar value. +class ValueMatcher { +public: + ValueMatcher(Value::ValueType value_type, const Scalar &expected_scalar, + Value::ContextType context_type) + : m_value_type(value_type), m_context_type(context_type), + m_expected_scalar(expected_scalar) { + assert(value_type == Value::ValueType::Scalar || + value_type == Value::ValueType::FileAddress || + value_type == Value::ValueType::LoadAddress); + } + + ValueMatcher(Value::ValueType value_type, + const std::vector &expected_bytes, + Value::ContextType context_type) + : m_value_type(value_type), m_context_type(context_type), + m_expected_bytes(expected_bytes) { + assert(value_type == Value::ValueType::HostAddress); + } + + // Typedef to hook into the gtest matcher machinery. + using is_gtest_matcher = void; + + bool MatchAndExplain(const Value &val, std::ostream *os) const; + + void DescribeTo(std::ostream *os) const; + + void DescribeNegationTo(std::ostream *os) const; + +private: + Value::ValueType m_value_type = Value::ValueType::Invalid; + Value::ContextType m_context_type = Value::ContextType::Invalid; + Scalar m_expected_scalar; + std::vector m_expected_bytes; + + bool MatchAndExplainImpl(const Value &val, llvm::raw_ostream &os) const; +}; + +/// Matcher for Value with Scalar, FileAddress, or LoadAddress types. +/// Use with llvm::HasValue() to match Expected: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(MatchScalarValue(...))); +testing::Matcher MatchScalarValue(Value::ValueType value_type, + const Scalar &expected_scalar, + Value::ContextType context_type); + +/// Matcher for Value with HostAddress type. +/// Use with llvm::HasValue() to match Expected: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(MatchHostValue(...))); +testing::Matcher +MatchHostValue(Value::ValueType value_type, + const std::vector &expected_bytes, + Value::ContextType context_type); + +/// Helper to match a Scalar value and context type. +/// Use with llvm::HasValue() to match Expected: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(IsScalar(42))); +testing::Matcher IsScalar(const Scalar &expected_scalar, + Value::ContextType context_type); + +/// Helper to match a LoadAddress value and context type. +/// Use with llvm::HasValue() to match Expected: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(IsLoadAddress(0x1000))); +testing::Matcher IsLoadAddress(const Scalar &expected_address, + Value::ContextType context_type); + +/// Helper to match a FileAddress value and context type. +/// Use with llvm::HasValue() to match Expected: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(IsFileAddress(Scalar(0x1000)))); +testing::Matcher IsFileAddress(const Scalar &expected_address, + Value::ContextType context_type); + +/// Helper to match a HostAddress value and context type. +/// Use with llvm::HasValue() to match Expected: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(IsHostValue({0x11, 0x22}))); +testing::Matcher IsHostValue(const std::vector &expected_bytes, + Value::ContextType context_type); + +/// Helper to create a scalar because Scalar's operator==() is really picky. +Scalar GetScalar(unsigned bits, uint64_t value, bool sign); + +/// Helper that combines IsScalar with llvm::HasValue for Expected. +/// Use it on an Expected like this: +/// EXPECT_THAT_EXPECTED(result, ExpectScalar(42)); +llvm::detail::ValueMatchesPoly> +ExpectScalar(const Scalar &expected_scalar, + Value::ContextType context_type = Value::ContextType::Invalid); + +/// Helper that combines GetScalar with ExpectScalar to get a precise scalar. +/// Use it on an Expected like this: +/// EXPECT_THAT_EXPECTED(result, ExpectScalar(8, 42, true)); +llvm::detail::ValueMatchesPoly> +ExpectScalar(unsigned bits, uint64_t value, bool sign, + Value::ContextType context_type = Value::ContextType::Invalid); + +/// Helper that combines IsLoadAddress with llvm::HasValue for Expected. +/// Use it on an Expected like this: +/// EXPECT_THAT_EXPECTED(result, ExpectLoadAddress(0x1000)); +llvm::detail::ValueMatchesPoly> ExpectLoadAddress( + const Scalar &expected_address, + Value::ContextType context_type = Value::ContextType::Invalid); + +/// Helper that combines IsFileAddress with llvm::HasValue for Expected. +/// Use it on an Expected like this: +/// EXPECT_THAT_EXPECTED(result, ExpectFileAddress(Scalar(0x2000))); +llvm::detail::ValueMatchesPoly> ExpectFileAddress( + const Scalar &expected_address, + Value::ContextType context_type = Value::ContextType::Invalid); + +/// Helper that combines IsHostValue with llvm::HasValue for Expected. +/// Use it on an Expected like this: +/// EXPECT_THAT_EXPECTED(result, ExpectHostAddress({0x11, 0x22})); +llvm::detail::ValueMatchesPoly> ExpectHostAddress( + const std::vector &expected_bytes, + Value::ContextType context_type = Value::ContextType::Invalid); + +} // namespace lldb_private + +#endif // LLDB_UNITTESTS_EXPRESSION_VALUEMATCHER_H