1 // Copyright 2024 The PDFium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifndef CORE_FXCRT_NUMERICS_CHECKED_MATH_IMPL_H_
6 #define CORE_FXCRT_NUMERICS_CHECKED_MATH_IMPL_H_
7
8 #include <stddef.h>
9 #include <stdint.h>
10
11 #include <climits>
12 #include <cmath>
13 #include <cstdlib>
14 #include <limits>
15 #include <type_traits>
16
17 #include "core/fxcrt/numerics/safe_conversions.h"
18 #include "core/fxcrt/numerics/safe_math_shared_impl.h"
19
20 namespace pdfium {
21 namespace internal {
22
23 template <typename T>
CheckedAddImpl(T x,T y,T * result)24 constexpr bool CheckedAddImpl(T x, T y, T* result) {
25 static_assert(std::is_integral<T>::value, "Type must be integral");
26 // Since the value of x+y is undefined if we have a signed type, we compute
27 // it using the unsigned type of the same size.
28 using UnsignedDst = typename std::make_unsigned<T>::type;
29 using SignedDst = typename std::make_signed<T>::type;
30 const UnsignedDst ux = static_cast<UnsignedDst>(x);
31 const UnsignedDst uy = static_cast<UnsignedDst>(y);
32 const UnsignedDst uresult = static_cast<UnsignedDst>(ux + uy);
33 // Addition is valid if the sign of (x + y) is equal to either that of x or
34 // that of y.
35 if (std::is_signed<T>::value
36 ? static_cast<SignedDst>((uresult ^ ux) & (uresult ^ uy)) < 0
37 : uresult < uy) // Unsigned is either valid or underflow.
38 return false;
39 *result = static_cast<T>(uresult);
40 return true;
41 }
42
43 template <typename T, typename U, class Enable = void>
44 struct CheckedAddOp {};
45
46 template <typename T, typename U>
47 struct CheckedAddOp<T,
48 U,
49 typename std::enable_if<std::is_integral<T>::value &&
50 std::is_integral<U>::value>::type> {
51 using result_type = typename MaxExponentPromotion<T, U>::type;
52 template <typename V>
53 static constexpr bool Do(T x, U y, V* result) {
54 if constexpr (CheckedAddFastOp<T, U>::is_supported)
55 return CheckedAddFastOp<T, U>::Do(x, y, result);
56
57 // Double the underlying type up to a full machine word.
58 using FastPromotion = typename FastIntegerArithmeticPromotion<T, U>::type;
59 using Promotion =
60 typename std::conditional<(IntegerBitsPlusSign<FastPromotion>::value >
61 IntegerBitsPlusSign<intptr_t>::value),
62 typename BigEnoughPromotion<T, U>::type,
63 FastPromotion>::type;
64 // Fail if either operand is out of range for the promoted type.
65 // TODO(jschuh): This could be made to work for a broader range of values.
66 if (BASE_NUMERICS_UNLIKELY(!IsValueInRangeForNumericType<Promotion>(x) ||
67 !IsValueInRangeForNumericType<Promotion>(y))) {
68 return false;
69 }
70
71 Promotion presult = {};
72 bool is_valid = true;
73 if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
74 presult = static_cast<Promotion>(x) + static_cast<Promotion>(y);
75 } else {
76 is_valid = CheckedAddImpl(static_cast<Promotion>(x),
77 static_cast<Promotion>(y), &presult);
78 }
79 if (!is_valid || !IsValueInRangeForNumericType<V>(presult))
80 return false;
81 *result = static_cast<V>(presult);
82 return true;
83 }
84 };
85
86 template <typename T>
87 constexpr bool CheckedSubImpl(T x, T y, T* result) {
88 static_assert(std::is_integral<T>::value, "Type must be integral");
89 // Since the value of x+y is undefined if we have a signed type, we compute
90 // it using the unsigned type of the same size.
91 using UnsignedDst = typename std::make_unsigned<T>::type;
92 using SignedDst = typename std::make_signed<T>::type;
93 const UnsignedDst ux = static_cast<UnsignedDst>(x);
94 const UnsignedDst uy = static_cast<UnsignedDst>(y);
95 const UnsignedDst uresult = static_cast<UnsignedDst>(ux - uy);
96 // Subtraction is valid if either x and y have same sign, or (x-y) and x have
97 // the same sign.
98 if (std::is_signed<T>::value
99 ? static_cast<SignedDst>((uresult ^ ux) & (ux ^ uy)) < 0
100 : x < y)
101 return false;
102 *result = static_cast<T>(uresult);
103 return true;
104 }
105
106 template <typename T, typename U, class Enable = void>
107 struct CheckedSubOp {};
108
109 template <typename T, typename U>
110 struct CheckedSubOp<T,
111 U,
112 typename std::enable_if<std::is_integral<T>::value &&
113 std::is_integral<U>::value>::type> {
114 using result_type = typename MaxExponentPromotion<T, U>::type;
115 template <typename V>
116 static constexpr bool Do(T x, U y, V* result) {
117 if constexpr (CheckedSubFastOp<T, U>::is_supported)
118 return CheckedSubFastOp<T, U>::Do(x, y, result);
119
120 // Double the underlying type up to a full machine word.
121 using FastPromotion = typename FastIntegerArithmeticPromotion<T, U>::type;
122 using Promotion =
123 typename std::conditional<(IntegerBitsPlusSign<FastPromotion>::value >
124 IntegerBitsPlusSign<intptr_t>::value),
125 typename BigEnoughPromotion<T, U>::type,
126 FastPromotion>::type;
127 // Fail if either operand is out of range for the promoted type.
128 // TODO(jschuh): This could be made to work for a broader range of values.
129 if (BASE_NUMERICS_UNLIKELY(!IsValueInRangeForNumericType<Promotion>(x) ||
130 !IsValueInRangeForNumericType<Promotion>(y))) {
131 return false;
132 }
133
134 Promotion presult = {};
135 bool is_valid = true;
136 if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
137 presult = static_cast<Promotion>(x) - static_cast<Promotion>(y);
138 } else {
139 is_valid = CheckedSubImpl(static_cast<Promotion>(x),
140 static_cast<Promotion>(y), &presult);
141 }
142 if (!is_valid || !IsValueInRangeForNumericType<V>(presult))
143 return false;
144 *result = static_cast<V>(presult);
145 return true;
146 }
147 };
148
149 template <typename T>
150 constexpr bool CheckedMulImpl(T x, T y, T* result) {
151 static_assert(std::is_integral<T>::value, "Type must be integral");
152 // Since the value of x*y is potentially undefined if we have a signed type,
153 // we compute it using the unsigned type of the same size.
154 using UnsignedDst = typename std::make_unsigned<T>::type;
155 using SignedDst = typename std::make_signed<T>::type;
156 const UnsignedDst ux = SafeUnsignedAbs(x);
157 const UnsignedDst uy = SafeUnsignedAbs(y);
158 const UnsignedDst uresult = static_cast<UnsignedDst>(ux * uy);
159 const bool is_negative =
160 std::is_signed<T>::value && static_cast<SignedDst>(x ^ y) < 0;
161 // We have a fast out for unsigned identity or zero on the second operand.
162 // After that it's an unsigned overflow check on the absolute value, with
163 // a +1 bound for a negative result.
164 if (uy > UnsignedDst(!std::is_signed<T>::value || is_negative) &&
165 ux > (std::numeric_limits<T>::max() + UnsignedDst(is_negative)) / uy)
166 return false;
167 *result = static_cast<T>(is_negative ? 0 - uresult : uresult);
168 return true;
169 }
170
171 template <typename T, typename U, class Enable = void>
172 struct CheckedMulOp {};
173
174 template <typename T, typename U>
175 struct CheckedMulOp<T,
176 U,
177 typename std::enable_if<std::is_integral<T>::value &&
178 std::is_integral<U>::value>::type> {
179 using result_type = typename MaxExponentPromotion<T, U>::type;
180 template <typename V>
181 static constexpr bool Do(T x, U y, V* result) {
182 if constexpr (CheckedMulFastOp<T, U>::is_supported)
183 return CheckedMulFastOp<T, U>::Do(x, y, result);
184
185 using Promotion = typename FastIntegerArithmeticPromotion<T, U>::type;
186 // Verify the destination type can hold the result (always true for 0).
187 if (BASE_NUMERICS_UNLIKELY((!IsValueInRangeForNumericType<Promotion>(x) ||
188 !IsValueInRangeForNumericType<Promotion>(y)) &&
189 x && y)) {
190 return false;
191 }
192
193 Promotion presult = {};
194 bool is_valid = true;
195 if (CheckedMulFastOp<Promotion, Promotion>::is_supported) {
196 // The fast op may be available with the promoted type.
197 // The casts here are safe because of the "value in range" conditional
198 // above.
199 is_valid = CheckedMulFastOp<Promotion, Promotion>::Do(
200 static_cast<Promotion>(x), static_cast<Promotion>(y), &presult);
201 } else if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
202 presult = static_cast<Promotion>(x) * static_cast<Promotion>(y);
203 } else {
204 is_valid = CheckedMulImpl(static_cast<Promotion>(x),
205 static_cast<Promotion>(y), &presult);
206 }
207 if (!is_valid || !IsValueInRangeForNumericType<V>(presult))
208 return false;
209 *result = static_cast<V>(presult);
210 return true;
211 }
212 };
213
214 // Division just requires a check for a zero denominator or an invalid negation
215 // on signed min/-1.
216 template <typename T, typename U, class Enable = void>
217 struct CheckedDivOp {};
218
219 template <typename T, typename U>
220 struct CheckedDivOp<T,
221 U,
222 typename std::enable_if<std::is_integral<T>::value &&
223 std::is_integral<U>::value>::type> {
224 using result_type = typename MaxExponentPromotion<T, U>::type;
225 template <typename V>
226 static constexpr bool Do(T x, U y, V* result) {
227 if (BASE_NUMERICS_UNLIKELY(!y))
228 return false;
229
230 // The overflow check can be compiled away if we don't have the exact
231 // combination of types needed to trigger this case.
232 using Promotion = typename BigEnoughPromotion<T, U>::type;
233 if (BASE_NUMERICS_UNLIKELY(
234 (std::is_signed<T>::value && std::is_signed<U>::value &&
235 IsTypeInRangeForNumericType<T, Promotion>::value &&
236 static_cast<Promotion>(x) ==
237 std::numeric_limits<Promotion>::lowest() &&
238 y == static_cast<U>(-1)))) {
239 return false;
240 }
241
242 // This branch always compiles away if the above branch wasn't removed.
243 if (BASE_NUMERICS_UNLIKELY((!IsValueInRangeForNumericType<Promotion>(x) ||
244 !IsValueInRangeForNumericType<Promotion>(y)) &&
245 x)) {
246 return false;
247 }
248
249 const Promotion presult = Promotion(x) / Promotion(y);
250 if (!IsValueInRangeForNumericType<V>(presult))
251 return false;
252 *result = static_cast<V>(presult);
253 return true;
254 }
255 };
256
257 template <typename T, typename U, class Enable = void>
258 struct CheckedModOp {};
259
260 template <typename T, typename U>
261 struct CheckedModOp<T,
262 U,
263 typename std::enable_if<std::is_integral<T>::value &&
264 std::is_integral<U>::value>::type> {
265 using result_type = typename MaxExponentPromotion<T, U>::type;
266 template <typename V>
267 static constexpr bool Do(T x, U y, V* result) {
268 if (BASE_NUMERICS_UNLIKELY(!y))
269 return false;
270
271 using Promotion = typename BigEnoughPromotion<T, U>::type;
272 if (BASE_NUMERICS_UNLIKELY(
273 (std::is_signed<T>::value && std::is_signed<U>::value &&
274 IsTypeInRangeForNumericType<T, Promotion>::value &&
275 static_cast<Promotion>(x) ==
276 std::numeric_limits<Promotion>::lowest() &&
277 y == static_cast<U>(-1)))) {
278 *result = 0;
279 return true;
280 }
281
282 const Promotion presult =
283 static_cast<Promotion>(x) % static_cast<Promotion>(y);
284 if (!IsValueInRangeForNumericType<V>(presult))
285 return false;
286 *result = static_cast<Promotion>(presult);
287 return true;
288 }
289 };
290
291 template <typename T, typename U, class Enable = void>
292 struct CheckedLshOp {};
293
294 // Left shift. Shifts less than 0 or greater than or equal to the number
295 // of bits in the promoted type are undefined. Shifts of negative values
296 // are undefined. Otherwise it is defined when the result fits.
297 template <typename T, typename U>
298 struct CheckedLshOp<T,
299 U,
300 typename std::enable_if<std::is_integral<T>::value &&
301 std::is_integral<U>::value>::type> {
302 using result_type = T;
303 template <typename V>
304 static constexpr bool Do(T x, U shift, V* result) {
305 // Disallow negative numbers and verify the shift is in bounds.
306 if (BASE_NUMERICS_LIKELY(!IsValueNegative(x) &&
307 as_unsigned(shift) <
308 as_unsigned(std::numeric_limits<T>::digits))) {
309 // Shift as unsigned to avoid undefined behavior.
310 *result = static_cast<V>(as_unsigned(x) << shift);
311 // If the shift can be reversed, we know it was valid.
312 return *result >> shift == x;
313 }
314
315 // Handle the legal corner-case of a full-width signed shift of zero.
316 if (!std::is_signed<T>::value || x ||
317 as_unsigned(shift) != as_unsigned(std::numeric_limits<T>::digits))
318 return false;
319 *result = 0;
320 return true;
321 }
322 };
323
324 template <typename T, typename U, class Enable = void>
325 struct CheckedRshOp {};
326
327 // Right shift. Shifts less than 0 or greater than or equal to the number
328 // of bits in the promoted type are undefined. Otherwise, it is always defined,
329 // but a right shift of a negative value is implementation-dependent.
330 template <typename T, typename U>
331 struct CheckedRshOp<T,
332 U,
333 typename std::enable_if<std::is_integral<T>::value &&
334 std::is_integral<U>::value>::type> {
335 using result_type = T;
336 template <typename V>
337 static constexpr bool Do(T x, U shift, V* result) {
338 // Use sign conversion to push negative values out of range.
339 if (BASE_NUMERICS_UNLIKELY(as_unsigned(shift) >=
340 IntegerBitsPlusSign<T>::value)) {
341 return false;
342 }
343
344 const T tmp = x >> shift;
345 if (!IsValueInRangeForNumericType<V>(tmp))
346 return false;
347 *result = static_cast<V>(tmp);
348 return true;
349 }
350 };
351
352 template <typename T, typename U, class Enable = void>
353 struct CheckedAndOp {};
354
355 // For simplicity we support only unsigned integer results.
356 template <typename T, typename U>
357 struct CheckedAndOp<T,
358 U,
359 typename std::enable_if<std::is_integral<T>::value &&
360 std::is_integral<U>::value>::type> {
361 using result_type = typename std::make_unsigned<
362 typename MaxExponentPromotion<T, U>::type>::type;
363 template <typename V>
364 static constexpr bool Do(T x, U y, V* result) {
365 const result_type tmp =
366 static_cast<result_type>(x) & static_cast<result_type>(y);
367 if (!IsValueInRangeForNumericType<V>(tmp))
368 return false;
369 *result = static_cast<V>(tmp);
370 return true;
371 }
372 };
373
374 template <typename T, typename U, class Enable = void>
375 struct CheckedOrOp {};
376
377 // For simplicity we support only unsigned integers.
378 template <typename T, typename U>
379 struct CheckedOrOp<T,
380 U,
381 typename std::enable_if<std::is_integral<T>::value &&
382 std::is_integral<U>::value>::type> {
383 using result_type = typename std::make_unsigned<
384 typename MaxExponentPromotion<T, U>::type>::type;
385 template <typename V>
386 static constexpr bool Do(T x, U y, V* result) {
387 const result_type tmp =
388 static_cast<result_type>(x) | static_cast<result_type>(y);
389 if (!IsValueInRangeForNumericType<V>(tmp))
390 return false;
391 *result = static_cast<V>(tmp);
392 return true;
393 }
394 };
395
396 template <typename T, typename U, class Enable = void>
397 struct CheckedXorOp {};
398
399 // For simplicity we support only unsigned integers.
400 template <typename T, typename U>
401 struct CheckedXorOp<T,
402 U,
403 typename std::enable_if<std::is_integral<T>::value &&
404 std::is_integral<U>::value>::type> {
405 using result_type = typename std::make_unsigned<
406 typename MaxExponentPromotion<T, U>::type>::type;
407 template <typename V>
408 static constexpr bool Do(T x, U y, V* result) {
409 const result_type tmp =
410 static_cast<result_type>(x) ^ static_cast<result_type>(y);
411 if (!IsValueInRangeForNumericType<V>(tmp))
412 return false;
413 *result = static_cast<V>(tmp);
414 return true;
415 }
416 };
417
418 // Max doesn't really need to be implemented this way because it can't fail,
419 // but it makes the code much cleaner to use the MathOp wrappers.
420 template <typename T, typename U, class Enable = void>
421 struct CheckedMaxOp {};
422
423 template <typename T, typename U>
424 struct CheckedMaxOp<
425 T,
426 U,
427 typename std::enable_if<std::is_arithmetic<T>::value &&
428 std::is_arithmetic<U>::value>::type> {
429 using result_type = typename MaxExponentPromotion<T, U>::type;
430 template <typename V>
431 static constexpr bool Do(T x, U y, V* result) {
432 const result_type tmp = IsGreater<T, U>::Test(x, y)
433 ? static_cast<result_type>(x)
434 : static_cast<result_type>(y);
435 if (!IsValueInRangeForNumericType<V>(tmp))
436 return false;
437 *result = static_cast<V>(tmp);
438 return true;
439 }
440 };
441
442 // Min doesn't really need to be implemented this way because it can't fail,
443 // but it makes the code much cleaner to use the MathOp wrappers.
444 template <typename T, typename U, class Enable = void>
445 struct CheckedMinOp {};
446
447 template <typename T, typename U>
448 struct CheckedMinOp<
449 T,
450 U,
451 typename std::enable_if<std::is_arithmetic<T>::value &&
452 std::is_arithmetic<U>::value>::type> {
453 using result_type = typename LowestValuePromotion<T, U>::type;
454 template <typename V>
455 static constexpr bool Do(T x, U y, V* result) {
456 const result_type tmp = IsLess<T, U>::Test(x, y)
457 ? static_cast<result_type>(x)
458 : static_cast<result_type>(y);
459 if (!IsValueInRangeForNumericType<V>(tmp))
460 return false;
461 *result = static_cast<V>(tmp);
462 return true;
463 }
464 };
465
466 // This is just boilerplate that wraps the standard floating point arithmetic.
467 // A macro isn't the nicest solution, but it beats rewriting these repeatedly.
468 #define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP) \
469 template <typename T, typename U> \
470 struct Checked##NAME##Op< \
471 T, U, \
472 typename std::enable_if<std::is_floating_point<T>::value || \
473 std::is_floating_point<U>::value>::type> { \
474 using result_type = typename MaxExponentPromotion<T, U>::type; \
475 template <typename V> \
476 static constexpr bool Do(T x, U y, V* result) { \
477 using Promotion = typename MaxExponentPromotion<T, U>::type; \
478 const Promotion presult = x OP y; \
479 if (!IsValueInRangeForNumericType<V>(presult)) \
480 return false; \
481 *result = static_cast<V>(presult); \
482 return true; \
483 } \
484 };
485
486 BASE_FLOAT_ARITHMETIC_OPS(Add, +)
487 BASE_FLOAT_ARITHMETIC_OPS(Sub, -)
488 BASE_FLOAT_ARITHMETIC_OPS(Mul, *)
489 BASE_FLOAT_ARITHMETIC_OPS(Div, /)
490
491 #undef BASE_FLOAT_ARITHMETIC_OPS
492
493 // Floats carry around their validity state with them, but integers do not. So,
494 // we wrap the underlying value in a specialization in order to hide that detail
495 // and expose an interface via accessors.
496 enum NumericRepresentation {
497 NUMERIC_INTEGER,
498 NUMERIC_FLOATING,
499 NUMERIC_UNKNOWN
500 };
501
502 template <typename NumericType>
503 struct GetNumericRepresentation {
504 static const NumericRepresentation value =
505 std::is_integral<NumericType>::value
506 ? NUMERIC_INTEGER
507 : (std::is_floating_point<NumericType>::value ? NUMERIC_FLOATING
508 : NUMERIC_UNKNOWN);
509 };
510
511 template <typename T,
512 NumericRepresentation type = GetNumericRepresentation<T>::value>
513 class CheckedNumericState {};
514
515 // Integrals require quite a bit of additional housekeeping to manage state.
516 template <typename T>
517 class CheckedNumericState<T, NUMERIC_INTEGER> {
518 public:
519 template <typename Src = int>
520 constexpr explicit CheckedNumericState(Src value = 0, bool is_valid = true)
521 : is_valid_(is_valid && IsValueInRangeForNumericType<T>(value)),
522 value_(WellDefinedConversionOrZero(value, is_valid_)) {
523 static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
524 }
525
526 template <typename Src>
527 constexpr CheckedNumericState(const CheckedNumericState<Src>& rhs)
528 : CheckedNumericState(rhs.value(), rhs.is_valid()) {}
529
530 constexpr bool is_valid() const { return is_valid_; }
531
532 constexpr T value() const { return value_; }
533
534 private:
535 // Ensures that a type conversion does not trigger undefined behavior.
536 template <typename Src>
537 static constexpr T WellDefinedConversionOrZero(Src value, bool is_valid) {
538 using SrcType = typename internal::UnderlyingType<Src>::type;
539 return (std::is_integral<SrcType>::value || is_valid)
540 ? static_cast<T>(value)
541 : 0;
542 }
543
544 // is_valid_ precedes value_ because member initializers in the constructors
545 // are evaluated in field order, and is_valid_ must be read when initializing
546 // value_.
547 bool is_valid_;
548 T value_;
549 };
550
551 // Floating points maintain their own validity, but need translation wrappers.
552 template <typename T>
553 class CheckedNumericState<T, NUMERIC_FLOATING> {
554 public:
555 template <typename Src = double>
556 constexpr explicit CheckedNumericState(Src value = 0.0, bool is_valid = true)
557 : value_(WellDefinedConversionOrNaN(
558 value,
559 is_valid && IsValueInRangeForNumericType<T>(value))) {}
560
561 template <typename Src>
562 constexpr CheckedNumericState(const CheckedNumericState<Src>& rhs)
563 : CheckedNumericState(rhs.value(), rhs.is_valid()) {}
564
565 constexpr bool is_valid() const {
566 // Written this way because std::isfinite is not reliably constexpr.
567 return IsConstantEvaluated()
568 ? value_ <= std::numeric_limits<T>::max() &&
569 value_ >= std::numeric_limits<T>::lowest()
570 : std::isfinite(value_);
571 }
572
573 constexpr T value() const { return value_; }
574
575 private:
576 // Ensures that a type conversion does not trigger undefined behavior.
577 template <typename Src>
578 static constexpr T WellDefinedConversionOrNaN(Src value, bool is_valid) {
579 using SrcType = typename internal::UnderlyingType<Src>::type;
580 return (StaticDstRangeRelationToSrcRange<T, SrcType>::value ==
581 NUMERIC_RANGE_CONTAINED ||
582 is_valid)
583 ? static_cast<T>(value)
584 : std::numeric_limits<T>::quiet_NaN();
585 }
586
587 T value_;
588 };
589
590 } // namespace internal
591 } // namespace pdfium
592
593 #endif // CORE_FXCRT_NUMERICS_CHECKED_MATH_IMPL_H_
594