/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* Provides checked integers, detecting integer overflow and divide-by-0. */
// Probe for builtin math overflow support. Disabled for 32-bit builds for now // since "gcc -m32" claims to support these but its implementation is buggy. // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82274 // Also disabled for clang before version 7 (resp. Xcode clang 10.0.1): while // clang 5 and 6 have a working __builtin_add_overflow, it is not constexpr. #ifdefined(HAVE_64BIT_BUILD) # ifdefined(__has_builtin) && \
(!defined(__clang_major__) || \
(!defined(__apple_build_version__) && __clang_major__ >= 7) || \
(defined(__apple_build_version__) && \
MOZILLA_CHECKEDINT_COMPARABLE_VERSION( \
__clang_major__, __clang_minor__, __clang_patchlevel__) >= \
MOZILLA_CHECKEDINT_COMPARABLE_VERSION(10, 0, 1))) # define MOZ_HAS_BUILTIN_OP_OVERFLOW (__has_builtin(__builtin_add_overflow)) # elif defined(__GNUC__) // (clang also defines __GNUC__ but it supports __has_builtin since at least // v3.1 (released in 2012) so it won't get here.) # define MOZ_HAS_BUILTIN_OP_OVERFLOW (__GNUC__ >= 5) # else # define MOZ_HAS_BUILTIN_OP_OVERFLOW (0) # endif #else # define MOZ_HAS_BUILTIN_OP_OVERFLOW (0) #endif
#undef MOZILLA_CHECKEDINT_COMPARABLE_VERSION
namespace mozilla {
template <typename T> class CheckedInt;
namespace detail {
/* * Step 1: manually record supported types * * What's nontrivial here is that there are different families of integer * types: basic integer types and stdint types. It is merrily undefined which * types from one family may be just typedefs for a type from another family. * * For example, on GCC 4.6, aside from the basic integer types, the only other * type that isn't just a typedef for some of them, is int8_t.
*/
template <typename T>
constexpr bool HasSignBit(T aX) { // In C++, right bit shifts on negative values is undefined by the standard. // Notice that signed-to-unsigned conversions are always well-defined in the // standard, as the value congruent modulo 2**n as expected. By contrast, // unsigned-to-signed is only well-defined if the value is representable. returnbool(std::make_unsigned_t<T>(aX) >> PositionOfSignBit<T>::value);
}
// Bitwise ops may return a larger type, so it's good to use this inline // helper guaranteeing that the result is really of type T. template <typename T>
constexpr T BinaryComplement(T aX) { return ~aX;
}
template <typename T>
constexpr bool IsAddValid(T aX, T aY) { #if MOZ_HAS_BUILTIN_OP_OVERFLOW
T dummy; return !__builtin_add_overflow(aX, aY, &dummy); #else // Addition is valid if the sign of aX+aY is equal to either that of aX or // that of aY. Since the value of aX+aY is undefined if we have a signed // type, we compute it using the unsigned type of the same size. Beware! // These bitwise operations can return a larger integer type, if T was a // small type like int8_t, so we explicitly cast to T.
template <typename T>
constexpr bool IsSubValid(T aX, T aY) { #if MOZ_HAS_BUILTIN_OP_OVERFLOW
T dummy; return !__builtin_sub_overflow(aX, aY, &dummy); #else // Subtraction is valid if either aX and aY have same sign, or aX-aY and aX // have same sign. Since the value of aX-aY is undefined if we have a signed // type, we compute it using the unsigned type of the same size.
std::make_unsigned_t<T> ux = aX;
std::make_unsigned_t<T> uy = aY;
std::make_unsigned_t<T> result = ux - uy;
template <typename T>
constexpr bool IsDivValid(T aX, T aY) { // Keep in mind that in the signed case, min/-1 is invalid because // abs(min)>max. return aY != 0 && !(std::is_signed_v<T> &&
aX == std::numeric_limits<T>::min() && aY == T(-1));
}
/* * Mod is pretty simple. * For now, let's just use the ANSI C definition: * If aX or aY are negative, the results are implementation defined. * Consider these invalid. * Undefined for aY=0. * The result will never exceed either aX or aY. * * Checking that aX>=0 is a warning when T is unsigned.
*/
template <typename T> struct NegateImpl<T, false> { static constexpr CheckedInt<T> negate(const CheckedInt<T>& aVal) { // Handle negation separately for signed/unsigned, for simpler code and to // avoid an MSVC warning negating an unsigned value.
static_assert(detail::IsInRange<T>(0), "Integer type can't represent 0"); return CheckedInt<T>(T(0), aVal.isValid() && aVal.mValue == 0);
}
};
template <typename T> struct NegateImpl<T, true> { static constexpr CheckedInt<T> negate(const CheckedInt<T>& aVal) { // Watch out for the min-value, which (with twos-complement) can't be // negated as -min-value is then (max-value + 1). if (!aVal.isValid() || aVal.mValue == std::numeric_limits<T>::min()) { return CheckedInt<T>(aVal.mValue, false);
} /* For some T, arithmetic ops automatically promote to a wider type, so * explitly do the narrowing cast here. The narrowing cast is valid because
* we did the check for min value above. */ return CheckedInt<T>(T(-aVal.mValue), true);
}
};
} // namespace detail
/* * Step 3: Now define the CheckedInt class.
*/
/** * @class CheckedInt * @brief Integer wrapper class checking for integer overflow and other errors * @param T the integer type to wrap. Can be any type among the following: * - any basic integer type such as |int| * - any stdint type such as |int8_t| * * This class implements guarded integer arithmetic. Do a computation, check * that isValid() returns true, you then have a guarantee that no problem, such * as integer overflow, happened during this computation, and you can call * value() to get the plain integer value. * * The arithmetic operators in this class are guaranteed not to raise a signal * (e.g. in case of a division by zero). * * For example, suppose that you want to implement a function that computes * (aX+aY)/aZ, that doesn't crash if aZ==0, and that reports on error (divide by * zero or integer overflow). You could code it as follows: @code bool computeXPlusYOverZ(int aX, int aY, int aZ, int* aResult) { CheckedInt<int> checkedResult = (CheckedInt<int>(aX) + aY) / aZ; if (checkedResult.isValid()) { *aResult = checkedResult.value(); return true; } else { return false; } } @endcode * * Implicit conversion from plain integers to checked integers is allowed. The * plain integer is checked to be in range before being casted to the * destination type. This means that the following lines all compile, and the * resulting CheckedInts are correctly detected as valid or invalid: * @code // 1 is of type int, is found to be in range for uint8_t, x is valid CheckedInt<uint8_t> x(1); // -1 is of type int, is found not to be in range for uint8_t, x is invalid CheckedInt<uint8_t> x(-1); // -1 is of type int, is found to be in range for int8_t, x is valid CheckedInt<int8_t> x(-1); // 1000 is of type int16_t, is found not to be in range for int8_t, // x is invalid CheckedInt<int8_t> x(int16_t(1000)); // 3123456789 is of type uint32_t, is found not to be in range for int32_t, // x is invalid CheckedInt<int32_t> x(uint32_t(3123456789)); * @endcode * Implicit conversion from * checked integers to plain integers is not allowed. As shown in the * above example, to get the value of a checked integer as a normal integer, * call value(). * * Arithmetic operations between checked and plain integers is allowed; the * result type is the type of the checked integer. * * Checked integers of different types cannot be used in the same arithmetic * expression. * * There are convenience typedefs for all stdint types, of the following form * (these are just 2 examples): @code typedef CheckedInt<int32_t> CheckedInt32; typedef CheckedInt<uint16_t> CheckedUint16; @endcode
*/ template <typename T> class CheckedInt { protected:
T mValue; bool mIsValid;
template <typename U>
constexpr CheckedInt(U aValue, bool aIsValid)
: mValue(aValue), mIsValid(aIsValid) {
static_assert(std::is_same_v<T, U>, "this constructor must accept only T values");
static_assert(detail::IsSupported<T>::value, "This type is not supported by CheckedInt");
}
friendstruct detail::NegateImpl<T>;
public: /** * Constructs a checked integer with given @a value. The checked integer is * initialized as valid or invalid depending on whether the @a value * is in range. * * This constructor is not explicit. Instead, the type of its argument is a * separate template parameter, ensuring that no conversion is performed * before this constructor is actually called. As explained in the above * documentation for class CheckedInt, this constructor checks that its * argument is valid.
*/ template <typename U>
MOZ_IMPLICIT MOZ_NO_ARITHMETIC_EXPR_IN_ARGUMENT constexpr CheckedInt(U aValue)
: mValue(T(aValue)), mIsValid(detail::IsInRange<T>(aValue)) {
static_assert(
detail::IsSupported<T>::value && detail::IsSupported<U>::value, "This type is not supported by CheckedInt");
}
/** Constructs a valid checked integer with initial value 0 */
constexpr CheckedInt() : mValue(T(0)), mIsValid(true) {
static_assert(detail::IsSupported<T>::value, "This type is not supported by CheckedInt");
static_assert(detail::IsInRange<T>(0), "Integer type can't represent 0");
}
/** @returns the actual value */
constexpr T value() const {
MOZ_DIAGNOSTIC_ASSERT(
mIsValid, "Invalid checked integer (division by zero or integer overflow)"); return mValue;
}
/** * @returns true if the checked integer is valid, i.e. is not the result * of an invalid operation or of an operation involving an invalid checked * integer
*/
constexpr bool isValid() const { return mIsValid; }
/** * @returns true if the left and right hand sides are valid * and have the same value. * * Note that these semantics are the reason why we don't offer * a operator!=. Indeed, we'd want to have a!=b be equivalent to !(a==b) * but that would mean that whenever a or b is invalid, a!=b * is always true, which would be very confusing. * * For similar reasons, operators <, >, <=, >= would be very tricky to * specify, so we just avoid offering them. * * Notice that these == semantics are made more reasonable by these facts: * 1. a==b implies equality at the raw data level * (the converse is false, as a==b is never true among invalids) * 2. This is similar to the behavior of IEEE floats, where a==b * means that a and b have the same value *and* neither is NaN.
*/
constexpr booloperator==(const CheckedInt& aOther) const { return mIsValid && aOther.mIsValid && mValue == aOther.mValue;
}
// Implement castToCheckedInt<T>(x), making sure that // - it allows x to be either a CheckedInt<T> or any integer type // that can be casted to T // - if x is already a CheckedInt<T>, we just return a reference to it, // instead of copying it (optimization)
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.