blob: 9b4bc81622e5b7a99d1c6ee5a67dd9cafca87fb2 [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 "garnet/bin/zxdb/expr/cast.h"
#include "garnet/bin/zxdb/common/err.h"
#include "garnet/bin/zxdb/expr/expr_value.h"
#include "garnet/bin/zxdb/symbols/base_type.h"
#include "garnet/bin/zxdb/symbols/modified_type.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() == Symbol::kTagPointerType;
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);
}
// Craetes 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.
}
// 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 coersion 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 all integers and pointers of the same size to be converted by
// copying.
return IsIntegerLike(a) && IsIntegerLike(b);
}
} // namespace
Err CoerceValueTo(const ExprValue& source, const fxl::RefPtr<Type>& dest_type,
const ExprValueSource& dest_source, ExprValue* result) {
// There are several findamental 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);
}
// Conversions between different types of ints (truncate or extend).
if (IsIntegerLike(concrete_from) && IsIntegerLike(concrete_to)) {
*result = ExprValue(
dest_type,
CastToIntegerOfSize(source.data(), IsSignedBaseType(concrete_from),
concrete_to->byte_size()),
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);
}
return Err("Can't cast from '%s' to '%s'.",
source.type()->GetFullName().c_str(),
dest_type->GetFullName().c_str());
}
} // namespace zxdb