diff options
author | jschuh@chromium.org <jschuh@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-02-27 13:49:04 +0000 |
---|---|---|
committer | jschuh@chromium.org <jschuh@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-02-27 13:49:04 +0000 |
commit | 5bfecbc58808a6e46158564f4bfef9fc38cb7aa4 (patch) | |
tree | 11b9113b647ae5586fcd1795770257d4786760f4 /base/numerics | |
parent | d1093eb83ca3cac9604fd35bfb2001ed938845de (diff) | |
download | chromium_src-5bfecbc58808a6e46158564f4bfef9fc38cb7aa4.zip chromium_src-5bfecbc58808a6e46158564f4bfef9fc38cb7aa4.tar.gz chromium_src-5bfecbc58808a6e46158564f4bfef9fc38cb7aa4.tar.bz2 |
Add support for safe math operations in base/numerics
Add checked numeric templates.
BUG=332611
Review URL: https://codereview.chromium.org/141583008
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@253789 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base/numerics')
-rw-r--r-- | base/numerics/safe_conversions.h | 13 | ||||
-rw-r--r-- | base/numerics/safe_conversions_impl.h | 260 | ||||
-rw-r--r-- | base/numerics/safe_math.h | 271 | ||||
-rw-r--r-- | base/numerics/safe_math_impl.h | 502 | ||||
-rw-r--r-- | base/numerics/safe_numerics_unittest.cc | 434 |
5 files changed, 1294 insertions, 186 deletions
diff --git a/base/numerics/safe_conversions.h b/base/numerics/safe_conversions.h index 03ef96d..fe85fc6 100644 --- a/base/numerics/safe_conversions.h +++ b/base/numerics/safe_conversions.h @@ -16,7 +16,8 @@ namespace base { // for the destination type. template <typename Dst, typename Src> inline bool IsValueInRangeForNumericType(Src value) { - return internal::RangeCheck<Dst>(value) == internal::TYPE_VALID; + return internal::DstRangeRelationToSrcRange<Dst>(value) == + internal::RANGE_VALID; } // checked_cast<> is analogous to static_cast<> for numeric types, @@ -37,18 +38,18 @@ inline Dst saturated_cast(Src value) { if (std::numeric_limits<Dst>::is_iec559) return static_cast<Dst>(value); - switch (internal::RangeCheck<Dst>(value)) { - case internal::TYPE_VALID: + switch (internal::DstRangeRelationToSrcRange<Dst>(value)) { + case internal::RANGE_VALID: return static_cast<Dst>(value); - case internal::TYPE_UNDERFLOW: + case internal::RANGE_UNDERFLOW: return std::numeric_limits<Dst>::min(); - case internal::TYPE_OVERFLOW: + case internal::RANGE_OVERFLOW: return std::numeric_limits<Dst>::max(); // Should fail only on attempting to assign NaN to a saturated integer. - case internal::TYPE_INVALID: + case internal::RANGE_INVALID: CHECK(false); return std::numeric_limits<Dst>::max(); } diff --git a/base/numerics/safe_conversions_impl.h b/base/numerics/safe_conversions_impl.h index 8bc3021..f05d553 100644 --- a/base/numerics/safe_conversions_impl.h +++ b/base/numerics/safe_conversions_impl.h @@ -8,172 +8,206 @@ #include <limits> #include "base/macros.h" +#include "base/template_util.h" namespace base { namespace internal { -enum DstSign { - DST_UNSIGNED, - DST_SIGNED +// The std library doesn't provide a binary max_exponent for integers, however +// we can compute one by adding one to the number of non-sign bits. This allows +// for accurate range comparisons between floating point and integer types. +template <typename NumericType> +struct MaxExponent { + static const int value = std::numeric_limits<NumericType>::is_iec559 + ? std::numeric_limits<NumericType>::max_exponent + : (sizeof(NumericType) * 8 + 1 - + std::numeric_limits<NumericType>::is_signed); }; -enum SrcSign { - SRC_UNSIGNED, - SRC_SIGNED +enum IntegerRepresentation { + INTEGER_REPRESENTATION_UNSIGNED, + INTEGER_REPRESENTATION_SIGNED }; -enum DstRange { - OVERLAPS_RANGE, - CONTAINS_RANGE +// A range for a given nunmeric Src type is contained for a given numeric Dst +// type if both numeric_limits<Src>::max() <= numeric_limits<Dst>::max() and +// numeric_limits<Src>::min() >= numeric_limits<Dst>::min() are true. +// We implement this as template specializations rather than simple static +// comparisons to ensure type correctness in our comparisons. +enum NumericRangeRepresentation { + NUMERIC_RANGE_NOT_CONTAINED, + NUMERIC_RANGE_CONTAINED }; // Helper templates to statically determine if our destination type can contain -// all values represented by the source type. - -template <typename Dst, typename Src, - DstSign IsDstSigned = std::numeric_limits<Dst>::is_signed ? - DST_SIGNED : DST_UNSIGNED, - SrcSign IsSrcSigned = std::numeric_limits<Src>::is_signed ? - SRC_SIGNED : SRC_UNSIGNED> -struct StaticRangeCheck {}; - -template <typename Dst, typename Src> -struct StaticRangeCheck<Dst, Src, DST_SIGNED, SRC_SIGNED> { - typedef std::numeric_limits<Dst> DstLimits; - typedef std::numeric_limits<Src> SrcLimits; - // Compare based on max_exponent, which we must compute for integrals. - static const size_t kDstMaxExponent = DstLimits::is_iec559 ? - DstLimits::max_exponent : - (sizeof(Dst) * 8 - 1); - static const size_t kSrcMaxExponent = SrcLimits::is_iec559 ? - SrcLimits::max_exponent : - (sizeof(Src) * 8 - 1); - static const DstRange value = kDstMaxExponent >= kSrcMaxExponent ? - CONTAINS_RANGE : OVERLAPS_RANGE; +// maximum and minimum values represented by the source type. + +template < + typename Dst, + typename Src, + IntegerRepresentation DstSign = std::numeric_limits<Dst>::is_signed + ? INTEGER_REPRESENTATION_SIGNED + : INTEGER_REPRESENTATION_UNSIGNED, + IntegerRepresentation SrcSign = + std::numeric_limits<Src>::is_signed + ? INTEGER_REPRESENTATION_SIGNED + : INTEGER_REPRESENTATION_UNSIGNED > +struct StaticDstRangeRelationToSrcRange; + +// Same sign: Dst is guaranteed to contain Src only if its range is equal or +// larger. +template <typename Dst, typename Src, IntegerRepresentation Sign> +struct StaticDstRangeRelationToSrcRange<Dst, Src, Sign, Sign> { + static const NumericRangeRepresentation value = + MaxExponent<Dst>::value >= MaxExponent<Src>::value + ? NUMERIC_RANGE_CONTAINED + : NUMERIC_RANGE_NOT_CONTAINED; }; +// Unsigned to signed: Dst is guaranteed to contain source only if its range is +// larger. template <typename Dst, typename Src> -struct StaticRangeCheck<Dst, Src, DST_UNSIGNED, SRC_UNSIGNED> { - static const DstRange value = sizeof(Dst) >= sizeof(Src) ? - CONTAINS_RANGE : OVERLAPS_RANGE; +struct StaticDstRangeRelationToSrcRange<Dst, + Src, + INTEGER_REPRESENTATION_SIGNED, + INTEGER_REPRESENTATION_UNSIGNED> { + static const NumericRangeRepresentation value = + MaxExponent<Dst>::value > MaxExponent<Src>::value + ? NUMERIC_RANGE_CONTAINED + : NUMERIC_RANGE_NOT_CONTAINED; }; +// Signed to unsigned: Dst cannot be statically determined to contain Src. template <typename Dst, typename Src> -struct StaticRangeCheck<Dst, Src, DST_SIGNED, SRC_UNSIGNED> { - typedef std::numeric_limits<Dst> DstLimits; - typedef std::numeric_limits<Src> SrcLimits; - // Compare based on max_exponent, which we must compute for integrals. - static const size_t kDstMaxExponent = DstLimits::is_iec559 ? - DstLimits::max_exponent : - (sizeof(Dst) * 8 - 1); - static const size_t kSrcMaxExponent = sizeof(Src) * 8; - static const DstRange value = kDstMaxExponent >= kSrcMaxExponent ? - CONTAINS_RANGE : OVERLAPS_RANGE; +struct StaticDstRangeRelationToSrcRange<Dst, + Src, + INTEGER_REPRESENTATION_UNSIGNED, + INTEGER_REPRESENTATION_SIGNED> { + static const NumericRangeRepresentation value = NUMERIC_RANGE_NOT_CONTAINED; }; -template <typename Dst, typename Src> -struct StaticRangeCheck<Dst, Src, DST_UNSIGNED, SRC_SIGNED> { - static const DstRange value = OVERLAPS_RANGE; +enum RangeConstraint { + RANGE_VALID = 0x0, // Value can be represented by the destination type. + RANGE_UNDERFLOW = 0x1, // Value would overflow. + RANGE_OVERFLOW = 0x2, // Value would underflow. + RANGE_INVALID = RANGE_UNDERFLOW | RANGE_OVERFLOW // Invalid (i.e. NaN). }; +// Helper function for coercing an int back to a RangeContraint. +inline RangeConstraint GetRangeConstraint(int integer_range_constraint) { + DCHECK(integer_range_constraint >= RANGE_VALID && + integer_range_constraint <= RANGE_INVALID); + return static_cast<RangeConstraint>(integer_range_constraint); +} -enum RangeCheckResult { - TYPE_VALID = 0, // Value can be represented by the destination type. - TYPE_UNDERFLOW = 1, // Value would overflow. - TYPE_OVERFLOW = 2, // Value would underflow. - TYPE_INVALID = 3 // Source value is invalid (i.e. NaN). -}; - -// This macro creates a RangeCheckResult from an upper and lower bound +// This function creates a RangeConstraint from an upper and lower bound // check by taking advantage of the fact that only NaN can be out of range in // both directions at once. -#define BASE_NUMERIC_RANGE_CHECK_RESULT(is_in_upper_bound, is_in_lower_bound) \ - RangeCheckResult(((is_in_upper_bound) ? 0 : TYPE_OVERFLOW) | \ - ((is_in_lower_bound) ? 0 : TYPE_UNDERFLOW)) +inline RangeConstraint GetRangeConstraint(bool is_in_upper_bound, + bool is_in_lower_bound) { + return GetRangeConstraint((is_in_upper_bound ? 0 : RANGE_OVERFLOW) | + (is_in_lower_bound ? 0 : RANGE_UNDERFLOW)); +} -template <typename Dst, - typename Src, - DstSign IsDstSigned = std::numeric_limits<Dst>::is_signed ? - DST_SIGNED : DST_UNSIGNED, - SrcSign IsSrcSigned = std::numeric_limits<Src>::is_signed ? - SRC_SIGNED : SRC_UNSIGNED, - DstRange IsSrcRangeContained = StaticRangeCheck<Dst, Src>::value> -struct RangeCheckImpl {}; +template < + typename Dst, + typename Src, + IntegerRepresentation DstSign = std::numeric_limits<Dst>::is_signed + ? INTEGER_REPRESENTATION_SIGNED + : INTEGER_REPRESENTATION_UNSIGNED, + IntegerRepresentation SrcSign = std::numeric_limits<Src>::is_signed + ? INTEGER_REPRESENTATION_SIGNED + : INTEGER_REPRESENTATION_UNSIGNED, + NumericRangeRepresentation DstRange = + StaticDstRangeRelationToSrcRange<Dst, Src>::value > +struct DstRangeRelationToSrcRangeImpl; // The following templates are for ranges that must be verified at runtime. We // split it into checks based on signedness to avoid confusing casts and // compiler warnings on signed an unsigned comparisons. -// Dst range always contains the result: nothing to check. -template <typename Dst, typename Src, DstSign IsDstSigned, SrcSign IsSrcSigned> -struct RangeCheckImpl<Dst, Src, IsDstSigned, IsSrcSigned, CONTAINS_RANGE> { - static RangeCheckResult Check(Src value) { - return TYPE_VALID; - } +// Dst range is statically determined to contain Src: Nothing to check. +template <typename Dst, + typename Src, + IntegerRepresentation DstSign, + IntegerRepresentation SrcSign> +struct DstRangeRelationToSrcRangeImpl<Dst, + Src, + DstSign, + SrcSign, + NUMERIC_RANGE_CONTAINED> { + static RangeConstraint Check(Src value) { return RANGE_VALID; } }; -// Signed to signed narrowing. +// Signed to signed narrowing: Both the upper and lower boundaries may be +// exceeded. template <typename Dst, typename Src> -struct RangeCheckImpl<Dst, Src, DST_SIGNED, SRC_SIGNED, OVERLAPS_RANGE> { - static RangeCheckResult Check(Src value) { - typedef std::numeric_limits<Dst> DstLimits; - return DstLimits::is_iec559 ? - BASE_NUMERIC_RANGE_CHECK_RESULT( - value <= static_cast<Src>(DstLimits::max()), - value >= static_cast<Src>(DstLimits::max() * -1)) : - BASE_NUMERIC_RANGE_CHECK_RESULT( - value <= static_cast<Src>(DstLimits::max()), - value >= static_cast<Src>(DstLimits::min())); +struct DstRangeRelationToSrcRangeImpl<Dst, + Src, + INTEGER_REPRESENTATION_SIGNED, + INTEGER_REPRESENTATION_SIGNED, + NUMERIC_RANGE_NOT_CONTAINED> { + static RangeConstraint Check(Src value) { + return std::numeric_limits<Dst>::is_iec559 + ? GetRangeConstraint(value <= std::numeric_limits<Dst>::max(), + value >= -std::numeric_limits<Dst>::max()) + : GetRangeConstraint(value <= std::numeric_limits<Dst>::max(), + value >= std::numeric_limits<Dst>::min()); } }; -// Unsigned to unsigned narrowing. +// Unsigned to unsigned narrowing: Only the upper boundary can be exceeded. template <typename Dst, typename Src> -struct RangeCheckImpl<Dst, Src, DST_UNSIGNED, SRC_UNSIGNED, OVERLAPS_RANGE> { - static RangeCheckResult Check(Src value) { - typedef std::numeric_limits<Dst> DstLimits; - return BASE_NUMERIC_RANGE_CHECK_RESULT( - value <= static_cast<Src>(DstLimits::max()), true); +struct DstRangeRelationToSrcRangeImpl<Dst, + Src, + INTEGER_REPRESENTATION_UNSIGNED, + INTEGER_REPRESENTATION_UNSIGNED, + NUMERIC_RANGE_NOT_CONTAINED> { + static RangeConstraint Check(Src value) { + return GetRangeConstraint(value <= std::numeric_limits<Dst>::max(), true); } }; -// Unsigned to signed. +// Unsigned to signed: The upper boundary may be exceeded. template <typename Dst, typename Src> -struct RangeCheckImpl<Dst, Src, DST_SIGNED, SRC_UNSIGNED, OVERLAPS_RANGE> { - static RangeCheckResult Check(Src value) { - typedef std::numeric_limits<Dst> DstLimits; - return sizeof(Dst) > sizeof(Src) ? TYPE_VALID : - BASE_NUMERIC_RANGE_CHECK_RESULT( - value <= static_cast<Src>(DstLimits::max()), true); +struct DstRangeRelationToSrcRangeImpl<Dst, + Src, + INTEGER_REPRESENTATION_SIGNED, + INTEGER_REPRESENTATION_UNSIGNED, + NUMERIC_RANGE_NOT_CONTAINED> { + static RangeConstraint Check(Src value) { + return sizeof(Dst) > sizeof(Src) + ? RANGE_VALID + : GetRangeConstraint( + value <= static_cast<Src>(std::numeric_limits<Dst>::max()), + true); } }; -// Signed to unsigned. +// Signed to unsigned: The upper boundary may be exceeded for a narrower Dst, +// and any negative value exceeds the lower boundary. template <typename Dst, typename Src> -struct RangeCheckImpl<Dst, Src, DST_UNSIGNED, SRC_SIGNED, OVERLAPS_RANGE> { - static RangeCheckResult Check(Src value) { - typedef std::numeric_limits<Dst> DstLimits; - typedef std::numeric_limits<Src> SrcLimits; - // Compare based on max_exponent, which we must compute for integrals. - static const size_t kDstMaxExponent = sizeof(Dst) * 8; - static const size_t kSrcMaxExponent = SrcLimits::is_iec559 ? - SrcLimits::max_exponent : - (sizeof(Src) * 8 - 1); - return (kDstMaxExponent >= kSrcMaxExponent) ? - BASE_NUMERIC_RANGE_CHECK_RESULT(true, value >= static_cast<Src>(0)) : - BASE_NUMERIC_RANGE_CHECK_RESULT( - value <= static_cast<Src>(DstLimits::max()), - value >= static_cast<Src>(0)); +struct DstRangeRelationToSrcRangeImpl<Dst, + Src, + INTEGER_REPRESENTATION_UNSIGNED, + INTEGER_REPRESENTATION_SIGNED, + NUMERIC_RANGE_NOT_CONTAINED> { + static RangeConstraint Check(Src value) { + return (MaxExponent<Dst>::value >= MaxExponent<Src>::value) + ? GetRangeConstraint(true, value >= static_cast<Src>(0)) + : GetRangeConstraint( + value <= static_cast<Src>(std::numeric_limits<Dst>::max()), + value >= static_cast<Src>(0)); } }; template <typename Dst, typename Src> -inline RangeCheckResult RangeCheck(Src value) { +inline RangeConstraint DstRangeRelationToSrcRange(Src value) { COMPILE_ASSERT(std::numeric_limits<Src>::is_specialized, argument_must_be_numeric); COMPILE_ASSERT(std::numeric_limits<Dst>::is_specialized, result_must_be_numeric); - return RangeCheckImpl<Dst, Src>::Check(value); + return DstRangeRelationToSrcRangeImpl<Dst, Src>::Check(value); } } // namespace internal diff --git a/base/numerics/safe_math.h b/base/numerics/safe_math.h new file mode 100644 index 0000000..51a534f --- /dev/null +++ b/base/numerics/safe_math.h @@ -0,0 +1,271 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_SAFE_MATH_H_ +#define BASE_SAFE_MATH_H_ + +#include "base/numerics/safe_math_impl.h" + +namespace base { + +namespace internal { + +// CheckedNumeric implements all the logic and operators for detecting integer +// boundary conditions such as overflow, underflow, and invalid conversions. +// The CheckedNumeric type implicitly converts from floating point and integer +// data types, and contains overloads for basic arithmetic operations (i.e.: +, +// -, *, /, %). +// +// The following methods convert from CheckedNumeric to standard numeric values: +// IsValid() - Returns true if the underlying numeric value is valid (i.e. has +// has not wrapped and is not the result of an invalid conversion). +// ValueOrDie() - Returns the underlying value. If the state is not valid this +// call will crash on a CHECK. +// ValueOrDefault() - Returns the current value, or the supplied default if the +// state is not valid. +// ValueFloating() - Returns the underlying floating point value (valid only +// only for floating point CheckedNumeric types). +// +// Bitwise operations are explicitly not supported, because correct +// handling of some cases (e.g. sign manipulation) is ambiguous. Comparison +// operations are explicitly not supported because they could result in a crash +// on a CHECK condition. You should use patterns like the following for these +// operations: +// Bitwise operation: +// CheckedNumeric<int> checked_int = untrusted_input_value; +// int x = checked_int.ValueOrDefault(0) | kFlagValues; +// Comparison: +// CheckedNumeric<size_t> checked_size; +// CheckedNumeric<int> checked_size = untrusted_input_value; +// checked_size = checked_size + HEADER LENGTH; +// if (checked_size.IsValid() && checked_size.ValueOrDie() < buffer_size) +// Do stuff... +template <typename T> +class CheckedNumeric { + public: + typedef T type; + + CheckedNumeric() {} + + // Copy constructor. + template <typename Src> + CheckedNumeric(const CheckedNumeric<Src>& rhs) + : state_(rhs.ValueUnsafe(), rhs.validity()) {} + + template <typename Src> + CheckedNumeric(Src value, RangeConstraint validity) + : state_(value, validity) {} + + // This is not an explicit constructor because we implicitly upgrade regular + // numerics to CheckedNumerics to make them easier to use. + template <typename Src> + CheckedNumeric(Src value) + : state_(value) { + COMPILE_ASSERT(std::numeric_limits<Src>::is_specialized, + argument_must_be_numeric); + } + + // IsValid() is the public API to test if a CheckedNumeric is currently valid. + bool IsValid() const { return validity() == RANGE_VALID; } + + // ValueOrDie() The primary accessor for the underlying value. If the current + // state is not valid it will CHECK and crash. + T ValueOrDie() const { + CHECK(IsValid()); + return state_.value(); + } + + // ValueOrDefault(T default_value) A convenience method that returns the + // current value if the state is valid, and the supplied default_value for + // any other state. + T ValueOrDefault(T default_value) const { + return IsValid() ? state_.value() : default_value; + } + + // ValueFloating() - Since floating point values include their validity state, + // we provide an easy method for extracting them directly, without a risk of + // crashing on a CHECK. + T ValueFloating() const { + COMPILE_ASSERT(std::numeric_limits<T>::is_iec559, argument_must_be_float); + return CheckedNumeric<T>::cast(*this).ValueUnsafe(); + } + + // validity() - DO NOT USE THIS IN EXTERNAL CODE - It is public right now for + // tests and to avoid a big matrix of friend operator overloads. But the + // values it returns are likely to change in the future. + // Returns: current validity state (i.e. valid, overflow, underflow, nan). + // TODO(jschuh): crbug.com/332611 Figure out and implement semantics for + // saturation/wrapping so we can expose this state consistently and implement + // saturated arithmetic. + RangeConstraint validity() const { return state_.validity(); } + + // ValueUnsafe() - DO NOT USE THIS IN EXTERNAL CODE - It is public right now + // for tests and to avoid a big matrix of friend operator overloads. But the + // values it returns are likely to change in the future. + // Returns: the raw numeric value, regardless of the current state. + // TODO(jschuh): crbug.com/332611 Figure out and implement semantics for + // saturation/wrapping so we can expose this state consistently and implement + // saturated arithmetic. + T ValueUnsafe() const { return state_.value(); } + + // Prototypes for the supported arithmetic operator overloads. + template <typename Src> CheckedNumeric& operator+=(Src rhs); + template <typename Src> CheckedNumeric& operator-=(Src rhs); + template <typename Src> CheckedNumeric& operator*=(Src rhs); + template <typename Src> CheckedNumeric& operator/=(Src rhs); + template <typename Src> CheckedNumeric& operator%=(Src rhs); + + CheckedNumeric operator-() const { + RangeConstraint validity; + T value = CheckedNeg(state_.value(), &validity); + // Negation is always valid for floating point. + if (std::numeric_limits<T>::is_iec559) + return CheckedNumeric<T>(value); + + validity = GetRangeConstraint(state_.validity() | validity); + return CheckedNumeric<T>(value, validity); + } + + CheckedNumeric Abs() const { + RangeConstraint validity; + T value = CheckedAbs(state_.value(), &validity); + // Absolute value is always valid for floating point. + if (std::numeric_limits<T>::is_iec559) + return CheckedNumeric<T>(value); + + validity = GetRangeConstraint(state_.validity() | validity); + return CheckedNumeric<T>(value, validity); + } + + CheckedNumeric& operator++() { + *this += 1; + return *this; + } + + CheckedNumeric operator++(int) { + CheckedNumeric value = *this; + *this += 1; + return value; + } + + CheckedNumeric& operator--() { + *this -= 1; + return *this; + } + + CheckedNumeric operator--(int) { + CheckedNumeric value = *this; + *this -= 1; + return value; + } + + // These static methods behave like a convenience cast operator targeting + // the desired CheckedNumeric type. As an optimization, a reference is + // returned when Src is the same type as T. + template <typename Src> + static CheckedNumeric<T> cast( + Src u, + typename enable_if<std::numeric_limits<Src>::is_specialized, int>::type = + 0) { + return u; + } + + template <typename Src> + static CheckedNumeric<T> cast( + const CheckedNumeric<Src>& u, + typename enable_if<!is_same<Src, T>::value, int>::type = 0) { + return u; + } + + static const CheckedNumeric<T>& cast(const CheckedNumeric<T>& u) { return u; } + + private: + CheckedNumericState<T> state_; +}; + +// This is the boilerplate for the standard arithmetic operator overloads. A +// macro isn't the prettiest solution, but it beats rewriting these five times. +// Some details worth noting are: +// * We apply the standard arithmetic promotions. +// * We skip range checks for floating points. +// * We skip range checks for destination integers with sufficient range. +// TODO(jschuh): extract these out into templates. +#define BASE_NUMERIC_ARITHMETIC_OPERATORS(NAME, OP, COMPOUND_OP) \ + /* Binary arithmetic operator for CheckedNumerics of the same type. */ \ + template <typename T> \ + CheckedNumeric<typename ArithmeticPromotion<T>::type> operator OP( \ + const CheckedNumeric<T>& lhs, const CheckedNumeric<T>& rhs) { \ + typedef typename ArithmeticPromotion<T>::type Promotion; \ + /* Floating point always takes the fast path */ \ + if (std::numeric_limits<T>::is_iec559) \ + return CheckedNumeric<T>(lhs.ValueUnsafe() OP rhs.ValueUnsafe()); \ + if (IsIntegerArithmeticSafe<Promotion, T, T>::value) \ + return CheckedNumeric<Promotion>( \ + lhs.ValueUnsafe() OP rhs.ValueUnsafe(), \ + GetRangeConstraint(rhs.validity() | lhs.validity())); \ + RangeConstraint validity = RANGE_VALID; \ + T result = Checked##NAME(static_cast<Promotion>(lhs.ValueUnsafe()), \ + static_cast<Promotion>(rhs.ValueUnsafe()), \ + &validity); \ + return CheckedNumeric<Promotion>( \ + result, \ + GetRangeConstraint(validity | lhs.validity() | rhs.validity())); \ + } \ + /* Assignment arithmetic operator implementation from CheckedNumeric. */ \ + template <typename T> \ + template <typename Src> \ + CheckedNumeric<T>& CheckedNumeric<T>::operator COMPOUND_OP(Src rhs) { \ + *this = CheckedNumeric<T>::cast(*this) OP CheckedNumeric<Src>::cast(rhs); \ + return *this; \ + } \ + /* Binary arithmetic operator for CheckedNumeric of different type. */ \ + template <typename T, typename Src> \ + CheckedNumeric<typename ArithmeticPromotion<T, Src>::type> operator OP( \ + const CheckedNumeric<Src>& lhs, const CheckedNumeric<T>& rhs) { \ + typedef typename ArithmeticPromotion<T, Src>::type Promotion; \ + if (IsIntegerArithmeticSafe<Promotion, T, Src>::value) \ + return CheckedNumeric<Promotion>( \ + lhs.ValueUnsafe() OP rhs.ValueUnsafe(), \ + GetRangeConstraint(rhs.validity() | lhs.validity())); \ + return CheckedNumeric<Promotion>::cast(lhs) \ + OP CheckedNumeric<Promotion>::cast(rhs); \ + } \ + /* Binary arithmetic operator for left CheckedNumeric and right numeric. */ \ + template <typename T, typename Src> \ + CheckedNumeric<typename ArithmeticPromotion<T, Src>::type> operator OP( \ + const CheckedNumeric<T>& lhs, Src rhs) { \ + typedef typename ArithmeticPromotion<T, Src>::type Promotion; \ + if (IsIntegerArithmeticSafe<Promotion, T, Src>::value) \ + return CheckedNumeric<Promotion>(lhs.ValueUnsafe() OP rhs, \ + lhs.validity()); \ + return CheckedNumeric<Promotion>::cast(lhs) \ + OP CheckedNumeric<Promotion>::cast(rhs); \ + } \ + /* Binary arithmetic operator for right numeric and left CheckedNumeric. */ \ + template <typename T, typename Src> \ + CheckedNumeric<typename ArithmeticPromotion<T, Src>::type> operator OP( \ + Src lhs, const CheckedNumeric<T>& rhs) { \ + typedef typename ArithmeticPromotion<T, Src>::type Promotion; \ + if (IsIntegerArithmeticSafe<Promotion, T, Src>::value) \ + return CheckedNumeric<Promotion>(lhs OP rhs.ValueUnsafe(), \ + rhs.validity()); \ + return CheckedNumeric<Promotion>::cast(lhs) \ + OP CheckedNumeric<Promotion>::cast(rhs); \ + } + +BASE_NUMERIC_ARITHMETIC_OPERATORS(Add, +, += ) +BASE_NUMERIC_ARITHMETIC_OPERATORS(Sub, -, -= ) +BASE_NUMERIC_ARITHMETIC_OPERATORS(Mul, *, *= ) +BASE_NUMERIC_ARITHMETIC_OPERATORS(Div, /, /= ) +BASE_NUMERIC_ARITHMETIC_OPERATORS(Mod, %, %= ) + +#undef BASE_NUMERIC_ARITHMETIC_OPERATORS + +} // namespace internal + +using internal::CheckedNumeric; + +} // namespace base + +#endif // BASE_SAFE_MATH_H_ diff --git a/base/numerics/safe_math_impl.h b/base/numerics/safe_math_impl.h new file mode 100644 index 0000000..6ea8ff4 --- /dev/null +++ b/base/numerics/safe_math_impl.h @@ -0,0 +1,502 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SAFE_MATH_IMPL_H_ +#define SAFE_MATH_IMPL_H_ + +#include <stdint.h> + +#include <cmath> +#include <cstdlib> +#include <limits> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/template_util.h" + +namespace base { +namespace internal { + +// Everything from here up to the floating point operations is portable C++, +// but it may not be fast. This code could be split based on +// platform/architecture and replaced with potentially faster implementations. + +// Integer promotion templates used by the portable checked integer arithmetic. +template <size_t Size, bool IsSigned> +struct IntegerForSizeAndSign; +template <> +struct IntegerForSizeAndSign<1, true> { + typedef int8_t type; +}; +template <> +struct IntegerForSizeAndSign<1, false> { + typedef uint8_t type; +}; +template <> +struct IntegerForSizeAndSign<2, true> { + typedef int16_t type; +}; +template <> +struct IntegerForSizeAndSign<2, false> { + typedef uint16_t type; +}; +template <> +struct IntegerForSizeAndSign<4, true> { + typedef int32_t type; +}; +template <> +struct IntegerForSizeAndSign<4, false> { + typedef uint32_t type; +}; +template <> +struct IntegerForSizeAndSign<8, true> { + typedef int64_t type; +}; +template <> +struct IntegerForSizeAndSign<8, false> { + typedef uint64_t type; +}; + +// WARNING: We have no IntegerForSizeAndSign<16, *>. If we ever add one to +// support 128-bit math, then the ArithmeticPromotion template below will need +// to be updated (or more likely replaced with a decltype expression). + +template <typename Integer> +struct UnsignedIntegerForSize { + typedef typename enable_if< + std::numeric_limits<Integer>::is_integer, + typename IntegerForSizeAndSign<sizeof(Integer), false>::type>::type type; +}; + +template <typename Integer> +struct SignedIntegerForSize { + typedef typename enable_if< + std::numeric_limits<Integer>::is_integer, + typename IntegerForSizeAndSign<sizeof(Integer), true>::type>::type type; +}; + +template <typename Integer> +struct TwiceWiderInteger { + typedef typename enable_if< + std::numeric_limits<Integer>::is_integer, + typename IntegerForSizeAndSign< + sizeof(Integer) * 2, + std::numeric_limits<Integer>::is_signed>::type>::type type; +}; + +template <typename Integer> +struct PositionOfSignBit { + static const typename enable_if<std::numeric_limits<Integer>::is_integer, + size_t>::type value = 8 * sizeof(Integer) - 1; +}; + +// Helper templates for integer manipulations. + +template <typename T> +bool HasSignBit(T x) { + // Cast to unsigned since right shift on signed is undefined. + return !!(static_cast<typename UnsignedIntegerForSize<T>::type>(x) >> + PositionOfSignBit<T>::value); +} + +// This wrapper undoes the standard integer promotions. +template <typename T> +T BinaryComplement(T x) { + return ~x; +} + +// Here are the actual portable checked integer math implementations. +// TODO(jschuh): Break this code out from the enable_if pattern and find a clean +// way to coalesce things into the CheckedNumericState specializations below. + +template <typename T> +typename enable_if<std::numeric_limits<T>::is_integer, T>::type +CheckedAdd(T x, T y, RangeConstraint* validity) { + // Since the value of x+y is undefined if we have a signed type, we compute + // it using the unsigned type of the same size. + typedef typename UnsignedIntegerForSize<T>::type UnsignedDst; + UnsignedDst ux = static_cast<UnsignedDst>(x); + UnsignedDst uy = static_cast<UnsignedDst>(y); + UnsignedDst uresult = ux + uy; + // Addition is valid if the sign of (x + y) is equal to either that of x or + // that of y. + if (std::numeric_limits<T>::is_signed) { + if (HasSignBit(BinaryComplement((uresult ^ ux) & (uresult ^ uy)))) + *validity = RANGE_VALID; + else // Direction of wrap is inverse of result sign. + *validity = HasSignBit(uresult) ? RANGE_OVERFLOW : RANGE_UNDERFLOW; + + } else { // Unsigned is either valid or overflow. + *validity = BinaryComplement(x) >= y ? RANGE_VALID : RANGE_OVERFLOW; + } + return static_cast<T>(uresult); +} + +template <typename T> +typename enable_if<std::numeric_limits<T>::is_integer, T>::type +CheckedSub(T x, T y, RangeConstraint* validity) { + // Since the value of x+y is undefined if we have a signed type, we compute + // it using the unsigned type of the same size. + typedef typename UnsignedIntegerForSize<T>::type UnsignedDst; + UnsignedDst ux = static_cast<UnsignedDst>(x); + UnsignedDst uy = static_cast<UnsignedDst>(y); + UnsignedDst uresult = ux - uy; + // Subtraction is valid if either x and y have same sign, or (x-y) and x have + // the same sign. + if (std::numeric_limits<T>::is_signed) { + if (HasSignBit(BinaryComplement((uresult ^ ux) & (ux ^ uy)))) + *validity = RANGE_VALID; + else // Direction of wrap is inverse of result sign. + *validity = HasSignBit(uresult) ? RANGE_OVERFLOW : RANGE_UNDERFLOW; + + } else { // Unsigned is either valid or underflow. + *validity = x >= y ? RANGE_VALID : RANGE_UNDERFLOW; + } + return static_cast<T>(uresult); +} + +// Integer multiplication is a bit complicated. In the fast case we just +// we just promote to a twice wider type, and range check the result. In the +// slow case we need to manually check that the result won't be truncated by +// checking with division against the appropriate bound. +template <typename T> +typename enable_if< + std::numeric_limits<T>::is_integer && sizeof(T) * 2 <= sizeof(uintmax_t), + T>::type +CheckedMul(T x, T y, RangeConstraint* validity) { + typedef typename TwiceWiderInteger<T>::type IntermediateType; + IntermediateType tmp = + static_cast<IntermediateType>(x) * static_cast<IntermediateType>(y); + *validity = DstRangeRelationToSrcRange<T>(tmp); + return static_cast<T>(tmp); +} + +template <typename T> +typename enable_if<std::numeric_limits<T>::is_integer&& std::numeric_limits< + T>::is_signed&&(sizeof(T) * 2 > sizeof(uintmax_t)), + T>::type +CheckedMul(T x, T y, RangeConstraint* validity) { + // if either side is zero then the result will be zero. + if (!(x || y)) { + return RANGE_VALID; + + } else if (x > 0) { + if (y > 0) + *validity = + x <= std::numeric_limits<T>::max() / y ? RANGE_VALID : RANGE_OVERFLOW; + else + *validity = y >= std::numeric_limits<T>::min() / x ? RANGE_VALID + : RANGE_UNDERFLOW; + + } else { + if (y > 0) + *validity = x >= std::numeric_limits<T>::min() / y ? RANGE_VALID + : RANGE_UNDERFLOW; + else + *validity = + y >= std::numeric_limits<T>::max() / x ? RANGE_VALID : RANGE_OVERFLOW; + } + + return x * y; +} + +template <typename T> +typename enable_if<std::numeric_limits<T>::is_integer && + !std::numeric_limits<T>::is_signed && + (sizeof(T) * 2 > sizeof(uintmax_t)), + T>::type +CheckedMul(T x, T y, RangeConstraint* validity) { + *validity = (y == 0 || x <= std::numeric_limits<T>::max() / y) + ? RANGE_VALID + : RANGE_OVERFLOW; + return x * y; +} + +// Division just requires a check for an invalid negation on signed min/-1. +template <typename T> +T CheckedDiv( + T x, + T y, + RangeConstraint* validity, + typename enable_if<std::numeric_limits<T>::is_integer, int>::type = 0) { + if (std::numeric_limits<T>::is_signed && x == std::numeric_limits<T>::min() && + y == static_cast<T>(-1)) { + *validity = RANGE_OVERFLOW; + return std::numeric_limits<T>::min(); + } + + *validity = RANGE_VALID; + return x / y; +} + +template <typename T> +typename enable_if< + std::numeric_limits<T>::is_integer&& std::numeric_limits<T>::is_signed, + T>::type +CheckedMod(T x, T y, RangeConstraint* validity) { + *validity = y > 0 ? RANGE_VALID : RANGE_INVALID; + return x % y; +} + +template <typename T> +typename enable_if< + std::numeric_limits<T>::is_integer && !std::numeric_limits<T>::is_signed, + T>::type +CheckedMod(T x, T y, RangeConstraint* validity) { + *validity = RANGE_VALID; + return x % y; +} + +template <typename T> +typename enable_if< + std::numeric_limits<T>::is_integer&& std::numeric_limits<T>::is_signed, + T>::type +CheckedNeg(T value, RangeConstraint* validity) { + *validity = + value != std::numeric_limits<T>::min() ? RANGE_VALID : RANGE_OVERFLOW; + // The negation of signed min is min, so catch that one. + return -value; +} + +template <typename T> +typename enable_if< + std::numeric_limits<T>::is_integer && !std::numeric_limits<T>::is_signed, + T>::type +CheckedNeg(T value, RangeConstraint* validity) { + // The only legal unsigned negation is zero. + *validity = value ? RANGE_UNDERFLOW : RANGE_VALID; + return static_cast<T>( + -static_cast<typename SignedIntegerForSize<T>::type>(value)); +} + +template <typename T> +typename enable_if< + std::numeric_limits<T>::is_integer&& std::numeric_limits<T>::is_signed, + T>::type +CheckedAbs(T value, RangeConstraint* validity) { + *validity = + value != std::numeric_limits<T>::min() ? RANGE_VALID : RANGE_OVERFLOW; + return std::abs(value); +} + +template <typename T> +typename enable_if< + std::numeric_limits<T>::is_integer && !std::numeric_limits<T>::is_signed, + T>::type +CheckedAbs(T value, RangeConstraint* validity) { + // Absolute value of a positive is just its identiy. + *validity = RANGE_VALID; + return value; +} + +// These are the floating point stubs that the compiler needs to see. Only the +// negation operation is ever called. +#define BASE_FLOAT_ARITHMETIC_STUBS(NAME) \ + template <typename T> \ + typename enable_if<std::numeric_limits<T>::is_iec559, T>::type \ + Checked##NAME(T, T, RangeConstraint*) { \ + NOTREACHED(); \ + return 0; \ + } + +BASE_FLOAT_ARITHMETIC_STUBS(Add) +BASE_FLOAT_ARITHMETIC_STUBS(Sub) +BASE_FLOAT_ARITHMETIC_STUBS(Mul) +BASE_FLOAT_ARITHMETIC_STUBS(Div) +BASE_FLOAT_ARITHMETIC_STUBS(Mod) + +#undef BASE_FLOAT_ARITHMETIC_STUBS + +template <typename T> +typename enable_if<std::numeric_limits<T>::is_iec559, T>::type CheckedNeg( + T value, + RangeConstraint*) { + return -value; +} + +template <typename T> +typename enable_if<std::numeric_limits<T>::is_iec559, T>::type CheckedAbs( + T value, + RangeConstraint*) { + return std::abs(value); +} + +// Floats carry around their validity state with them, but integers do not. So, +// we wrap the underlying value in a specialization in order to hide that detail +// and expose an interface via accessors. +enum NumericRepresentation { + NUMERIC_INTEGER, + NUMERIC_FLOATING, + NUMERIC_UNKNOWN +}; + +template <typename NumericType> +struct GetNumericRepresentation { + static const NumericRepresentation value = + std::numeric_limits<NumericType>::is_integer + ? NUMERIC_INTEGER + : (std::numeric_limits<NumericType>::is_iec559 ? NUMERIC_FLOATING + : NUMERIC_UNKNOWN); +}; + +template <typename T, NumericRepresentation type = + GetNumericRepresentation<T>::value> +class CheckedNumericState {}; + +// Integrals require quite a bit of additional housekeeping to manage state. +template <typename T> +class CheckedNumericState<T, NUMERIC_INTEGER> { + private: + T value_; + RangeConstraint validity_; + + public: + template <typename Src, NumericRepresentation type> + friend class CheckedNumericState; + + CheckedNumericState() : value_(0), validity_(RANGE_VALID) {} + + template <typename Src> + CheckedNumericState(Src value, RangeConstraint validity) + : value_(value), + validity_(GetRangeConstraint(validity | + DstRangeRelationToSrcRange<T>(value))) { + COMPILE_ASSERT(std::numeric_limits<Src>::is_specialized, + argument_must_be_numeric); + } + + // Copy constructor. + template <typename Src> + CheckedNumericState(const CheckedNumericState<Src>& rhs) + : value_(static_cast<T>(rhs.value())), + validity_(GetRangeConstraint( + rhs.validity() | DstRangeRelationToSrcRange<T>(rhs.value()))) {} + + template <typename Src> + explicit CheckedNumericState( + Src value, + typename enable_if<std::numeric_limits<Src>::is_specialized, int>::type = + 0) + : value_(static_cast<T>(value)), + validity_(DstRangeRelationToSrcRange<T>(value)) {} + + RangeConstraint validity() const { return validity_; } + T value() const { return value_; } +}; + +// Floating points maintain their own validity, but need translation wrappers. +template <typename T> +class CheckedNumericState<T, NUMERIC_FLOATING> { + private: + T value_; + + public: + template <typename Src, NumericRepresentation type> + friend class CheckedNumericState; + + CheckedNumericState() : value_(0.0) {} + + template <typename Src> + CheckedNumericState( + Src value, + RangeConstraint validity, + typename enable_if<std::numeric_limits<Src>::is_integer, int>::type = 0) { + switch (DstRangeRelationToSrcRange<T>(value)) { + case RANGE_VALID: + value_ = static_cast<T>(value); + break; + + case RANGE_UNDERFLOW: + value_ = -std::numeric_limits<T>::infinity(); + break; + + case RANGE_OVERFLOW: + value_ = std::numeric_limits<T>::infinity(); + break; + + case RANGE_INVALID: + value_ = std::numeric_limits<T>::quiet_NaN(); + break; + + default: + NOTREACHED(); + } + } + + template <typename Src> + explicit CheckedNumericState( + Src value, + typename enable_if<std::numeric_limits<Src>::is_specialized, int>::type = + 0) + : value_(static_cast<T>(value)) {} + + // Copy constructor. + template <typename Src> + CheckedNumericState(const CheckedNumericState<Src>& rhs) + : value_(static_cast<T>(rhs.value())) {} + + RangeConstraint validity() const { + return GetRangeConstraint(value_ <= std::numeric_limits<T>::max(), + value_ >= -std::numeric_limits<T>::max()); + } + T value() const { return value_; } +}; + +// For integers less than 128-bit and floats 32-bit or larger, we can distil +// C/C++ arithmetic promotions down to two simple rules: +// 1. The type with the larger maximum exponent always takes precedence. +// 2. The resulting type must be promoted to at least an int. +// The following template specializations implement that promotion logic. +enum ArithmeticPromotionCategory { + LEFT_PROMOTION, + RIGHT_PROMOTION, + DEFAULT_PROMOTION +}; + +template <typename Lhs, + typename Rhs = Lhs, + ArithmeticPromotionCategory Promotion = + (MaxExponent<Lhs>::value > MaxExponent<Rhs>::value) + ? (MaxExponent<Lhs>::value > MaxExponent<int>::value + ? LEFT_PROMOTION + : DEFAULT_PROMOTION) + : (MaxExponent<Rhs>::value > MaxExponent<int>::value + ? RIGHT_PROMOTION + : DEFAULT_PROMOTION) > +struct ArithmeticPromotion; + +template <typename Lhs, typename Rhs> +struct ArithmeticPromotion<Lhs, Rhs, LEFT_PROMOTION> { + typedef Lhs type; +}; + +template <typename Lhs, typename Rhs> +struct ArithmeticPromotion<Lhs, Rhs, RIGHT_PROMOTION> { + typedef Rhs type; +}; + +template <typename Lhs, typename Rhs> +struct ArithmeticPromotion<Lhs, Rhs, DEFAULT_PROMOTION> { + typedef int type; +}; + +// We can statically check if operations on the provided types can wrap, so we +// can skip the checked operations if they're not needed. So, for an integer we +// care if the destination type preserves the sign and is twice the width of +// the source. +template <typename T, typename Lhs, typename Rhs> +struct IsIntegerArithmeticSafe { + static const bool value = !std::numeric_limits<T>::is_iec559 && + StaticDstRangeRelationToSrcRange<T, Lhs>::value == + NUMERIC_RANGE_CONTAINED && + sizeof(T) >= (2 * sizeof(Lhs)) && + StaticDstRangeRelationToSrcRange<T, Rhs>::value != + NUMERIC_RANGE_CONTAINED && + sizeof(T) >= (2 * sizeof(Rhs)); +}; + +} // namespace internal +} // namespace base + +#endif // SAFE_MATH_IMPL_H_ diff --git a/base/numerics/safe_numerics_unittest.cc b/base/numerics/safe_numerics_unittest.cc index 39eee0a..09ad130 100644 --- a/base/numerics/safe_numerics_unittest.cc +++ b/base/numerics/safe_numerics_unittest.cc @@ -2,17 +2,279 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "base/numerics/safe_conversions.h" - +#if defined(COMPILER_MSVC) && defined(ARCH_CPU_32_BITS) +#include <mmintrin.h> +#endif #include <stdint.h> #include <limits> #include "base/compiler_specific.h" +#include "base/numerics/safe_conversions.h" +#include "base/numerics/safe_math.h" +#include "base/template_util.h" #include "testing/gtest/include/gtest/gtest.h" -namespace base { -namespace internal { +using std::numeric_limits; +using base::CheckedNumeric; +using base::checked_cast; +using base::saturated_cast; +using base::internal::MaxExponent; +using base::internal::RANGE_VALID; +using base::internal::RANGE_INVALID; +using base::internal::RANGE_OVERFLOW; +using base::internal::RANGE_UNDERFLOW; +using base::enable_if; + +// MSVS 2013 ia32 may not reset the FPU between calculations, and the test +// framework masks the exceptions. So we just force a manual reset after NaN. +inline void ResetFloatingPointUnit() { +#if defined(COMPILER_MSVC) && defined(ARCH_CPU_32_BITS) + _mm_empty(); +#endif +} + +// Helper macros to wrap displaying the conversion types and line numbers. +#define TEST_EXPECTED_VALIDITY(expected, actual) \ + EXPECT_EQ(expected, CheckedNumeric<Dst>(actual).validity()) \ + << "Result test: Value " << +(actual).ValueUnsafe() << " as " << dst \ + << " on line " << line; + +#define TEST_EXPECTED_VALUE(expected, actual) \ + EXPECT_EQ(static_cast<Dst>(expected), \ + CheckedNumeric<Dst>(actual).ValueUnsafe()) \ + << "Result test: Value " << +((actual).ValueUnsafe()) << " as " << dst \ + << " on line " << line; + +// Signed integer arithmetic. +template <typename Dst> +static void TestSpecializedArithmetic( + const char* dst, + int line, + typename enable_if< + numeric_limits<Dst>::is_integer&& numeric_limits<Dst>::is_signed, + int>::type = 0) { + typedef numeric_limits<Dst> DstLimits; + TEST_EXPECTED_VALIDITY(RANGE_OVERFLOW, + -CheckedNumeric<Dst>(DstLimits::min())); + TEST_EXPECTED_VALIDITY(RANGE_OVERFLOW, + CheckedNumeric<Dst>(DstLimits::min()).Abs()); + TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(-1).Abs()); + + TEST_EXPECTED_VALIDITY(RANGE_VALID, + CheckedNumeric<Dst>(DstLimits::max()) + -1); + TEST_EXPECTED_VALIDITY(RANGE_UNDERFLOW, + CheckedNumeric<Dst>(DstLimits::min()) + -1); + TEST_EXPECTED_VALIDITY( + RANGE_UNDERFLOW, + CheckedNumeric<Dst>(-DstLimits::max()) + -DstLimits::max()); + + TEST_EXPECTED_VALIDITY(RANGE_UNDERFLOW, + CheckedNumeric<Dst>(DstLimits::min()) - 1); + TEST_EXPECTED_VALIDITY(RANGE_VALID, + CheckedNumeric<Dst>(DstLimits::min()) - -1); + TEST_EXPECTED_VALIDITY( + RANGE_OVERFLOW, + CheckedNumeric<Dst>(DstLimits::max()) - -DstLimits::max()); + TEST_EXPECTED_VALIDITY( + RANGE_UNDERFLOW, + CheckedNumeric<Dst>(-DstLimits::max()) - DstLimits::max()); + + TEST_EXPECTED_VALIDITY(RANGE_UNDERFLOW, + CheckedNumeric<Dst>(DstLimits::min()) * 2); + + TEST_EXPECTED_VALIDITY(RANGE_OVERFLOW, + CheckedNumeric<Dst>(DstLimits::min()) / -1); + TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(-1) / 2); + + // Modulus is legal only for integers. + TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>() % 1); + TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) % 1); + TEST_EXPECTED_VALUE(-1, CheckedNumeric<Dst>(-1) % 2); + TEST_EXPECTED_VALIDITY(RANGE_INVALID, CheckedNumeric<Dst>(-1) % -2); + TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(DstLimits::min()) % 2); + TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(DstLimits::max()) % 2); + // Test all the different modulus combinations. + TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) % CheckedNumeric<Dst>(1)); + TEST_EXPECTED_VALUE(0, 1 % CheckedNumeric<Dst>(1)); + TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) % 1); + CheckedNumeric<Dst> checked_dst = 1; + TEST_EXPECTED_VALUE(0, checked_dst %= 1); +} + +// Unsigned integer arithmetic. +template <typename Dst> +static void TestSpecializedArithmetic( + const char* dst, + int line, + typename enable_if< + numeric_limits<Dst>::is_integer && !numeric_limits<Dst>::is_signed, + int>::type = 0) { + typedef numeric_limits<Dst> DstLimits; + TEST_EXPECTED_VALIDITY(RANGE_VALID, -CheckedNumeric<Dst>(DstLimits::min())); + TEST_EXPECTED_VALIDITY(RANGE_VALID, + CheckedNumeric<Dst>(DstLimits::min()).Abs()); + TEST_EXPECTED_VALIDITY(RANGE_UNDERFLOW, + CheckedNumeric<Dst>(DstLimits::min()) + -1); + TEST_EXPECTED_VALIDITY(RANGE_UNDERFLOW, + CheckedNumeric<Dst>(DstLimits::min()) - 1); + TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(DstLimits::min()) * 2); + TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) / 2); + + // Modulus is legal only for integers. + TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>() % 1); + TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) % 1); + TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) % 2); + TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(DstLimits::min()) % 2); + TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(DstLimits::max()) % 2); + // Test all the different modulus combinations. + TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) % CheckedNumeric<Dst>(1)); + TEST_EXPECTED_VALUE(0, 1 % CheckedNumeric<Dst>(1)); + TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) % 1); + CheckedNumeric<Dst> checked_dst = 1; + TEST_EXPECTED_VALUE(0, checked_dst %= 1); +} + +// Floating point arithmetic. +template <typename Dst> +void TestSpecializedArithmetic( + const char* dst, + int line, + typename enable_if<numeric_limits<Dst>::is_iec559, int>::type = 0) { + typedef numeric_limits<Dst> DstLimits; + TEST_EXPECTED_VALIDITY(RANGE_VALID, -CheckedNumeric<Dst>(DstLimits::min())); + + TEST_EXPECTED_VALIDITY(RANGE_VALID, + CheckedNumeric<Dst>(DstLimits::min()).Abs()); + TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(-1).Abs()); + + TEST_EXPECTED_VALIDITY(RANGE_VALID, + CheckedNumeric<Dst>(DstLimits::min()) + -1); + TEST_EXPECTED_VALIDITY(RANGE_VALID, + CheckedNumeric<Dst>(DstLimits::max()) + 1); + TEST_EXPECTED_VALIDITY( + RANGE_UNDERFLOW, + CheckedNumeric<Dst>(-DstLimits::max()) + -DstLimits::max()); + + TEST_EXPECTED_VALIDITY( + RANGE_OVERFLOW, + CheckedNumeric<Dst>(DstLimits::max()) - -DstLimits::max()); + TEST_EXPECTED_VALIDITY( + RANGE_UNDERFLOW, + CheckedNumeric<Dst>(-DstLimits::max()) - DstLimits::max()); + + TEST_EXPECTED_VALIDITY(RANGE_VALID, + CheckedNumeric<Dst>(DstLimits::min()) * 2); + + TEST_EXPECTED_VALUE(-0.5, CheckedNumeric<Dst>(-1.0) / 2); + EXPECT_EQ(static_cast<Dst>(1.0), CheckedNumeric<Dst>(1.0).ValueFloating()); +} + +// Generic arithmetic tests. +template <typename Dst> +static void TestArithmetic(const char* dst, int line) { + typedef numeric_limits<Dst> DstLimits; + + EXPECT_EQ(true, CheckedNumeric<Dst>().IsValid()); + EXPECT_EQ(false, + CheckedNumeric<Dst>(CheckedNumeric<Dst>(DstLimits::max()) * + DstLimits::max()).IsValid()); + EXPECT_EQ(static_cast<Dst>(0), CheckedNumeric<Dst>().ValueOrDie()); + EXPECT_EQ(static_cast<Dst>(0), CheckedNumeric<Dst>().ValueOrDefault(1)); + EXPECT_EQ(static_cast<Dst>(1), + CheckedNumeric<Dst>(CheckedNumeric<Dst>(DstLimits::max()) * + DstLimits::max()).ValueOrDefault(1)); + + // Test the operator combinations. + TEST_EXPECTED_VALUE(2, CheckedNumeric<Dst>(1) + CheckedNumeric<Dst>(1)); + TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) - CheckedNumeric<Dst>(1)); + TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) * CheckedNumeric<Dst>(1)); + TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) / CheckedNumeric<Dst>(1)); + TEST_EXPECTED_VALUE(2, 1 + CheckedNumeric<Dst>(1)); + TEST_EXPECTED_VALUE(0, 1 - CheckedNumeric<Dst>(1)); + TEST_EXPECTED_VALUE(1, 1 * CheckedNumeric<Dst>(1)); + TEST_EXPECTED_VALUE(1, 1 / CheckedNumeric<Dst>(1)); + TEST_EXPECTED_VALUE(2, CheckedNumeric<Dst>(1) + 1); + TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) - 1); + TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) * 1); + TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) / 1); + CheckedNumeric<Dst> checked_dst = 1; + TEST_EXPECTED_VALUE(2, checked_dst += 1); + checked_dst = 1; + TEST_EXPECTED_VALUE(0, checked_dst -= 1); + checked_dst = 1; + TEST_EXPECTED_VALUE(1, checked_dst *= 1); + checked_dst = 1; + TEST_EXPECTED_VALUE(1, checked_dst /= 1); + + // Generic negation. + TEST_EXPECTED_VALUE(0, -CheckedNumeric<Dst>()); + TEST_EXPECTED_VALUE(-1, -CheckedNumeric<Dst>(1)); + TEST_EXPECTED_VALUE(1, -CheckedNumeric<Dst>(-1)); + TEST_EXPECTED_VALUE(static_cast<Dst>(DstLimits::max() * -1), + -CheckedNumeric<Dst>(DstLimits::max())); + + // Generic absolute value. + TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>().Abs()); + TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1).Abs()); + TEST_EXPECTED_VALUE(DstLimits::max(), + CheckedNumeric<Dst>(DstLimits::max()).Abs()); + + // Generic addition. + TEST_EXPECTED_VALUE(1, (CheckedNumeric<Dst>() + 1)); + TEST_EXPECTED_VALUE(2, (CheckedNumeric<Dst>(1) + 1)); + TEST_EXPECTED_VALUE(0, (CheckedNumeric<Dst>(-1) + 1)); + TEST_EXPECTED_VALIDITY(RANGE_VALID, + CheckedNumeric<Dst>(DstLimits::min()) + 1); + TEST_EXPECTED_VALIDITY( + RANGE_OVERFLOW, CheckedNumeric<Dst>(DstLimits::max()) + DstLimits::max()); + + // Generic subtraction. + TEST_EXPECTED_VALUE(-1, (CheckedNumeric<Dst>() - 1)); + TEST_EXPECTED_VALUE(0, (CheckedNumeric<Dst>(1) - 1)); + TEST_EXPECTED_VALUE(-2, (CheckedNumeric<Dst>(-1) - 1)); + TEST_EXPECTED_VALIDITY(RANGE_VALID, + CheckedNumeric<Dst>(DstLimits::max()) - 1); + + // Generic multiplication. + TEST_EXPECTED_VALUE(0, (CheckedNumeric<Dst>() * 1)); + TEST_EXPECTED_VALUE(1, (CheckedNumeric<Dst>(1) * 1)); + TEST_EXPECTED_VALUE(-2, (CheckedNumeric<Dst>(-1) * 2)); + TEST_EXPECTED_VALIDITY( + RANGE_OVERFLOW, CheckedNumeric<Dst>(DstLimits::max()) * DstLimits::max()); + + // Generic division. + TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>() / 1); + TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) / 1); + TEST_EXPECTED_VALUE(DstLimits::min() / 2, + CheckedNumeric<Dst>(DstLimits::min()) / 2); + TEST_EXPECTED_VALUE(DstLimits::max() / 2, + CheckedNumeric<Dst>(DstLimits::max()) / 2); + + TestSpecializedArithmetic<Dst>(dst, line); +} + +// Helper macro to wrap displaying the conversion types and line numbers. +#define TEST_ARITHMETIC(Dst) TestArithmetic<Dst>(#Dst, __LINE__) + +TEST(SafeNumerics, SignedIntegerMath) { + TEST_ARITHMETIC(int8_t); + TEST_ARITHMETIC(int); + TEST_ARITHMETIC(intptr_t); + TEST_ARITHMETIC(intmax_t); +} + +TEST(SafeNumerics, UnsignedIntegerMath) { + TEST_ARITHMETIC(uint8_t); + TEST_ARITHMETIC(unsigned int); + TEST_ARITHMETIC(uintptr_t); + TEST_ARITHMETIC(uintmax_t); +} + +TEST(SafeNumerics, FloatingPointMath) { + TEST_ARITHMETIC(float); + TEST_ARITHMETIC(double); +} // Enumerates the five different conversions types we need to test. enum NumericConversionType { @@ -27,17 +289,17 @@ enum NumericConversionType { template <typename Dst, typename Src, NumericConversionType conversion> struct TestNumericConversion {}; -// EXPECT_EQ wrapper providing specific detail on test failures. -#define TEST_EXPECTED_RANGE(expected, actual) \ - EXPECT_EQ(expected, RangeCheck<Dst>(actual)) << \ - "Conversion test: " << src << " value " << actual << \ - " to " << dst << " on line " << line; +// EXPECT_EQ wrappers providing specific detail on test failures. +#define TEST_EXPECTED_RANGE(expected, actual) \ + EXPECT_EQ(expected, base::internal::DstRangeRelationToSrcRange<Dst>(actual)) \ + << "Conversion test: " << src << " value " << actual << " to " << dst \ + << " on line " << line; template <typename Dst, typename Src> struct TestNumericConversion<Dst, Src, SIGN_PRESERVING_VALUE_PRESERVING> { static void Test(const char *dst, const char *src, int line) { - typedef std::numeric_limits<Src> SrcLimits; - typedef std::numeric_limits<Dst> DstLimits; + typedef numeric_limits<Src> SrcLimits; + typedef numeric_limits<Dst> DstLimits; // Integral to floating. COMPILE_ASSERT((DstLimits::is_iec559 && SrcLimits::is_integer) || // Not floating to integral and... @@ -49,16 +311,33 @@ struct TestNumericConversion<Dst, Src, SIGN_PRESERVING_VALUE_PRESERVING> { (DstLimits::is_signed && sizeof(Dst) > sizeof(Src)))), comparison_must_be_sign_preserving_and_value_preserving); - TEST_EXPECTED_RANGE(TYPE_VALID, SrcLimits::max()); - TEST_EXPECTED_RANGE(TYPE_VALID, static_cast<Src>(1)); + const CheckedNumeric<Dst> checked_dst = SrcLimits::max(); + ; + TEST_EXPECTED_VALIDITY(RANGE_VALID, checked_dst); + if (MaxExponent<Dst>::value > MaxExponent<Src>::value) { + if (MaxExponent<Dst>::value >= MaxExponent<Src>::value * 2 - 1) { + // At least twice larger type. + TEST_EXPECTED_VALIDITY(RANGE_VALID, SrcLimits::max() * checked_dst); + + } else { // Larger, but not at least twice as large. + TEST_EXPECTED_VALIDITY(RANGE_OVERFLOW, SrcLimits::max() * checked_dst); + TEST_EXPECTED_VALIDITY(RANGE_VALID, checked_dst + 1); + } + } else { // Same width type. + TEST_EXPECTED_VALIDITY(RANGE_OVERFLOW, checked_dst + 1); + } + + TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::max()); + TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(1)); if (SrcLimits::is_iec559) { - TEST_EXPECTED_RANGE(TYPE_VALID, SrcLimits::max() * static_cast<Src>(-1)); - TEST_EXPECTED_RANGE(TYPE_OVERFLOW, SrcLimits::infinity()); - TEST_EXPECTED_RANGE(TYPE_UNDERFLOW, SrcLimits::infinity() * -1); - TEST_EXPECTED_RANGE(TYPE_INVALID, SrcLimits::quiet_NaN()); - } else if (std::numeric_limits<Src>::is_signed) { - TEST_EXPECTED_RANGE(TYPE_VALID, static_cast<Src>(-1)); - TEST_EXPECTED_RANGE(TYPE_VALID, SrcLimits::min()); + TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::max() * static_cast<Src>(-1)); + TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::infinity()); + TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::infinity() * -1); + TEST_EXPECTED_RANGE(RANGE_INVALID, SrcLimits::quiet_NaN()); + ResetFloatingPointUnit(); + } else if (numeric_limits<Src>::is_signed) { + TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(-1)); + TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::min()); } } }; @@ -66,27 +345,35 @@ struct TestNumericConversion<Dst, Src, SIGN_PRESERVING_VALUE_PRESERVING> { template <typename Dst, typename Src> struct TestNumericConversion<Dst, Src, SIGN_PRESERVING_NARROW> { static void Test(const char *dst, const char *src, int line) { - typedef std::numeric_limits<Src> SrcLimits; - typedef std::numeric_limits<Dst> DstLimits; + typedef numeric_limits<Src> SrcLimits; + typedef numeric_limits<Dst> DstLimits; COMPILE_ASSERT(SrcLimits::is_signed == DstLimits::is_signed, destination_and_source_sign_must_be_the_same); COMPILE_ASSERT(sizeof(Dst) < sizeof(Src) || (DstLimits::is_integer && SrcLimits::is_iec559), destination_must_be_narrower_than_source); - TEST_EXPECTED_RANGE(TYPE_OVERFLOW, SrcLimits::max()); - TEST_EXPECTED_RANGE(TYPE_VALID, static_cast<Src>(1)); + const CheckedNumeric<Dst> checked_dst; + TEST_EXPECTED_VALIDITY(RANGE_OVERFLOW, checked_dst + SrcLimits::max()); + TEST_EXPECTED_VALUE(1, checked_dst + static_cast<Src>(1)); + TEST_EXPECTED_VALIDITY(RANGE_UNDERFLOW, checked_dst - SrcLimits::max()); + + TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::max()); + TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(1)); if (SrcLimits::is_iec559) { - TEST_EXPECTED_RANGE(TYPE_UNDERFLOW, SrcLimits::max() * -1); - TEST_EXPECTED_RANGE(TYPE_VALID, static_cast<Src>(-1)); - TEST_EXPECTED_RANGE(TYPE_OVERFLOW, SrcLimits::infinity()); - TEST_EXPECTED_RANGE(TYPE_UNDERFLOW, SrcLimits::infinity() * -1); - TEST_EXPECTED_RANGE(TYPE_INVALID, SrcLimits::quiet_NaN()); + TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::max() * -1); + TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(-1)); + TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::infinity()); + TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::infinity() * -1); + TEST_EXPECTED_RANGE(RANGE_INVALID, SrcLimits::quiet_NaN()); + ResetFloatingPointUnit(); } else if (SrcLimits::is_signed) { - TEST_EXPECTED_RANGE(TYPE_UNDERFLOW, SrcLimits::min()); - TEST_EXPECTED_RANGE(TYPE_VALID, static_cast<Src>(-1)); + TEST_EXPECTED_VALUE(-1, checked_dst - static_cast<Src>(1)); + TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::min()); + TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(-1)); } else { - TEST_EXPECTED_RANGE(TYPE_VALID, SrcLimits::min()); + TEST_EXPECTED_VALIDITY(RANGE_INVALID, checked_dst - static_cast<Src>(1)); + TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::min()); } } }; @@ -94,41 +381,53 @@ struct TestNumericConversion<Dst, Src, SIGN_PRESERVING_NARROW> { template <typename Dst, typename Src> struct TestNumericConversion<Dst, Src, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL> { static void Test(const char *dst, const char *src, int line) { - typedef std::numeric_limits<Src> SrcLimits; - typedef std::numeric_limits<Dst> DstLimits; + typedef numeric_limits<Src> SrcLimits; + typedef numeric_limits<Dst> DstLimits; COMPILE_ASSERT(sizeof(Dst) >= sizeof(Src), destination_must_be_equal_or_wider_than_source); COMPILE_ASSERT(SrcLimits::is_signed, source_must_be_signed); COMPILE_ASSERT(!DstLimits::is_signed, destination_must_be_unsigned); - TEST_EXPECTED_RANGE(TYPE_UNDERFLOW, SrcLimits::min()); - TEST_EXPECTED_RANGE(TYPE_VALID, SrcLimits::max()); - TEST_EXPECTED_RANGE(TYPE_VALID, static_cast<Src>(1)); - TEST_EXPECTED_RANGE(TYPE_UNDERFLOW, static_cast<Src>(-1)); + const CheckedNumeric<Dst> checked_dst; + TEST_EXPECTED_VALUE(SrcLimits::max(), checked_dst + SrcLimits::max()); + TEST_EXPECTED_VALIDITY(RANGE_UNDERFLOW, checked_dst + static_cast<Src>(-1)); + TEST_EXPECTED_VALIDITY(RANGE_UNDERFLOW, checked_dst + -SrcLimits::max()); + + TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::min()); + TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::max()); + TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(1)); + TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, static_cast<Src>(-1)); } }; template <typename Dst, typename Src> struct TestNumericConversion<Dst, Src, SIGN_TO_UNSIGN_NARROW> { static void Test(const char *dst, const char *src, int line) { - typedef std::numeric_limits<Src> SrcLimits; - typedef std::numeric_limits<Dst> DstLimits; + typedef numeric_limits<Src> SrcLimits; + typedef numeric_limits<Dst> DstLimits; COMPILE_ASSERT((DstLimits::is_integer && SrcLimits::is_iec559) || (sizeof(Dst) < sizeof(Src)), destination_must_be_narrower_than_source); COMPILE_ASSERT(SrcLimits::is_signed, source_must_be_signed); COMPILE_ASSERT(!DstLimits::is_signed, destination_must_be_unsigned); - TEST_EXPECTED_RANGE(TYPE_OVERFLOW, SrcLimits::max()); - TEST_EXPECTED_RANGE(TYPE_VALID, static_cast<Src>(1)); - TEST_EXPECTED_RANGE(TYPE_UNDERFLOW, static_cast<Src>(-1)); + const CheckedNumeric<Dst> checked_dst; + TEST_EXPECTED_VALUE(1, checked_dst + static_cast<Src>(1)); + TEST_EXPECTED_VALIDITY(RANGE_OVERFLOW, checked_dst + SrcLimits::max()); + TEST_EXPECTED_VALIDITY(RANGE_UNDERFLOW, checked_dst + static_cast<Src>(-1)); + TEST_EXPECTED_VALIDITY(RANGE_UNDERFLOW, checked_dst + -SrcLimits::max()); + + TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::max()); + TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(1)); + TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, static_cast<Src>(-1)); if (SrcLimits::is_iec559) { - TEST_EXPECTED_RANGE(TYPE_UNDERFLOW, SrcLimits::max() * -1); - TEST_EXPECTED_RANGE(TYPE_OVERFLOW, SrcLimits::infinity()); - TEST_EXPECTED_RANGE(TYPE_UNDERFLOW, SrcLimits::infinity() * -1); - TEST_EXPECTED_RANGE(TYPE_INVALID, SrcLimits::quiet_NaN()); + TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::max() * -1); + TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::infinity()); + TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::infinity() * -1); + TEST_EXPECTED_RANGE(RANGE_INVALID, SrcLimits::quiet_NaN()); + ResetFloatingPointUnit(); } else { - TEST_EXPECTED_RANGE(TYPE_UNDERFLOW, SrcLimits::min()); + TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::min()); } } }; @@ -136,16 +435,21 @@ struct TestNumericConversion<Dst, Src, SIGN_TO_UNSIGN_NARROW> { template <typename Dst, typename Src> struct TestNumericConversion<Dst, Src, UNSIGN_TO_SIGN_NARROW_OR_EQUAL> { static void Test(const char *dst, const char *src, int line) { - typedef std::numeric_limits<Src> SrcLimits; - typedef std::numeric_limits<Dst> DstLimits; + typedef numeric_limits<Src> SrcLimits; + typedef numeric_limits<Dst> DstLimits; COMPILE_ASSERT(sizeof(Dst) <= sizeof(Src), destination_must_be_narrower_or_equal_to_source); COMPILE_ASSERT(!SrcLimits::is_signed, source_must_be_unsigned); COMPILE_ASSERT(DstLimits::is_signed, destination_must_be_signed); - TEST_EXPECTED_RANGE(TYPE_VALID, SrcLimits::min()); - TEST_EXPECTED_RANGE(TYPE_OVERFLOW, SrcLimits::max()); - TEST_EXPECTED_RANGE(TYPE_VALID, static_cast<Src>(1)); + const CheckedNumeric<Dst> checked_dst; + TEST_EXPECTED_VALUE(1, checked_dst + static_cast<Src>(1)); + TEST_EXPECTED_VALIDITY(RANGE_OVERFLOW, checked_dst + SrcLimits::max()); + TEST_EXPECTED_VALUE(SrcLimits::min(), checked_dst + SrcLimits::min()); + + TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::min()); + TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::max()); + TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(1)); } }; @@ -153,7 +457,7 @@ struct TestNumericConversion<Dst, Src, UNSIGN_TO_SIGN_NARROW_OR_EQUAL> { #define TEST_NUMERIC_CONVERSION(d, s, t) \ TestNumericConversion<d, s, t>::Test(#d, #s, __LINE__) -TEST(SafeNumerics, IntMinConversions) { +TEST(SafeNumerics, IntMinOperations) { TEST_NUMERIC_CONVERSION(int8_t, int8_t, SIGN_PRESERVING_VALUE_PRESERVING); TEST_NUMERIC_CONVERSION(uint8_t, uint8_t, SIGN_PRESERVING_VALUE_PRESERVING); @@ -171,7 +475,7 @@ TEST(SafeNumerics, IntMinConversions) { TEST_NUMERIC_CONVERSION(int8_t, uintmax_t, UNSIGN_TO_SIGN_NARROW_OR_EQUAL); } -TEST(SafeNumerics, IntConversions) { +TEST(SafeNumerics, IntOperations) { TEST_NUMERIC_CONVERSION(int, int, SIGN_PRESERVING_VALUE_PRESERVING); TEST_NUMERIC_CONVERSION(unsigned int, unsigned int, SIGN_PRESERVING_VALUE_PRESERVING); @@ -196,7 +500,7 @@ TEST(SafeNumerics, IntConversions) { TEST_NUMERIC_CONVERSION(int, uintmax_t, UNSIGN_TO_SIGN_NARROW_OR_EQUAL); } -TEST(SafeNumerics, IntMaxConversions) { +TEST(SafeNumerics, IntMaxOperations) { TEST_NUMERIC_CONVERSION(intmax_t, intmax_t, SIGN_PRESERVING_VALUE_PRESERVING); TEST_NUMERIC_CONVERSION(uintmax_t, uintmax_t, SIGN_PRESERVING_VALUE_PRESERVING); @@ -219,7 +523,7 @@ TEST(SafeNumerics, IntMaxConversions) { TEST_NUMERIC_CONVERSION(intmax_t, uintmax_t, UNSIGN_TO_SIGN_NARROW_OR_EQUAL); } -TEST(SafeNumerics, FloatConversions) { +TEST(SafeNumerics, FloatOperations) { TEST_NUMERIC_CONVERSION(float, intmax_t, SIGN_PRESERVING_VALUE_PRESERVING); TEST_NUMERIC_CONVERSION(float, uintmax_t, SIGN_PRESERVING_VALUE_PRESERVING); @@ -230,7 +534,7 @@ TEST(SafeNumerics, FloatConversions) { TEST_NUMERIC_CONVERSION(float, double, SIGN_PRESERVING_NARROW); } -TEST(SafeNumerics, DoubleConversions) { +TEST(SafeNumerics, DoubleOperations) { TEST_NUMERIC_CONVERSION(double, intmax_t, SIGN_PRESERVING_VALUE_PRESERVING); TEST_NUMERIC_CONVERSION(double, uintmax_t, SIGN_PRESERVING_VALUE_PRESERVING); @@ -239,7 +543,7 @@ TEST(SafeNumerics, DoubleConversions) { SIGN_PRESERVING_VALUE_PRESERVING); } -TEST(SafeNumerics, SizeTConversions) { +TEST(SafeNumerics, SizeTOperations) { TEST_NUMERIC_CONVERSION(size_t, int, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL); TEST_NUMERIC_CONVERSION(int, size_t, UNSIGN_TO_SIGN_NARROW_OR_EQUAL); } @@ -254,11 +558,11 @@ TEST(SafeNumerics, CastTests) { int small_positive = 1; int small_negative = -1; double double_small = 1.0; - double double_large = std::numeric_limits<double>::max(); - double double_infinity = std::numeric_limits<float>::infinity(); + double double_large = numeric_limits<double>::max(); + double double_infinity = numeric_limits<float>::infinity(); // Just test that the cast compiles, since the other tests cover logic. - EXPECT_EQ(0, base::checked_cast<int>(static_cast<size_t>(0))); + EXPECT_EQ(0, checked_cast<int>(static_cast<size_t>(0))); // Test various saturation corner cases. EXPECT_EQ(saturated_cast<int>(small_negative), @@ -269,12 +573,8 @@ TEST(SafeNumerics, CastTests) { static_cast<unsigned>(0)); EXPECT_EQ(saturated_cast<int>(double_small), static_cast<int>(double_small)); - EXPECT_EQ(saturated_cast<int>(double_large), - std::numeric_limits<int>::max()); + EXPECT_EQ(saturated_cast<int>(double_large), numeric_limits<int>::max()); EXPECT_EQ(saturated_cast<float>(double_large), double_infinity); EXPECT_EQ(saturated_cast<float>(-double_large), -double_infinity); } -} // namespace internal -} // namespace base - |