| // 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->AsModifiedType()) | |
| return modified_type->tag() == DwarfTag::kPointerType; | |
| // Enums count. | |
| if (t->AsEnumeration()) | |
| return true; | |
| const BaseType* base_type = t->AsBaseType(); | |
| 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->AsBaseType(); | |
| if (!base_type) | |
| return false; | |
| return BaseType::IsSigned(base_type->base_type()); | |
| } | |
| bool IsBooleanBaseType(const Type* type) { | |
| const BaseType* base_type = type->AsBaseType(); | |
| if (!base_type) | |
| return false; | |
| return base_type->base_type() == BaseType::kBaseTypeBoolean; | |
| } | |
| bool IsFloatingPointBaseType(const Type* type) { | |
| const BaseType* base_type = type->AsBaseType(); | |
| 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(), 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[0], &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) { | |
| bool value = false; | |
| if (IsIntegerLike(concrete_from)) { | |
| // All integer-like sources just look for non-zero bytes. | |
| for (uint8_t cur : source.data()) { | |
| 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->AsCollection(); | |
| if (!derived_collection) | |
| return std::nullopt; | |
| const Collection* base_collection = base->AsCollection(); | |
| 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->AsModifiedType(); | |
| const ModifiedType* modified_to = concrete_to->AsModifiedType(); | |
| 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())); | |
| } | |
| // Get the pointed-to or referenced types. | |
| const Type* refed_from_abstract = modified_from->modified().Get()->AsType(); | |
| const Type* refed_to_abstract = modified_to->modified().Get()->AsType(); | |
| 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.")); | |
| // 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()); | |
| // 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(); | |
| 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 |