/* -*- 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/. */ #include "mozilla/CheckedInt.h" #include #include #include using namespace mozilla; int gIntegerTypesTested = 0; int gTestsPassed = 0; int gTestsFailed = 0; void verifyImplFunction(bool aX, bool aExpected, const char* aFile, int aLine, int aSize, bool aIsTSigned) { if (aX == aExpected) { gTestsPassed++; } else { gTestsFailed++; std::cerr << "Test failed at " << aFile << ":" << aLine; std::cerr << " with T a "; if (aIsTSigned) { std::cerr << "signed"; } else { std::cerr << "unsigned"; } std::cerr << " " << CHAR_BIT * aSize << "-bit integer type" << std::endl; } } #define VERIFY_IMPL(x, expected) \ verifyImplFunction((x), (expected), __FILE__, __LINE__, sizeof(T), \ std::is_signed_v) #define VERIFY(x) VERIFY_IMPL(x, true) #define VERIFY_IS_FALSE(x) VERIFY_IMPL(x, false) #define VERIFY_IS_VALID(x) VERIFY_IMPL((x).isValid(), true) #define VERIFY_IS_INVALID(x) VERIFY_IMPL((x).isValid(), false) #define VERIFY_IS_VALID_IF(x, condition) VERIFY_IMPL((x).isValid(), (condition)) template struct testTwiceBiggerType { static void run() { VERIFY( detail::IsSupported::Type>::value); VERIFY(sizeof(typename detail::TwiceBiggerType::Type) == 2 * sizeof(T)); VERIFY(bool(std::is_signed_v::Type>) == bool(std::is_signed_v)); } }; template struct testTwiceBiggerType { static void run() { VERIFY_IS_FALSE( detail::IsSupported::Type>::value); } }; template void test() { static bool alreadyRun = false; // Integer types from different families may just be typedefs for types from // other families. E.g. int32_t might be just a typedef for int. No point // re-running the same tests then. if (alreadyRun) { return; } alreadyRun = true; VERIFY(detail::IsSupported::value); const bool isTSigned = std::is_signed_v; VERIFY(bool(isTSigned) == !bool(T(-1) > T(0))); testTwiceBiggerType::run(); using unsignedT = std::make_unsigned_t; VERIFY(sizeof(unsignedT) == sizeof(T)); VERIFY(std::is_signed_v == false); const CheckedInt max(std::numeric_limits::max()); const CheckedInt min(std::numeric_limits::min()); // Check MinValue and MaxValue, since they are custom implementations and a // mistake there could potentially NOT be caught by any other tests... while // making everything wrong! unsignedT bit = 1; unsignedT unsignedMinValue(min.value()); unsignedT unsignedMaxValue(max.value()); for (size_t i = 0; i < sizeof(T) * CHAR_BIT - 1; i++) { VERIFY((unsignedMinValue & bit) == 0); bit <<= 1; } VERIFY((unsignedMinValue & bit) == (isTSigned ? bit : unsignedT(0))); VERIFY(unsignedMaxValue == unsignedT(~unsignedMinValue)); const CheckedInt zero(0); const CheckedInt one(1); const CheckedInt two(2); const CheckedInt three(3); const CheckedInt four(4); /* Addition / subtraction checks */ VERIFY_IS_VALID(zero + zero); VERIFY(zero + zero == zero); VERIFY_IS_FALSE(zero + zero == one); // Check == doesn't always return true VERIFY_IS_VALID(zero + one); VERIFY(zero + one == one); VERIFY_IS_VALID(one + one); VERIFY(one + one == two); const CheckedInt maxMinusOne = max - one; const CheckedInt maxMinusTwo = max - two; VERIFY_IS_VALID(maxMinusOne); VERIFY_IS_VALID(maxMinusTwo); VERIFY_IS_VALID(maxMinusOne + one); VERIFY_IS_VALID(maxMinusTwo + one); VERIFY_IS_VALID(maxMinusTwo + two); VERIFY(maxMinusOne + one == max); VERIFY(maxMinusTwo + one == maxMinusOne); VERIFY(maxMinusTwo + two == max); VERIFY_IS_VALID(max + zero); VERIFY_IS_VALID(max - zero); VERIFY_IS_INVALID(max + one); VERIFY_IS_INVALID(max + two); VERIFY_IS_INVALID(max + maxMinusOne); VERIFY_IS_INVALID(max + max); const CheckedInt minPlusOne = min + one; const CheckedInt minPlusTwo = min + two; VERIFY_IS_VALID(minPlusOne); VERIFY_IS_VALID(minPlusTwo); VERIFY_IS_VALID(minPlusOne - one); VERIFY_IS_VALID(minPlusTwo - one); VERIFY_IS_VALID(minPlusTwo - two); VERIFY(minPlusOne - one == min); VERIFY(minPlusTwo - one == minPlusOne); VERIFY(minPlusTwo - two == min); const CheckedInt minMinusOne = min - one; VERIFY_IS_VALID(min + zero); VERIFY_IS_VALID(min - zero); VERIFY_IS_INVALID(min - one); VERIFY_IS_INVALID(min - two); VERIFY_IS_INVALID(min - minMinusOne); VERIFY_IS_VALID(min - min); const CheckedInt maxOverTwo = max / two; VERIFY_IS_VALID(maxOverTwo + maxOverTwo); VERIFY_IS_VALID(maxOverTwo + one); VERIFY((maxOverTwo + one) - one == maxOverTwo); VERIFY_IS_VALID(maxOverTwo - maxOverTwo); VERIFY(maxOverTwo - maxOverTwo == zero); const CheckedInt minOverTwo = min / two; VERIFY_IS_VALID(minOverTwo + minOverTwo); VERIFY_IS_VALID(minOverTwo + one); VERIFY((minOverTwo + one) - one == minOverTwo); VERIFY_IS_VALID(minOverTwo - minOverTwo); VERIFY(minOverTwo - minOverTwo == zero); VERIFY_IS_INVALID(min - one); VERIFY_IS_INVALID(min - two); if (isTSigned) { VERIFY_IS_INVALID(min + min); VERIFY_IS_INVALID(minOverTwo + minOverTwo + minOverTwo); VERIFY_IS_INVALID(zero - min + min); VERIFY_IS_INVALID(one - min + min); } /* Modulo checks */ VERIFY_IS_INVALID(zero % zero); VERIFY_IS_INVALID(one % zero); VERIFY_IS_VALID(zero % one); VERIFY_IS_VALID(zero % max); VERIFY_IS_VALID(one % max); VERIFY_IS_VALID(max % one); VERIFY_IS_VALID(max % max); if (isTSigned) { const CheckedInt minusOne = zero - one; VERIFY_IS_INVALID(minusOne % minusOne); VERIFY_IS_INVALID(zero % minusOne); VERIFY_IS_INVALID(one % minusOne); VERIFY_IS_INVALID(minusOne % one); VERIFY_IS_INVALID(min % min); VERIFY_IS_INVALID(zero % min); VERIFY_IS_INVALID(min % one); } /* Unary operator- checks */ const CheckedInt negOne = -one; const CheckedInt negTwo = -two; if (isTSigned) { VERIFY_IS_VALID(-max); VERIFY_IS_INVALID(-min); VERIFY(-max - min == one); VERIFY_IS_VALID(-max - one); VERIFY_IS_VALID(negOne); VERIFY_IS_VALID(-max + negOne); VERIFY_IS_VALID(negOne + one); VERIFY(negOne + one == zero); VERIFY_IS_VALID(negTwo); VERIFY_IS_VALID(negOne + negOne); VERIFY(negOne + negOne == negTwo); } else { VERIFY_IS_INVALID(-max); VERIFY_IS_VALID(-min); VERIFY(min == zero); VERIFY_IS_INVALID(negOne); } /* multiplication checks */ VERIFY_IS_VALID(zero * zero); VERIFY(zero * zero == zero); VERIFY_IS_VALID(zero * one); VERIFY(zero * one == zero); VERIFY_IS_VALID(one * zero); VERIFY(one * zero == zero); VERIFY_IS_VALID(one * one); VERIFY(one * one == one); VERIFY_IS_VALID(one * three); VERIFY(one * three == three); VERIFY_IS_VALID(two * two); VERIFY(two * two == four); VERIFY_IS_INVALID(max * max); VERIFY_IS_INVALID(maxOverTwo * max); VERIFY_IS_INVALID(maxOverTwo * maxOverTwo); const CheckedInt maxApproxSqrt(T(T(1) << (CHAR_BIT * sizeof(T) / 2))); VERIFY_IS_VALID(maxApproxSqrt); VERIFY_IS_VALID(maxApproxSqrt * two); VERIFY_IS_INVALID(maxApproxSqrt * maxApproxSqrt); VERIFY_IS_INVALID(maxApproxSqrt * maxApproxSqrt * maxApproxSqrt); if (isTSigned) { VERIFY_IS_INVALID(min * min); VERIFY_IS_INVALID(minOverTwo * min); VERIFY_IS_INVALID(minOverTwo * minOverTwo); const CheckedInt minApproxSqrt = -maxApproxSqrt; VERIFY_IS_VALID(minApproxSqrt); VERIFY_IS_VALID(minApproxSqrt * two); VERIFY_IS_INVALID(minApproxSqrt * maxApproxSqrt); VERIFY_IS_INVALID(minApproxSqrt * minApproxSqrt); } // make sure to check all 4 paths in signed multiplication validity check. // test positive * positive VERIFY_IS_VALID(max * one); VERIFY(max * one == max); VERIFY_IS_INVALID(max * two); VERIFY_IS_VALID(maxOverTwo * two); VERIFY((maxOverTwo + maxOverTwo) == (maxOverTwo * two)); if (isTSigned) { // test positive * negative VERIFY_IS_VALID(max * negOne); VERIFY_IS_VALID(-max); VERIFY(max * negOne == -max); VERIFY_IS_VALID(one * min); VERIFY_IS_INVALID(max * negTwo); VERIFY_IS_VALID(maxOverTwo * negTwo); VERIFY_IS_VALID(two * minOverTwo); VERIFY_IS_VALID((maxOverTwo + one) * negTwo); VERIFY_IS_INVALID((maxOverTwo + two) * negTwo); VERIFY_IS_INVALID(two * (minOverTwo - one)); // test negative * positive VERIFY_IS_VALID(min * one); VERIFY_IS_VALID(minPlusOne * one); VERIFY_IS_INVALID(min * two); VERIFY_IS_VALID(minOverTwo * two); VERIFY(minOverTwo * two == min); VERIFY_IS_INVALID((minOverTwo - one) * negTwo); VERIFY_IS_INVALID(negTwo * max); VERIFY_IS_VALID(minOverTwo * two); VERIFY(minOverTwo * two == min); VERIFY_IS_VALID(negTwo * maxOverTwo); VERIFY_IS_INVALID((minOverTwo - one) * two); VERIFY_IS_VALID(negTwo * (maxOverTwo + one)); VERIFY_IS_INVALID(negTwo * (maxOverTwo + two)); // test negative * negative VERIFY_IS_INVALID(min * negOne); VERIFY_IS_VALID(minPlusOne * negOne); VERIFY(minPlusOne * negOne == max); VERIFY_IS_INVALID(min * negTwo); VERIFY_IS_INVALID(minOverTwo * negTwo); VERIFY_IS_INVALID(negOne * min); VERIFY_IS_VALID(negOne * minPlusOne); VERIFY(negOne * minPlusOne == max); VERIFY_IS_INVALID(negTwo * min); VERIFY_IS_INVALID(negTwo * minOverTwo); } /* Division checks */ VERIFY_IS_VALID(one / one); VERIFY(one / one == one); VERIFY_IS_VALID(three / three); VERIFY(three / three == one); VERIFY_IS_VALID(four / two); VERIFY(four / two == two); VERIFY((four * three) / four == three); // Check that div by zero is invalid VERIFY_IS_INVALID(zero / zero); VERIFY_IS_INVALID(one / zero); VERIFY_IS_INVALID(two / zero); VERIFY_IS_INVALID(negOne / zero); VERIFY_IS_INVALID(max / zero); VERIFY_IS_INVALID(min / zero); if (isTSigned) { // Check that min / -1 is invalid VERIFY_IS_INVALID(min / negOne); // Check that the test for div by -1 isn't banning other numerators than min VERIFY_IS_VALID(one / negOne); VERIFY_IS_VALID(zero / negOne); VERIFY_IS_VALID(negOne / negOne); VERIFY_IS_VALID(max / negOne); } /* Check that invalidity is correctly preserved by arithmetic ops */ const CheckedInt someInvalid = max + max; VERIFY_IS_INVALID(someInvalid + zero); VERIFY_IS_INVALID(someInvalid - zero); VERIFY_IS_INVALID(zero + someInvalid); VERIFY_IS_INVALID(zero - someInvalid); VERIFY_IS_INVALID(-someInvalid); VERIFY_IS_INVALID(someInvalid * zero); VERIFY_IS_INVALID(someInvalid * one); VERIFY_IS_INVALID(zero * someInvalid); VERIFY_IS_INVALID(one * someInvalid); VERIFY_IS_INVALID(someInvalid / zero); VERIFY_IS_INVALID(someInvalid / one); VERIFY_IS_INVALID(zero / someInvalid); VERIFY_IS_INVALID(one / someInvalid); VERIFY_IS_INVALID(someInvalid % zero); VERIFY_IS_INVALID(someInvalid % one); VERIFY_IS_INVALID(zero % someInvalid); VERIFY_IS_INVALID(one % someInvalid); VERIFY_IS_INVALID(someInvalid + someInvalid); VERIFY_IS_INVALID(someInvalid - someInvalid); VERIFY_IS_INVALID(someInvalid * someInvalid); VERIFY_IS_INVALID(someInvalid / someInvalid); VERIFY_IS_INVALID(someInvalid % someInvalid); // Check that mixing checked integers with plain integers in expressions is // allowed VERIFY(one + T(2) == three); VERIFY(2 + one == three); { CheckedInt x = one; x += 2; VERIFY(x == three); } VERIFY(two - 1 == one); VERIFY(2 - one == one); { CheckedInt x = two; x -= 1; VERIFY(x == one); } VERIFY(one * 2 == two); VERIFY(2 * one == two); { CheckedInt x = one; x *= 2; VERIFY(x == two); } VERIFY(four / 2 == two); VERIFY(4 / two == two); { CheckedInt x = four; x /= 2; VERIFY(x == two); } VERIFY(three % 2 == one); VERIFY(3 % two == one); { CheckedInt x = three; x %= 2; VERIFY(x == one); } VERIFY(one == 1); VERIFY(1 == one); VERIFY_IS_FALSE(two == 1); VERIFY_IS_FALSE(1 == two); VERIFY_IS_FALSE(someInvalid == 1); VERIFY_IS_FALSE(1 == someInvalid); // Check that compound operators work when both sides of the expression // are checked integers { CheckedInt x = one; x += two; VERIFY(x == three); } { CheckedInt x = two; x -= one; VERIFY(x == one); } { CheckedInt x = one; x *= two; VERIFY(x == two); } { CheckedInt x = four; x /= two; VERIFY(x == two); } { CheckedInt x = three; x %= two; VERIFY(x == one); } // Check that compound operators work when both sides of the expression // are checked integers and the right-hand side is invalid { CheckedInt x = one; x += someInvalid; VERIFY_IS_INVALID(x); } { CheckedInt x = two; x -= someInvalid; VERIFY_IS_INVALID(x); } { CheckedInt x = one; x *= someInvalid; VERIFY_IS_INVALID(x); } { CheckedInt x = four; x /= someInvalid; VERIFY_IS_INVALID(x); } { CheckedInt x = three; x %= someInvalid; VERIFY_IS_INVALID(x); } // Check simple casting between different signedness and sizes. { CheckedInt foo = CheckedInt(2).toChecked(); VERIFY_IS_VALID(foo); VERIFY(foo == 2); } { CheckedInt foo = CheckedInt(255).toChecked(); VERIFY_IS_VALID(foo); VERIFY(foo == 255); } { CheckedInt foo = CheckedInt(256).toChecked(); VERIFY_IS_INVALID(foo); } { CheckedInt foo = CheckedInt(-2).toChecked(); VERIFY_IS_INVALID(foo); } // Check that construction of CheckedInt from an integer value of a // mismatched type is checked Also check casting between all types. #define VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE2(U, V, PostVExpr) \ { \ bool isUSigned = std::is_signed_v; \ VERIFY_IS_VALID(CheckedInt(V(0) PostVExpr)); \ VERIFY_IS_VALID(CheckedInt(V(1) PostVExpr)); \ VERIFY_IS_VALID(CheckedInt(V(100) PostVExpr)); \ if (isUSigned) { \ VERIFY_IS_VALID_IF(CheckedInt(V(-1) PostVExpr), isTSigned); \ } \ if (sizeof(U) > sizeof(T)) { \ VERIFY_IS_INVALID(CheckedInt( \ V(std::numeric_limits::max()) PostVExpr + one.value())); \ } \ VERIFY_IS_VALID_IF( \ CheckedInt(std::numeric_limits::max()), \ (sizeof(T) > sizeof(U) || \ ((sizeof(T) == sizeof(U)) && (isUSigned || !isTSigned)))); \ VERIFY_IS_VALID_IF(CheckedInt(std::numeric_limits::min()), \ isUSigned == false ? 1 \ : bool(isTSigned) == false ? 0 \ : sizeof(T) >= sizeof(U)); \ } #define VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(U) \ VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE2(U, U, +zero) \ VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE2(U, CheckedInt, .toChecked()) VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(int8_t) VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(uint8_t) VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(int16_t) VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(uint16_t) VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(int32_t) VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(uint32_t) VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(int64_t) VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(uint64_t) typedef signed char signedChar; typedef unsigned char unsignedChar; typedef unsigned short unsignedShort; typedef unsigned int unsignedInt; typedef unsigned long unsignedLong; typedef long long longLong; typedef unsigned long long unsignedLongLong; VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(char) VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(signedChar) VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(unsignedChar) VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(short) VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(unsignedShort) VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(int) VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(unsignedInt) VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(long) VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(unsignedLong) VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(longLong) VERIFY_CONSTRUCTION_FROM_INTEGER_TYPE(unsignedLongLong) /* Test increment/decrement operators */ CheckedInt x, y; x = one; y = x++; VERIFY(x == two); VERIFY(y == one); x = one; y = ++x; VERIFY(x == two); VERIFY(y == two); x = one; y = x--; VERIFY(x == zero); VERIFY(y == one); x = one; y = --x; VERIFY(x == zero); VERIFY(y == zero); x = max; VERIFY_IS_VALID(x++); x = max; VERIFY_IS_INVALID(++x); x = min; VERIFY_IS_VALID(x--); x = min; VERIFY_IS_INVALID(--x); gIntegerTypesTested++; } int main() { test(); test(); test(); test(); test(); test(); test(); test(); test(); test(); test(); test(); test(); test(); test(); test(); test(); test(); test(); const int MIN_TYPES_TESTED = 9; if (gIntegerTypesTested < MIN_TYPES_TESTED) { std::cerr << "Only " << gIntegerTypesTested << " have been tested. " << "This should not be less than " << MIN_TYPES_TESTED << "." << std::endl; gTestsFailed++; } std::cerr << gTestsFailed << " tests failed, " << gTestsPassed << " tests passed out of " << gTestsFailed + gTestsPassed << " tests, covering " << gIntegerTypesTested << " distinct integer types." << std::endl; return gTestsFailed > 0; }