/** * Copyright (C) 2018-present MongoDB, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the Server Side Public License, version 1, * as published by MongoDB, Inc. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * Server Side Public License for more details. * * You should have received a copy of the Server Side Public License * along with this program. If not, see * . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the Server Side Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #include "mongo/platform/basic.h" #include #include "mongo/platform/overflow_arithmetic.h" #include "mongo/stdx/type_traits.h" #include "mongo/unittest/unittest.h" namespace mongo { namespace { template constexpr T kMin = std::numeric_limits::min(); template constexpr T kMax = std::numeric_limits::max(); template bool runTest(bool oflow, F f, A a, B b, T r = {}) { T result; bool oflowed = f(a, b, &result); if (oflowed != oflow) return false; if (oflow) return true; return result == r; } // Expect `f(a,b) == r`. template auto test(F f, A a, B b, stdx::type_identity_t r) { return runTest(false, f, a, b, r); }; // Expect `f(a,b)` overflows. template auto testOflow(F f, A a, B b) { return runTest(true, f, a, b); }; // Polymorphic lambdas to defer overload resolution until execution time. constexpr auto polyMul = [](auto&&... a) { return overflow::mul(a...); }; constexpr auto polyAdd = [](auto&&... a) { return overflow::add(a...); }; constexpr auto polySub = [](auto&&... a) { return overflow::sub(a...); }; TEST(OverflowArithmetic, SignedMultiplicationTests) { using T = int64_t; static constexpr auto f = polyMul; ASSERT(test(f, 0, kMax, 0)); ASSERT(test(f, 0, kMin, 0)); ASSERT(test(f, 1, kMax, kMax)); ASSERT(test(f, 1, kMin, kMin)); ASSERT(test(f, -1, kMax, kMin + 1)); ASSERT(test(f, 1000, 57, 57000)); ASSERT(test(f, 1000, -57, -57000)); ASSERT(test(f, -1000, -57, 57000)); ASSERT(test(f, 0x3fffffffffffffff, 2, 0x7ffffffffffffffe)); ASSERT(test(f, 0x3fffffffffffffff, -2, -0x7ffffffffffffffe)); ASSERT(test(f, -0x3fffffffffffffff, -2, 0x7ffffffffffffffe)); ASSERT(testOflow(f, -1, kMin)); ASSERT(testOflow(f, 2, kMax)); ASSERT(testOflow(f, -2, kMax)); ASSERT(testOflow(f, 2, kMin)); ASSERT(testOflow(f, -2, kMin)); ASSERT(testOflow(f, kMin, kMax)); ASSERT(testOflow(f, kMax, kMax)); ASSERT(testOflow(f, kMin, kMin)); ASSERT(testOflow(f, 1LL << 62, 8)); ASSERT(testOflow(f, -(1LL << 62), 8)); ASSERT(testOflow(f, -(1LL << 62), -8)); } TEST(OverflowArithmetic, UnignedMultiplicationTests) { using T = uint64_t; static constexpr auto f = polyMul; ASSERT(test(f, 0, kMax, 0)); ASSERT(test(f, 1, kMax, kMax)); ASSERT(test(f, 1000, 57, 57000)); ASSERT(test(f, 0x3fffffffffffffff, 2, 0x7ffffffffffffffe)); ASSERT(test(f, 0x7fffffffffffffff, 2, 0xfffffffffffffffe)); ASSERT(testOflow(f, 2, kMax)); ASSERT(testOflow(f, kMax, kMax)); ASSERT(testOflow(f, 1LL << 62, 8)); ASSERT(testOflow(f, 0x7fffffffffffffff, 4)); } TEST(OverflowArithmetic, SignedAdditionTests) { using T = int64_t; static constexpr auto f = polyAdd; ASSERT(test(f, 0, kMax, kMax)); ASSERT(test(f, -1, kMax, kMax - 1)); ASSERT(test(f, 1, kMax - 1, kMax)); ASSERT(test(f, 0, kMin, kMin)); ASSERT(test(f, 1, kMin, kMin + 1)); ASSERT(test(f, -1, kMin + 1, kMin)); ASSERT(test(f, kMax, kMin, -1)); ASSERT(test(f, 1, 1, 2)); ASSERT(test(f, -1, -1, -2)); ASSERT(testOflow(f, kMax, 1)); ASSERT(testOflow(f, kMax, kMax)); ASSERT(testOflow(f, kMin, -1)); ASSERT(testOflow(f, kMin, kMin)); } TEST(OverflowArithmetic, UnsignedAdditionTests) { using T = uint64_t; static constexpr auto f = polyAdd; ASSERT(test(f, 0, kMax, kMax)); ASSERT(test(f, 1, kMax - 1, kMax)); ASSERT(test(f, 1, 1, 2)); ASSERT(testOflow(f, kMax, 1)); ASSERT(testOflow(f, kMax, kMax)); } TEST(OverflowArithmetic, SignedSubtractionTests) { using T = int64_t; static constexpr auto f = polySub; ASSERT(test(f, kMax, 0, kMax)); ASSERT(test(f, kMax, 1, kMax - 1)); ASSERT(test(f, kMax - 1, -1, kMax)); ASSERT(test(f, kMin, 0, kMin)); ASSERT(test(f, kMin, -1, kMin + 1)); ASSERT(test(f, kMin + 1, 1, kMin)); ASSERT(test(f, kMax, kMax, 0)); ASSERT(test(f, kMin, kMin, 0)); ASSERT(test(f, 0, 0, 0)); ASSERT(test(f, 1, 1, 0)); ASSERT(test(f, 0, 1, -1)); ASSERT(testOflow(f, 0, kMin)); ASSERT(testOflow(f, kMax, -1)); ASSERT(testOflow(f, kMax, kMin)); ASSERT(testOflow(f, kMin, 1)); ASSERT(testOflow(f, kMin, kMax)); } TEST(OverflowArithmetic, UnsignedSubtractionTests) { using T = uint64_t; static constexpr auto f = polySub; ASSERT(test(f, kMax, 0, kMax)); ASSERT(test(f, kMax, 1, kMax - 1)); ASSERT(test(f, kMax, kMax, 0)); ASSERT(test(f, 0, 0, 0)); ASSERT(test(f, 1, 1, 0)); ASSERT(testOflow(f, 0, 1)); ASSERT(testOflow(f, 0, kMax)); } TEST(OverflowArithmetic, SafeModTests) { // Mod -1 should not overflow for LLONG_MIN or INT_MIN. auto minLong = std::numeric_limits::min(); auto minInt = std::numeric_limits::min(); ASSERT_EQ(overflow::safeMod(minLong, -1LL), 0); ASSERT_EQ(overflow::safeMod(minInt, -1), 0); // A divisor of 0 throws a user assertion. ASSERT_THROWS_CODE(overflow::safeMod(minLong, 0LL), AssertionException, 51259); ASSERT_THROWS_CODE(overflow::safeMod(minInt, 0), AssertionException, 51259); } TEST(OverflowArithmetic, HeterogeneousArguments) { { int r; ASSERT_FALSE(overflow::mul(1L, 2ULL, &r)); ASSERT_EQ(r, 2); } { unsigned long long r; ASSERT_TRUE(overflow::mul(-1, 2, &r)); } } } // namespace } // namespace mongo