• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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