blob: 542c9d91fba2b2c8ee749e5202dfa7410a6a7de7 [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/expr_value.h"
#include "src/developer/debug/zxdb/expr/resolve_collection.h"
#include "src/developer/debug/zxdb/symbols/base_type.h"
#include "src/developer/debug/zxdb/symbols/collection.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;
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;
int kind = base_type->base_type();
return kind == BaseType::kBaseTypeSigned ||
kind == BaseType::kBaseTypeSignedChar;
}
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);
}
// Creates an ExprValue with the contents of the given "value". The size of
// "value" must match the destination type. This function always places the
// output into *result and returns an empty Err() for the convenience of the
// callers.
template <typename T>
Err CreateValue(T value, const fxl::RefPtr<Type>& dest_type,
const ExprValueSource& dest_source, ExprValue* result) {
FXL_DCHECK(sizeof(T) == dest_type->byte_size());
std::vector<uint8_t> dest_bytes;
dest_bytes.resize(sizeof(T));
memcpy(&dest_bytes[0], &value, sizeof(T));
*result = ExprValue(dest_type, std::move(dest_bytes), dest_source);
return Err();
}
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);
}
Err CastFloatToInt(const ExprValue& source, const fxl::RefPtr<Type>& dest_type,
const Type* concrete_dest_type,
const ExprValueSource& dest_source, ExprValue* result) {
double source_value;
Err err = source.PromoteToDouble(&source_value);
if (err.has_error())
return err;
if (IsSignedBaseType(concrete_dest_type)) {
*result = CastFloatToIntT<int64_t>(source_value, dest_type, dest_source);
return Err();
} else {
*result = CastFloatToIntT<uint64_t>(source_value, dest_type, dest_source);
return Err();
}
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>
Err CastIntToFloatT(const ExprValue& source, const fxl::RefPtr<Type>& dest_type,
const ExprValueSource& dest_source, ExprValue* result) {
// Get the integer out as a 64-bit value of the correct sign.
Int source_int;
Err err = source.PromoteTo64(&source_int);
if (err.has_error())
return err;
return CreateValue(static_cast<Float>(source_int), dest_type, dest_source,
result);
}
Err CastIntToFloat(const ExprValue& source, bool source_is_signed,
const fxl::RefPtr<Type>& dest_type,
const ExprValueSource& dest_source, ExprValue* result) {
if (source_is_signed) {
if (dest_type->byte_size() == 4) {
return CastIntToFloatT<int64_t, float>(source, dest_type, dest_source,
result);
} else if (dest_type->byte_size() == 8) {
return CastIntToFloatT<int64_t, double>(source, dest_type, dest_source,
result);
}
} else {
if (dest_type->byte_size() == 4) {
return CastIntToFloatT<uint64_t, float>(source, dest_type, dest_source,
result);
} else if (dest_type->byte_size() == 8) {
return CastIntToFloatT<uint64_t, double>(source, dest_type, dest_source,
result);
}
}
return Err("Can't convert to floating-point number of size %u.",
dest_type->byte_size());
}
Err CastFloatToFloat(const ExprValue& source,
const fxl::RefPtr<Type>& dest_type,
const ExprValueSource& dest_source, ExprValue* result) {
if (source.data().size() == 4) {
float f = source.GetAs<float>();
if (dest_type->byte_size() == 4)
return CreateValue<float>(f, dest_type, dest_source, result);
else if (dest_type->byte_size() == 8)
return CreateValue<double>(f, dest_type, dest_source, result);
} else if (source.data().size() == 8) {
double d = source.GetAs<double>();
if (dest_type->byte_size() == 4)
return CreateValue<float>(d, dest_type, dest_source, result);
else if (dest_type->byte_size() == 8)
return CreateValue<double>(d, dest_type, dest_source, result);
}
return Err("Can't convert floating-point from size %zu to %u.",
source.data().size(), dest_type->byte_size());
}
Err CastNumberToBool(const ExprValue& source, const Type* concrete_from,
const fxl::RefPtr<Type>& dest_type,
const ExprValueSource& dest_source, ExprValue* result) {
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.
FXL_DCHECK(IsFloatingPointBaseType(concrete_from));
double double_value;
Err err = source.PromoteToDouble(&double_value);
if (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;
*result = ExprValue(dest_type, std::move(dest_data), dest_source);
return Err();
}
// Returns true if the two concrete types (as a result of calling
// Type::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 offset of the base class in the derived
// class. If not, returns an empty optional.
//
// The two types must have c-v qualifiers stripped.
std::optional<uint64_t> GetDerivedClassOffset(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<uint64_t> result;
VisitClassHierarchy(
derived_collection,
[&result, &base_name](const Collection* cur, uint64_t offset) {
if (cur->GetFullName() == base_name) {
result = offset;
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.
Err StaticCastPointerOrRef(const ExprValue& source,
const fxl::RefPtr<Type>& dest_type,
const Type* concrete_from, const Type* concrete_to,
const ExprValueSource& dest_source,
CastPointer cast_pointer, ExprValue* result) {
if (!DwarfTagIsPointerOrReference(concrete_from->tag()) ||
!DwarfTagIsPointerOrReference(concrete_to->tag()))
return 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 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.
*result = CastIntToInt(source, concrete_from, dest_type, dest_source);
return Err();
}
// 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 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 = modified_from->modified().Get()->AsType();
const Type* refed_to = modified_to->modified().Get()->AsType();
if (!refed_from || !refed_to) {
// Error decoding (not void* because that was already checked above).
return MakeCastError(concrete_from, concrete_to);
}
// Strip qualifiers to handle things like "pointer to const int".
refed_from = refed_from->GetConcreteType();
refed_to = refed_to->GetConcreteType();
if (refed_from->GetFullName() == refed_to->GetFullName()) {
// Source and dest are the same type.
*result = CastIntToInt(source, concrete_from, dest_type, dest_source);
return Err();
}
if (auto found_offset = GetDerivedClassOffset(refed_to, refed_from)) {
// Convert derived class ref/ptr to base class ref/ptr. This requires
// adjusting the pointer to point to where the base class is inside of the
// derived class.
// The 64-bit-edness of both pointers was checked above.
uint64_t ptr_value = source.GetAs<uint64_t>();
ptr_value += *found_offset;
return CreateValue(ptr_value, dest_type, dest_source, result);
}
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_offset = GetDerivedClassOffset(refed_from, refed_to)) {
uint64_t ptr_value = source.GetAs<uint64_t>();
ptr_value -= *found_offset;
return CreateValue(ptr_value, dest_type, dest_source, result);
}
}
return Err("Can't convert '%s' to unrelated type '%s'.",
concrete_from->GetFullName().c_str(),
concrete_to->GetFullName().c_str());
}
Err ImplicitCast(const ExprValue& source, const fxl::RefPtr<Type>& dest_type,
const ExprValueSource& dest_source, ExprValue* result) {
// There are several fundamental types of things that can be casted:
// - Aggregate types: Can only convert if they're the same.
// - Integers and integer-like things: This includes pointers.
// - Floating-point numbers.
// - Booleans.
// 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.
const Type* concrete_from = source.type()->GetConcreteType();
const Type* concrete_to = dest_type->GetConcreteType();
// Handles identical type conversions. This includes all aggregate types.
if (TypesAreBinaryCoercable(concrete_from, concrete_to)) {
*result = ExprValue(dest_type, source.data(), dest_source);
return Err();
}
// 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,
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(source, dest_type, concrete_from, concrete_to,
dest_source, kDisallowBaseToDerived, result);
}
// 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 were handled above.
if (IsIntegerLike(concrete_from) && IsIntegerLike(concrete_to)) {
*result = CastIntToInt(source, concrete_from, dest_type, dest_source);
return Err();
}
// Conversions between different types of floats.
if (IsFloatingPointBaseType(concrete_from) &&
IsFloatingPointBaseType(concrete_to))
return CastFloatToFloat(source, dest_type, dest_source, result);
// Conversions between ints and floats.
if (IsIntegerLike(concrete_to) && IsFloatingPointBaseType(concrete_from))
return CastFloatToInt(source, dest_type, concrete_to, dest_source, result);
if (IsFloatingPointBaseType(concrete_to) && IsIntegerLike(concrete_from)) {
return CastIntToFloat(source, IsSignedBaseType(concrete_from), dest_type,
dest_source, result);
}
// 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_offset = GetDerivedClassOffset(concrete_to, concrete_from)) {
// 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(source, dest_type, *found_offset, result);
}
return Err("Can't cast from '%s' to '%s'.",
source.type()->GetFullName().c_str(),
dest_type->GetFullName().c_str());
}
Err ReinterpretCast(const ExprValue& source, const fxl::RefPtr<Type>& dest_type,
const ExprValueSource& dest_source, ExprValue* result) {
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.
const Type* concrete_source = source.type()->GetConcreteType();
if (!IsIntegerLike(concrete_source))
return Err("Can't cast from a '%s'.", source.type()->GetFullName().c_str());
const Type* concrete_dest = dest_type->GetConcreteType();
if (!IsIntegerLike(concrete_dest))
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());
*result = ExprValue(dest_type, std::move(new_data), dest_source);
return Err();
}
Err StaticCast(const ExprValue& source, const fxl::RefPtr<Type>& dest_type,
const ExprValueSource& dest_source, ExprValue* result) {
// Our implicit cast is permissive enough to handle most cases including all
// number conversions, and casts to base types.
if (!ImplicitCast(source, dest_type, dest_source, result).has_error())
return Err();
// Get the types without "const", etc. modifiers.
const Type* concrete_from = source.type()->GetConcreteType();
const Type* concrete_to = dest_type->GetConcreteType();
// Static casts explicitly allow conversion of pointers to a derived class
// my modifying the address being pointed to.
return StaticCastPointerOrRef(source, dest_type, concrete_from, concrete_to,
dest_source, kAllowBaseToDerived, result);
}
} // namespace
const char* CastTypeToString(CastType type) {
switch (type) {
case CastType::kImplicit:
return "implicit";
case CastType::kC:
return "C";
case CastType::kReinterpret:
return "reinterpret_cast";
case CastType::kStatic:
return "static_cast";
}
return "<invalid>";
}
Err CastExprValue(CastType cast_type, const ExprValue& source,
const fxl::RefPtr<Type>& dest_type, ExprValue* result,
const ExprValueSource& dest_source) {
switch (cast_type) {
case CastType::kImplicit:
return ImplicitCast(source, dest_type, dest_source, result);
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.
if (!StaticCast(source, dest_type, dest_source, result).has_error())
return Err();
return ReinterpretCast(source, dest_type, dest_source, result);
}
case CastType::kReinterpret:
return ReinterpretCast(source, dest_type, dest_source, result);
case CastType::kStatic:
return StaticCast(source, dest_type, dest_source, result);
}
FXL_NOTREACHED();
return Err("Internal error.");
}
bool CastShouldFollowReferences(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.
const Type* concrete_from = source.type()->GetConcreteType();
const Type* concrete_to = dest_type->GetConcreteType();
// 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.
}
} // namespace zxdb