diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 96f06550..908d55b1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -140,6 +140,7 @@ set( LIBEXIV2_PRIVATE_HDR canonmn_int.hpp tifffwd_int.hpp tiffimage_int.hpp tiffvisitor_int.hpp + safe_op.hpp ) diff --git a/src/safe_op.hpp b/src/safe_op.hpp new file mode 100644 index 00000000..014b7f3a --- /dev/null +++ b/src/safe_op.hpp @@ -0,0 +1,310 @@ +// ********************************************************* -*- C++ -*- +/* + * Copyright (C) 2004-2017 Exiv2 maintainers + * + * This program is part of the Exiv2 distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. + */ +/*! + @file safe_op.hpp + @brief Overflow checks for integers + @author Dan Čermák (D4N) + dan.cermak@cgc-instruments.com + @date 14-Dec-17, D4N: created + */ + +#ifndef SAFE_OP_HPP_ +#define SAFE_OP_HPP_ + +#include +#include + +#ifdef _MSC_VER +#include +#endif + +/*! + * @brief Arithmetic operations with overflow checks + */ +namespace Safe +{ + /*! + * @brief Helper structs for providing integer overflow checks. + * + * This namespace contains the internal helper structs fallback_add_overflow + * and builtin_add_overflow. Both have a public static member function add + * with the following interface: + * + * bool add(T summand_1, T summand_2, T& result) + * + * where T is the type over which the struct is templated. + * + * The function performs a check whether the addition summand_1 + summand_2 + * can be performed without an overflow. If the operation would overflow, + * true is returned and the addition is not performed if it would result in + * undefined behavior. If no overflow occurs, the sum is saved in result and + * false is returned. + * + * fallback_add_overflow implements a portable but slower overflow check. + * builtin_add_overflow uses compiler builtins (when available) and should + * be considerably faster. As builtins are not available for all types, + * builtin_add_overflow falls back to fallback_add_overflow when no builtin + * is available. + */ + namespace Internal + { + /*! + * @brief Helper struct to determine whether a type is signed or unsigned + + * This struct is a backport of std::is_signed from C++11. It has a public + * enum with the property VALUE which is true when the type is signed or + * false if it is unsigned. + */ + template + struct is_signed + { + enum + { + VALUE = T(-1) < T(0) + }; + }; + + /*! + * @brief Helper struct for SFINAE, from C++11 + + * This struct has a public typedef called type typedef'd to T if B is + * true. Otherwise there is no typedef. + */ + template + struct enable_if + { + }; + + /*! + * @brief Specialization of enable_if for the case B == true + */ + template + struct enable_if + { + typedef T type; + }; + + /*! + * @brief Fallback overflow checker, specialized via SFINAE + * + * This struct implements a 'fallback' addition with an overflow check, + * i.e. it does not rely on compiler intrinsics. It is specialized via + * SFINAE for signed and unsigned integer types and provides a public + * static member function add. + */ + template + struct fallback_add_overflow; + + /*! + * @brief Overload of fallback_add_overflow for signed integers + */ + template + struct fallback_add_overflow::VALUE>::type> + { + /*! + * @brief Adds the two summands only if no overflow occurs + * + * This function performs a check if summand_1 + summand_2 would + * overflow and returns true in that case. If no overflow occurs, + * the sum is saved in result and false is returned. + * + * @return true on overflow, false on no overflow + * + * The check for an overflow is performed before the addition to + * ensure that no undefined behavior occurs. The value in result is + * only valid when the function returns false. + * + * Further information: + * https://wiki.sei.cmu.edu/confluence/display/c/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow + */ + static bool add(T summand_1, T summand_2, T& result) + { + if (((summand_2 >= 0) && (summand_1 > std::numeric_limits::max() - summand_2)) || + ((summand_2 < 0) && (summand_1 < std::numeric_limits::min() - summand_2))) { + return true; + } else { + result = summand_1 + summand_2; + return false; + } + } + }; + + /*! + * @brief Overload of fallback_add_overflow for unsigned integers + */ + template + struct fallback_add_overflow::VALUE>::type> + { + /*! + * @brief Adds the two summands only if no overflow occurs + * + * This function performs a check if summand_1 + summand_2 would + * overflow and returns true in that case. If no overflow occurs, + * the sum is saved in result and false is returned. + * + * @return true on overflow, false on no overflow + * + * Further information: + * https://wiki.sei.cmu.edu/confluence/display/c/INT30-C.+Ensure+that+unsigned+integer+operations+do+not+wrap + */ + static bool add(T summand_1, T summand_2, T& result) + { + if (summand_1 > std::numeric_limits::max() - summand_2) { + return true; + } else { + result = summand_1 + summand_2; + return false; + } + } + }; + + /*! + * @brief Overflow checker using compiler intrinsics + * + * This struct provides an add function with the same interface & + * behavior as fallback_add_overload::add but it relies on compiler + * intrinsics instead. This version should be considerably faster than + * the fallback version as it can fully utilize available CPU + * instructions & the compiler's diagnostic. + * + * However, as some compilers don't provide intrinsics for certain + * types, the default implementation of add is the version from falback. + * + * The struct is explicitly specialized for each type via #ifdefs for + * each compiler. + */ + template + struct builtin_add_overflow + { + /*! + * @brief Add summand_1 and summand_2 and check for overflows. + * + * This is the default add() function that uses + * fallback_add_overflow::add(). All specializations must have + * exactly the same interface and behave the same way. + */ + static inline bool add(T summand_1, T summand_2, T& result) + { + return fallback_add_overflow::add(summand_1, summand_2, result); + } + }; + +#if defined(__GNUC__) || defined(__clang__) +#if __GNUC__ >= 5 + +/*! + * This macro pastes a specialization of builtin_add_overflow using gcc's & + * clang's __builtin_(s/u)add(l)(l)_overlow() + * + * The add function is implemented by forwarding the parameters to the intrinsic + * and returning its value. + * + * The intrinsics are documented here: + * https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html#Integer-Overflow-Builtins + */ +#define SPECIALIZE_builtin_add_overflow(type, builtin_name) \ + template <> \ + struct builtin_add_overflow \ + { \ + static inline bool add(type summand_1, type summand_2, type& result) \ + { \ + return builtin_name(summand_1, summand_2, &result); \ + } \ + } + + SPECIALIZE_builtin_add_overflow(int, __builtin_sadd_overflow); + SPECIALIZE_builtin_add_overflow(long, __builtin_saddl_overflow); + SPECIALIZE_builtin_add_overflow(long long, __builtin_saddll_overflow); + + SPECIALIZE_builtin_add_overflow(unsigned int, __builtin_uadd_overflow); + SPECIALIZE_builtin_add_overflow(unsigned long, __builtin_uaddl_overflow); + SPECIALIZE_builtin_add_overflow(unsigned long long, __builtin_uaddll_overflow); + +#undef SPECIALIZE_builtin_add_overflow +#endif + +#elif defined(_MSC_VER) + +/*! + * This macro pastes a specialization of builtin_add_overflow using MSVC's + * U(Int/Long/LongLong)Add. + * + * The add function is implemented by forwarding the parameters to the + * intrinsic. As MSVC's intrinsics return S_OK on success, this specialization + * returns whether the intrinsics return value does not equal S_OK. This ensures + * a uniform interface of the add function (false is returned when no overflow + * occurs, true on overflow). + * + * The intrinsics are documented here: + * https://msdn.microsoft.com/en-us/library/windows/desktop/ff516460(v=vs.85).aspx + */ +#define SPECIALIZE_builtin_add_overflow_WIN(type, builtin_name) \ + template <> \ + struct builtin_add_overflow \ + { \ + static inline bool add(type summand_1, type summand_2, type& result) \ + { \ + return builtin_name(summand_1, summand_2, &result) != S_OK; \ + } \ + } + + SPECIALIZE_builtin_add_overflow_WIN(unsigned int, UIntAdd); + SPECIALIZE_builtin_add_overflow_WIN(unsigned long, ULongAdd); + SPECIALIZE_builtin_add_overflow_WIN(unsigned long long, ULongLongAdd); + +#undef SPECIALIZE_builtin_add_overflow_WIN + +#endif + + } // namespace Internal + + /*! + * @brief Safe addition, throws an exception on overflow. + * + * This function returns the result of summand_1 and summand_2 only when the + * operation would not overflow, otherwise an exception of type + * std::overflow_error is thrown. + * + * @param[in] summand_1, summand_2 summands to be summed up + * @return the sum of summand_1 and summand_2 + * @throws std::overflow_error if the addition would overflow + * + * This function utilizes compiler builtins when available and should have a + * very small performance hit then. When builtins are unavailable, a more + * extensive check is required. + * + * Builtins are available for the following configurations: + * - GCC/Clang for signed and unsigned int, long and long long (not char & short) + * - MSVC for unsigned int, long and long long + */ + template + T add(T summand_1, T summand_2) + { + T res = 0; + if (Internal::builtin_add_overflow::add(summand_1, summand_2, res)) { + throw std::overflow_error("Overflow in addition"); + } + return res; + } + +} // namespace Safe + +#endif // SAFE_OP_HPP_ diff --git a/unitTests/CMakeLists.txt b/unitTests/CMakeLists.txt index 5b62c260..d2e3dd01 100644 --- a/unitTests/CMakeLists.txt +++ b/unitTests/CMakeLists.txt @@ -3,6 +3,7 @@ add_executable(unit_tests mainTestRunner.cpp test_types.cpp test_tiffheader.cpp test_futils.cpp + test_safe_op.cpp ) #TODO Use GTest::GTest once we upgrade the minimum CMake version required diff --git a/unitTests/test_safe_op.cpp b/unitTests/test_safe_op.cpp new file mode 100644 index 00000000..83f76038 --- /dev/null +++ b/unitTests/test_safe_op.cpp @@ -0,0 +1,183 @@ +#include + +#include "gtestwrapper.h" + +namespace si = Safe::Internal; + +/*! + * struct that contains test numbers for Safe::add. Since different test values + * are required for signed and unsigned types, this struct is specialized via + * SFINAE. + * + * Each overload has two arrays: + * T summand[] - the test numbers + * bool overflow[][] - overflow[i][j] indicates whether summand[i] + summand[j] + * is expected to overflow + * + * The numbers in summand are chosen accordingly to test all "interesting" cases + * and cause some overflows. The actual test should simply check all possible + * sums of summand against the value in overflow[][] + */ +template +struct AdditionTestValues; + +/*! + * Overload for unsigned types. + */ +template +struct AdditionTestValues::VALUE>::type> +{ + static const size_t case_count = 5; + static const T summand[case_count]; + static const bool overflow[case_count][case_count]; +}; + +template +const T AdditionTestValues::VALUE>::type>::summand[] = { + 0, 1, 2, static_cast(std::numeric_limits::max() - 1), std::numeric_limits::max()}; + +template +const bool + AdditionTestValues::VALUE>::type>::overflow[case_count][case_count] = { + // 0 + {false, false, false, false, false}, + // 1 + {false, false, false, false, true}, + // 2 + {false, false, false, true, true}, + // max - 1 + {false, false, true, true, true}, + // max + {false, true, true, true, true}}; + +/*! + * Overload for signed integers + */ +template +struct AdditionTestValues::VALUE>::type> +{ + static const size_t case_count = 8; + static const T summand[case_count]; + static const bool overflow[case_count][case_count]; +}; + +template +const T AdditionTestValues::VALUE>::type>::summand[] = { + std::numeric_limits::min(), + static_cast(std::numeric_limits::min() + 1), + -1, + 0, + 1, + 2, + static_cast(std::numeric_limits::max() - 1), + std::numeric_limits::max()}; + +template +const bool + AdditionTestValues::VALUE>::type>::overflow[case_count][case_count] = { + // min + {true, true, true, false, false, false, false, false}, + // min + 1 + {true, true, false, false, false, false, false, false}, + // -1 + {true, false, false, false, false, false, false, false}, + // 0 + {false, false, false, false, false, false, false, false}, + // 1 + {false, false, false, false, false, false, false, true}, + // 2 + {false, false, false, false, false, false, true, true}, + // max - 1 + {false, false, false, false, false, true, true, true}, + // max + {false, false, false, false, true, true, true, true}}; + +/*! + * Test the addition of all combinations of AdditionTestValues::summand[i], + * AdditionTestValues::summand[j] using fallback_add_overflow::add and + * builtin_add_overflow::add. The return value is checked against + * AdditionTestValues::overflow[i][j]. If no overflow occurs, then the result + * is checked too. + */ +template +void test_add() +{ + typedef AdditionTestValues TestValues; + +#define TEST_ADD(func) \ + for (size_t i = 0; i < TestValues::case_count; ++i) { \ + for (size_t j = 0; j < TestValues::case_count; ++j) { \ + T res = 0; \ + ASSERT_EQ(func(TestValues::summand[i], TestValues::summand[j], res), TestValues::overflow[i][j]); \ + if (!TestValues::overflow[i][j]) { \ + ASSERT_EQ(res, TestValues::summand[i] + TestValues::summand[j]); \ + } \ + } \ + } + + TEST_ADD(si::fallback_add_overflow::add) + TEST_ADD(si::builtin_add_overflow::add) + +#undef TEST_ADD +} + +/*! + * Test the addition of all combinations of AdditionTestValues::summand[i], + * AdditionTestValues::summand[j] using Safe::add. + * + * It is checked whether an exception is thrown when + * AdditionTestValues::overflow[i][j] is true, otherwise the result is + * checked. + */ +template +void test_safe_add() +{ + typedef AdditionTestValues TestValues; + + for (size_t i = 0; i < TestValues::case_count; ++i) { + for (size_t j = 0; j < TestValues::case_count; ++j) { + if (TestValues::overflow[i][j]) { + ASSERT_THROW(Safe::add(TestValues::summand[i], TestValues::summand[j]), std::overflow_error); + } else { + ASSERT_EQ(Safe::add(TestValues::summand[i], TestValues::summand[j]), + TestValues::summand[i] + TestValues::summand[j]); + } + } + } +} + +TEST(lowLevelAddOverflow, checkUnsignedOverflow) +{ + test_add(); + test_add(); + test_add(); + test_add(); + test_add(); +} + +TEST(lowLevelAddOverflow, checkSignedOverflow) +{ + test_add(); + test_add(); + test_add(); + test_add(); + test_add(); +} + +TEST(safeAdd, checkUnsignedOverflow) +{ + test_safe_add(); + test_safe_add(); + test_safe_add(); + test_safe_add(); + test_safe_add(); +} + +TEST(safeAdd, checkSignedOverflow) +{ + test_safe_add(); + test_safe_add(); + test_safe_add(); + test_safe_add(); + test_safe_add(); +}