gecko/mfbt/CheckedInt.h
Ehsan Akhgari ecb73205b2 Bug 867348 - Part 2: Apply MOZ_NO_ARITHMETIC_EXPR_IN_ARGUMENT to CheckedInt's constructor; r=jrmuizel,cpearce
Note that the analysis currently just looks at the AST subtree of the
function call site and is therefore unable to correctly deal with cases
such as the last two hunks of the change to OggCodecState.cpp.  Fixing
the analysis to deal with that would be very difficult, so we currently
adjust the code so that it compiles.  The first hunk in that file though
is a real bug that this analysis found.
2014-12-18 15:27:05 -05:00

792 lines
23 KiB
C++

/* -*- 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. */
#ifndef mozilla_CheckedInt_h
#define mozilla_CheckedInt_h
#include <stdint.h>
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/IntegerTypeTraits.h"
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.
*/
struct UnsupportedType {};
template<typename IntegerType>
struct IsSupportedPass2
{
static const bool value = false;
};
template<typename IntegerType>
struct IsSupported
{
static const bool value = IsSupportedPass2<IntegerType>::value;
};
template<>
struct IsSupported<int8_t>
{ static const bool value = true; };
template<>
struct IsSupported<uint8_t>
{ static const bool value = true; };
template<>
struct IsSupported<int16_t>
{ static const bool value = true; };
template<>
struct IsSupported<uint16_t>
{ static const bool value = true; };
template<>
struct IsSupported<int32_t>
{ static const bool value = true; };
template<>
struct IsSupported<uint32_t>
{ static const bool value = true; };
template<>
struct IsSupported<int64_t>
{ static const bool value = true; };
template<>
struct IsSupported<uint64_t>
{ static const bool value = true; };
template<>
struct IsSupportedPass2<char>
{ static const bool value = true; };
template<>
struct IsSupportedPass2<signed char>
{ static const bool value = true; };
template<>
struct IsSupportedPass2<unsigned char>
{ static const bool value = true; };
template<>
struct IsSupportedPass2<short>
{ static const bool value = true; };
template<>
struct IsSupportedPass2<unsigned short>
{ static const bool value = true; };
template<>
struct IsSupportedPass2<int>
{ static const bool value = true; };
template<>
struct IsSupportedPass2<unsigned int>
{ static const bool value = true; };
template<>
struct IsSupportedPass2<long>
{ static const bool value = true; };
template<>
struct IsSupportedPass2<unsigned long>
{ static const bool value = true; };
template<>
struct IsSupportedPass2<long long>
{ static const bool value = true; };
template<>
struct IsSupportedPass2<unsigned long long>
{ static const bool value = true; };
/*
* Step 2: Implement the actual validity checks.
*
* Ideas taken from IntegerLib, code different.
*/
template<typename IntegerType, size_t Size = sizeof(IntegerType)>
struct TwiceBiggerType
{
typedef typename detail::StdintTypeForSizeAndSignedness<
sizeof(IntegerType) * 2,
IsSigned<IntegerType>::value
>::Type Type;
};
template<typename IntegerType>
struct TwiceBiggerType<IntegerType, 8>
{
typedef UnsupportedType Type;
};
template<typename T>
inline 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.
return bool(typename MakeUnsigned<T>::Type(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>
inline T
BinaryComplement(T aX)
{
return ~aX;
}
template<typename T,
typename U,
bool IsTSigned = IsSigned<T>::value,
bool IsUSigned = IsSigned<U>::value>
struct DoesRangeContainRange
{
};
template<typename T, typename U, bool Signedness>
struct DoesRangeContainRange<T, U, Signedness, Signedness>
{
static const bool value = sizeof(T) >= sizeof(U);
};
template<typename T, typename U>
struct DoesRangeContainRange<T, U, true, false>
{
static const bool value = sizeof(T) > sizeof(U);
};
template<typename T, typename U>
struct DoesRangeContainRange<T, U, false, true>
{
static const bool value = false;
};
template<typename T,
typename U,
bool IsTSigned = IsSigned<T>::value,
bool IsUSigned = IsSigned<U>::value,
bool DoesTRangeContainURange = DoesRangeContainRange<T, U>::value>
struct IsInRangeImpl {};
template<typename T, typename U, bool IsTSigned, bool IsUSigned>
struct IsInRangeImpl<T, U, IsTSigned, IsUSigned, true>
{
static bool run(U)
{
return true;
}
};
template<typename T, typename U>
struct IsInRangeImpl<T, U, true, true, false>
{
static bool run(U aX)
{
return aX <= MaxValue<T>::value && aX >= MinValue<T>::value;
}
};
template<typename T, typename U>
struct IsInRangeImpl<T, U, false, false, false>
{
static bool run(U aX)
{
return aX <= MaxValue<T>::value;
}
};
template<typename T, typename U>
struct IsInRangeImpl<T, U, true, false, false>
{
static bool run(U aX)
{
return sizeof(T) > sizeof(U) || aX <= U(MaxValue<T>::value);
}
};
template<typename T, typename U>
struct IsInRangeImpl<T, U, false, true, false>
{
static bool run(U aX)
{
return sizeof(T) >= sizeof(U)
? aX >= 0
: aX >= 0 && aX <= U(MaxValue<T>::value);
}
};
template<typename T, typename U>
inline bool
IsInRange(U aX)
{
return IsInRangeImpl<T, U>::run(aX);
}
template<typename T>
inline bool
IsAddValid(T aX, T aY)
{
// 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.
typename MakeUnsigned<T>::Type ux = aX;
typename MakeUnsigned<T>::Type uy = aY;
typename MakeUnsigned<T>::Type result = ux + uy;
return IsSigned<T>::value
? HasSignBit(BinaryComplement(T((result ^ aX) & (result ^ aY))))
: BinaryComplement(aX) >= aY;
}
template<typename T>
inline bool
IsSubValid(T aX, T aY)
{
// 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.
typename MakeUnsigned<T>::Type ux = aX;
typename MakeUnsigned<T>::Type uy = aY;
typename MakeUnsigned<T>::Type result = ux - uy;
return IsSigned<T>::value
? HasSignBit(BinaryComplement(T((result ^ aX) & (aX ^ aY))))
: aX >= aY;
}
template<typename T,
bool IsTSigned = IsSigned<T>::value,
bool TwiceBiggerTypeIsSupported =
IsSupported<typename TwiceBiggerType<T>::Type>::value>
struct IsMulValidImpl {};
template<typename T, bool IsTSigned>
struct IsMulValidImpl<T, IsTSigned, true>
{
static bool run(T aX, T aY)
{
typedef typename TwiceBiggerType<T>::Type TwiceBiggerType;
TwiceBiggerType product = TwiceBiggerType(aX) * TwiceBiggerType(aY);
return IsInRange<T>(product);
}
};
template<typename T>
struct IsMulValidImpl<T, true, false>
{
static bool run(T aX, T aY)
{
const T max = MaxValue<T>::value;
const T min = MinValue<T>::value;
if (aX == 0 || aY == 0) {
return true;
}
if (aX > 0) {
return aY > 0
? aX <= max / aY
: aY >= min / aX;
}
// If we reach this point, we know that aX < 0.
return aY > 0
? aX >= min / aY
: aY >= max / aX;
}
};
template<typename T>
struct IsMulValidImpl<T, false, false>
{
static bool run(T aX, T aY)
{
return aY == 0 || aX <= MaxValue<T>::value / aY;
}
};
template<typename T>
inline bool
IsMulValid(T aX, T aY)
{
return IsMulValidImpl<T>::run(aX, aY);
}
template<typename T>
inline 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 &&
!(IsSigned<T>::value && aX == MinValue<T>::value && aY == T(-1));
}
template<typename T, bool IsTSigned = IsSigned<T>::value>
struct IsModValidImpl;
template<typename T>
inline bool
IsModValid(T aX, T aY)
{
return IsModValidImpl<T>::run(aX, aY);
}
/*
* 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 IsModValidImpl<T, false>
{
static inline bool run(T aX, T aY)
{
return aY >= 1;
}
};
template<typename T>
struct IsModValidImpl<T, true>
{
static inline bool run(T aX, T aY)
{
if (aX < 0) {
return false;
}
return aY >= 1;
}
};
template<typename T, bool IsSigned = IsSigned<T>::value>
struct NegateImpl;
template<typename T>
struct NegateImpl<T, false>
{
static 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.
return CheckedInt<T>(0, aVal.isValid() && aVal.mValue == 0);
}
};
template<typename T>
struct NegateImpl<T, true>
{
static 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 == MinValue<T>::value) {
return CheckedInt<T>(aVal.mValue, false);
}
return CheckedInt<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>
CheckedInt(U aValue, bool aIsValid) : mValue(aValue), mIsValid(aIsValid)
{
static_assert(detail::IsSupported<T>::value &&
detail::IsSupported<U>::value,
"This type is not supported by CheckedInt");
}
friend struct 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>
CheckedInt(U aValue) MOZ_NO_ARITHMETIC_EXPR_IN_ARGUMENT
: 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");
}
template<typename U>
friend class CheckedInt;
template<typename U>
CheckedInt<U> toChecked() const
{
CheckedInt<U> ret(mValue);
ret.mIsValid = ret.mIsValid && mIsValid;
return ret;
}
/** Constructs a valid checked integer with initial value 0 */
CheckedInt() : mValue(0), mIsValid(true)
{
static_assert(detail::IsSupported<T>::value,
"This type is not supported by CheckedInt");
}
/** @returns the actual value */
T value() const
{
MOZ_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
*/
bool isValid() const
{
return mIsValid;
}
template<typename U>
friend CheckedInt<U> operator +(const CheckedInt<U>& aLhs,
const CheckedInt<U>& aRhs);
template<typename U>
CheckedInt& operator +=(U aRhs);
CheckedInt& operator +=(const CheckedInt<T>& aRhs);
template<typename U>
friend CheckedInt<U> operator -(const CheckedInt<U>& aLhs,
const CheckedInt<U>& aRhs);
template<typename U>
CheckedInt& operator -=(U aRhs);
CheckedInt& operator -=(const CheckedInt<T>& aRhs);
template<typename U>
friend CheckedInt<U> operator *(const CheckedInt<U>& aLhs,
const CheckedInt<U>& aRhs);
template<typename U>
CheckedInt& operator *=(U aRhs);
CheckedInt& operator *=(const CheckedInt<T>& aRhs);
template<typename U>
friend CheckedInt<U> operator /(const CheckedInt<U>& aLhs,
const CheckedInt<U>& aRhs);
template<typename U>
CheckedInt& operator /=(U aRhs);
CheckedInt& operator /=(const CheckedInt<T>& aRhs);
template<typename U>
friend CheckedInt<U> operator %(const CheckedInt<U>& aLhs,
const CheckedInt<U>& aRhs);
template<typename U>
CheckedInt& operator %=(U aRhs);
CheckedInt& operator %=(const CheckedInt<T>& aRhs);
CheckedInt operator -() const
{
return detail::NegateImpl<T>::negate(*this);
}
/**
* @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.
*/
bool operator ==(const CheckedInt& aOther) const
{
return mIsValid && aOther.mIsValid && mValue == aOther.mValue;
}
/** prefix ++ */
CheckedInt& operator++()
{
*this += 1;
return *this;
}
/** postfix ++ */
CheckedInt operator++(int)
{
CheckedInt tmp = *this;
*this += 1;
return tmp;
}
/** prefix -- */
CheckedInt& operator--()
{
*this -= 1;
return *this;
}
/** postfix -- */
CheckedInt operator--(int)
{
CheckedInt tmp = *this;
*this -= 1;
return tmp;
}
private:
/**
* The !=, <, <=, >, >= operators are disabled:
* see the comment on operator==.
*/
template<typename U> bool operator !=(U aOther) const MOZ_DELETE;
template<typename U> bool operator < (U aOther) const MOZ_DELETE;
template<typename U> bool operator <=(U aOther) const MOZ_DELETE;
template<typename U> bool operator > (U aOther) const MOZ_DELETE;
template<typename U> bool operator >=(U aOther) const MOZ_DELETE;
};
#define MOZ_CHECKEDINT_BASIC_BINARY_OPERATOR(NAME, OP) \
template<typename T> \
inline CheckedInt<T> \
operator OP(const CheckedInt<T>& aLhs, const CheckedInt<T>& aRhs) \
{ \
if (!detail::Is##NAME##Valid(aLhs.mValue, aRhs.mValue)) { \
return CheckedInt<T>(0, false); \
} \
return CheckedInt<T>(aLhs.mValue OP aRhs.mValue, \
aLhs.mIsValid && aRhs.mIsValid); \
}
MOZ_CHECKEDINT_BASIC_BINARY_OPERATOR(Add, +)
MOZ_CHECKEDINT_BASIC_BINARY_OPERATOR(Sub, -)
MOZ_CHECKEDINT_BASIC_BINARY_OPERATOR(Mul, *)
MOZ_CHECKEDINT_BASIC_BINARY_OPERATOR(Div, /)
MOZ_CHECKEDINT_BASIC_BINARY_OPERATOR(Mod, %)
#undef MOZ_CHECKEDINT_BASIC_BINARY_OPERATOR
// 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)
namespace detail {
template<typename T, typename U>
struct CastToCheckedIntImpl
{
typedef CheckedInt<T> ReturnType;
static CheckedInt<T> run(U aU) { return aU; }
};
template<typename T>
struct CastToCheckedIntImpl<T, CheckedInt<T> >
{
typedef const CheckedInt<T>& ReturnType;
static const CheckedInt<T>& run(const CheckedInt<T>& aU) { return aU; }
};
} // namespace detail
template<typename T, typename U>
inline typename detail::CastToCheckedIntImpl<T, U>::ReturnType
castToCheckedInt(U aU)
{
static_assert(detail::IsSupported<T>::value &&
detail::IsSupported<U>::value,
"This type is not supported by CheckedInt");
return detail::CastToCheckedIntImpl<T, U>::run(aU);
}
#define MOZ_CHECKEDINT_CONVENIENCE_BINARY_OPERATORS(OP, COMPOUND_OP) \
template<typename T> \
template<typename U> \
CheckedInt<T>& CheckedInt<T>::operator COMPOUND_OP(U aRhs) \
{ \
*this = *this OP castToCheckedInt<T>(aRhs); \
return *this; \
} \
template<typename T> \
CheckedInt<T>& CheckedInt<T>::operator COMPOUND_OP(const CheckedInt<T>& aRhs) \
{ \
*this = *this OP aRhs; \
return *this; \
} \
template<typename T, typename U> \
inline CheckedInt<T> operator OP(const CheckedInt<T>& aLhs, U aRhs) \
{ \
return aLhs OP castToCheckedInt<T>(aRhs); \
} \
template<typename T, typename U> \
inline CheckedInt<T> operator OP(U aLhs, const CheckedInt<T>& aRhs) \
{ \
return castToCheckedInt<T>(aLhs) OP aRhs; \
}
MOZ_CHECKEDINT_CONVENIENCE_BINARY_OPERATORS(+, +=)
MOZ_CHECKEDINT_CONVENIENCE_BINARY_OPERATORS(*, *=)
MOZ_CHECKEDINT_CONVENIENCE_BINARY_OPERATORS(-, -=)
MOZ_CHECKEDINT_CONVENIENCE_BINARY_OPERATORS(/, /=)
MOZ_CHECKEDINT_CONVENIENCE_BINARY_OPERATORS(%, %=)
#undef MOZ_CHECKEDINT_CONVENIENCE_BINARY_OPERATORS
template<typename T, typename U>
inline bool
operator ==(const CheckedInt<T>& aLhs, U aRhs)
{
return aLhs == castToCheckedInt<T>(aRhs);
}
template<typename T, typename U>
inline bool
operator ==(U aLhs, const CheckedInt<T>& aRhs)
{
return castToCheckedInt<T>(aLhs) == aRhs;
}
// Convenience typedefs.
typedef CheckedInt<int8_t> CheckedInt8;
typedef CheckedInt<uint8_t> CheckedUint8;
typedef CheckedInt<int16_t> CheckedInt16;
typedef CheckedInt<uint16_t> CheckedUint16;
typedef CheckedInt<int32_t> CheckedInt32;
typedef CheckedInt<uint32_t> CheckedUint32;
typedef CheckedInt<int64_t> CheckedInt64;
typedef CheckedInt<uint64_t> CheckedUint64;
} // namespace mozilla
#endif /* mozilla_CheckedInt_h */