| // 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/eval_operators.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/mock_expr_node.h" |
| #include "src/developer/debug/zxdb/symbols/base_type.h" |
| #include "src/developer/debug/zxdb/symbols/type_test_support.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| using debug_ipc::RegisterID; |
| |
| class EvalOperators : public TestWithLoop { |
| public: |
| EvalOperators() : eval_context_(fxl::MakeRefCounted<MockEvalContext>()) {} |
| ~EvalOperators() = default; |
| |
| fxl::RefPtr<MockEvalContext>& eval_context() { return eval_context_; } |
| |
| ErrOrValue SyncEvalBinaryOperator(const ExprValue& left, ExprTokenType op, |
| const ExprValue& right) { |
| ErrOrValue result((ExprValue())); |
| EvalBinaryOperator(eval_context(), left, ExprToken(op, "", 0), right, |
| [&result](ErrOrValue value) { result = value; }); |
| loop().RunUntilNoTasks(); |
| return result; |
| } |
| |
| ErrOrValue SyncEvalUnaryOperator(ExprTokenType op, const ExprValue& right) { |
| ErrOrValue result((ExprValue())); |
| EvalUnaryOperator(eval_context(), ExprToken(op, "", 0), right, |
| [&result](ErrOrValue value) { result = value; }); |
| return result; |
| } |
| |
| template <typename T> |
| [[clang::no_sanitize("signed-integer-overflow")]] // Allows -INT32_MAX overflow. |
| void DoUnaryMinusTest(T in) { |
| ExprValue original(in); |
| |
| ErrOrValue out = SyncEvalUnaryOperator(ExprTokenType::kMinus, original); |
| ASSERT_TRUE(out.ok()) << out.err().msg(); |
| |
| // This checked that the type conversions have followed C rules. This is the expected value |
| // (int/unsigned unchanged, everything smaller than an int is promoted to an int, everything |
| // larger remains unchanged). |
| auto expected = -in; |
| |
| // The type of the output should be the same as the input for unary '-'. |
| // TODO(brettw) the actual type pointer should be the same. |
| EXPECT_EQ(sizeof(expected), out.value().data().size()); |
| if (std::is_floating_point<decltype(expected)>::value) { |
| EXPECT_EQ(BaseType::kBaseTypeFloat, out.value().GetBaseType()); |
| } else if (std::is_unsigned<decltype(expected)>::value) { |
| EXPECT_EQ(BaseType::kBaseTypeUnsigned, out.value().GetBaseType()); |
| } else { |
| EXPECT_EQ(BaseType::kBaseTypeSigned, out.value().GetBaseType()); |
| } |
| EXPECT_EQ(expected, out.value().GetAs<decltype(expected)>()); |
| } |
| |
| template <typename T> |
| void DoUnaryMinusTypeTest() { |
| DoUnaryMinusTest<T>(0); |
| DoUnaryMinusTest<T>(static_cast<T>(5)); |
| DoUnaryMinusTest<T>(static_cast<T>(-5)); |
| DoUnaryMinusTest<T>(std::numeric_limits<T>::max()); |
| DoUnaryMinusTest<T>(std::numeric_limits<T>::lowest()); |
| } |
| |
| private: |
| fxl::RefPtr<MockEvalContext> eval_context_; |
| }; |
| |
| } // namespace |
| |
| TEST_F(EvalOperators, AssignmentMem) { |
| auto int32_type = MakeInt32Type(); |
| |
| // The casting test provides most tests for conversions so this test just |
| // checks that the correct values are written and returned. |
| constexpr uint64_t kAddress = 0x98723461923; |
| ExprValue dest(int32_type, {0, 0, 0, 0}, ExprValueSource(kAddress)); |
| |
| std::vector<uint8_t> data{0x12, 0x34, 0x56, 0x78}; |
| ExprValue source(int32_type, data, ExprValueSource()); |
| |
| ErrOrValue out = SyncEvalBinaryOperator(dest, ExprTokenType::kEquals, source); |
| |
| // Written value returned. |
| ASSERT_FALSE(out.has_error()); |
| EXPECT_EQ(source, out.value()); |
| |
| // Memory written to target. |
| auto mem_writes = eval_context()->data_provider()->GetMemoryWrites(); |
| ASSERT_EQ(1u, mem_writes.size()); |
| EXPECT_EQ(kAddress, mem_writes[0].first); |
| EXPECT_EQ(data, mem_writes[0].second); |
| } |
| |
| TEST_F(EvalOperators, AssignmentBad) { |
| auto int32_type = MakeInt32Type(); |
| |
| ExprValue source(42); // Value we'll assign from. |
| |
| // Assignment to a temporary. |
| ExprValue temp_value(0, fxl::RefPtr<Type>(), ExprValueSource(ExprValueSource::Type::kTemporary)); |
| ErrOrValue out = SyncEvalBinaryOperator(temp_value, ExprTokenType::kEquals, source); |
| ASSERT_TRUE(out.has_error()); |
| EXPECT_EQ("Can't assign to a temporary.", out.err().msg()); |
| |
| // Assignment to a constant. |
| ExprValue const_value(0, fxl::RefPtr<Type>(), ExprValueSource(ExprValueSource::Type::kConstant)); |
| out = SyncEvalBinaryOperator(const_value, ExprTokenType::kEquals, source); |
| ASSERT_TRUE(out.has_error()); |
| EXPECT_EQ("Can't assign to a constant.", out.err().msg()); |
| } |
| |
| TEST_F(EvalOperators, AssignmentFullRegister) { |
| // Assign to a full regular register. |
| auto int64_type = MakeUint64Type(); |
| ExprValue dest(static_cast<uint64_t>(0), int64_type, ExprValueSource(RegisterID::kX64_rax)); |
| |
| constexpr uint64_t kValue = 0x12345678; |
| ExprValue source(kValue); |
| |
| ErrOrValue out = SyncEvalBinaryOperator(dest, ExprTokenType::kEquals, source); |
| |
| // Written value returned. |
| ASSERT_FALSE(out.has_error()); |
| EXPECT_EQ(source, out.value()); |
| |
| // Register written to target. |
| auto reg_writes = eval_context()->data_provider()->GetRegisterWrites(); |
| ASSERT_EQ(1u, reg_writes.size()); |
| EXPECT_EQ(RegisterID::kX64_rax, reg_writes[0].first); |
| EXPECT_EQ(source.data(), reg_writes[0].second); |
| } |
| |
| TEST_F(EvalOperators, AssignmentBitfieldRegister) { |
| // |- AH -| |
| // Byte: 7 6 5 4 3 2 1 0 |
| // RAX: -------- -------- -------- -------- -------- -------- -----==- -------- |
| // ^^ dest bits |
| |
| // Assign to bit #1-2 (next-to-low and the next highest one) of a subregister that's itself 8 bits |
| // from the low but of the rax register. |
| ExprValue dest(static_cast<uint8_t>(0), fxl::RefPtr<Type>(), |
| ExprValueSource(RegisterID::kX64_ah, 2, 1)); |
| |
| // Existing register value has each byte numbered. Both reads and writes should be for the |
| // canonical register. |
| eval_context()->data_provider()->AddRegisterValue(RegisterID::kX64_rax, false, |
| std::vector<uint8_t>{0, 1, 2, 3, 4, 5, 6, 7}); |
| |
| constexpr uint8_t kValue = 0x3; // Set both bits to 1. |
| ExprValue source(kValue); |
| |
| ErrOrValue out = SyncEvalBinaryOperator(dest, ExprTokenType::kEquals, source); |
| |
| // Written value returned. |
| ASSERT_FALSE(out.has_error()); |
| EXPECT_EQ(source, out.value()); |
| |
| // Register written to target. |
| auto reg_writes = eval_context()->data_provider()->GetRegisterWrites(); |
| ASSERT_EQ(1u, reg_writes.size()); |
| EXPECT_EQ(RegisterID::kX64_rax, reg_writes[0].first); |
| std::vector<uint8_t> expected{0, 7, 2, 3, 4, 5, 6, 7}; // Set bits 1-2 of byte 1. |
| EXPECT_EQ(expected, reg_writes[0].second); |
| } |
| |
| TEST_F(EvalOperators, AssignmentVectorRegister) { |
| // Writing the next-to-highest 64-bit word fo the 256-bit "ymm0" register. The "192" is |
| // 128 - 64 (unused high word) - 64 (word we're changing). |
| auto double_type = fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeFloat, 8, "double"); |
| ExprValue dest(static_cast<double>(0), fxl::RefPtr<Type>(), |
| ExprValueSource(RegisterID::kX64_ymm0, 64, 128)); |
| |
| // Existing 512-bit register value has each 16-bit word numbered. Both reads and writes should be |
| // for the canonical register. |
| // clang-format off |
| std::vector<uint8_t> original{ 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, |
| 8, 0, 9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, |
| 16, 0, 17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, |
| 24, 0, 25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0 }; |
| eval_context()->data_provider()->AddRegisterValue(RegisterID::kX64_zmm0, false, original); |
| // clang-format on |
| |
| std::vector<uint8_t> new_data{0x91, 0x92, 0x93, 0x04, 0x95, 0x96, 0x97, 0x98}; |
| ExprValue source(double_type, new_data); |
| |
| ErrOrValue out = SyncEvalBinaryOperator(dest, ExprTokenType::kEquals, source); |
| |
| // Written value returned. |
| ASSERT_FALSE(out.has_error()); |
| EXPECT_EQ(source, out.value()); |
| |
| // Register written to target. |
| auto reg_writes = eval_context()->data_provider()->GetRegisterWrites(); |
| ASSERT_EQ(1u, reg_writes.size()); |
| EXPECT_EQ(RegisterID::kX64_zmm0, reg_writes[0].first); |
| std::vector<uint8_t> expected = original; |
| for (size_t i = 0; i < new_data.size(); i++) |
| expected[16 + i] = new_data[i]; |
| EXPECT_EQ(expected, reg_writes[0].second); |
| } |
| |
| TEST_F(EvalOperators, IntArithmetic) { |
| // Simple signed arithmatic of 32-bit types. We promote all math results to 64-bit. |
| ErrOrValue out = SyncEvalBinaryOperator(ExprValue(static_cast<int32_t>(12)), ExprTokenType::kPlus, |
| ExprValue(static_cast<int32_t>(-1))); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(11, out.value().GetAs<int64_t>()); |
| |
| // Type promotion to larger size. This uses a custom 64-bit int type so we can tell it's been |
| // preserved. This is "127 + (-2)" |
| auto weird_64 = fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeSigned, 8, "Weird64"); |
| out = |
| SyncEvalBinaryOperator(ExprValue(static_cast<int8_t>(0x7f)), ExprTokenType::kPlus, |
| ExprValue(weird_64, {0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(125, out.value().GetAs<int64_t>()); |
| EXPECT_EQ(weird_64.get(), out.value().type()); |
| |
| // Promotion to unsigned when sizes match. |
| auto int32_type = MakeInt32Type(); |
| auto uint32_type = MakeUint32Type(); |
| out = SyncEvalBinaryOperator(ExprValue(int32_type, {1, 0, 0, 0}), ExprTokenType::kPlus, |
| ExprValue(uint32_type, {2, 0, 0, 0})); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(3, out.value().GetAs<int64_t>()); |
| EXPECT_EQ(BaseType::kBaseTypeUnsigned, out.value().type()->AsBaseType()->base_type()); |
| |
| // Signed subtraction. |
| out = SyncEvalBinaryOperator(ExprValue(static_cast<int8_t>(100)), ExprTokenType::kMinus, |
| ExprValue(static_cast<int8_t>(-100))); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(200, out.value().GetAs<int64_t>()); |
| |
| // Overflow of input type with multiplication. |
| out = SyncEvalBinaryOperator(ExprValue(static_cast<int8_t>(100)), ExprTokenType::kStar, |
| ExprValue(static_cast<int8_t>(100))); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(10000, out.value().GetAs<int64_t>()); |
| |
| // Boundary condition, should promote to unsigned 64-bit and do the multiplication. |
| out = SyncEvalBinaryOperator(ExprValue(static_cast<uint32_t>(0xffffffff)), ExprTokenType::kStar, |
| ExprValue(static_cast<uint32_t>(0xffffffff))); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(0xfffffffe00000001, out.value().GetAs<uint64_t>()); |
| |
| // Signed integer division. |
| out = SyncEvalBinaryOperator(ExprValue(100), ExprTokenType::kSlash, ExprValue(-12)); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(-8, out.value().GetAs<int64_t>()); |
| |
| // Unsigned integer division. "100 / (unsigned)-12" does give 0. |
| out = SyncEvalBinaryOperator(ExprValue(100), ExprTokenType::kSlash, |
| ExprValue(static_cast<unsigned>(-12))); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(0, out.value().GetAs<int64_t>()); |
| |
| // Modulo. |
| out = SyncEvalBinaryOperator(ExprValue(108), ExprTokenType::kPercent, |
| ExprValue(static_cast<unsigned>(100))); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(8, out.value().GetAs<int64_t>()); |
| |
| // Division by 0. |
| out = SyncEvalBinaryOperator(ExprValue(108), ExprTokenType::kSlash, ExprValue(0)); |
| EXPECT_TRUE(out.has_error()); |
| EXPECT_EQ("Division by 0.", out.err().msg()); |
| |
| // Modulo by 0. |
| out = SyncEvalBinaryOperator(ExprValue(108), ExprTokenType::kPercent, ExprValue(0)); |
| EXPECT_TRUE(out.has_error()); |
| EXPECT_EQ("Division by 0.", out.err().msg()); |
| |
| // Bitwise | |
| out = SyncEvalBinaryOperator(ExprValue(0b0100), ExprTokenType::kBitwiseOr, ExprValue(0b1100)); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(0b1100, out.value().GetAs<int64_t>()); |
| |
| // Bitwise & |
| out = SyncEvalBinaryOperator(ExprValue(0b0100), ExprTokenType::kAmpersand, ExprValue(0b1100)); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(0b0100, out.value().GetAs<int64_t>()); |
| |
| // ^ |
| out = SyncEvalBinaryOperator(ExprValue(0b0100), ExprTokenType::kCaret, ExprValue(0b1100)); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(0b1000, out.value().GetAs<int64_t>()); |
| |
| // << |
| out = SyncEvalBinaryOperator(ExprValue(0b0100), ExprTokenType::kShiftLeft, ExprValue(2)); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(0b10000, out.value().GetAs<int64_t>()); |
| |
| // >> |
| out = SyncEvalBinaryOperator(ExprValue(0b0100), ExprTokenType::kShiftRight, ExprValue(2)); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(0b1, out.value().GetAs<int64_t>()); |
| } |
| |
| TEST_F(EvalOperators, FloatArithmetic) { |
| // Double-precision division. |
| ErrOrValue out = SyncEvalBinaryOperator(ExprValue(21.0), ExprTokenType::kSlash, ExprValue(10.0)); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(21.0 / 10.0, out.value().GetAs<double>()); |
| |
| // Floating-point division. |
| out = SyncEvalBinaryOperator(ExprValue(21.0f), ExprTokenType::kSlash, ExprValue(10.0f)); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(21.0f / 10.0f, out.value().GetAs<float>()); |
| |
| // Promotion from float to double. |
| out = SyncEvalBinaryOperator(ExprValue(21.0f), ExprTokenType::kSlash, ExprValue(10.0)); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(21.0 / 10.0, out.value().GetAs<double>()); |
| |
| // Promotion from int to float. |
| out = SyncEvalBinaryOperator(ExprValue(21), ExprTokenType::kSlash, ExprValue(10.0f)); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(21.0f / 10.0f, out.value().GetAs<float>()); |
| |
| // Division by 0. |
| out = SyncEvalBinaryOperator(ExprValue(21.0), ExprTokenType::kSlash, ExprValue(0.0)); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(21.0 / 0.0, out.value().GetAs<double>()); // Should be "inf". |
| |
| // Modulo is an error. |
| out = SyncEvalBinaryOperator(ExprValue(21.0), ExprTokenType::kPercent, ExprValue(5)); |
| EXPECT_TRUE(out.has_error()); |
| // Note: empty '' is because the test infrastructure doesn't set up a "value" for the token is |
| // passes in. In real life it will be '%'. |
| EXPECT_EQ("Operator '' not defined for floating point.", out.err().msg()); |
| } |
| |
| TEST_F(EvalOperators, PointerArithmetic) { |
| auto int32_type = MakeInt32Type(); |
| auto int32_ptr_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, int32_type); |
| |
| auto int64_type = MakeInt64Type(); |
| auto int64_ptr_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, int64_type); |
| |
| constexpr uint64_t kPtrVal1 = 0x123400; |
| ExprValue int32_ptr(kPtrVal1, int32_ptr_type); |
| ExprValue eight(8); |
| |
| // int32_ptr + 8. |
| ErrOrValue out = SyncEvalBinaryOperator(int32_ptr, ExprTokenType::kPlus, eight); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(kPtrVal1 + (8 * sizeof(int32_t)), out.value().GetAs<uint64_t>()); |
| |
| // 8 + int32_ptr. |
| out = SyncEvalBinaryOperator(eight, ExprTokenType::kPlus, int32_ptr); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(kPtrVal1 + (8 * sizeof(int32_t)), out.value().GetAs<uint64_t>()); |
| |
| // int32_ptr - 8. |
| out = SyncEvalBinaryOperator(int32_ptr, ExprTokenType::kMinus, eight); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(kPtrVal1 - (8 * sizeof(int32_t)), out.value().GetAs<uint64_t>()); |
| |
| // 8 - int32_ptr -> Error. |
| out = SyncEvalBinaryOperator(eight, ExprTokenType::kMinus, int32_ptr); |
| EXPECT_TRUE(out.has_error()); |
| |
| // int32_ptr - int32_ptr2. |
| constexpr uint64_t kPtrVal2 = 0x120000; |
| ExprValue int32_ptr2(kPtrVal2, int32_ptr_type); |
| out = SyncEvalBinaryOperator(int32_ptr, ExprTokenType::kMinus, int32_ptr2); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| int64_t ptr1_2_diff = static_cast<int64_t>((kPtrVal1 - kPtrVal2) / sizeof(int32_t)); |
| EXPECT_EQ(ptr1_2_diff, out.value().GetAs<int64_t>()); |
| |
| // int32_ptr2 - int32_ptr. |
| out = SyncEvalBinaryOperator(int32_ptr2, ExprTokenType::kMinus, int32_ptr); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(-ptr1_2_diff, out.value().GetAs<int64_t>()); |
| |
| // int32_ptr * 8 -> Error. |
| out = SyncEvalBinaryOperator(int32_ptr, ExprTokenType::kStar, eight); |
| EXPECT_TRUE(out.has_error()); |
| |
| // int32_ptr2 + int32_ptr -> error; |
| out = SyncEvalBinaryOperator(int32_ptr2, ExprTokenType::kPlus, int32_ptr); |
| EXPECT_TRUE(out.has_error()); |
| |
| // int32_ptr - int64_ptr -> Error. |
| constexpr uint64_t kPtrVal3 = 0x9900; |
| ExprValue int64_ptr(kPtrVal3, int64_ptr_type); |
| out = SyncEvalBinaryOperator(int32_ptr, ExprTokenType::kMinus, int64_ptr); |
| ASSERT_TRUE(out.has_error()); |
| EXPECT_EQ("Can't subtract pointers of different types 'int32_t*' and 'int64_t*'.", |
| out.err().msg()); |
| |
| // Two pointers near overflow. |
| constexpr uint64_t kLargePtr1 = 0xffffffffffffff00; |
| ExprValue large_ptr1(kLargePtr1, int32_ptr_type); |
| constexpr uint64_t kLargePtr2 = 0xffffffffffffff80; |
| ExprValue large_ptr2(kLargePtr2, int32_ptr_type); |
| |
| // large_ptr1 - large_ptr2. |
| out = SyncEvalBinaryOperator(large_ptr1, ExprTokenType::kMinus, large_ptr2); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ((-0x80) / static_cast<int>(sizeof(int32_t)), out.value().GetAs<int64_t>()); |
| |
| // large_ptr2 - large_ptr1. |
| out = SyncEvalBinaryOperator(large_ptr2, ExprTokenType::kMinus, large_ptr1); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(0x80 / static_cast<int>(sizeof(int32_t)), out.value().GetAs<int64_t>()); |
| |
| // large_ptr1 + 8. |
| out = SyncEvalBinaryOperator(large_ptr1, ExprTokenType::kPlus, eight); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(kLargePtr1 + (8 * sizeof(int32_t)), out.value().GetAs<uint64_t>()); |
| |
| // Wraparound of 64-bit pointer addition. This threshold will force 0xffffffffffffff00 to wrap |
| // when doing int32_t operations. |
| ExprValue threshold(static_cast<int>(0x100 / sizeof(uint32_t))); |
| out = SyncEvalBinaryOperator(large_ptr1, ExprTokenType::kPlus, threshold); |
| ASSERT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(0u, out.value().GetAs<uint64_t>()); |
| |
| // Try | which should fail on pointers. |
| out = SyncEvalBinaryOperator(large_ptr1, ExprTokenType::kBitwiseOr, eight); |
| ASSERT_TRUE(out.has_error()); |
| } |
| |
| TEST_F(EvalOperators, UnaryMinus) { |
| // Test the limits of all built-in types. |
| DoUnaryMinusTypeTest<int8_t>(); |
| DoUnaryMinusTypeTest<uint8_t>(); |
| DoUnaryMinusTypeTest<int16_t>(); |
| DoUnaryMinusTypeTest<uint16_t>(); |
| DoUnaryMinusTypeTest<int32_t>(); |
| DoUnaryMinusTypeTest<uint32_t>(); |
| DoUnaryMinusTypeTest<int64_t>(); |
| DoUnaryMinusTypeTest<uint64_t>(); |
| DoUnaryMinusTypeTest<float>(); |
| DoUnaryMinusTypeTest<double>(); |
| |
| // Try an unsupported value (a 3-byte signed). This should throw an error and |
| // compute an empty value. |
| ExprValue original(fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeUnsigned, 3, "uint24_t"), |
| {0, 0, 0}); |
| ErrOrValue out = SyncEvalUnaryOperator(ExprTokenType::kMinus, original); |
| ASSERT_TRUE(out.err().has_error()); |
| // Note: in real life the operator string will be inside the '' but the test harness doesn't |
| // set the actual operator text. |
| EXPECT_EQ("Unsupported size for unary operator ''.", out.err().msg()); |
| } |
| |
| TEST_F(EvalOperators, UnaryBang) { |
| // Nonzero char -> false. |
| ErrOrValue out = SyncEvalUnaryOperator(ExprTokenType::kBang, ExprValue('a')); |
| ASSERT_TRUE(out.ok()); |
| ASSERT_EQ(1u, out.value().data().size()); |
| EXPECT_EQ(0, out.value().GetAs<uint8_t>()); |
| EXPECT_EQ("bool", out.value().type()->GetFullName()); |
| |
| // !0 in 64-bit = true. |
| out = SyncEvalUnaryOperator(ExprTokenType::kBang, ExprValue(static_cast<uint64_t>(0))); |
| ASSERT_TRUE(out.ok()); |
| EXPECT_EQ(1, out.value().GetAs<uint8_t>()); |
| |
| // Pointer. |
| auto ptr_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, MakeInt32Type()); |
| out = SyncEvalUnaryOperator(ExprTokenType::kBang, ExprValue(ptr_type, {1, 2, 3, 4, 5, 6, 7, 8})); |
| ASSERT_TRUE(out.ok()); |
| EXPECT_EQ(0, out.value().GetAs<uint8_t>()); |
| |
| // Double. |
| out = SyncEvalUnaryOperator(ExprTokenType::kBang, ExprValue(0.0)); |
| ASSERT_TRUE(out.ok()); |
| EXPECT_EQ(1, out.value().GetAs<uint8_t>()); |
| |
| // Try one that's not a number. |
| auto coll = MakeCollectionType(DwarfTag::kStructureType, "Struct", {}); |
| coll->set_byte_size(4); |
| out = SyncEvalUnaryOperator(ExprTokenType::kBang, ExprValue(coll, {0, 0, 0, 0})); |
| ASSERT_TRUE(out.has_error()); |
| EXPECT_EQ("Invalid non-numeric type 'Struct' for operator.", out.err().msg()); |
| |
| // Use a typedef for the type to test concrete type resolution. |
| auto myint_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kTypedef, MakeInt32Type()); |
| out = SyncEvalUnaryOperator(ExprTokenType::kBang, ExprValue(myint_type, {42, 0, 0, 0})); |
| ASSERT_TRUE(out.ok()); |
| EXPECT_EQ(0, out.value().GetAs<uint8_t>()); |
| } |
| |
| TEST_F(EvalOperators, UnaryTilde) { |
| // ~ Promotes to "int" so smaller types become 4 bytes. |
| ErrOrValue out = SyncEvalUnaryOperator(ExprTokenType::kTilde, ExprValue('a')); |
| ASSERT_TRUE(out.ok()); |
| ASSERT_EQ(4u, out.value().data().size()); |
| EXPECT_EQ(~'a', out.value().GetAs<int32_t>()); |
| EXPECT_EQ("int32_t", out.value().type()->GetFullName()); |
| |
| uint64_t ff64 = 0xff; |
| out = SyncEvalUnaryOperator(ExprTokenType::kTilde, ExprValue(ff64)); |
| ASSERT_TRUE(out.ok()); |
| ASSERT_EQ(8u, out.value().data().size()); |
| EXPECT_EQ(~ff64, out.value().GetAs<uint64_t>()); |
| EXPECT_EQ("uint64_t", out.value().type()->GetFullName()); |
| } |
| |
| TEST_F(EvalOperators, Comparison) { |
| // (int8_t)1 == (int)1 |
| ExprValue char_one(static_cast<int8_t>(1)); |
| EXPECT_EQ(1u, char_one.data().size()); // Validate construction. |
| ExprValue int_one(static_cast<int32_t>(1)); |
| ErrOrValue out = SyncEvalBinaryOperator(char_one, ExprTokenType::kEquality, int_one); |
| ASSERT_TRUE(out.ok()); |
| ASSERT_EQ(1u, out.value().data().size()); |
| EXPECT_EQ(1, out.value().GetAs<uint8_t>()); |
| EXPECT_EQ("bool", out.value().type()->GetFullName()); |
| |
| // (int)1 != (int8_t)1 |
| out = SyncEvalBinaryOperator(char_one, ExprTokenType::kInequality, int_one); |
| EXPECT_FALSE(out.value().GetAs<uint8_t>()); |
| |
| // 1.0 <= 1 |
| ExprValue double_one(1.0); |
| out = SyncEvalBinaryOperator(double_one, ExprTokenType::kLessEqual, int_one); |
| EXPECT_TRUE(out.value().GetAs<uint8_t>()); |
| |
| // 1.0 < 1 |
| out = SyncEvalBinaryOperator(double_one, ExprTokenType::kLess, int_one); |
| EXPECT_FALSE(out.value().GetAs<uint8_t>()); |
| |
| // 0 > 1.0 |
| ExprValue int_zero(0); |
| out = SyncEvalBinaryOperator(int_zero, ExprTokenType::kGreater, double_one); |
| EXPECT_FALSE(out.value().GetAs<uint8_t>()); |
| |
| // 0 >= 1.0 |
| out = SyncEvalBinaryOperator(int_zero, ExprTokenType::kGreaterEqual, double_one); |
| EXPECT_FALSE(out.value().GetAs<uint8_t>()); |
| |
| // 1 >= 1.0 |
| out = SyncEvalBinaryOperator(int_one, ExprTokenType::kGreaterEqual, double_one); |
| EXPECT_TRUE(out.value().GetAs<uint8_t>()); |
| |
| // true > 0 |
| ExprValue true_value(true); |
| out = SyncEvalBinaryOperator(true_value, ExprTokenType::kGreater, int_zero); |
| EXPECT_TRUE(out.value().GetAs<uint8_t>()); |
| |
| // 0 <=> 1 is recognised but an error. |
| out = SyncEvalBinaryOperator(int_zero, ExprTokenType::kSpaceship, int_one); |
| ASSERT_FALSE(out.ok()); |
| } |
| |
| TEST_F(EvalOperators, Logical) { |
| // (int8_t)1 || (int)1 |
| ExprValue char_one(static_cast<int8_t>(1)); |
| ExprValue int_one(static_cast<int32_t>(1)); |
| ErrOrValue out = SyncEvalBinaryOperator(char_one, ExprTokenType::kLogicalOr, int_one); |
| ASSERT_TRUE(out.ok()); |
| ASSERT_EQ(1u, out.value().data().size()); |
| EXPECT_EQ(1, out.value().GetAs<uint8_t>()); |
| EXPECT_EQ("bool", out.value().type()->GetFullName()); |
| |
| // 1 || 0 |
| ExprValue int_zero(0); |
| out = SyncEvalBinaryOperator(int_one, ExprTokenType::kLogicalOr, int_zero); |
| EXPECT_EQ(1, out.value().GetAs<uint8_t>()); |
| |
| // 0 || 0 |
| out = SyncEvalBinaryOperator(int_zero, ExprTokenType::kLogicalOr, int_zero); |
| EXPECT_EQ(0, out.value().GetAs<uint8_t>()); |
| |
| // 1 && 1 |
| out = SyncEvalBinaryOperator(int_one, ExprTokenType::kDoubleAnd, int_one); |
| EXPECT_EQ(1, out.value().GetAs<uint8_t>()); |
| |
| // 0 && 1 |
| out = SyncEvalBinaryOperator(int_zero, ExprTokenType::kDoubleAnd, int_one); |
| EXPECT_EQ(0, out.value().GetAs<uint8_t>()); |
| } |
| |
| // Tests that && and || don't evaluate the right-hand side if not necessary. |
| TEST_F(EvalOperators, LogicalShortCircuit) { |
| // 1 || <error> |
| auto or_node = fxl::MakeRefCounted<BinaryOpExprNode>( |
| fxl::MakeRefCounted<MockExprNode>(true, ExprValue(1)), |
| ExprToken(ExprTokenType::kLogicalOr, "||", 0), |
| fxl::MakeRefCounted<MockExprNode>(true, Err("Should not eval."))); |
| |
| // Should evalutate to true and not error. |
| bool called = false; |
| or_node->Eval(eval_context(), [&called](ErrOrValue v) { |
| called = true; |
| EXPECT_FALSE(v.has_error()); |
| EXPECT_EQ(1, v.value().GetAs<uint8_t>()); |
| }); |
| EXPECT_TRUE(called); // Should eval synchronously. |
| |
| // 0 && <error> |
| auto and_node = fxl::MakeRefCounted<BinaryOpExprNode>( |
| fxl::MakeRefCounted<MockExprNode>(true, ExprValue(0)), |
| ExprToken(ExprTokenType::kDoubleAnd, "&&", 0), |
| fxl::MakeRefCounted<MockExprNode>(true, Err("Should not eval."))); |
| |
| // Should evalutate to true and not error. |
| called = false; |
| and_node->Eval(eval_context(), [&called](ErrOrValue v) { |
| called = true; |
| EXPECT_FALSE(v.has_error()); |
| EXPECT_EQ(0, v.value().GetAs<uint8_t>()); |
| }); |
| EXPECT_TRUE(called); // Should eval synchronously. |
| } |
| |
| // These aren't supported but we check that the right error is given. |
| TEST_F(EvalOperators, InPlace) { |
| const char kUnimplementedMsg[] = |
| "In-place update operators (++, --, +=, >>=, etc.) aren't supported."; |
| |
| ExprValue int_zero(0); |
| auto out = SyncEvalBinaryOperator(int_zero, ExprTokenType::kPlusPlus, int_zero); |
| ASSERT_TRUE(out.has_error()); |
| EXPECT_EQ(kUnimplementedMsg, out.err().msg()); |
| |
| out = SyncEvalBinaryOperator(int_zero, ExprTokenType::kStarEquals, int_zero); |
| ASSERT_TRUE(out.has_error()); |
| EXPECT_EQ(kUnimplementedMsg, out.err().msg()); |
| } |
| |
| } // namespace zxdb |