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