Skip to content
Snippets Groups Projects
Commit 0758dd98 authored by Stanca Serban's avatar Stanca Serban
Browse files

Backed out changeset 39cfeb7cffda (bug 646323) for causing bustages in Casting.h. CLOSED TREE

parent 3155509b
No related branches found
No related tags found
No related merge requests found
......@@ -12,9 +12,8 @@
#include "mozilla/Assertions.h"
#include <cstring>
#include <limits.h>
#include <type_traits>
#include <limits>
#include <cmath>
namespace mozilla {
......@@ -66,133 +65,156 @@ inline To BitwiseCast(const From aFrom) {
namespace detail {
template <typename T>
constexpr int64_t safe_integer() {
static_assert(std::is_floating_point_v<T>);
return std::pow(2, std::numeric_limits<T>::digits);
}
enum ToSignedness { ToIsSigned, ToIsUnsigned };
enum FromSignedness { FromIsSigned, FromIsUnsigned };
template <typename From, typename To,
FromSignedness =
std::is_signed_v<From> ? FromIsSigned : FromIsUnsigned,
ToSignedness = std::is_signed_v<To> ? ToIsSigned : ToIsUnsigned>
struct BoundsCheckImpl;
// Implicit conversions on operands to binary operations make this all a bit
// hard to verify. Attempt to ease the pain below by *only* comparing values
// that are obviously the same type (and will undergo no further conversions),
// even when it's not strictly necessary, for explicitness.
enum UUComparison { FromIsBigger, FromIsNotBigger };
// Unsigned-to-unsigned range check
template <typename From, typename To,
UUComparison =
(sizeof(From) > sizeof(To)) ? FromIsBigger : FromIsNotBigger>
struct UnsignedUnsignedCheck;
template <typename From, typename To>
struct UnsignedUnsignedCheck<From, To, FromIsBigger> {
public:
static bool checkBounds(const From aFrom) { return aFrom <= From(To(-1)); }
};
template <typename From, typename To>
struct UnsignedUnsignedCheck<From, To, FromIsNotBigger> {
public:
static bool checkBounds(const From aFrom) { return true; }
};
template <typename From, typename To>
struct BoundsCheckImpl<From, To, FromIsUnsigned, ToIsUnsigned> {
public:
static bool checkBounds(const From aFrom) {
return UnsignedUnsignedCheck<From, To>::checkBounds(aFrom);
}
};
template <typename T>
constexpr uint64_t safe_integer_unsigned() {
static_assert(std::is_floating_point_v<T>);
return std::pow(2, std::numeric_limits<T>::digits);
}
// Signed-to-unsigned range check
// This is working around https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81676,
// fixed in gcc-10
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
template <typename In, typename Out>
bool IsInBounds(In aIn) {
constexpr bool inSigned = std::is_signed_v<In>;
constexpr bool outSigned = std::is_signed_v<Out>;
constexpr bool bothSigned = inSigned && outSigned;
constexpr bool bothUnsigned = !inSigned && !outSigned;
constexpr bool inFloat = std::is_floating_point_v<In>;
constexpr bool outFloat = std::is_floating_point_v<Out>;
constexpr bool bothFloat = inFloat && outFloat;
constexpr bool noneFloat = !inFloat && !outFloat;
constexpr Out outMax = std::numeric_limits<Out>::max();
constexpr Out outMin = std::numeric_limits<Out>::lowest();
// This selects the widest of two types, and is used to cast throughout.
using select_widest = std::conditional_t<(sizeof(In) > sizeof(Out)), In, Out>;
if constexpr (bothFloat) {
if (aIn > select_widest(outMax) || aIn < select_widest(outMin)) {
return false;
}
}
// Normal casting applies, the floating point number is floored.
if constexpr (inFloat && !outFloat) {
static_assert(sizeof(aIn) <= sizeof(int64_t));
// Check if the input floating point is larger than the output bounds. This
// catches situations where the input is a float larger than the max of the
// output type.
if (aIn < static_cast<double>(outMin) ||
aIn > static_cast<double>(outMax)) {
template <typename From, typename To>
struct BoundsCheckImpl<From, To, FromIsSigned, ToIsUnsigned> {
public:
static bool checkBounds(const From aFrom) {
if (aFrom < 0) {
return false;
}
// At this point we know that the input can be converted to an integer.
// Check if it's larger than the bounds of the target integer.
if (outSigned) {
int64_t asInteger = static_cast<int64_t>(aIn);
if (asInteger < outMin || asInteger > outMax) {
return false;
}
} else {
uint64_t asInteger = static_cast<uint64_t>(aIn);
if (asInteger < 0 || asInteger > outMax) {
return false;
}
if (sizeof(To) >= sizeof(From)) {
return true;
}
return aFrom <= From(To(-1));
}
};
// Checks if the integer is representable exactly as a floating point value of
// a specific width.
if constexpr (!inFloat && outFloat) {
if constexpr (inSigned) {
if (aIn < -safe_integer<Out>() || aIn > safe_integer<Out>()) {
return false;
}
} else {
if (aIn >= safe_integer_unsigned<Out>()) {
return false;
}
}
// Unsigned-to-signed range check
enum USComparison { FromIsSmaller, FromIsNotSmaller };
template <typename From, typename To,
USComparison =
(sizeof(From) < sizeof(To)) ? FromIsSmaller : FromIsNotSmaller>
struct UnsignedSignedCheck;
template <typename From, typename To>
struct UnsignedSignedCheck<From, To, FromIsSmaller> {
public:
static bool checkBounds(const From aFrom) { return true; }
};
template <typename From, typename To>
struct UnsignedSignedCheck<From, To, FromIsNotSmaller> {
public:
static bool checkBounds(const From aFrom) {
const To MaxValue = To((1ULL << (CHAR_BIT * sizeof(To) - 1)) - 1);
return aFrom <= From(MaxValue);
}
};
if constexpr (noneFloat) {
if constexpr (bothUnsigned) {
if (aIn > select_widest(outMax)) {
return false;
}
}
if constexpr (bothSigned) {
if (aIn > select_widest(outMax) || aIn < select_widest(outMin)) {
return false;
}
}
if constexpr (inSigned && !outSigned) {
if (aIn < 0 || std::make_unsigned_t<In>(aIn) > outMax) {
return false;
}
}
if constexpr (!inSigned && outSigned) {
if (aIn > select_widest(outMax)) {
return false;
}
template <typename From, typename To>
struct BoundsCheckImpl<From, To, FromIsUnsigned, ToIsSigned> {
public:
static bool checkBounds(const From aFrom) {
return UnsignedSignedCheck<From, To>::checkBounds(aFrom);
}
};
// Signed-to-signed range check
template <typename From, typename To>
struct BoundsCheckImpl<From, To, FromIsSigned, ToIsSigned> {
public:
static bool checkBounds(const From aFrom) {
if (sizeof(From) <= sizeof(To)) {
return true;
}
const To MaxValue = To((1ULL << (CHAR_BIT * sizeof(To) - 1)) - 1);
const To MinValue = -MaxValue - To(1);
return From(MinValue) <= aFrom && From(aFrom) <= From(MaxValue);
}
};
template <typename From, typename To,
bool TypesAreIntegral =
std::is_integral_v<From>&& std::is_integral_v<To>>
class BoundsChecker;
template <typename From>
class BoundsChecker<From, From, true> {
public:
static bool checkBounds(const From aFrom) { return true; }
};
template <typename From, typename To>
class BoundsChecker<From, To, true> {
public:
static bool checkBounds(const From aFrom) {
return BoundsCheckImpl<From, To>::checkBounds(aFrom);
}
return true;
};
template <typename From, typename To>
inline bool IsInBounds(const From aFrom) {
return BoundsChecker<From, To>::checkBounds(aFrom);
}
#pragma GCC diagnostic pop
} // namespace detail
/**
* Cast a value of type |From| to a value of type |To|, asserting that the cast
* will be a safe cast per C++ (that is, that |to| is in the range of values
* permitted for the type |From|).
* In particular, this will fail if a integer cannot be represented exactly as a
* floating point value, because it's too large.
* Cast a value of integral type |From| to a value of integral type |To|,
* asserting that the cast will be a safe cast per C++ (that is, that |to| is in
* the range of values permitted for the type |From|).
*/
template <typename To, typename From>
inline To AssertedCast(const From aFrom) {
static_assert(std::is_arithmetic_v<To> && std::is_arithmetic_v<From>);
MOZ_ASSERT((detail::IsInBounds<From, To>(aFrom)));
return static_cast<To>(aFrom);
}
/**
* Cast a value of numeric type |From| to a value of numeric type |To|, release
* asserting that the cast will be a safe cast per C++ (that is, that |to| is in
* the range of values permitted for the type |From|).
* In particular, this will fail if a integer cannot be represented exactly as a
* floating point value, because it's too large.
* Cast a value of integral type |From| to a value of integral type |To|,
* release asserting that the cast will be a safe cast per C++ (that is, that
* |to| is in the range of values permitted for the type |From|).
*/
template <typename To, typename From>
inline To ReleaseAssertedCast(const From aFrom) {
static_assert(std::is_arithmetic_v<To> && std::is_arithmetic_v<From>);
MOZ_RELEASE_ASSERT((detail::IsInBounds<From, To>(aFrom)));
return static_cast<To>(aFrom);
}
......
......@@ -5,20 +5,12 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/Casting.h"
#include "mozilla/ThreadSafety.h"
#include <stdint.h>
#include <cstdint>
#include <limits>
#include <type_traits>
using mozilla::AssertedCast;
using mozilla::BitwiseCast;
using mozilla::detail::IsInBounds;
static const uint8_t floatMantissaBitsPlusOne = 24;
static const uint8_t doubleMantissaBitsPlusOne = 53;
template <typename Uint, typename Ulong, bool = (sizeof(Uint) == sizeof(Ulong))>
struct UintUlongBitwiseCast;
......@@ -96,160 +88,12 @@ static void TestToSmallerSize() {
MOZ_RELEASE_ASSERT((!IsInBounds<int64_t, uint32_t>(int64_t(UINT32_MAX) + 1)));
}
template <typename In, typename Out>
void checkBoundariesFloating(In aEpsilon = {}, Out aIntegerOffset = {}) {
// Check the max value of the input float can't be represented as an integer.
// This is true for all floating point and integer width.
MOZ_RELEASE_ASSERT((!IsInBounds<In, Out>(std::numeric_limits<In>::max())));
// Check that the max value of the integer, as a float, minus an offset that
// depends on the magnitude, can be represented as an integer.
MOZ_RELEASE_ASSERT((IsInBounds<In, Out>(
static_cast<In>(std::numeric_limits<Out>::max() - aIntegerOffset))));
// Check that the max value of the integer, plus a number that depends on the
// magnitude of the number, can't be represented as this integer (because it
// becomes too big).
MOZ_RELEASE_ASSERT((!IsInBounds<In, Out>(
aEpsilon + static_cast<In>(std::numeric_limits<Out>::max()))));
if constexpr (std::is_signed_v<In>) {
// Same for negative numbers.
MOZ_RELEASE_ASSERT(
(!IsInBounds<In, Out>(std::numeric_limits<In>::lowest())));
MOZ_RELEASE_ASSERT((IsInBounds<In, Out>(
static_cast<In>(std::numeric_limits<Out>::lowest()))));
MOZ_RELEASE_ASSERT((!IsInBounds<In, Out>(
static_cast<In>(std::numeric_limits<Out>::lowest()) - aEpsilon)));
} else {
// Check for negative floats and unsigned integer types.
MOZ_RELEASE_ASSERT((!IsInBounds<In, Out>(static_cast<In>(-1))));
}
}
void TestFloatConversion() {
MOZ_RELEASE_ASSERT((!IsInBounds<uint64_t, float>(UINT64_MAX)));
MOZ_RELEASE_ASSERT((!IsInBounds<uint32_t, float>(UINT32_MAX)));
MOZ_RELEASE_ASSERT((IsInBounds<uint16_t, float>(UINT16_MAX)));
MOZ_RELEASE_ASSERT((IsInBounds<uint8_t, float>(UINT8_MAX)));
MOZ_RELEASE_ASSERT((!IsInBounds<int64_t, float>(INT64_MAX)));
MOZ_RELEASE_ASSERT((!IsInBounds<int64_t, float>(INT64_MIN)));
MOZ_RELEASE_ASSERT((!IsInBounds<int32_t, float>(INT32_MAX)));
MOZ_RELEASE_ASSERT((!IsInBounds<int32_t, float>(INT32_MIN)));
MOZ_RELEASE_ASSERT((IsInBounds<int16_t, float>(INT16_MAX)));
MOZ_RELEASE_ASSERT((IsInBounds<int16_t, float>(INT16_MIN)));
MOZ_RELEASE_ASSERT((IsInBounds<int8_t, float>(INT8_MAX)));
MOZ_RELEASE_ASSERT((IsInBounds<int8_t, float>(INT8_MIN)));
MOZ_RELEASE_ASSERT((!IsInBounds<uint64_t, double>(UINT64_MAX)));
MOZ_RELEASE_ASSERT((IsInBounds<uint32_t, double>(UINT32_MAX)));
MOZ_RELEASE_ASSERT((IsInBounds<uint16_t, double>(UINT16_MAX)));
MOZ_RELEASE_ASSERT((IsInBounds<uint8_t, double>(UINT8_MAX)));
MOZ_RELEASE_ASSERT((!IsInBounds<int64_t, double>(INT64_MAX)));
MOZ_RELEASE_ASSERT((!IsInBounds<int64_t, double>(INT64_MIN)));
MOZ_RELEASE_ASSERT((IsInBounds<int32_t, double>(INT32_MAX)));
MOZ_RELEASE_ASSERT((IsInBounds<int32_t, double>(INT32_MIN)));
MOZ_RELEASE_ASSERT((IsInBounds<int16_t, double>(INT16_MAX)));
MOZ_RELEASE_ASSERT((IsInBounds<int16_t, double>(INT16_MIN)));
MOZ_RELEASE_ASSERT((IsInBounds<int8_t, double>(INT8_MAX)));
MOZ_RELEASE_ASSERT((IsInBounds<int8_t, double>(INT8_MIN)));
// Floor check
MOZ_RELEASE_ASSERT((IsInBounds<float, uint64_t>(4.3)));
MOZ_RELEASE_ASSERT((AssertedCast<uint64_t>(4.3f) == 4u));
MOZ_RELEASE_ASSERT((IsInBounds<float, uint32_t>(4.3)));
MOZ_RELEASE_ASSERT((AssertedCast<uint32_t>(4.3f) == 4u));
MOZ_RELEASE_ASSERT((IsInBounds<float, uint16_t>(4.3)));
MOZ_RELEASE_ASSERT((AssertedCast<uint16_t>(4.3f) == 4u));
MOZ_RELEASE_ASSERT((IsInBounds<float, uint8_t>(4.3)));
MOZ_RELEASE_ASSERT((AssertedCast<uint8_t>(4.3f) == 4u));
MOZ_RELEASE_ASSERT((IsInBounds<float, int64_t>(4.3)));
MOZ_RELEASE_ASSERT((AssertedCast<int64_t>(4.3f) == 4u));
MOZ_RELEASE_ASSERT((IsInBounds<float, int32_t>(4.3)));
MOZ_RELEASE_ASSERT((AssertedCast<int32_t>(4.3f) == 4u));
MOZ_RELEASE_ASSERT((IsInBounds<float, int16_t>(4.3)));
MOZ_RELEASE_ASSERT((AssertedCast<int16_t>(4.3f) == 4u));
MOZ_RELEASE_ASSERT((IsInBounds<float, int8_t>(4.3)));
MOZ_RELEASE_ASSERT((AssertedCast<int8_t>(4.3f) == 4u));
MOZ_RELEASE_ASSERT((IsInBounds<float, int64_t>(-4.3)));
MOZ_RELEASE_ASSERT((AssertedCast<int64_t>(-4.3f) == -4));
MOZ_RELEASE_ASSERT((IsInBounds<float, int32_t>(-4.3)));
MOZ_RELEASE_ASSERT((AssertedCast<int32_t>(-4.3f) == -4));
MOZ_RELEASE_ASSERT((IsInBounds<float, int16_t>(-4.3)));
MOZ_RELEASE_ASSERT((AssertedCast<int16_t>(-4.3f) == -4));
MOZ_RELEASE_ASSERT((IsInBounds<float, int8_t>(-4.3)));
MOZ_RELEASE_ASSERT((AssertedCast<int8_t>(-4.3f) == -4));
// Bound check for float to unsigned integer conversion. The parameters are
// espilons and offsets allowing to check boundaries, that depend on the
// magnitude of the numbers.
checkBoundariesFloating<double, uint64_t>(2049.);
checkBoundariesFloating<double, uint32_t>(1.);
checkBoundariesFloating<double, uint16_t>(1.);
checkBoundariesFloating<double, uint8_t>(1.);
// Large number because of the lack of precision of floats at this magnitude
checkBoundariesFloating<float, uint64_t>(1.1e12f);
checkBoundariesFloating<float, uint32_t>(1.f, 128u);
checkBoundariesFloating<float, uint16_t>(1.f);
checkBoundariesFloating<float, uint8_t>(1.f);
checkBoundariesFloating<double, int64_t>(1025.);
checkBoundariesFloating<double, int32_t>(1.);
checkBoundariesFloating<double, int16_t>(1.);
checkBoundariesFloating<double, int8_t>(1.);
// Large number because of the lack of precision of floats at this magnitude
checkBoundariesFloating<float, int64_t>(1.1e12f);
checkBoundariesFloating<float, int32_t>(256.f, 64u);
checkBoundariesFloating<float, int16_t>(1.f);
checkBoundariesFloating<float, int8_t>(1.f);
// Integer to floating point, boundary cases
MOZ_RELEASE_ASSERT(!(IsInBounds<int64_t, float>(
int64_t(std::pow(2, floatMantissaBitsPlusOne)) + 1)));
MOZ_RELEASE_ASSERT((IsInBounds<int64_t, float>(
int64_t(std::pow(2, floatMantissaBitsPlusOne)))));
MOZ_RELEASE_ASSERT((IsInBounds<int64_t, float>(
int64_t(std::pow(2, floatMantissaBitsPlusOne)) - 1)));
MOZ_RELEASE_ASSERT(!(IsInBounds<int64_t, float>(
int64_t(-std::pow(2, floatMantissaBitsPlusOne)) - 1)));
MOZ_RELEASE_ASSERT((IsInBounds<int64_t, float>(
int64_t(-std::pow(2, floatMantissaBitsPlusOne)))));
MOZ_RELEASE_ASSERT((IsInBounds<int64_t, float>(
int64_t(-std::pow(2, floatMantissaBitsPlusOne)) + 1)));
MOZ_RELEASE_ASSERT(!(IsInBounds<int64_t, double>(
uint64_t(std::pow(2, doubleMantissaBitsPlusOne)) + 1)));
MOZ_RELEASE_ASSERT((IsInBounds<int64_t, double>(
uint64_t(std::pow(2, doubleMantissaBitsPlusOne)))));
MOZ_RELEASE_ASSERT((IsInBounds<int64_t, double>(
uint64_t(std::pow(2, doubleMantissaBitsPlusOne)) - 1)));
MOZ_RELEASE_ASSERT(!(IsInBounds<int64_t, double>(
int64_t(-std::pow(2, doubleMantissaBitsPlusOne)) - 1)));
MOZ_RELEASE_ASSERT((IsInBounds<int64_t, double>(
int64_t(-std::pow(2, doubleMantissaBitsPlusOne)))));
MOZ_RELEASE_ASSERT((IsInBounds<int64_t, double>(
int64_t(-std::pow(2, doubleMantissaBitsPlusOne)) + 1)));
MOZ_RELEASE_ASSERT(!(IsInBounds<uint64_t, double>(UINT64_MAX)));
MOZ_RELEASE_ASSERT(!(IsInBounds<int64_t, double>(INT64_MAX)));
MOZ_RELEASE_ASSERT(!(IsInBounds<int64_t, double>(INT64_MIN)));
MOZ_RELEASE_ASSERT(
!(IsInBounds<double, float>(std::numeric_limits<double>::max())));
MOZ_RELEASE_ASSERT(
!(IsInBounds<double, float>(-std::numeric_limits<double>::max())));
}
int main() {
TestBitwiseCast();
TestSameSize();
TestToBiggerSize();
TestToSmallerSize();
TestFloatConversion();
return 0;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment