#define _SQUID_SRC_SQUIDMATH_H
#include "base/forward.h"
+#include "base/Optional.h"
#include <limits>
#include <type_traits>
// built-ins like __builtin_add_overflow() instead of manual overflow checks.
/// std::enable_if_t replacement until C++14
-/// simplifies Sum() declarations below
+/// simplifies declarations further below
template <bool B, class T = void>
using EnableIfType = typename std::enable_if<B,T>::type;
/// detects a pair of unsigned types
-/// reduces code duplication in Sum() declarations below
+/// reduces code duplication in declarations further below
template <typename T, typename U>
using AllUnsigned = typename std::conditional<
std::is_unsigned<T>::value && std::is_unsigned<U>::value,
std::false_type
>::type;
+// TODO: Replace with std::cmp_less() after migrating to C++20.
+/// whether integer a is less than integer b, with correct overflow handling
+template <typename A, typename B>
+constexpr bool
+Less(const A a, const B b) {
+ // The casts below make standard C++ integer conversions explicit. They
+ // quell compiler warnings about signed/unsigned comparison. The first two
+ // lines exclude different-sign a and b, making the casts/comparison safe.
+ using AB = typename std::common_type<A, B>::type;
+ return
+ (a >= 0 && b < 0) ? false :
+ (a < 0 && b >= 0) ? true :
+ /* (a >= 0) == (b >= 0) */ static_cast<AB>(a) < static_cast<AB>(b);
+}
+
+/// ensure that T is supported by NaturalSum() and friends
+template<typename T>
+constexpr bool
+AssertNaturalType()
+{
+ static_assert(std::numeric_limits<T>::is_bounded, "std::numeric_limits<T>::max() is meaningful");
+ static_assert(std::numeric_limits<T>::is_exact, "no silent loss of precision");
+ static_assert(!std::is_enum<T>::value, "no silent creation of non-enumerated values");
+ return true; // for static_assert convenience in C++11 constexpr callers
+}
+
+// TODO: Investigate whether this optimization can be expanded to [signed] types
+// A and B when std::numeric_limits<decltype(A(0)+B(0))>::is_modulo is true.
+/// This IncreaseSumInternal() overload is optimized for speed.
/// \returns a non-overflowing sum of the two unsigned arguments (or nothing)
-template <typename T, typename U, EnableIfType<AllUnsigned<T,U>::value, int> = 0>
-Optional<T>
-Sum(const T a, const U b) {
- // Instead of computing the largest type dynamically, we simply go by T and
- // reject cases like Sum(0, ULLONG_MAX) that would overflow on return.
- // TODO: Consider using std::common_type<T, U> in the return type instead.
- static_assert(sizeof(T) >= sizeof(U), "Sum() return type can fit its (unsigned) result");
-
- // this optimized implementation relies on unsigned overflows
- static_assert(std::is_unsigned<T>::value, "the first Sum(a,b) argument is unsigned");
- static_assert(std::is_unsigned<U>::value, "the second Sum(a,b) argument is unsigned");
- const auto sum = a + b;
- // when a+b overflows, the result becomes smaller than any operand
- return (sum < a) ? Optional<T>() : Optional<T>(sum);
+/// \prec both argument types are unsigned
+template <typename S, typename A, typename B, EnableIfType<AllUnsigned<A,B>::value, int> = 0>
+Optional<S>
+IncreaseSumInternal(const A a, const B b) {
+ // paranoid: AllUnsigned<A,B> precondition established that already
+ static_assert(std::is_unsigned<A>::value, "AllUnsigned dispatch worked for A");
+ static_assert(std::is_unsigned<B>::value, "AllUnsigned dispatch worked for B");
+
+ // TODO: Just call AssertNaturalType() after upgrading to C++14.
+ static_assert(AssertNaturalType<S>(), "S is a supported type");
+ static_assert(AssertNaturalType<A>(), "A is a supported type");
+ static_assert(AssertNaturalType<B>(), "B is a supported type");
+
+ // we should only be called by IncreaseSum(); it forces integer promotion
+ static_assert(std::is_same<A, decltype(+a)>::value, "a will not be promoted");
+ static_assert(std::is_same<B, decltype(+b)>::value, "b will not be promoted");
+ // and without integer promotions, a sum of unsigned integers is unsigned
+ static_assert(std::is_unsigned<decltype(a+b)>::value, "a+b is unsigned");
+
+ // with integer promotions ruled out, a or b can only undergo integer
+ // conversion to the higher rank type (A or B, we do not know which)
+ using AB = typename std::common_type<A, B>::type;
+ static_assert(std::is_same<AB, A>::value || std::is_same<AB, B>::value, "no unexpected conversions");
+ static_assert(std::is_same<AB, decltype(a+b)>::value, "lossless assignment");
+ const AB sum = a + b;
+
+ static_assert(std::numeric_limits<AB>::is_modulo, "we can detect overflows");
+ // 1. modulo math: overflowed sum is smaller than any of its operands
+ // 2. the sum may overflow S (i.e. the return base type)
+ // We do not need Less() here because we compare promoted unsigned types.
+ return (sum >= a && sum <= std::numeric_limits<S>::max()) ?
+ Optional<S>(sum) : Optional<S>();
}
-/// \returns a non-overflowing sum of the two signed arguments (or nothing)
-template <typename T, typename U, EnableIfType<!AllUnsigned<T,U>::value, int> = 0>
-Optional<T> constexpr
-Sum(const T a, const U b) {
- // Instead of computing the largest type dynamically, we simply go by T and
- // reject cases like Sum(0, LLONG_MAX) that would overflow on return.
- static_assert(sizeof(T) >= sizeof(U), "Sum() return type can fit its (signed) result");
-
- // tests below avoid undefined behavior of signed under/overflows
- return b >= 0 ?
- ((a > std::numeric_limits<U>::max() - b) ? Optional<T>() : Optional<T>(a + b)):
- ((a < std::numeric_limits<U>::min() - b) ? Optional<T>() : Optional<T>(a + b));
+/// This IncreaseSumInternal() overload supports a larger variety of types.
+/// \returns a non-overflowing sum of the two arguments (or nothing)
+/// \returns nothing if at least one of the arguments is negative
+/// \prec at least one of the argument types is signed
+template <typename S, typename A, typename B, EnableIfType<!AllUnsigned<A,B>::value, int> = 0>
+Optional<S> constexpr
+IncreaseSumInternal(const A a, const B b) {
+ static_assert(AssertNaturalType<S>(), "S is a supported type");
+ static_assert(AssertNaturalType<A>(), "A is a supported type");
+ static_assert(AssertNaturalType<B>(), "B is a supported type");
+
+ // we should only be called by IncreaseSum() that does integer promotion
+ static_assert(std::is_same<A, decltype(+a)>::value, "a will not be promoted");
+ static_assert(std::is_same<B, decltype(+b)>::value, "b will not be promoted");
+
+ return
+ // We could support a non-under/overflowing sum of negative numbers, but
+ // our callers use negative values specially (e.g., for do-not-use or
+ // do-not-limit settings) and are not supposed to do math with them.
+ (a < 0 || b < 0) ? Optional<S>() :
+ // To avoid undefined behavior of signed overflow, we must not compute
+ // the raw a+b sum if it may overflow. When A is not B, a or b undergoes
+ // (safe for non-negatives) integer conversion in these expressions, so
+ // we do not know the resulting a+b type AB and its maximum. We must
+ // also detect subsequent casting-to-S overflows.
+ // Overflow condition: (a + b > maxAB) or (a + b > maxS).
+ // A is an integer promotion of S, so maxS <= maxA <= maxAB.
+ // Since maxS <= maxAB, it is sufficient to just check: a + b > maxS,
+ // which is the same as the overflow-safe condition here: maxS - a < b.
+ // Finally, (maxS - a) cannot overflow because a is not negative and
+ // cannot underflow because a is a promotion of s: 0 <= a <= maxS.
+ Less(std::numeric_limits<S>::max() - a, b) ? Optional<S>() :
+ Optional<S>(a + b);
+}
+
+/// argument pack expansion termination for IncreaseSum<S, T, Args...>()
+template <typename S, typename T>
+Optional<S>
+IncreaseSum(const S s, const T t)
+{
+ // Force (always safe) integer promotions now, to give EnableIfType<>
+ // promoted types instead of entering IncreaseSumInternal<AllUnsigned>(s,t)
+ // but getting a _signed_ promoted value of s or t in s + t.
+ return IncreaseSumInternal<S>(+s, +t);
}
/// \returns a non-overflowing sum of the arguments (or nothing)
-template <typename T, typename... Args>
-Optional<T>
-Sum(const T first, Args... args) {
- if (const auto others = Sum<T>(args...)) {
- return Sum<T>(first, others.value());
+template <typename S, typename T, typename... Args>
+Optional<S>
+IncreaseSum(const S sum, const T t, const Args... args) {
+ if (const auto head = IncreaseSum(sum, t)) {
+ return IncreaseSum(head.value(), args...);
} else {
- return Optional<T>();
+ return Optional<S>();
}
}
+/// \returns an exact, non-overflowing sum of the arguments (or nothing)
+template <typename SummationType, typename... Args>
+Optional<SummationType>
+NaturalSum(const Args... args) {
+ return IncreaseSum<SummationType>(0, args...);
+}
+
+/// Safely resets the given variable to NaturalSum() of the given arguments.
+/// If the sum overflows, resets to variable's maximum possible value.
+/// \returns the new variable value (like an assignment operator would)
+template <typename S, typename... Args>
+S
+SetToNaturalSumOrMax(S &var, const Args... args)
+{
+ var = NaturalSum<S>(args...).value_or(std::numeric_limits<S>::max());
+ return var;
+}
+
#endif /* _SQUID_SRC_SQUIDMATH_H */
--- /dev/null
+/*
+ * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+#include "squid.h"
+#include "base/Optional.h"
+#include "compat/cppunit.h"
+#include "SquidMath.h"
+#include "unitTestMain.h"
+
+class TestMath: public CPPUNIT_NS::TestFixture
+{
+ CPPUNIT_TEST_SUITE( TestMath );
+ CPPUNIT_TEST( testNaturalSum );
+ CPPUNIT_TEST_SUITE_END();
+
+protected:
+ void testNaturalSum();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION( TestMath );
+
+/// bit-width-specific integers, for developer convenience and code readability
+/// @{
+static const auto min64s = std::numeric_limits<int64_t>::min();
+static const auto min8s = std::numeric_limits<int8_t>::min();
+static const auto zero8s = int8_t(0);
+static const auto zero8u = uint8_t(0);
+static const auto zero64s = int64_t(0);
+static const auto zero64u = uint64_t(0);
+static const auto one8s = int8_t(1);
+static const auto one8u = uint8_t(1);
+static const auto one64s = int64_t(1);
+static const auto one64u = uint64_t(1);
+static const auto max8s = std::numeric_limits<int8_t>::max();
+static const auto max8u = std::numeric_limits<uint8_t>::max();
+static const auto max64s = std::numeric_limits<int64_t>::max();
+static const auto max64u = std::numeric_limits<uint64_t>::max();
+/// @}
+
+/// helper functions to convert NaturalSum<S>(a,b,...) calls to strings
+/// @{
+
+template <typename A>
+static std::string
+TypeToString()
+{
+ const std::string prefix = std::is_signed<A>::value ? "" : "u";
+ return prefix + "int" + std::to_string(sizeof(A)*8);
+}
+
+template <typename A>
+static std::string
+OperandToString(const A a)
+{
+ return TypeToString<A>() + '(' + std::to_string(+a) + ')';
+}
+
+template <typename S, typename A, typename B>
+static std::string
+SumToString(const A a, const B b)
+{
+ return TypeToString<S>() + ": " + OperandToString(a) + " + " + OperandToString(b);
+}
+
+template <typename S, typename A, typename B, typename C>
+static std::string
+SumToString(const A a, const B b, const C c)
+{
+ return TypeToString<S>() + ": " + OperandToString(a) + " + " + OperandToString(b) + " + " + OperandToString(c);
+}
+
+/// @}
+
+/// ends argument recursion for RawSum() with parameters
+template <typename S>
+static S
+RawSum()
+{
+ return S(0);
+}
+
+/// helper function to add up an arbitrary number of arbitrary-type integers
+/// while converting every number to type S and ignoring any under/overflows
+template <typename S, typename A, typename... Args>
+static S
+RawSum(A a, Args... args)
+{
+ return S(a) + RawSum<S>(args...);
+}
+
+/// Tests NaturalSum<S>() calls that are supposed to succeed.
+/// Implemented as a class to pass it as a template template parameter.
+template <typename S>
+class SuccessSumTester
+{
+public:
+ template <typename... Args>
+ static S Test(Args... args)
+ {
+ // to show every non-overflowing sum to be tested:
+ //std::cout << SumToString<S>(args...) << " = " << +sum << "\n";
+
+ const auto ns = NaturalSum<S>(args...);
+ CPPUNIT_ASSERT_MESSAGE(SumToString<S>(args...) + " does not overflow",
+ ns.has_value());
+
+ const auto sum = ns.value();
+ const auto expected = RawSum<S>(args...);
+ CPPUNIT_ASSERT_MESSAGE(
+ SumToString<S>(args...) + " = " + OperandToString(expected) + " rather than " + OperandToString(sum),
+ sum == expected);
+
+ return sum;
+ }
+};
+
+/// Tests NaturalSum<S>() calls that are supposed to overflow.
+/// Implemented as a class to pass it as a template template parameter.
+template <typename S>
+class OverflowSumTester
+{
+public:
+ template <typename... Args>
+ static void Test(Args... args)
+ {
+ // to show every overflowing sum to be tested:
+ //std::cout << SumToString<S>(args...) << " = overflow\n";
+
+ CPPUNIT_ASSERT_MESSAGE(SumToString<S>(args...) + " must overflow",
+ !NaturalSum<S>(args...).has_value());
+ }
+};
+
+/// checks that the summation outcome is unaffected by (not) adding zeros
+template <typename S, template<typename> class Tester, typename A, typename B>
+static void
+TestWithZeros(const A a, const B b)
+{
+ Tester<S>::Test(a, b);
+
+ Tester<S>::Test(zero8u, a, b);
+ Tester<S>::Test(zero8s, a, b);
+ Tester<S>::Test(zero64u, a, b);
+ Tester<S>::Test(zero64s, a, b);
+ Tester<S>::Test(a, zero8u, b);
+ Tester<S>::Test(a, zero8s, b);
+ Tester<S>::Test(a, zero64u, b);
+ Tester<S>::Test(a, zero64s, b);
+ Tester<S>::Test(a, b, zero8u);
+ Tester<S>::Test(a, b, zero8s);
+ Tester<S>::Test(a, b, zero64u);
+ Tester<S>::Test(a, b, zero64s);
+}
+
+/// checks that the summation outcome is unaffected by the order of operands
+template <typename S, template<typename> class Tester, typename A, typename B>
+static void
+TestOrder(const A a, const B b)
+{
+ TestWithZeros<S, Tester>(a, b);
+ TestWithZeros<S, Tester>(b, a);
+}
+
+/// checks that a+b and similar sums overflow for summation types A and B
+template <typename A, typename B>
+static void
+TestOverflowForEitherSummationType(const A a, const B b)
+{
+ TestOrder<A, OverflowSumTester>(a, b);
+ TestOrder<B, OverflowSumTester>(a, b);
+}
+
+/// checks that a+b and similar sums succeed for summation type A but overflow
+/// for summation type B
+template <typename A, typename B>
+static void
+TestSuccessForFirstSummationType(const A a, const B b)
+{
+ TestOrder<A, SuccessSumTester>(a, b);
+ TestOrder<B, OverflowSumTester>(a, b);
+}
+
+/// \returns successful a+b value using summation type S (which defaults to A)
+template <typename A, typename... Args, typename S = A>
+static S
+GoodSum(const A a, Args... args)
+{
+ return SuccessSumTester<S>::Test(a, args...);
+}
+
+void
+TestMath::testNaturalSum()
+{
+ /*
+ * To simplify spelling out of these repetitive test cases, we let function
+ * parameters determine the summation type. Regular code should not do that,
+ * and our public summation APIs do not provide this dangerous shortcut.
+ */
+
+ // negative parameters are banned in any position
+ TestOverflowForEitherSummationType(min64s, zero8s);
+ TestOverflowForEitherSummationType(min64s, zero8u);
+ TestOverflowForEitherSummationType(min64s, max64s);
+ TestOverflowForEitherSummationType(min64s, max64u);
+ TestOverflowForEitherSummationType(min8s, zero8s);
+ TestOverflowForEitherSummationType(min8s, zero8s);
+ TestOverflowForEitherSummationType(min8s, zero8u);
+ TestOverflowForEitherSummationType(min8s, max64s);
+ TestOverflowForEitherSummationType(min8s, max64u);
+ TestOverflowForEitherSummationType(-1, -1);
+ TestOverflowForEitherSummationType(-1, zero8s);
+ TestOverflowForEitherSummationType(-1, zero8u);
+ TestOverflowForEitherSummationType(-1, max64s);
+ TestOverflowForEitherSummationType(-1, max64u);
+
+ // these overflow regardless of which parameter determines the summation type
+ TestOverflowForEitherSummationType(max8u, one8u);
+ TestOverflowForEitherSummationType(max8u, one8s);
+ TestOverflowForEitherSummationType(max8u, max8s);
+ TestOverflowForEitherSummationType(max8s, one8s);
+ TestOverflowForEitherSummationType(max64u, one8u);
+ TestOverflowForEitherSummationType(max64u, one8s);
+ TestOverflowForEitherSummationType(max64u, one64u);
+ TestOverflowForEitherSummationType(max64u, one64s);
+ TestOverflowForEitherSummationType(max64u, max64s);
+ TestOverflowForEitherSummationType(max64s, one8u);
+ TestOverflowForEitherSummationType(max64s, one8s);
+ TestOverflowForEitherSummationType(max64s, one64s);
+
+ // these overflow only if the second parameter determines the summation type
+ TestSuccessForFirstSummationType(one8u, max8s);
+ TestSuccessForFirstSummationType(one64u, max8u);
+ TestSuccessForFirstSummationType(one64u, max64s);
+ TestSuccessForFirstSummationType(one64s, max8u);
+ TestSuccessForFirstSummationType(one64s, max8s);
+ TestSuccessForFirstSummationType(max64u, zero8u);
+ TestSuccessForFirstSummationType(max64u, zero8s);
+ TestSuccessForFirstSummationType(max64s, zero8u);
+ TestSuccessForFirstSummationType(max64s, zero8s);
+
+ // a few sums with known values
+ CPPUNIT_ASSERT_EQUAL(zero8s, GoodSum(zero8s, zero8u));
+ CPPUNIT_ASSERT_EQUAL(zero64s, GoodSum(zero64s, zero64u));
+ CPPUNIT_ASSERT_EQUAL(2, GoodSum(1, 1));
+ CPPUNIT_ASSERT_EQUAL(uint64_t(2), GoodSum(one64u, one64s));
+ CPPUNIT_ASSERT_EQUAL(6u, GoodSum(1u, 2, 3));
+ CPPUNIT_ASSERT_EQUAL(max64u, GoodSum(zero64u, max64u));
+ CPPUNIT_ASSERT_EQUAL(max64s, GoodSum(zero64s, max64s));
+ CPPUNIT_ASSERT_EQUAL(one64u + max64s, GoodSum(one64u, max64s));
+ CPPUNIT_ASSERT_EQUAL(max64u, GoodSum(max64u, zero8s));
+ CPPUNIT_ASSERT_EQUAL(max64s, GoodSum(max64s, zero8s));
+
+ // long argument lists (odd and even lengths)
+ CPPUNIT_ASSERT_EQUAL(15, NaturalSum<int>(1, 2, 3, 4, 5).value());
+ CPPUNIT_ASSERT_EQUAL(21, NaturalSum<int>(1, 2, 3, 4, 5, 6).value());
+
+ // test SetToNaturalSumOrMax() when the sum is too big for the variable
+ long expires = 0;
+ const auto result = SetToNaturalSumOrMax(expires, max64u, zero8u);
+ CPPUNIT_ASSERT_EQUAL(std::numeric_limits<long>::max(), expires);
+ CPPUNIT_ASSERT_EQUAL(expires, result);
+}
+