/src/exiv2/src/safe_op.hpp
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | |
3 | | #ifndef SAFE_OP_HPP_ |
4 | | #define SAFE_OP_HPP_ |
5 | | |
6 | | #include <limits> |
7 | | #include <stdexcept> |
8 | | #include <type_traits> |
9 | | |
10 | | #ifdef _MSC_VER |
11 | | #include <Intsafe.h> |
12 | | #endif |
13 | | |
14 | | /*! |
15 | | * @brief Arithmetic operations with overflow checks |
16 | | */ |
17 | | namespace Safe { |
18 | | /*! |
19 | | * @brief Helper functions for providing integer overflow checks. |
20 | | * |
21 | | * This namespace contains internal helper functions fallback_$op_overflow |
22 | | * and builtin_$op_overflow (where $op is an arithmetic operation like add, |
23 | | * subtract, etc.). Both provide the following interface: |
24 | | * |
25 | | * bool fallback/builtin_$op_overflow(T first, T second, T& result); |
26 | | * |
27 | | * where T is an integer type. |
28 | | * |
29 | | * Each function performs checks whether first $op second can be safely |
30 | | * performed without overflows. If yes, the result is saved in result and |
31 | | * false is returned. Otherwise true is returned and the contents of result |
32 | | * are unspecified. |
33 | | * |
34 | | * fallback_$op_overflow implements a portable but slower overflow check. |
35 | | * builtin_$op_overflow uses compiler builtins (when available) and should |
36 | | * be faster. As builtins are not available for all types, |
37 | | * builtin_$op_overflow falls back to fallback_$op_overflow when no builtin |
38 | | * is available. |
39 | | */ |
40 | | namespace Internal { |
41 | | /*! |
42 | | * @brief Check the addition of two numbers for overflows for signed |
43 | | * integer types larger than int or with the same size as int. |
44 | | * |
45 | | * This function performs a check if summand_1 + summand_2 would |
46 | | * overflow and returns true in that case. If no overflow occurs, |
47 | | * the sum is saved in result and false is returned. |
48 | | * |
49 | | * @return true on overflow, false on no overflow |
50 | | * |
51 | | * @param[in] summand_1 The summand with is added |
52 | | * @param[in] summand_2 The summand with is added |
53 | | * @param[out] result Result of the addition, only populated when no |
54 | | * overflow occurs. |
55 | | * |
56 | | * Further information: |
57 | | * https://wiki.sei.cmu.edu/confluence/display/c/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow |
58 | | * https://wiki.sei.cmu.edu/confluence/display/c/INT02-C.+Understand+integer+conversion+rules |
59 | | * https://wiki.sei.cmu.edu/confluence/display/c/INT30-C.+Ensure+that+unsigned+integer+operations+do+not+wrap |
60 | | */ |
61 | | template <typename T> |
62 | | bool fallback_add_overflow(T summand_1, T summand_2, T& result) { |
63 | | if constexpr (std::is_signed_v<T> && sizeof(T) >= sizeof(int)) { |
64 | | if (((summand_2 >= 0) && (summand_1 > std::numeric_limits<T>::max() - summand_2)) || |
65 | | ((summand_2 < 0) && (summand_1 < std::numeric_limits<T>::min() - summand_2))) |
66 | | return true; |
67 | | result = summand_1 + summand_2; |
68 | | return false; |
69 | | } else if constexpr (std::is_signed_v<T> && sizeof(T) < sizeof(int)) { |
70 | | const int res = summand_1 + summand_2; |
71 | | if ((res > std::numeric_limits<T>::max()) || (res < std::numeric_limits<T>::min())) { |
72 | | return true; |
73 | | } |
74 | | result = static_cast<T>(res); |
75 | | return false; |
76 | | } else { |
77 | | result = summand_1 + summand_2; |
78 | | return result < summand_1; |
79 | | } |
80 | | } |
81 | | |
82 | | /*! |
83 | | * @brief Overflow addition check using compiler intrinsics. |
84 | | * |
85 | | * This function behaves exactly like fallback_add_overflow() but it |
86 | | * relies on compiler intrinsics instead. This version should be faster |
87 | | * than the fallback version as it can fully utilize available CPU |
88 | | * instructions & the compiler's diagnostic. |
89 | | * |
90 | | * However, as some compilers don't provide intrinsics for certain |
91 | | * types, the default implementation is the version from fallback. |
92 | | * |
93 | | * This function is fully specialized for each compiler. |
94 | | */ |
95 | | template <typename T> |
96 | 3.21M | bool builtin_add_overflow(T summand_1, T summand_2, T& result) { |
97 | 3.21M | #if (defined(__GNUC__) || defined(__clang__)) && (__GNUC__ >= 5 || __clang_major__ >= 3) |
98 | | if constexpr (std::is_same_v<T, int>) |
99 | | return __builtin_sadd_overflow(summand_1, summand_2, &result); |
100 | | else if constexpr (std::is_same_v<T, long>) |
101 | 28.2k | return __builtin_saddl_overflow(summand_1, summand_2, &result); |
102 | | else if constexpr (std::is_same_v<T, long long>) |
103 | | return __builtin_saddll_overflow(summand_1, summand_2, &result); |
104 | | else if constexpr (std::is_same_v<T, unsigned int>) |
105 | 5.38k | return __builtin_uadd_overflow(summand_1, summand_2, &result); |
106 | | else if constexpr (std::is_same_v<T, unsigned long>) |
107 | 3.17M | return __builtin_uaddl_overflow(summand_1, summand_2, &result); |
108 | | else if constexpr (std::is_same_v<T, unsigned long long>) |
109 | | return __builtin_uaddll_overflow(summand_1, summand_2, &result); |
110 | | else |
111 | | #endif |
112 | | return fallback_add_overflow(summand_1, summand_2, result); |
113 | 3.21M | } bool Safe::Internal::builtin_add_overflow<unsigned int>(unsigned int, unsigned int, unsigned int&) Line | Count | Source | 96 | 5.38k | bool builtin_add_overflow(T summand_1, T summand_2, T& result) { | 97 | 5.38k | #if (defined(__GNUC__) || defined(__clang__)) && (__GNUC__ >= 5 || __clang_major__ >= 3) | 98 | | if constexpr (std::is_same_v<T, int>) | 99 | | return __builtin_sadd_overflow(summand_1, summand_2, &result); | 100 | | else if constexpr (std::is_same_v<T, long>) | 101 | | return __builtin_saddl_overflow(summand_1, summand_2, &result); | 102 | | else if constexpr (std::is_same_v<T, long long>) | 103 | | return __builtin_saddll_overflow(summand_1, summand_2, &result); | 104 | | else if constexpr (std::is_same_v<T, unsigned int>) | 105 | 5.38k | return __builtin_uadd_overflow(summand_1, summand_2, &result); | 106 | | else if constexpr (std::is_same_v<T, unsigned long>) | 107 | | return __builtin_uaddl_overflow(summand_1, summand_2, &result); | 108 | | else if constexpr (std::is_same_v<T, unsigned long long>) | 109 | | return __builtin_uaddll_overflow(summand_1, summand_2, &result); | 110 | | else | 111 | | #endif | 112 | | return fallback_add_overflow(summand_1, summand_2, result); | 113 | 5.38k | } |
bool Safe::Internal::builtin_add_overflow<unsigned long>(unsigned long, unsigned long, unsigned long&) Line | Count | Source | 96 | 3.17M | bool builtin_add_overflow(T summand_1, T summand_2, T& result) { | 97 | 3.17M | #if (defined(__GNUC__) || defined(__clang__)) && (__GNUC__ >= 5 || __clang_major__ >= 3) | 98 | | if constexpr (std::is_same_v<T, int>) | 99 | | return __builtin_sadd_overflow(summand_1, summand_2, &result); | 100 | | else if constexpr (std::is_same_v<T, long>) | 101 | | return __builtin_saddl_overflow(summand_1, summand_2, &result); | 102 | | else if constexpr (std::is_same_v<T, long long>) | 103 | | return __builtin_saddll_overflow(summand_1, summand_2, &result); | 104 | | else if constexpr (std::is_same_v<T, unsigned int>) | 105 | | return __builtin_uadd_overflow(summand_1, summand_2, &result); | 106 | | else if constexpr (std::is_same_v<T, unsigned long>) | 107 | 3.17M | return __builtin_uaddl_overflow(summand_1, summand_2, &result); | 108 | | else if constexpr (std::is_same_v<T, unsigned long long>) | 109 | | return __builtin_uaddll_overflow(summand_1, summand_2, &result); | 110 | | else | 111 | | #endif | 112 | | return fallback_add_overflow(summand_1, summand_2, result); | 113 | 3.17M | } |
bool Safe::Internal::builtin_add_overflow<long>(long, long, long&) Line | Count | Source | 96 | 28.2k | bool builtin_add_overflow(T summand_1, T summand_2, T& result) { | 97 | 28.2k | #if (defined(__GNUC__) || defined(__clang__)) && (__GNUC__ >= 5 || __clang_major__ >= 3) | 98 | | if constexpr (std::is_same_v<T, int>) | 99 | | return __builtin_sadd_overflow(summand_1, summand_2, &result); | 100 | | else if constexpr (std::is_same_v<T, long>) | 101 | 28.2k | return __builtin_saddl_overflow(summand_1, summand_2, &result); | 102 | | else if constexpr (std::is_same_v<T, long long>) | 103 | | return __builtin_saddll_overflow(summand_1, summand_2, &result); | 104 | | else if constexpr (std::is_same_v<T, unsigned int>) | 105 | | return __builtin_uadd_overflow(summand_1, summand_2, &result); | 106 | | else if constexpr (std::is_same_v<T, unsigned long>) | 107 | | return __builtin_uaddl_overflow(summand_1, summand_2, &result); | 108 | | else if constexpr (std::is_same_v<T, unsigned long long>) | 109 | | return __builtin_uaddll_overflow(summand_1, summand_2, &result); | 110 | | else | 111 | | #endif | 112 | | return fallback_add_overflow(summand_1, summand_2, result); | 113 | 28.2k | } |
|
114 | | } // namespace Internal |
115 | | |
116 | | /*! |
117 | | * @brief Safe addition, throws an exception on overflow. |
118 | | * |
119 | | * This function returns the result of summand_1 and summand_2 only when the |
120 | | * operation would not overflow, otherwise an exception of type |
121 | | * std::overflow_error is thrown. |
122 | | * |
123 | | * @param[in] summand_1 summand to be summed up |
124 | | * @param[in] summand_2 summand to be summed up |
125 | | * @return the sum of summand_1 and summand_2 |
126 | | * @throws std::overflow_error if the addition would overflow |
127 | | * |
128 | | * This function utilizes compiler builtins when available and should have a |
129 | | * very small performance hit then. When builtins are unavailable, a more |
130 | | * extensive check is required. |
131 | | * |
132 | | * Builtins are available for the following configurations: |
133 | | * - GCC/Clang for signed and unsigned int, long and long long (not char & short) |
134 | | * - MSVC for unsigned int, long and long long |
135 | | */ |
136 | | template <typename T> |
137 | 3.21M | T add(T summand_1, T summand_2) { |
138 | 3.21M | T res = 0; |
139 | 3.21M | if (Internal::builtin_add_overflow(summand_1, summand_2, res)) { |
140 | 37 | throw std::overflow_error("Overflow in addition"); |
141 | 37 | } |
142 | 3.21M | return res; |
143 | 3.21M | } unsigned int Safe::add<unsigned int>(unsigned int, unsigned int) Line | Count | Source | 137 | 5.38k | T add(T summand_1, T summand_2) { | 138 | 5.38k | T res = 0; | 139 | 5.38k | if (Internal::builtin_add_overflow(summand_1, summand_2, res)) { | 140 | 31 | throw std::overflow_error("Overflow in addition"); | 141 | 31 | } | 142 | 5.35k | return res; | 143 | 5.38k | } |
unsigned long Safe::add<unsigned long>(unsigned long, unsigned long) Line | Count | Source | 137 | 3.17M | T add(T summand_1, T summand_2) { | 138 | 3.17M | T res = 0; | 139 | 3.17M | if (Internal::builtin_add_overflow(summand_1, summand_2, res)) { | 140 | 4 | throw std::overflow_error("Overflow in addition"); | 141 | 4 | } | 142 | 3.17M | return res; | 143 | 3.17M | } |
long Safe::add<long>(long, long) Line | Count | Source | 137 | 28.2k | T add(T summand_1, T summand_2) { | 138 | 28.2k | T res = 0; | 139 | 28.2k | if (Internal::builtin_add_overflow(summand_1, summand_2, res)) { | 140 | 2 | throw std::overflow_error("Overflow in addition"); | 141 | 2 | } | 142 | 28.2k | return res; | 143 | 28.2k | } |
|
144 | | |
145 | | /*! |
146 | | * @brief Calculates the absolute value of a number without producing |
147 | | * negative values. |
148 | | * |
149 | | * The "standard" implementation of `abs(num)` (`num < 0 ? -num : num`) |
150 | | * produces negative values when `num` is the smallest negative number. This |
151 | | * is caused by `-1 * INTMAX = INTMIN + 1`, i.e. the real result of |
152 | | * `abs(INTMIN)` overflows the integer type and results in `INTMIN` again |
153 | | * (this is not guaranteed as it invokes undefined behavior). |
154 | | * |
155 | | * This function does not exhibit this behavior, it returns |
156 | | * `std::numeric_limits<T>::max()` when the input is |
157 | | * `std::numeric_limits<T>::min()`. The downside of this is that two |
158 | | * negative values produce the same absolute value: |
159 | | * `std::numeric_limits<T>::min()` and `std::numeric_limits<T>::min() + 1`. |
160 | | * |
161 | | * @tparam T a signed integer type |
162 | | * @param[in] num The number which absolute value should be computed. |
163 | | * @throws Never throws an exception. |
164 | | * @return The absolute value of `num` or `std::numeric_limits<T>::max()` |
165 | | * when `num == std::numeric_limits<T>::min()`. |
166 | | */ |
167 | | template <typename T> |
168 | | T abs(T num) noexcept { |
169 | | if constexpr (std::is_signed_v<T>) { |
170 | | if (num == std::numeric_limits<T>::min()) |
171 | | return std::numeric_limits<T>::max(); |
172 | | return num < 0 ? -num : num; |
173 | | } else { |
174 | | return num; |
175 | | } |
176 | | } |
177 | | |
178 | | } // namespace Safe |
179 | | |
180 | | #endif // SAFE_OP_HPP_ |