| // Copyright 2024 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| #include "pw_numeric/checked_arithmetic.h" |
| |
| #include "pw_unit_test/framework.h" |
| |
| // Emits a TEST() for a given templated function with the given type. |
| // The test name is prefixed with the type name. |
| #define TEST_FUNC_FOR_TYPE(suite, name, func, type) \ |
| TEST(suite, name##_##type) { func<type>(); } |
| |
| // Emits a TEST() for a templated function named by the concatention of the |
| // test suite and test name, invoked with the given type. |
| #define TEST_FOR_TYPE(suite, name, type) \ |
| TEST_FUNC_FOR_TYPE(suite, name, suite##name, type) |
| |
| // Emits a TEST_FOR_TYPE() for all common <cstdint> types. |
| #define TEST_FOR_STDINT_TYPES(suite, func) \ |
| TEST_FOR_TYPE(suite, func, uint8_t) \ |
| TEST_FOR_TYPE(suite, func, int8_t) \ |
| TEST_FOR_TYPE(suite, func, uint16_t) \ |
| TEST_FOR_TYPE(suite, func, int16_t) \ |
| TEST_FOR_TYPE(suite, func, uint32_t) \ |
| TEST_FOR_TYPE(suite, func, int32_t) \ |
| TEST_FOR_TYPE(suite, func, uint64_t) \ |
| TEST_FOR_TYPE(suite, func, int64_t) |
| |
| namespace { |
| |
| // pw::CheckedAdd() |
| |
| template <typename T> |
| void CheckedAddWorks() { |
| static constexpr T kMax = std::numeric_limits<T>::max(); |
| |
| EXPECT_EQ(pw::CheckedAdd<T>(0, 0), 0); |
| EXPECT_EQ(pw::CheckedAdd<T>(0, 1), 1); |
| EXPECT_EQ(pw::CheckedAdd<T>(1, 0), 1); |
| EXPECT_EQ(pw::CheckedAdd<T>(1, 2), 3); |
| EXPECT_EQ(pw::CheckedAdd<T>(kMax - 1, 1), kMax); |
| } |
| |
| template <typename T> |
| void CheckedAddDetectsOverflow() { |
| static constexpr T kMax = std::numeric_limits<T>::max(); |
| |
| EXPECT_EQ(pw::CheckedAdd<T>(kMax, 1), std::nullopt); |
| EXPECT_EQ(pw::CheckedAdd<T>(1, kMax), std::nullopt); |
| EXPECT_EQ(pw::CheckedAdd<T>(kMax, kMax), std::nullopt); |
| } |
| |
| TEST_FOR_STDINT_TYPES(CheckedAdd, Works) |
| TEST_FOR_STDINT_TYPES(CheckedAdd, DetectsOverflow) |
| |
| // pw::CheckedIncrement() |
| |
| template <typename T> |
| void CheckedIncrementWorks() { |
| constexpr T kBase = 100; |
| T val = kBase; |
| |
| ASSERT_TRUE(pw::CheckedIncrement(val, 0u)); |
| EXPECT_EQ(val, kBase); |
| |
| ASSERT_TRUE(pw::CheckedIncrement(val, 1u)); |
| EXPECT_EQ(val, kBase + 1); |
| |
| ASSERT_TRUE(pw::CheckedIncrement(val, 2u)); |
| EXPECT_EQ(val, kBase + 1 + 2); |
| } |
| |
| template <typename T> |
| void CheckedIncrementDetectsOverflow() { |
| static constexpr T kMin = std::numeric_limits<T>::min(); |
| static constexpr T kMax = std::numeric_limits<T>::max(); |
| |
| { |
| // kMax + 1 => overflow |
| static constexpr T kInitialVal = kMax; |
| T val = kInitialVal; |
| EXPECT_FALSE(pw::CheckedIncrement(val, 1)); |
| EXPECT_EQ(val, kInitialVal); // val is unchanged |
| } |
| |
| { |
| // 1 + kMax => overflow |
| static constexpr T kInitialVal = 1; |
| T val = kInitialVal; |
| EXPECT_FALSE(pw::CheckedIncrement(val, kMax)); |
| EXPECT_EQ(val, kInitialVal); // val is unchanged |
| } |
| |
| { |
| // kMin + (-1) => overflow |
| static constexpr T kInitialVal = kMin; |
| T val = kInitialVal; |
| EXPECT_FALSE(pw::CheckedIncrement(val, -1)); |
| EXPECT_EQ(val, kInitialVal); // val is unchanged |
| } |
| |
| { |
| // kHalfMax + kHalfMax => overflow |
| static constexpr T kHalfMax = (kMax / 2) + 1; |
| static constexpr T kInitialVal = kHalfMax; |
| T val = kInitialVal; |
| EXPECT_FALSE(pw::CheckedIncrement(val, kHalfMax)); |
| EXPECT_EQ(val, kInitialVal); // val is unchanged |
| } |
| } |
| |
| TEST_FOR_STDINT_TYPES(CheckedIncrement, Works) |
| TEST_FOR_STDINT_TYPES(CheckedIncrement, DetectsOverflow) |
| |
| // pw::CheckedSub() |
| |
| template <typename T> |
| void CheckedSubWorks() { |
| static constexpr T kMax = std::numeric_limits<T>::max(); |
| |
| EXPECT_EQ(pw::CheckedSub<T>(0, 0), 0); |
| EXPECT_EQ(pw::CheckedSub<T>(1, 0), 1); |
| EXPECT_EQ(pw::CheckedSub<T>(1, 1), 0); |
| EXPECT_EQ(pw::CheckedSub<T>(3, 2), 1); |
| EXPECT_EQ(pw::CheckedSub<T>(kMax, 1), kMax - 1); |
| } |
| |
| template <typename T> |
| void CheckedSubDetectsOverflow() { |
| static constexpr T kMax = std::numeric_limits<T>::max(); |
| static constexpr T kMin = std::numeric_limits<T>::min(); |
| |
| EXPECT_FALSE(pw::CheckedSub<T>(kMin, 1)); |
| EXPECT_FALSE(pw::CheckedSub<T>(kMin, kMax)); |
| } |
| |
| TEST_FOR_STDINT_TYPES(CheckedSub, Works) |
| TEST_FOR_STDINT_TYPES(CheckedSub, DetectsOverflow) |
| |
| // pw::CheckedDecrement() |
| |
| template <typename T> |
| void CheckedDecrementWorks() { |
| constexpr T kBase = 100; |
| T val = kBase; |
| |
| ASSERT_TRUE(pw::CheckedDecrement(val, 0u)); |
| EXPECT_EQ(val, kBase); |
| |
| ASSERT_TRUE(pw::CheckedDecrement(val, 1u)); |
| EXPECT_EQ(val, kBase - 1); |
| |
| ASSERT_TRUE(pw::CheckedDecrement(val, 2u)); |
| EXPECT_EQ(val, kBase - 1 - 2); |
| } |
| |
| template <typename T> |
| void CheckedDecrementDetectsOverflow() { |
| static constexpr T kMax = std::numeric_limits<T>::max(); |
| static constexpr T kMin = std::numeric_limits<T>::min(); |
| |
| { |
| // kMin - 1 => overflow |
| static constexpr T kInitialVal = kMin; |
| T val = kInitialVal; |
| EXPECT_FALSE(pw::CheckedDecrement(val, 1)); |
| EXPECT_EQ(val, kInitialVal); // val is unchanged |
| } |
| |
| { |
| // kMax - (-1) => overflow |
| static constexpr T kInitialVal = kMax; |
| T val = kInitialVal; |
| EXPECT_FALSE(pw::CheckedDecrement(val, -1)); |
| EXPECT_EQ(val, kInitialVal); // val is unchanged |
| } |
| } |
| |
| TEST_FOR_STDINT_TYPES(CheckedDecrement, Works) |
| TEST_FOR_STDINT_TYPES(CheckedDecrement, DetectsOverflow) |
| |
| // pw::CheckedMul() |
| |
| template <typename T> |
| void CheckedMulWorks() { |
| static constexpr T kMax = std::numeric_limits<T>::max(); |
| static constexpr T kMin = std::numeric_limits<T>::min(); |
| |
| EXPECT_EQ(pw::CheckedMul<T>(0, 0), 0); |
| EXPECT_EQ(pw::CheckedMul<T>(1, 0), 0); |
| EXPECT_EQ(pw::CheckedMul<T>(1, 1), 1); |
| EXPECT_EQ(pw::CheckedMul<T>(3, 2), 6); |
| EXPECT_EQ(pw::CheckedMul<T>(kMax, 1), kMax); |
| EXPECT_EQ(pw::CheckedMul<T>(kMin, 1), kMin); |
| } |
| |
| template <typename T> |
| void CheckedMulDetectsOverflow() { |
| static constexpr T kMax = std::numeric_limits<T>::max(); |
| static constexpr T kMin = std::numeric_limits<T>::min(); |
| |
| EXPECT_FALSE(pw::CheckedMul<T>(kMax, 2)); |
| if (std::is_signed_v<T>) { |
| EXPECT_FALSE(pw::CheckedMul<T>(kMin, 2)); |
| } |
| } |
| |
| TEST_FOR_STDINT_TYPES(CheckedMul, Works) |
| TEST_FOR_STDINT_TYPES(CheckedMul, DetectsOverflow) |
| |
| } // namespace |