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();
+}