| // 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 <optional> |
| |
| #include "src/developer/debug/zxdb/common/err.h" |
| #include "src/developer/debug/zxdb/expr/eval_context.h" |
| #include "src/developer/debug/zxdb/expr/expr_value.h" |
| #include "src/developer/debug/zxdb/expr/resolve_collection.h" |
| #include "src/developer/debug/zxdb/expr/resolve_ptr_ref.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/modified_type.h" |
| #include "src/developer/debug/zxdb/symbols/visit_scopes.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| // Returns true if this type is enough like an integer to support conversion to another number type. |
| // This includes all base types except floating point. |
| bool IsIntegerLike(const Type* t) { |
| // Pointers count. |
| if (const ModifiedType* modified_type = t->As<ModifiedType>()) |
| return modified_type->tag() == DwarfTag::kPointerType; |
| |
| // Enums count. |
| if (t->As<Enumeration>()) |
| return true; |
| |
| const BaseType* base_type = t->As<BaseType>(); |
| if (!base_type) |
| return false; |
| |
| int kind = base_type->base_type(); |
| return kind == BaseType::kBaseTypeAddress || kind == BaseType::kBaseTypeBoolean || |
| kind == BaseType::kBaseTypeSigned || kind == BaseType::kBaseTypeSignedChar || |
| kind == BaseType::kBaseTypeUnsigned || kind == BaseType::kBaseTypeUnsignedChar || |
| kind == BaseType::kBaseTypeUTF; |
| } |
| |
| bool IsSignedBaseType(const Type* type) { |
| const BaseType* base_type = type->As<BaseType>(); |
| if (!base_type) |
| return false; |
| return BaseType::IsSigned(base_type->base_type()); |
| } |
| |
| bool IsBooleanBaseType(const Type* type) { |
| const BaseType* base_type = type->As<BaseType>(); |
| if (!base_type) |
| return false; |
| return base_type->base_type() == BaseType::kBaseTypeBoolean; |
| } |
| |
| bool IsFloatingPointBaseType(const Type* type) { |
| const BaseType* base_type = type->As<BaseType>(); |
| if (!base_type) |
| return false; |
| return base_type->base_type() == BaseType::kBaseTypeFloat; |
| } |
| |
| // Numbers include integers and floating point. |
| bool IsNumberLike(const Type* t) { return IsIntegerLike(t) || IsFloatingPointBaseType(t); } |
| |
| std::vector<uint8_t> CastToIntegerOfSize(const std::vector<uint8_t>& source, bool source_is_signed, |
| size_t dest_size) { |
| if (source.size() > dest_size) { |
| // Truncate. Assume little-endian so copy from the beginning to get the low bits. |
| return std::vector<uint8_t>(source.begin(), source.begin() + dest_size); |
| } else if (source.size() < dest_size) { |
| // Extend. |
| std::vector<uint8_t> result = source; |
| if (source_is_signed && result.back() & 0b10000000) { |
| // Sign-extend. |
| result.resize(dest_size, 0xff); |
| } else { |
| // 0-extend. |
| result.resize(dest_size); |
| } |
| return result; |
| } |
| return source; // No change. |
| } |
| |
| ExprValue CastIntToInt(const ExprValue& source, const Type* source_type, |
| const fxl::RefPtr<Type>& dest_type, const ExprValueSource& dest_source) { |
| return ExprValue(dest_type, |
| CastToIntegerOfSize(source.data().bytes(), IsSignedBaseType(source_type), |
| dest_type->byte_size()), |
| dest_source); |
| } |
| |
| // The "Int64" parameter is either "uint64_t" or "int64_t" depending on the signedness of the |
| // integer desired. |
| template <typename Int64> |
| ExprValue CastFloatToIntT(double double_value, const fxl::RefPtr<Type>& dest_type, |
| const ExprValueSource& dest_source) { |
| Int64 int64_value = static_cast<Int64>(double_value); |
| |
| std::vector<uint8_t> int64_data; |
| int64_data.resize(sizeof(Int64)); |
| memcpy(int64_data.data(), &int64_value, sizeof(Int64)); |
| |
| // CastToIntegerOfSize will downcast the int64 to the desired result size. |
| return ExprValue(dest_type, CastToIntegerOfSize(int64_data, true, dest_type->byte_size()), |
| dest_source); |
| } |
| |
| ErrOrValue CastFloatToInt(const ExprValue& source, const fxl::RefPtr<Type>& dest_type, |
| const Type* concrete_dest_type, const ExprValueSource& dest_source) { |
| double source_value; |
| if (Err err = source.PromoteToDouble(&source_value); err.has_error()) |
| return err; |
| |
| if (IsSignedBaseType(concrete_dest_type)) |
| return CastFloatToIntT<int64_t>(source_value, dest_type, dest_source); |
| else |
| return CastFloatToIntT<uint64_t>(source_value, dest_type, dest_source); |
| |
| return Err("Can't convert a floating-point of size %u to an integer.", |
| source.type()->byte_size()); |
| } |
| |
| // Converts an integer value into to a binary representation of a float/double. The "Int" template |
| // type should be a [u]int64_t of the signedness of the source type, and the "Float" type is the |
| // output type required. |
| template <typename Int, typename Float> |
| ErrOrValue CastIntToFloatT(const ExprValue& source, const fxl::RefPtr<Type>& dest_type, |
| const ExprValueSource& dest_source) { |
| // Get the integer out as a 64-bit value of the correct sign. |
| Int source_int; |
| if (Err err = source.PromoteTo64(&source_int); err.has_error()) |
| return err; |
| |
| return ExprValue(static_cast<Float>(source_int), dest_type, dest_source); |
| } |
| |
| ErrOrValue CastIntToFloat(const ExprValue& source, bool source_is_signed, |
| const fxl::RefPtr<Type>& dest_type, const ExprValueSource& dest_source) { |
| if (source_is_signed) { |
| if (dest_type->byte_size() == 4) |
| return CastIntToFloatT<int64_t, float>(source, dest_type, dest_source); |
| else if (dest_type->byte_size() == 8) |
| return CastIntToFloatT<int64_t, double>(source, dest_type, dest_source); |
| } else { |
| if (dest_type->byte_size() == 4) |
| return CastIntToFloatT<uint64_t, float>(source, dest_type, dest_source); |
| else if (dest_type->byte_size() == 8) |
| return CastIntToFloatT<uint64_t, double>(source, dest_type, dest_source); |
| } |
| |
| return Err("Can't convert to floating-point number of size %u.", dest_type->byte_size()); |
| } |
| |
| ErrOrValue CastFloatToFloat(const ExprValue& source, const fxl::RefPtr<Type>& dest_type, |
| const ExprValueSource& dest_source) { |
| if (source.data().size() == 4) { |
| float f = source.GetAs<float>(); |
| if (dest_type->byte_size() == 4) |
| return ExprValue(f, dest_type, dest_source); |
| else if (dest_type->byte_size() == 8) |
| return ExprValue(static_cast<double>(f), dest_type, dest_source); |
| } else if (source.data().size() == 8) { |
| double d = source.GetAs<double>(); |
| if (dest_type->byte_size() == 4) |
| return ExprValue(static_cast<float>(d), dest_type, dest_source); |
| else if (dest_type->byte_size() == 8) |
| return ExprValue(d, dest_type, dest_source); |
| } |
| |
| return Err("Can't convert floating-point from size %zu to %u.", source.data().size(), |
| dest_type->byte_size()); |
| } |
| |
| ErrOrValue CastNumberToBool(const ExprValue& source, const Type* concrete_from, |
| const fxl::RefPtr<Type>& dest_type, |
| const ExprValueSource& dest_source) { |
| if (!source.data().all_valid()) |
| return Err::OptimizedOut(); |
| |
| bool value = false; |
| |
| if (IsIntegerLike(concrete_from)) { |
| // All integer-like sources just look for non-zero bytes. |
| for (uint8_t cur : source.data().bytes()) { |
| if (cur) { |
| value = true; |
| break; |
| } |
| } |
| } else { |
| // Floating-point-like sources which can't do a byte-by-byte comparison. |
| FX_DCHECK(IsFloatingPointBaseType(concrete_from)); |
| double double_value; |
| if (Err err = source.PromoteToDouble(&double_value); err.has_error()) |
| return err; |
| |
| // Use C++ casting rules to convert to bool. |
| value = !!double_value; |
| } |
| |
| // The data buffer that will be returned, matching the size of the boolean. |
| std::vector<uint8_t> dest_data; |
| dest_data.resize(dest_type->byte_size()); |
| if (value) |
| dest_data[0] = 1; |
| |
| return ExprValue(dest_type, std::move(dest_data), dest_source); |
| } |
| |
| // Returns true if the two concrete types (resulting from previously calling |
| // EvalContext::GetConcreteType()) can be coerced by copying the data. This includes things that are |
| // actually the same, as well as things like signed/unsigned conversions and pointer/int conversions |
| // that our very loose coercion rules support. |
| bool TypesAreBinaryCoercable(const Type* a, const Type* b) { |
| // TODO(brettw) need to handle bit fields. |
| if (a->byte_size() != b->byte_size()) |
| return false; // Sizes must match or copying definitely won't work. |
| |
| // It's possible for things to have the same type but different Type objects depending on how the |
| // types were arrived at and whether the source and dest are from the same compilation unit. |
| // Assume if the string names of the types match as well as the size, it's the same type. |
| if (a->GetFullName() == b->GetFullName()) |
| return true; // Names match, assume same type. |
| |
| // Allow integers and pointers of the same size to be converted by copying. |
| if (a->tag() == DwarfTag::kPointerType && b->tag() == DwarfTag::kPointerType) { |
| // Don't allow pointer-to-pointer conversions because those might need to |
| // be adjusted according to base/derived classes. |
| return false; |
| } |
| return IsIntegerLike(a) && IsIntegerLike(b); |
| } |
| |
| // Checks whether the two input types have the specified base/derived relationship (this does not |
| // check for a relationship going in the opposite direction). If so, returns the path from derived |
| // to base. If not, returns an empty optional. |
| // |
| // The two types must have c-v qualifiers stripped. |
| std::optional<InheritancePath> GetDerivedClassPath(const Type* base, const Type* derived) { |
| const Collection* derived_collection = derived->As<Collection>(); |
| if (!derived_collection) |
| return std::nullopt; |
| |
| const Collection* base_collection = base->As<Collection>(); |
| if (!base_collection) |
| return std::nullopt; |
| std::string base_name = base_collection->GetFullName(); |
| |
| std::optional<InheritancePath> result; |
| VisitClassHierarchy(derived_collection, [&result, &base_name](const InheritancePath& path) { |
| // Since we only need the name, the base type doesn't need to be concrete. |
| if (path.base()->GetFullName() == base_name) { |
| result = path; |
| return VisitResult::kDone; |
| } |
| return VisitResult::kContinue; |
| }); |
| return result; |
| } |
| |
| Err MakeCastError(const Type* from, const Type* to) { |
| return Err("Can't cast '%s' to '%s'.", from->GetFullName().c_str(), to->GetFullName().c_str()); |
| } |
| |
| // Flag that indicates whether a base class' pointer or reference can be converted to a derived |
| // class' pointer or reference. Implicit casts don't do this, but if the user explicitly asks (e.g. |
| // "static_cast<Derived>") we allow it. |
| enum CastPointer { kAllowBaseToDerived, kDisallowBaseToDerived }; |
| |
| // Converts a pointer/reference to a pointer/reference to a different type according to approximate |
| // static_cast rules. |
| // |
| // The source and dest types should already be concrete (from EvalContext::GetConcreteType()). |
| void StaticCastPointerOrRef(const fxl::RefPtr<EvalContext>& eval_context, const ExprValue& source, |
| const fxl::RefPtr<Type>& dest_type, const Type* concrete_from, |
| const Type* concrete_to, const ExprValueSource& dest_source, |
| CastPointer cast_pointer, EvalCallback cb) { |
| if (!DwarfTagIsPointerOrReference(concrete_from->tag()) || |
| !DwarfTagIsPointerOrReference(concrete_to->tag())) |
| return cb(MakeCastError(concrete_from, concrete_to)); |
| |
| // The pointer/ref-ness must match from the source to the dest. This code treats rvalue references |
| // and regular references the same. |
| if ((concrete_from->tag() == DwarfTag::kPointerType) != |
| (concrete_to->tag() == DwarfTag::kPointerType) || |
| DwarfTagIsEitherReference(concrete_from->tag()) != |
| DwarfTagIsEitherReference(concrete_to->tag())) { |
| return cb(MakeCastError(concrete_from, concrete_to)); |
| } |
| |
| // Can assume they're ModifiedTypes due to tag checks above. |
| const ModifiedType* modified_from = concrete_from->As<ModifiedType>(); |
| const ModifiedType* modified_to = concrete_to->As<ModifiedType>(); |
| if (modified_from->ModifiesVoid() || modified_to->ModifiesVoid()) { |
| // Always allow conversions to and from void*. This technically handles void& which isn't |
| // expressible C++, but should be fine. |
| return cb(CastIntToInt(source, concrete_from, dest_type, dest_source)); |
| } |
| |
| // Currently we assume all pointers and references are 64-bit. |
| if (modified_from->byte_size() != sizeof(uint64_t) || |
| modified_to->byte_size() != sizeof(uint64_t)) { |
| return cb( |
| Err("Can only cast 64-bit pointers and references: " |
| "'%s' is %u bytes and '%s' is %u bytes.", |
| concrete_from->GetFullName().c_str(), concrete_from->byte_size(), |
| concrete_to->GetFullName().c_str(), concrete_to->byte_size())); |
| } |
| if (!source.data().all_valid()) |
| return cb(Err::OptimizedOut()); |
| |
| // Get the pointed-to or referenced types. |
| const Type* refed_from_abstract = modified_from->modified().Get()->As<Type>(); |
| const Type* refed_to_abstract = modified_to->modified().Get()->As<Type>(); |
| if (!refed_from_abstract || !refed_to_abstract) { |
| // Error decoding (not void* because that was already checked above). |
| return cb(MakeCastError(concrete_from, concrete_to)); |
| } |
| |
| // Strip qualifiers to handle things like "pointer to const int". |
| fxl::RefPtr<Type> refed_from = eval_context->GetConcreteType(refed_from_abstract); |
| fxl::RefPtr<Type> refed_to = eval_context->GetConcreteType(refed_to_abstract); |
| |
| if (refed_from->GetFullName() == refed_to->GetFullName()) { |
| // Source and dest are the same type. |
| return cb(CastIntToInt(source, concrete_from, dest_type, dest_source)); |
| } |
| |
| if (auto found_path = GetDerivedClassPath(refed_to.get(), refed_from.get())) { |
| // The 64-bit-edness of both pointers was checked above. |
| uint64_t ptr_value = source.GetAs<uint64_t>(); |
| ResolveInheritedPtr(eval_context, ptr_value, *found_path, |
| [dest_type, cb = std::move(cb)](ErrOr<TargetPointer> result) mutable { |
| if (result.has_error()) |
| cb(result.err()); |
| else |
| cb(ExprValue(result.value(), dest_type)); |
| }); |
| return; |
| } |
| |
| if (cast_pointer == kAllowBaseToDerived) { |
| // The reverse of the above case. This is used when the user knows a base class |
| // pointer/reference actually points to a specific derived class. |
| if (auto found_path = GetDerivedClassPath(refed_from.get(), refed_to.get())) { |
| if (std::optional<uint32_t> found_offset = found_path->BaseOffsetInDerived()) { |
| // Adjust the pointer based on the offset of the base class in the derived one. The |
| // 64-bit-edness of both pointers was checked above. |
| uint64_t ptr_value = source.GetAs<uint64_t>(); |
| ptr_value -= *found_offset; |
| return cb(ExprValue(ptr_value, dest_type, dest_source)); |
| } else { |
| // Can't statically upcast from a virtually-derived base class because there's no |
| // constant offset and the DWARF expressions only go in the derived->base direction. |
| return cb(Err("Can't upcast from the virtually-derived base class '%s' to '%s'.", |
| concrete_from->GetFullName().c_str(), concrete_to->GetFullName().c_str())); |
| } |
| } |
| } |
| |
| cb(Err("Can't convert '%s' to unrelated type '%s'.", concrete_from->GetFullName().c_str(), |
| concrete_to->GetFullName().c_str())); |
| } |
| |
| // Some types of casts requires that references be followed (e.g. int& -> long), while others |
| // require that they not be followed (e.g. BaseClass& -> DerivedClass&). This function |
| // determines if the source should have references followed before executing the cast. |
| bool CastShouldFollowReferences(const fxl::RefPtr<EvalContext>& eval_context, CastType cast_type, |
| const ExprValue& source, const fxl::RefPtr<Type>& dest_type) { |
| // Implicit casts never follow references. If you have two references: |
| // A& a; |
| // B& b; |
| // and do: |
| // a = b; |
| // This ends up being an implicit cast, but should assign the values, not convert references. This |
| // is different than an explicit cast: |
| // (B&)a; |
| // Which converts the reference itself. |
| if (cast_type == CastType::kImplicit) |
| return true; |
| |
| // Casting a reference to a reference needs to keep the reference information. Casting a reference |
| // to anything else means the reference should be stripped. |
| fxl::RefPtr<Type> concrete_from = eval_context->GetConcreteType(source.type()); |
| fxl::RefPtr<Type> concrete_to = eval_context->GetConcreteType(dest_type.get()); |
| |
| // Count rvalue references as references. This isn't always strictly valid since you can't static |
| // cast a Base&& to a Derived&&, but from a debugger perspective there's no reason not to allow |
| // this. |
| if (DwarfTagIsEitherReference(concrete_from->tag()) && |
| DwarfTagIsEitherReference(concrete_to->tag())) |
| return false; // Keep reference on source for casting. |
| |
| return true; // Follow reference. |
| } |
| |
| // Handles the synchronous "number" variants of an implicit cast. |
| // |
| // The dest_type is the original type that the output will be, which might be non-concrete (const, |
| // etc.). The concrete_to/from must be the concrete versions that we'll work off of. |
| ErrOrValue NumericImplicitCast(const ExprValue& source, const fxl::RefPtr<Type>& dest_type, |
| const Type* concrete_to, const Type* concrete_from, |
| const ExprValueSource& dest_source) { |
| // Handles identical type conversions. This includes all aggregate types. |
| if (TypesAreBinaryCoercable(concrete_from, concrete_to)) |
| return ExprValue(dest_type, source.data(), dest_source); |
| |
| // Conversions to bool. Conversions from bool will follow the standard "number to X" path where we |
| // assume the bool is like a number. |
| if (IsBooleanBaseType(concrete_to) && IsNumberLike(concrete_from)) |
| return CastNumberToBool(source, concrete_from, dest_type, dest_source); |
| |
| // Conversions between different types of ints, including pointers (truncate or extend). This lets |
| // us evaluate things like "ptr = 0x2a3512635" without elaborate casts. Pointer-to-pointer |
| // conversions need to check for derived classes so can't be handled by this function. |
| if (IsIntegerLike(concrete_from) && IsIntegerLike(concrete_to) && |
| !(concrete_from->tag() == DwarfTag::kPointerType && |
| concrete_to->tag() == DwarfTag::kPointerType)) |
| return CastIntToInt(source, concrete_from, dest_type, dest_source); |
| |
| // Conversions between different types of floats. |
| if (IsFloatingPointBaseType(concrete_from) && IsFloatingPointBaseType(concrete_to)) |
| return CastFloatToFloat(source, dest_type, dest_source); |
| |
| // Conversions between ints and floats. |
| if (IsIntegerLike(concrete_to) && IsFloatingPointBaseType(concrete_from)) |
| return CastFloatToInt(source, dest_type, concrete_to, dest_source); |
| if (IsFloatingPointBaseType(concrete_to) && IsIntegerLike(concrete_from)) |
| return CastIntToFloat(source, IsSignedBaseType(concrete_from), dest_type, dest_source); |
| |
| return Err("Can't cast from '%s' to '%s'.", source.type()->GetFullName().c_str(), |
| dest_type->GetFullName().c_str()); |
| } |
| |
| // Attempts an implicit cast, handling numbers (synchonous) and derived types (possibly asynchronous |
| // for virtual inheritance). |
| void ImplicitCast(const fxl::RefPtr<EvalContext>& eval_context, const ExprValue& source, |
| const fxl::RefPtr<Type>& dest_type, const ExprValueSource& dest_source, |
| EvalCallback cb) { |
| // Prevent crashes if we get bad types with no size. |
| if (source.data().size() == 0 || dest_type->byte_size() == 0) |
| return cb(Err("Type has 0 size.")); |
| if (!source.data().all_valid()) |
| return cb(Err::OptimizedOut()); |
| |
| // Get the types without "const", etc. modifiers. |
| fxl::RefPtr<Type> concrete_from = eval_context->GetConcreteType(source.type()); |
| fxl::RefPtr<Type> concrete_to = eval_context->GetConcreteType(dest_type.get()); |
| |
| ErrOrValue result = |
| NumericImplicitCast(source, dest_type, concrete_to.get(), concrete_from.get(), dest_source); |
| if (result.ok()) |
| return cb(result); |
| |
| // Pointer-to-pointer conversions. Allow anything that can be static_cast-ed which is permissive |
| // but a little more strict than in other conversions: if you have two unrelated pointers, |
| // converting magically between them is error prone. LLDB does this extra checking, while GDB |
| // always allows the conversions. |
| if (concrete_from->tag() == DwarfTag::kPointerType && |
| concrete_to->tag() == DwarfTag::kPointerType) { |
| // Note that implicit cast does not do this for references. If "a" and "b" are both references, |
| // we want "a = b" to copy the referenced objects, not the reference pointers. The reference |
| // conversion feature of this function is used for static casting where static_cast<A&>(b) |
| // refers to the reference address and not the referenced object. |
| return StaticCastPointerOrRef(eval_context, source, dest_type, concrete_from.get(), |
| concrete_to.get(), dest_source, kDisallowBaseToDerived, |
| std::move(cb)); |
| } |
| |
| // Conversions to base classes (on objects, not on pointers or references). e.g. "foo = bar" where |
| // foo's type is a base class of bar's. |
| if (auto found_path = GetDerivedClassPath(concrete_to.get(), concrete_from.get())) { |
| // Ignore the dest_source. ResolveInherited is extracting data from inside the source object |
| // which has a well-defined source location (unlike for all other casts that change the data so |
| // there isn't so clear a source). |
| return ResolveInherited(eval_context, source, *found_path, std::move(cb)); |
| } |
| |
| cb(Err("Can't cast from '%s' to '%s'.", source.type()->GetFullName().c_str(), |
| dest_type->GetFullName().c_str())); |
| } |
| |
| ErrOrValue ReinterpretCast(const fxl::RefPtr<EvalContext>& eval_context, const ExprValue& source, |
| const fxl::RefPtr<Type>& dest_type, const ExprValueSource& dest_source) { |
| if (!source.type()) |
| return Err("Can't cast from a null type."); |
| if (!dest_type) |
| return Err("Can't cast to a null type."); |
| |
| // The input and output types should both be integer-like (this includes pointers). This check is |
| // more restrictive than the "coerce" rules above because we don't want to support things like |
| // integer-to-double conversion. |
| fxl::RefPtr<Type> concrete_source = eval_context->GetConcreteType(source.type()); |
| if (!IsIntegerLike(concrete_source.get())) |
| return Err("Can't cast from a '%s'.", source.type()->GetFullName().c_str()); |
| |
| fxl::RefPtr<Type> concrete_dest = eval_context->GetConcreteType(dest_type.get()); |
| if (!IsIntegerLike(concrete_dest.get())) |
| return Err("Can't cast to a '%s'.", dest_type->GetFullName().c_str()); |
| |
| if (!source.data().all_valid()) |
| return Err::OptimizedOut(); |
| |
| // Our implementation of reinterpret_cast is just a bit cast with truncation or 0-fill (not sign |
| // extend). C++ would require the type sizes match and would prohibit most number-to-number |
| // conversions, but those restrictions aren't useful or even desirable in the case of a debugger |
| // handling user input. |
| auto new_data = source.data().bytes(); |
| new_data.resize(dest_type->byte_size()); |
| return ExprValue(dest_type, std::move(new_data), dest_source); |
| } |
| |
| void StaticCast(const fxl::RefPtr<EvalContext>& eval_context, const ExprValue& source, |
| const fxl::RefPtr<Type>& dest_type, const ExprValueSource& dest_source, |
| EvalCallback cb) { |
| // Our implicit cast is permissive enough to handle most cases including all number conversions, |
| // and casts to base types. |
| ImplicitCast(eval_context, source, dest_type, dest_source, |
| [eval_context, source, dest_type, dest_source, |
| cb = std::move(cb)](ErrOrValue result) mutable { |
| if (result.ok()) |
| return cb(result); |
| |
| // On failure, fall back on extra things allowed by static_cast. |
| |
| // Get the types without "const", etc. modifiers. |
| fxl::RefPtr<Type> concrete_from = eval_context->GetConcreteType(source.type()); |
| fxl::RefPtr<Type> concrete_to = eval_context->GetConcreteType(dest_type.get()); |
| |
| // Static casts explicitly allow conversion of pointers to a derived class my |
| // modifying the address being pointed to. |
| StaticCastPointerOrRef(eval_context, source, dest_type, concrete_from.get(), |
| concrete_to.get(), dest_source, kAllowBaseToDerived, |
| std::move(cb)); |
| }); |
| } |
| |
| // Implements the cast once references have been followed. |
| void DoCastExprValue(const fxl::RefPtr<EvalContext>& eval_context, CastType cast_type, |
| const ExprValue& source, const fxl::RefPtr<Type>& dest_type, |
| const ExprValueSource& dest_source, EvalCallback cb) { |
| switch (cast_type) { |
| case CastType::kImplicit: |
| ImplicitCast(eval_context, source, dest_type, dest_source, std::move(cb)); |
| break; |
| case CastType::kRust: // TODO(sadmac): This is almost correct. Make sure it's exactly correct. |
| case CastType::kC: { |
| // A C-style cast can do the following things. |
| // - const_cast |
| // - static_cast |
| // - static_cast followed by a const_cast |
| // - reinterpret_cast |
| // - reinterpret_cast followed by a const_cast |
| // |
| // Since the debugger ignores const in debugging, this ends up being a static cast falling |
| // back to a reinterpret cast. |
| StaticCast(eval_context, source, dest_type, dest_source, |
| [eval_context, source, dest_type, dest_source, |
| cb = std::move(cb)](ErrOrValue result) mutable { |
| if (result.ok()) { |
| cb(result); |
| } else { |
| // StaticCast couldn't handle. Fall back on ReinterpretCast. |
| cb(ReinterpretCast(eval_context, source, dest_type, dest_source)); |
| } |
| }); |
| break; |
| } |
| case CastType::kReinterpret: |
| cb(ReinterpretCast(eval_context, source, dest_type, dest_source)); |
| break; |
| case CastType::kStatic: |
| StaticCast(eval_context, source, dest_type, dest_source, std::move(cb)); |
| break; |
| } |
| } |
| |
| } // namespace |
| |
| const char* CastTypeToString(CastType type) { |
| switch (type) { |
| case CastType::kImplicit: |
| return "implicit"; |
| case CastType::kC: |
| return "C"; |
| case CastType::kRust: |
| return "Rust"; |
| case CastType::kReinterpret: |
| return "reinterpret_cast"; |
| case CastType::kStatic: |
| return "static_cast"; |
| } |
| return "<invalid>"; |
| } |
| |
| void CastExprValue(const fxl::RefPtr<EvalContext>& eval_context, CastType cast_type, |
| const ExprValue& source, const fxl::RefPtr<Type>& dest_type, |
| const ExprValueSource& dest_source, EvalCallback cb) { |
| if (CastShouldFollowReferences(eval_context, cast_type, source, dest_type)) { |
| // Need to asynchronously follow the reference before doing the cast. |
| EnsureResolveReference(eval_context, source, |
| [eval_context, cast_type, dest_type, cb = std::move(cb), |
| dest_source](ErrOrValue result) mutable { |
| if (result.has_error()) { |
| cb(result); |
| } else { |
| DoCastExprValue(eval_context, cast_type, result.value(), dest_type, |
| dest_source, std::move(cb)); |
| } |
| }); |
| } else { |
| // Non-reference value, can cast right away. |
| DoCastExprValue(eval_context, cast_type, source, dest_type, dest_source, std::move(cb)); |
| } |
| } |
| |
| ErrOrValue CastNumericExprValue(const fxl::RefPtr<EvalContext>& eval_context, |
| const ExprValue& source, const fxl::RefPtr<Type>& dest_type, |
| const ExprValueSource& dest_source) { |
| // Prevent crashes if we get bad types with no size. |
| if (source.data().size() == 0 || dest_type->byte_size() == 0) |
| return Err("Type has 0 size."); |
| |
| // Get the types without "const", etc. modifiers. |
| fxl::RefPtr<Type> concrete_from = eval_context->GetConcreteType(source.type()); |
| fxl::RefPtr<Type> concrete_to = eval_context->GetConcreteType(dest_type.get()); |
| |
| return NumericImplicitCast(source, dest_type, concrete_to.get(), concrete_from.get(), |
| dest_source); |
| } |
| |
| ErrOr<bool> CastNumericExprValueToBool(const fxl::RefPtr<EvalContext>& eval_context, |
| const ExprValue& source) { |
| fxl::RefPtr<Type> bool_type = |
| fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeBoolean, 1, "bool"); |
| auto concrete_from = eval_context->GetConcreteType(source.type()); |
| |
| ErrOrValue result = CastNumberToBool(source, concrete_from.get(), bool_type, ExprValueSource()); |
| if (result.has_error()) |
| return result.err(); |
| |
| return !!result.value().GetAs<uint8_t>(); |
| } |
| |
| } // namespace zxdb |