| //===----------------------------------------------------------------------===// |
| // |
| // 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<uint8_t> 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<unsigned>(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<unsigned>(m_expected_bytes[i])) |
| << ", got " << llvm::format("0x%02x", static_cast<unsigned>(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<Value> |
| 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<Value> |
| lldb_private::MatchHostValue(Value::ValueType value_type, |
| const std::vector<uint8_t> &expected_bytes, |
| Value::ContextType context_type) { |
| return ValueMatcher(value_type, expected_bytes, context_type); |
| } |
| |
| testing::Matcher<Value> |
| lldb_private::IsScalar(const Scalar &expected_scalar, |
| Value::ContextType context_type) { |
| return MatchScalarValue(Value::ValueType::Scalar, expected_scalar, |
| context_type); |
| } |
| |
| testing::Matcher<Value> |
| lldb_private::IsLoadAddress(const Scalar &expected_address, |
| Value::ContextType context_type) { |
| return MatchScalarValue(Value::ValueType::LoadAddress, expected_address, |
| context_type); |
| } |
| |
| testing::Matcher<Value> |
| lldb_private::IsFileAddress(const Scalar &expected_address, |
| Value::ContextType context_type) { |
| return MatchScalarValue(Value::ValueType::FileAddress, expected_address, |
| context_type); |
| } |
| |
| testing::Matcher<Value> |
| lldb_private::IsHostValue(const std::vector<uint8_t> &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<testing::Matcher<Value>> |
| lldb_private::ExpectScalar(const Scalar &expected_scalar, |
| Value::ContextType context_type) { |
| return llvm::HasValue(IsScalar(expected_scalar, context_type)); |
| } |
| |
| llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> |
| 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<testing::Matcher<Value>> |
| lldb_private::ExpectLoadAddress(const Scalar &expected_address, |
| Value::ContextType context_type) { |
| return llvm::HasValue(IsLoadAddress(expected_address, context_type)); |
| } |
| |
| llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> |
| lldb_private::ExpectFileAddress(const Scalar &expected_address, |
| Value::ContextType context_type) { |
| return llvm::HasValue(IsFileAddress(expected_address, context_type)); |
| } |
| |
| llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> |
| lldb_private::ExpectHostAddress(const std::vector<uint8_t> &expected_bytes, |
| Value::ContextType context_type) { |
| return llvm::HasValue(IsHostValue(expected_bytes, context_type)); |
| } |