Coverage Report

Created: 2026-09-14 06:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dng_sdk/source/dng_safe_arithmetic.cpp
Line
Count
Source
1
#include "dng_safe_arithmetic.h"
2
3
#include <cmath>
4
#include <limits>
5
6
#include "dng_exceptions.h"
7
8
// Implementation of safe integer arithmetic follows guidelines from
9
// https://www.securecoding.cert.org/confluence/display/c/INT30-C.+Ensure+that+unsigned+integer+operations+do+not+wrap
10
// and
11
// https://www.securecoding.cert.org/confluence/display/c/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow
12
13
namespace {
14
15
// Template functions for safe arithmetic. These functions are not exposed in
16
// the header for the time being to avoid having to add checks for the various
17
// constraints on the template argument (e.g. that it is integral and possibly
18
// signed or unsigned only). This should be done using a static_assert(), but
19
// we want to be portable to pre-C++11 compilers.
20
21
// Returns the result of adding arg1 and arg2 if it will fit in a T (where T is
22
// a signed or unsigned integer type). Otherwise, throws a dng_exception with
23
// error code dng_error_unknown.
24
template <class T>
25
554M
T SafeAdd(T arg1, T arg2) {
26
  // The condition is reformulated relative to the version on
27
  // www.securecoding.cert.org to check for valid instead of invalid cases. It
28
  // seems safer to enumerate the valid cases (and potentially miss one) than
29
  // enumerate the invalid cases.
30
  // If T is an unsigned type, the second half of the condition always evaluates
31
  // to false and will presumably be compiled out by the compiler.
32
554M
  if ((arg1 >= 0 && arg2 <= std::numeric_limits<T>::max() - arg1) ||
33
554M
      (arg1 < 0 && arg2 >= std::numeric_limits<T>::min() - arg1)) {
34
554M
    return arg1 + arg2;
35
554M
  } else {
36
1.51k
    ThrowProgramError("Arithmetic overflow");
37
1.51k
    abort();  // Never reached.
38
1.51k
  }
39
554M
}
dng_safe_arithmetic.cpp:int (anonymous namespace)::SafeAdd<int>(int, int)
Line
Count
Source
25
3.72M
T SafeAdd(T arg1, T arg2) {
26
  // The condition is reformulated relative to the version on
27
  // www.securecoding.cert.org to check for valid instead of invalid cases. It
28
  // seems safer to enumerate the valid cases (and potentially miss one) than
29
  // enumerate the invalid cases.
30
  // If T is an unsigned type, the second half of the condition always evaluates
31
  // to false and will presumably be compiled out by the compiler.
32
3.72M
  if ((arg1 >= 0 && arg2 <= std::numeric_limits<T>::max() - arg1) ||
33
3.72M
      (arg1 < 0 && arg2 >= std::numeric_limits<T>::min() - arg1)) {
34
3.72M
    return arg1 + arg2;
35
3.72M
  } else {
36
0
    ThrowProgramError("Arithmetic overflow");
37
0
    abort();  // Never reached.
38
0
  }
39
3.72M
}
dng_safe_arithmetic.cpp:long (anonymous namespace)::SafeAdd<long>(long, long)
Line
Count
Source
25
539M
T SafeAdd(T arg1, T arg2) {
26
  // The condition is reformulated relative to the version on
27
  // www.securecoding.cert.org to check for valid instead of invalid cases. It
28
  // seems safer to enumerate the valid cases (and potentially miss one) than
29
  // enumerate the invalid cases.
30
  // If T is an unsigned type, the second half of the condition always evaluates
31
  // to false and will presumably be compiled out by the compiler.
32
539M
  if ((arg1 >= 0 && arg2 <= std::numeric_limits<T>::max() - arg1) ||
33
539M
      (arg1 < 0 && arg2 >= std::numeric_limits<T>::min() - arg1)) {
34
539M
    return arg1 + arg2;
35
539M
  } else {
36
0
    ThrowProgramError("Arithmetic overflow");
37
0
    abort();  // Never reached.
38
0
  }
39
539M
}
dng_safe_arithmetic.cpp:unsigned int (anonymous namespace)::SafeAdd<unsigned int>(unsigned int, unsigned int)
Line
Count
Source
25
9.76M
T SafeAdd(T arg1, T arg2) {
26
  // The condition is reformulated relative to the version on
27
  // www.securecoding.cert.org to check for valid instead of invalid cases. It
28
  // seems safer to enumerate the valid cases (and potentially miss one) than
29
  // enumerate the invalid cases.
30
  // If T is an unsigned type, the second half of the condition always evaluates
31
  // to false and will presumably be compiled out by the compiler.
32
9.76M
  if ((arg1 >= 0 && arg2 <= std::numeric_limits<T>::max() - arg1) ||
33
9.76M
      (arg1 < 0 && arg2 >= std::numeric_limits<T>::min() - arg1)) {
34
9.76M
    return arg1 + arg2;
35
9.76M
  } else {
36
1.49k
    ThrowProgramError("Arithmetic overflow");
37
1.49k
    abort();  // Never reached.
38
1.49k
  }
39
9.76M
}
dng_safe_arithmetic.cpp:unsigned long (anonymous namespace)::SafeAdd<unsigned long>(unsigned long, unsigned long)
Line
Count
Source
25
1.65M
T SafeAdd(T arg1, T arg2) {
26
  // The condition is reformulated relative to the version on
27
  // www.securecoding.cert.org to check for valid instead of invalid cases. It
28
  // seems safer to enumerate the valid cases (and potentially miss one) than
29
  // enumerate the invalid cases.
30
  // If T is an unsigned type, the second half of the condition always evaluates
31
  // to false and will presumably be compiled out by the compiler.
32
1.65M
  if ((arg1 >= 0 && arg2 <= std::numeric_limits<T>::max() - arg1) ||
33
1.65M
      (arg1 < 0 && arg2 >= std::numeric_limits<T>::min() - arg1)) {
34
1.65M
    return arg1 + arg2;
35
1.65M
  } else {
36
25
    ThrowProgramError("Arithmetic overflow");
37
25
    abort();  // Never reached.
38
25
  }
39
1.65M
}
40
41
// Returns the result of multiplying arg1 and arg2 if it will fit in a T (where
42
// T is an unsigned integer type). Otherwise, throws a dng_exception with error
43
// code dng_error_unknown.
44
template <class T>
45
74.4M
T SafeUnsignedMult(T arg1, T arg2) {
46
74.4M
  if (arg1 == 0 || arg2 <= std::numeric_limits<T>::max() / arg1) {
47
74.3M
    return arg1 * arg2;
48
74.3M
  } else {
49
29.0k
    ThrowProgramError("Arithmetic overflow");
50
29.0k
    abort();  // Never reached.
51
29.0k
  }
52
74.4M
}
dng_safe_arithmetic.cpp:unsigned int (anonymous namespace)::SafeUnsignedMult<unsigned int>(unsigned int, unsigned int)
Line
Count
Source
45
73.9M
T SafeUnsignedMult(T arg1, T arg2) {
46
73.9M
  if (arg1 == 0 || arg2 <= std::numeric_limits<T>::max() / arg1) {
47
73.9M
    return arg1 * arg2;
48
73.9M
  } else {
49
29.0k
    ThrowProgramError("Arithmetic overflow");
50
29.0k
    abort();  // Never reached.
51
29.0k
  }
52
73.9M
}
dng_safe_arithmetic.cpp:unsigned long (anonymous namespace)::SafeUnsignedMult<unsigned long>(unsigned long, unsigned long)
Line
Count
Source
45
450k
T SafeUnsignedMult(T arg1, T arg2) {
46
450k
  if (arg1 == 0 || arg2 <= std::numeric_limits<T>::max() / arg1) {
47
450k
    return arg1 * arg2;
48
450k
  } else {
49
0
    ThrowProgramError("Arithmetic overflow");
50
0
    abort();  // Never reached.
51
0
  }
52
450k
}
53
54
}  // namespace
55
56
0
bool SafeInt32Add(std::int32_t arg1, std::int32_t arg2, std::int32_t *result) {
57
0
  try {
58
0
    *result = SafeInt32Add(arg1, arg2);
59
0
    return true;
60
0
  } catch (const dng_exception &) {
61
0
    return false;
62
0
  }
63
0
}
64
65
3.72M
std::int32_t SafeInt32Add(std::int32_t arg1, std::int32_t arg2) {
66
3.72M
  return SafeAdd<std::int32_t>(arg1, arg2);
67
3.72M
}
68
69
539M
std::int64_t SafeInt64Add(std::int64_t arg1, std::int64_t arg2) {
70
539M
  return SafeAdd<std::int64_t>(arg1, arg2);
71
539M
}
72
73
bool SafeUint32Add(std::uint32_t arg1, std::uint32_t arg2,
74
636k
                   std::uint32_t *result) {
75
636k
  try {
76
636k
    *result = SafeUint32Add(arg1, arg2);
77
636k
    return true;
78
636k
  } catch (const dng_exception &) {
79
691
    return false;
80
691
  }
81
636k
}
82
83
9.76M
std::uint32_t SafeUint32Add(std::uint32_t arg1, std::uint32_t arg2) {
84
9.76M
  return SafeAdd<std::uint32_t>(arg1, arg2);
85
9.76M
}
86
87
1.65M
std::uint64_t SafeUint64Add(std::uint64_t arg1, std::uint64_t arg2) {
88
1.65M
  return SafeAdd<std::uint64_t>(arg1, arg2);
89
1.65M
}
90
91
49.0M
bool SafeInt32Sub(std::int32_t arg1, std::int32_t arg2, std::int32_t *result) {
92
49.0M
  if ((arg2 >= 0 && arg1 >= std::numeric_limits<int32_t>::min() + arg2) ||
93
49.0M
      (arg2 < 0 && arg1 <= std::numeric_limits<int32_t>::max() + arg2)) {
94
49.0M
    *result = arg1 - arg2;
95
49.0M
    return true;
96
49.0M
  } else {
97
264
    return false;
98
264
  }
99
49.0M
}
100
101
1.24M
std::int32_t SafeInt32Sub(std::int32_t arg1, std::int32_t arg2) {
102
1.24M
  std::int32_t result = 0;
103
104
1.24M
  if (!SafeInt32Sub(arg1, arg2, &result)) {
105
0
    ThrowProgramError("Arithmetic overflow");
106
0
  }
107
108
1.24M
  return result;
109
1.24M
}
110
111
2.16M
std::uint32_t SafeUint32Sub(std::uint32_t arg1, std::uint32_t arg2) {
112
2.16M
  if (arg1 >= arg2) {
113
2.14M
    return arg1 - arg2;
114
2.14M
  } else {
115
20.1k
    ThrowProgramError("Arithmetic overflow");
116
20.1k
    abort();  // Never reached.
117
20.1k
  }
118
2.16M
}
119
120
bool SafeUint32Mult(std::uint32_t arg1, std::uint32_t arg2,
121
7.55M
                    std::uint32_t *result) {
122
7.55M
  try {
123
7.55M
    *result = SafeUint32Mult(arg1, arg2);
124
7.55M
    return true;
125
7.55M
  } catch (const dng_exception &) {
126
393
    return false;
127
393
  }
128
7.55M
}
129
130
bool SafeUint32Mult(std::uint32_t arg1, std::uint32_t arg2, std::uint32_t arg3,
131
0
                    std::uint32_t *result) {
132
0
  try {
133
0
    *result = SafeUint32Mult(arg1, arg2, arg3);
134
0
    return true;
135
0
  } catch (const dng_exception &) {
136
0
    return false;
137
0
  }
138
0
}
139
140
bool SafeUint32Mult(std::uint32_t arg1, std::uint32_t arg2, std::uint32_t arg3,
141
0
                    std::uint32_t arg4, std::uint32_t *result) {
142
0
  try {
143
0
    *result = SafeUint32Mult(arg1, arg2, arg3, arg4);
144
0
    return true;
145
0
  } catch (const dng_exception &) {
146
0
    return false;
147
0
  }
148
0
}
149
150
73.9M
std::uint32_t SafeUint32Mult(std::uint32_t arg1, std::uint32_t arg2) {
151
73.9M
  return SafeUnsignedMult<std::uint32_t>(arg1, arg2);
152
73.9M
}
153
154
std::uint32_t SafeUint32Mult(std::uint32_t arg1, std::uint32_t arg2,
155
1.07M
                             std::uint32_t arg3) {
156
1.07M
  return SafeUint32Mult(SafeUint32Mult(arg1, arg2), arg3);
157
1.07M
}
158
159
std::uint32_t SafeUint32Mult(std::uint32_t arg1, std::uint32_t arg2,
160
971k
                             std::uint32_t arg3, std::uint32_t arg4) {
161
971k
  return SafeUint32Mult(SafeUint32Mult(arg1, arg2, arg3), arg4);
162
971k
}
163
164
2.76k
std::int32_t SafeInt32Mult(std::int32_t arg1, std::int32_t arg2) {
165
2.76k
  const std::int64_t tmp =
166
2.76k
      static_cast<std::int64_t>(arg1) * static_cast<std::int64_t>(arg2);
167
2.76k
  if (tmp >= std::numeric_limits<std::int32_t>::min() &&
168
2.69k
      tmp <= std::numeric_limits<std::int32_t>::max()) {
169
2.57k
    return static_cast<std::int32_t>(tmp);
170
2.57k
  } else {
171
196
    ThrowProgramError("Arithmetic overflow");
172
196
    abort();
173
196
  }
174
2.76k
}
175
176
450k
std::size_t SafeSizetMult(std::size_t arg1, std::size_t arg2) {
177
450k
  return SafeUnsignedMult<std::size_t>(arg1, arg2);
178
450k
}
179
180
namespace dng_internal {
181
182
0
std::int64_t SafeInt64MultSlow(std::int64_t arg1, std::int64_t arg2) {
183
0
  bool overflow = true;
184
185
0
  if (arg1 > 0) {
186
0
    if (arg2 > 0) {
187
0
      overflow = (arg1 > std::numeric_limits<std::int64_t>::max() / arg2);
188
0
    } else {
189
0
      overflow = (arg2 < std::numeric_limits<std::int64_t>::min() / arg1);
190
0
    }
191
0
  } else {
192
0
    if (arg2 > 0) {
193
0
      overflow = (arg1 < std::numeric_limits<std::int64_t>::min() / arg2);
194
0
    } else {
195
0
      overflow = (arg1 != 0 &&
196
0
                  arg2 < std::numeric_limits<std::int64_t>::max() / arg1);
197
0
    }
198
0
  }
199
200
0
  if (overflow) {
201
0
    ThrowProgramError("Arithmetic overflow");
202
0
    abort();  // Never reached.
203
0
  } else {
204
0
    return arg1 * arg2;
205
0
  }
206
0
}
207
208
}  // namespace dng_internal
209
210
287k
std::uint32_t SafeUint32DivideUp(std::uint32_t arg1, std::uint32_t arg2) {
211
  // It might seem more intuitive to implement this function simply as
212
  //
213
  //   return arg2 == 0 ? 0 : (arg1 + arg2 - 1) / arg2;
214
  //
215
  // but the expression "arg1 + arg2" can wrap around.
216
217
287k
  if (arg2 == 0) {
218
0
    ThrowProgramError("Division by zero");
219
0
    abort();  // Never reached.
220
287k
  } else if (arg1 == 0) {
221
    // If arg1 is zero, return zero to avoid wraparound in the expression
222
    //   "arg1 - 1" below.
223
644
    return 0;
224
287k
  } else {
225
287k
    return (arg1 - 1) / arg2 + 1;
226
287k
  }
227
287k
}
228
229
bool RoundUpUint32ToMultiple(std::uint32_t val, std::uint32_t multiple_of,
230
2.58M
                             std::uint32_t *result) {
231
2.58M
  try {
232
2.58M
    *result = RoundUpUint32ToMultiple(val, multiple_of);
233
2.58M
    return true;
234
2.58M
  } catch (const dng_exception &) {
235
0
    return false;
236
0
  }
237
2.58M
}
238
239
std::uint32_t RoundUpUint32ToMultiple(std::uint32_t val,
240
2.62M
                                      std::uint32_t multiple_of) {
241
2.62M
  if (multiple_of == 0) {
242
0
    ThrowProgramError("multiple_of is zero in RoundUpUint32ToMultiple");
243
0
  }
244
245
2.62M
  const std::uint32_t remainder = val % multiple_of;
246
2.62M
  if (remainder == 0) {
247
1.74M
    return val;
248
1.74M
  } else {
249
878k
    return SafeUint32Add(val, multiple_of - remainder);
250
878k
  }
251
2.62M
}
252
253
8.63M
bool ConvertUint32ToInt32(std::uint32_t val, std::int32_t *result) {
254
8.63M
  try {
255
8.63M
    *result = ConvertUint32ToInt32(val);
256
8.63M
    return true;
257
8.63M
  } catch (const dng_exception &) {
258
22
    return false;
259
22
  }
260
8.63M
}
261
262
9.92M
std::int32_t ConvertUint32ToInt32(std::uint32_t val) {
263
9.92M
  const std::uint32_t kInt32MaxAsUint32 =
264
9.92M
      static_cast<std::uint32_t>(std::numeric_limits<std::int32_t>::max());
265
266
9.92M
  if (val <= kInt32MaxAsUint32) {
267
9.92M
    return static_cast<std::int32_t>(val);
268
9.92M
  } else {
269
34
    ThrowProgramError("Arithmetic overflow");
270
34
    abort();  // Never reached.
271
34
  }
272
9.92M
}
273
274
166k
std::int32_t ConvertDoubleToInt32(double val) {
275
166k
  const double kMin =
276
166k
      static_cast<double>(std::numeric_limits<std::int32_t>::min());
277
166k
  const double kMax =
278
166k
      static_cast<double>(std::numeric_limits<std::int32_t>::max());
279
  // NaNs will fail this test; they always compare false.
280
166k
  if (val > kMin - 1.0 && val < kMax + 1.0) {
281
163k
    return static_cast<std::int32_t>(val);
282
163k
  } else {
283
3.16k
    ThrowProgramError("Argument not in range in ConvertDoubleToInt32");
284
3.16k
    abort();  // Never reached.
285
3.16k
  }
286
166k
}
287
288
277M
std::uint32_t ConvertDoubleToUint32(double val) {
289
277M
  const double kMax =
290
277M
      static_cast<double>(std::numeric_limits<std::uint32_t>::max());
291
  // NaNs will fail this test; they always compare false.
292
277M
  if (val >= 0.0 && val < kMax + 1.0) {
293
277M
    return static_cast<std::uint32_t>(val);
294
277M
  } else {
295
862
    ThrowProgramError("Argument not in range in ConvertDoubleToUint32");
296
862
    abort();  // Never reached.
297
862
  }
298
277M
}
299
300
8.19k
float ConvertDoubleToFloat(double val) {
301
8.19k
  const double kMax = std::numeric_limits<float>::max();
302
8.19k
  if (val > kMax) {
303
253
    return std::numeric_limits<float>::infinity();
304
7.94k
  } else if (val < -kMax) {
305
415
    return -std::numeric_limits<float>::infinity();
306
7.53k
  } else {
307
    // The cases that end up here are:
308
    // - values in [-kMax, kMax]
309
    // - NaN (because it always compares false)
310
7.53k
    return static_cast<float>(val);
311
7.53k
  }
312
8.19k
}