blob: c0a619fffa3f14a96e75a150d1a58ef6f8c295aa [file] [log] [blame]
// 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