| // 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/cast.h" |
| |
| #include <limits> |
| |
| #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/eval_test_support.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/virtual_inheritance_test_setup.h" |
| #include "src/developer/debug/zxdb/symbols/base_type.h" |
| #include "src/developer/debug/zxdb/symbols/collection.h" |
| #include "src/developer/debug/zxdb/symbols/enumeration.h" |
| #include "src/developer/debug/zxdb/symbols/inherited_from.h" |
| #include "src/developer/debug/zxdb/symbols/modified_type.h" |
| #include "src/developer/debug/zxdb/symbols/type_test_support.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| class Cast : public TestWithLoop { |
| public: |
| ErrOrValue SyncCastExprValue(const fxl::RefPtr<EvalContext>& eval_context, CastType cast_type, |
| const ExprValue& source, const fxl::RefPtr<Type>& dest_type, |
| const ExprValueSource& dest_source = ExprValueSource()) { |
| ErrOrValue result(Err("Uncalled")); |
| bool called = false; |
| CastExprValue(eval_context, cast_type, source, dest_type, dest_source, |
| [&result, &called](ErrOrValue v) { |
| result = std::move(v); |
| called = true; |
| }); |
| |
| loop().RunUntilNoTasks(); |
| EXPECT_TRUE(called); |
| return result; |
| } |
| |
| // When a cast is supported by CastNumericExprValue, an implicit cast should give the same |
| // result. This runs both, expecting success, and that the answers match. |
| // |
| // To test a case where CastNumericExprValue() fails, call it directly since the more general case |
| // might return a different answer. |
| ExprValue SyncCastNumericExprValue(const fxl::RefPtr<EvalContext>& eval_context, |
| const ExprValue& source, const fxl::RefPtr<Type>& dest_type, |
| const ExprValueSource& dest_source = ExprValueSource()) { |
| ErrOrValue full_result = |
| SyncCastExprValue(eval_context, CastType::kImplicit, source, dest_type, dest_source); |
| EXPECT_TRUE(full_result.ok()); |
| if (full_result.has_error()) |
| return ExprValue(); |
| |
| ErrOrValue numeric_result = CastNumericExprValue(eval_context, source, dest_type, dest_source); |
| EXPECT_TRUE(numeric_result.ok()); |
| if (numeric_result.has_error()) |
| return ExprValue(); |
| |
| EXPECT_EQ(numeric_result.value(), full_result.value()); |
| return numeric_result.value(); |
| } |
| }; |
| |
| } // namespace |
| |
| // Tests both the implicit numeric and implicit regular cast functions. |
| TEST_F(Cast, Implicit) { |
| auto eval_context = fxl::MakeRefCounted<MockEvalContext>(); |
| |
| auto int32_type = MakeInt32Type(); |
| auto uint32_type = MakeInt32Type(); |
| auto int64_type = MakeInt64Type(); |
| auto uint64_type = MakeInt64Type(); |
| auto char_type = fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeSignedChar, 1, "char"); |
| auto bool_type = fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeBoolean, 1, "bool"); |
| auto float_type = fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeFloat, 4, "float"); |
| auto double_type = fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeFloat, 8, "double"); |
| |
| ExprValue int32_one(int32_type, {1, 0, 0, 0}); |
| ExprValue int32_minus_one(int32_type, {0xff, 0xff, 0xff, 0xff}); |
| |
| // Simple identity conversion. |
| ExprValue out = SyncCastNumericExprValue(eval_context, int32_one, int32_type); |
| EXPECT_EQ(int32_one, out); |
| |
| // Signed/unsigned conversion, should just copy the bits (end up with 0xffffffff), |
| out = SyncCastNumericExprValue(eval_context, int32_minus_one, uint32_type); |
| EXPECT_EQ(uint32_type.get(), out.type()); |
| EXPECT_EQ(int32_minus_one.data(), out.data()); |
| |
| // Signed integer promotion. |
| out = SyncCastNumericExprValue(eval_context, int32_minus_one, uint64_type); |
| EXPECT_EQ(uint64_type.get(), out.type()); |
| EXPECT_EQ(std::numeric_limits<uint64_t>::max(), out.GetAs<uint64_t>()); |
| |
| // Integer truncation |
| out = SyncCastNumericExprValue(eval_context, int32_minus_one, char_type); |
| EXPECT_EQ(char_type.get(), out.type()); |
| EXPECT_EQ(-1, out.GetAs<int8_t>()); |
| |
| // Zero integer to boolean. |
| ExprValue int32_zero(int32_type, {0, 0, 0, 0}); |
| out = SyncCastNumericExprValue(eval_context, int32_zero, bool_type); |
| EXPECT_EQ(bool_type.get(), out.type()); |
| EXPECT_EQ(0, out.GetAs<int8_t>()); |
| |
| // Nonzero integer to boolean. |
| out = SyncCastNumericExprValue(eval_context, int32_minus_one, bool_type); |
| EXPECT_EQ(bool_type.get(), out.type()); |
| EXPECT_EQ(1, out.GetAs<int8_t>()); |
| |
| // Zero floating point to boolean. |
| ExprValue float_minus_zero(float_type, {0, 0, 0, 0x80}); |
| out = SyncCastNumericExprValue(eval_context, float_minus_zero, bool_type); |
| EXPECT_EQ(0, out.GetAs<int8_t>()); |
| |
| // Nonzero floating point to boolean. |
| ExprValue float_one_third(float_type, {0xab, 0xaa, 0xaa, 0x3e}); |
| out = SyncCastNumericExprValue(eval_context, float_one_third, bool_type); |
| EXPECT_EQ(1, out.GetAs<int8_t>()); |
| |
| // Float to signed. |
| ExprValue float_minus_two(float_type, {0x00, 0x00, 0x00, 0xc0}); |
| out = SyncCastNumericExprValue(eval_context, float_minus_two, int32_type); |
| EXPECT_EQ(-2, out.GetAs<int32_t>()); |
| |
| // Float to unsigned. |
| out = SyncCastNumericExprValue(eval_context, float_minus_two, uint64_type); |
| EXPECT_EQ(static_cast<uint64_t>(-2), out.GetAs<uint64_t>()); |
| |
| // Floating point promotion. |
| out = SyncCastNumericExprValue(eval_context, float_minus_two, double_type); |
| EXPECT_EQ(-2.0, out.GetAs<double>()); |
| ExprValue double_minus_two = out; |
| |
| // Floating point truncation. |
| out = SyncCastNumericExprValue(eval_context, double_minus_two, float_type); |
| EXPECT_EQ(-2.0f, out.GetAs<float>()); |
| |
| // Pointer to integer with truncation. |
| auto ptr_to_double_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, double_type); |
| ExprValue ptr_value(ptr_to_double_type, {0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12}); |
| out = SyncCastNumericExprValue(eval_context, ptr_value, uint32_type); |
| EXPECT_EQ(0x9abcdef0, out.GetAs<uint32_t>()); |
| ExprValue big_int_value = out; |
| |
| // Integer to pointer with expansion. |
| out = SyncCastNumericExprValue(eval_context, int32_minus_one, ptr_to_double_type); |
| EXPECT_EQ(-1, out.GetAs<int64_t>()); |
| |
| // Test explicit boolean casts for the above values. |
| EXPECT_TRUE(CastNumericExprValueToBool(eval_context, int32_one).value()); |
| EXPECT_TRUE(CastNumericExprValueToBool(eval_context, int32_minus_one).value()); |
| EXPECT_FALSE(CastNumericExprValueToBool(eval_context, int32_zero).value()); |
| EXPECT_FALSE(CastNumericExprValueToBool(eval_context, float_minus_zero).value()); |
| EXPECT_TRUE(CastNumericExprValueToBool(eval_context, float_one_third).value()); |
| EXPECT_TRUE(CastNumericExprValueToBool(eval_context, float_minus_two).value()); |
| EXPECT_TRUE(CastNumericExprValueToBool(eval_context, double_minus_two).value()); |
| EXPECT_TRUE(CastNumericExprValueToBool(eval_context, ptr_value).value()); |
| } |
| |
| // Enums can be casted to and fro. |
| TEST_F(Cast, Enum) { |
| auto eval_context = fxl::MakeRefCounted<MockEvalContext>(); |
| |
| // Enumeration values used in both enums below. |
| Enumeration::Map values; |
| values[0] = "kZero"; |
| values[1] = "kOne"; |
| values[static_cast<uint64_t>(-1)] = "kMinus"; |
| |
| // An untyped enum (old-style C with no qualification). This one is unsigned. |
| auto untyped = fxl::MakeRefCounted<Enumeration>("Untyped", LazySymbol(), 4, false, values); |
| |
| // An explicitly-typed 8-bit signed enum |
| auto char_type = fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeSignedChar, 1, "char"); |
| auto typed = fxl::MakeRefCounted<Enumeration>("Untyped", char_type, 1, true, values); |
| |
| ExprValue untyped_value(untyped, {1, 0, 0, 0}); |
| ExprValue typed_value(typed, {1}); |
| |
| // Untyped to int32. |
| auto int32_type = MakeInt32Type(); |
| ErrOrValue out = SyncCastExprValue(eval_context, CastType::kImplicit, untyped_value, int32_type); |
| EXPECT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(1, out.value().GetAs<int32_t>()); |
| |
| // Typed to int32. |
| out = SyncCastExprValue(eval_context, CastType::kImplicit, typed_value, int32_type); |
| EXPECT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(1, out.value().GetAs<int32_t>()); |
| |
| // Untyped to char. |
| out = SyncCastExprValue(eval_context, CastType::kImplicit, untyped_value, char_type); |
| EXPECT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(1, out.value().GetAs<int8_t>()); |
| |
| // Typed to char. |
| out = SyncCastExprValue(eval_context, CastType::kImplicit, typed_value, char_type); |
| EXPECT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(1, out.value().GetAs<int8_t>()); |
| |
| // Signed char to untyped (should be sign-extended). |
| ExprValue char_minus_one(char_type, {0xff}); |
| out = SyncCastExprValue(eval_context, CastType::kImplicit, char_minus_one, untyped); |
| EXPECT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(-1, out.value().GetAs<int32_t>()); |
| |
| // Signed char to typed. |
| out = SyncCastExprValue(eval_context, CastType::kImplicit, char_minus_one, typed); |
| EXPECT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(-1, out.value().GetAs<int8_t>()); |
| } |
| |
| // Tests implicit casting when there are derived classes. |
| TEST_F(Cast, ImplicitDerived) { |
| auto eval_context = fxl::MakeRefCounted<MockEvalContext>(); |
| |
| DerivedClassTestSetup d; |
| |
| // Should be able to implicit cast Derived to Base1 object. |
| ErrOrValue out = |
| SyncCastExprValue(eval_context, CastType::kImplicit, d.derived_value, d.base1_type); |
| EXPECT_FALSE(out.has_error()); |
| EXPECT_EQ(d.base1_type.get(), out.value().type()); |
| EXPECT_EQ(d.base1_value, out.value()); |
| // Check the source explicitly since == doesn't check sources. |
| EXPECT_EQ(d.base1_value.source(), out.value().source()); |
| |
| // Same for base 2. |
| out = SyncCastExprValue(eval_context, CastType::kImplicit, d.derived_value, d.base2_type); |
| EXPECT_FALSE(out.has_error()); |
| EXPECT_EQ(d.base2_type.get(), out.value().type()); |
| EXPECT_EQ(d.base2_value, out.value()); |
| // Check the source explicitly since == doesn't check sources. |
| EXPECT_EQ(d.base2_value.source(), out.value().source()); |
| |
| // Should not be able to implicit cast from Base2 to Derived. |
| out.value() = ExprValue(); |
| out = SyncCastExprValue(eval_context, CastType::kImplicit, d.base2_value, d.derived_type); |
| EXPECT_TRUE(out.has_error()); |
| |
| // Pointer casting: should be able to implicit cast derived ptr to base ptr type. This data |
| // matches kDerivedAddr in 64-bit little endian. |
| out = SyncCastExprValue(eval_context, CastType::kImplicit, d.derived_ptr_value, d.base2_ptr_type); |
| EXPECT_FALSE(out.has_error()) << out.err().msg(); |
| |
| // That should have adjusted the pointer value appropriately. |
| EXPECT_EQ(d.base2_ptr_value, out.value()); |
| |
| // Should not allow implicit casts from Base to Derived. |
| out = SyncCastExprValue(eval_context, CastType::kImplicit, d.base2_ptr_value, d.derived_ptr_type); |
| EXPECT_TRUE(out.has_error()); |
| EXPECT_EQ("Can't convert 'Base2*' to unrelated type 'Derived*'.", out.err().msg()); |
| |
| // Cast a derived to void* is allowed, just a numeric copy. |
| auto void_ptr_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, LazySymbol()); |
| out = SyncCastExprValue(eval_context, CastType::kImplicit, d.derived_ptr_value, void_ptr_type); |
| EXPECT_FALSE(out.has_error()) << out.err().msg(); |
| ExprValue void_ptr_value(void_ptr_type, d.derived_ptr_value.data()); |
| EXPECT_EQ(void_ptr_value, out.value()); |
| |
| // Cast void* to any pointer type (in C this would require an explicit cast). |
| out = SyncCastExprValue(eval_context, CastType::kImplicit, void_ptr_value, d.derived_ptr_type); |
| EXPECT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(d.derived_ptr_value, out.value()); |
| } |
| |
| TEST_F(Cast, Reinterpret) { |
| auto eval_context = fxl::MakeRefCounted<MockEvalContext>(); |
| |
| auto int32_type = MakeInt32Type(); |
| auto uint32_type = MakeInt32Type(); |
| auto int64_type = MakeInt64Type(); |
| auto uint64_type = MakeInt64Type(); |
| auto char_type = fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeSignedChar, 1, "char"); |
| auto bool_type = fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeBoolean, 1, "bool"); |
| auto double_type = fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeFloat, 8, "double"); |
| |
| auto ptr_to_int32_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, int32_type); |
| auto ptr_to_void_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, LazySymbol()); |
| |
| ExprValue int32_minus_one(int32_type, {0xff, 0xff, 0xff, 0xff}); |
| ExprValue int64_big_num(int32_type, {8, 7, 6, 5, 4, 3, 2, 1}); |
| ExprValue ptr_to_void(ptr_to_void_type, {8, 7, 6, 5, 4, 3, 2, 1}); |
| ExprValue double_pi(double_type, {0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40}); |
| |
| // Two pointer types: reinterpret_cast<int32_t*>(ptr_to_void); |
| ErrOrValue out = |
| SyncCastExprValue(eval_context, CastType::kReinterpret, ptr_to_void, ptr_to_int32_type); |
| EXPECT_FALSE(out.has_error()); |
| EXPECT_EQ(0x0102030405060708u, out.value().GetAs<uint64_t>()); |
| EXPECT_EQ(ptr_to_int32_type.get(), out.value().type()); |
| |
| // Conversion from int to void*. C++ would prohibit this case because the integer is 32 bits and |
| // the pointer is 64, but the debugger allows it. This should not be sign extended. |
| out = SyncCastExprValue(eval_context, CastType::kReinterpret, int32_minus_one, ptr_to_void_type); |
| EXPECT_FALSE(out.has_error()); |
| EXPECT_EQ(0xffffffffu, out.value().GetAs<uint64_t>()); |
| |
| // Truncation of a number. This is also disallowed in C++. |
| out = SyncCastExprValue(eval_context, CastType::kReinterpret, int64_big_num, int32_type); |
| EXPECT_FALSE(out.has_error()); |
| EXPECT_EQ(4u, out.value().data().size()); |
| EXPECT_EQ(0x05060708u, out.value().GetAs<uint32_t>()); |
| |
| // Prohibit conversions between a double and a pointer: reinterpret_cast<void*>(3.14159265258979); |
| out = SyncCastExprValue(eval_context, CastType::kReinterpret, double_pi, ptr_to_void_type); |
| EXPECT_TRUE(out.has_error()); |
| EXPECT_EQ("Can't cast from a 'double'.", out.err().msg()); |
| } |
| |
| // Static cast is mostly implement as implicit cast. This only tests the additional behavior. |
| TEST_F(Cast, Static) { |
| auto eval_context = fxl::MakeRefCounted<MockEvalContext>(); |
| |
| DerivedClassTestSetup d; |
| |
| // Should NOT be able to static cast from Base2 to Derived. This is "static_cast<Derived>(base);" |
| ErrOrValue out = |
| SyncCastExprValue(eval_context, CastType::kStatic, d.base2_value, d.derived_type); |
| EXPECT_TRUE(out.has_error()); |
| |
| // Cast a derived class reference to a base class reference. This is |
| // "static_cast<Base&>(derived_reference); |
| out = SyncCastExprValue(eval_context, CastType::kStatic, d.derived_ref_value, d.base2_ref_type); |
| EXPECT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(d.base2_ref_value, out.value()); |
| |
| // Should be able to go from base->derived with pointers. This is "static_cast<Derived*>(&base);" |
| out = SyncCastExprValue(eval_context, CastType::kStatic, d.base2_ptr_value, d.derived_ptr_type); |
| EXPECT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(d.derived_ptr_value, out.value()); |
| |
| // Base->derived conversion for references. |
| out = SyncCastExprValue(eval_context, CastType::kStatic, d.base2_ref_value, d.derived_ref_type); |
| EXPECT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(d.derived_ref_value, out.value()); |
| |
| // Allow conversion of rvalue references and regular references. |
| auto derived_rvalue_type = |
| fxl::MakeRefCounted<ModifiedType>(DwarfTag::kRvalueReferenceType, d.derived_type); |
| ExprValue derived_rvalue_value(derived_rvalue_type, d.derived_ref_value.data()); |
| out = SyncCastExprValue(eval_context, CastType::kStatic, derived_rvalue_value, d.base2_ref_type); |
| EXPECT_FALSE(out.has_error()); |
| EXPECT_EQ(d.base2_ref_value, out.value()); |
| |
| // Don't allow reference->pointer or pointer->reference casts. |
| out = SyncCastExprValue(eval_context, CastType::kStatic, d.derived_ref_value, d.derived_ptr_type); |
| EXPECT_TRUE(out.has_error()); |
| out = SyncCastExprValue(eval_context, CastType::kStatic, d.derived_ptr_value, d.derived_ref_type); |
| EXPECT_TRUE(out.has_error()); |
| } |
| |
| TEST_F(Cast, C) { |
| auto eval_context = fxl::MakeRefCounted<MockEvalContext>(); |
| |
| DerivedClassTestSetup d; |
| |
| // A C-style cast should be like a static cast when casting between related types. |
| ErrOrValue out = |
| SyncCastExprValue(eval_context, CastType::kC, d.derived_ref_value, d.base2_ref_type); |
| EXPECT_FALSE(out.has_error()) << out.err().msg(); |
| EXPECT_EQ(d.base2_ref_value, out.value()); |
| |
| // When there are unrelated pointers, it should fall back to reinterpret. |
| auto double_type = fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeFloat, 8, "double"); |
| auto ptr_to_double_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, double_type); |
| ExprValue ptr_value(ptr_to_double_type, {0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12}); |
| out = SyncCastExprValue(eval_context, CastType::kC, ptr_value, d.base2_ptr_type); |
| EXPECT_FALSE(out.has_error()); |
| |
| // For the reinterpret cast, the type should be the new one, with the data being the original. |
| EXPECT_EQ(d.base2_ptr_type.get(), out.value().type()); |
| EXPECT_EQ(ptr_value.data(), out.value().data()); |
| |
| // Can't cast from a ptr to a ref. |
| out = SyncCastExprValue(eval_context, CastType::kC, ptr_value, d.base2_ref_type); |
| EXPECT_TRUE(out.has_error()); |
| } |
| |
| // Casting a pointer when there is virtual inheritance means evaluating a DWARF expression and |
| // fetching a bunch of memory to adjust the pointers. |
| // |
| // Note that virtual inheritance isn't inheritance with virtual methods, but rather: |
| // |
| // class Derived : public virtual Base {}; |
| // |
| // And this means that Base is accessed indirectly from a pointer in Derived, usually so that |
| // diamond inheritance can be resolved. See resolve_collection_unittest.cc for more virtual |
| // inheritance tests. |
| TEST_F(Cast, StaticCastVirtualInheritance) { |
| auto eval_context = fxl::MakeRefCounted<MockEvalContext>(); |
| VirtualInheritanceTestSetup setup; |
| setup.SaveMockData(eval_context->data_provider()); |
| |
| // Casing from "derived" to "base" covers two regular and one virtual inheritance step. This is |
| // the cast for literal objects. |
| ErrOrValue result = |
| SyncCastExprValue(eval_context, CastType::kStatic, setup.derived_value, setup.base); |
| ASSERT_TRUE(result.ok()) << result.err().msg(); |
| EXPECT_EQ(setup.base.get(), result.value().type()); |
| EXPECT_EQ(setup.kBaseAddress, result.value().source().address()); |
| |
| // The data of the base is the last 4 bytes of the full derived data. |
| std::vector<uint8_t> expected_base_data(setup.derived_value.data().end() - 4, |
| setup.derived_value.data().end()); |
| EXPECT_EQ(expected_base_data, result.value().data()); |
| |
| // Now do a cast of pointers with the same thing. |
| auto base_ptr_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, setup.base); |
| auto derived_ptr_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, setup.derived); |
| ExprValue derived_ptr(setup.kDerivedAddress, derived_ptr_type); |
| result = SyncCastExprValue(eval_context, CastType::kStatic, derived_ptr, base_ptr_type); |
| |
| ASSERT_TRUE(result.ok()) << result.err().msg(); |
| EXPECT_EQ(base_ptr_type.get(), result.value().type()); |
| EXPECT_EQ(setup.kBaseAddress, result.value().GetAs<uint64_t>()); |
| } |
| |
| } // namespace zxdb |