Coverage Report

Created: 2026-06-10 06:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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_