CheckedInt.h (21551B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* Provides checked integers, detecting integer overflow and divide-by-0. */ 8 9 #ifndef mozilla_CheckedInt_h 10 #define mozilla_CheckedInt_h 11 12 #include "mozilla/Assertions.h" 13 #include "mozilla/Attributes.h" 14 #include "mozilla/CheckedArithmetic.h" 15 16 #include <cstdint> 17 #include <limits> 18 #include <type_traits> 19 20 namespace mozilla { 21 22 template <typename T> 23 class CheckedInt; 24 25 namespace detail { 26 27 /* 28 * Step 1: manually record supported types 29 * 30 * What's nontrivial here is that there are different families of integer 31 * types: basic integer types and stdint types. It is merrily undefined which 32 * types from one family may be just typedefs for a type from another family. 33 * 34 * For example, on GCC 4.6, aside from the basic integer types, the only other 35 * type that isn't just a typedef for some of them, is int8_t. 36 */ 37 38 template <typename IntegerType> 39 struct IsSupportedPass2 { 40 static const bool value = false; 41 }; 42 43 template <typename IntegerType> 44 struct IsSupported { 45 static const bool value = IsSupportedPass2<IntegerType>::value; 46 }; 47 48 template <> 49 struct IsSupported<int8_t> { 50 static const bool value = true; 51 }; 52 53 template <> 54 struct IsSupported<uint8_t> { 55 static const bool value = true; 56 }; 57 58 template <> 59 struct IsSupported<int16_t> { 60 static const bool value = true; 61 }; 62 63 template <> 64 struct IsSupported<uint16_t> { 65 static const bool value = true; 66 }; 67 68 template <> 69 struct IsSupported<int32_t> { 70 static const bool value = true; 71 }; 72 73 template <> 74 struct IsSupported<uint32_t> { 75 static const bool value = true; 76 }; 77 78 template <> 79 struct IsSupported<int64_t> { 80 static const bool value = true; 81 }; 82 83 template <> 84 struct IsSupported<uint64_t> { 85 static const bool value = true; 86 }; 87 88 template <> 89 struct IsSupportedPass2<char> { 90 static const bool value = true; 91 }; 92 93 template <> 94 struct IsSupportedPass2<signed char> { 95 static const bool value = true; 96 }; 97 98 template <> 99 struct IsSupportedPass2<unsigned char> { 100 static const bool value = true; 101 }; 102 103 template <> 104 struct IsSupportedPass2<short> { 105 static const bool value = true; 106 }; 107 108 template <> 109 struct IsSupportedPass2<unsigned short> { 110 static const bool value = true; 111 }; 112 113 template <> 114 struct IsSupportedPass2<int> { 115 static const bool value = true; 116 }; 117 118 template <> 119 struct IsSupportedPass2<unsigned int> { 120 static const bool value = true; 121 }; 122 123 template <> 124 struct IsSupportedPass2<long> { 125 static const bool value = true; 126 }; 127 128 template <> 129 struct IsSupportedPass2<unsigned long> { 130 static const bool value = true; 131 }; 132 133 template <> 134 struct IsSupportedPass2<long long> { 135 static const bool value = true; 136 }; 137 138 template <> 139 struct IsSupportedPass2<unsigned long long> { 140 static const bool value = true; 141 }; 142 143 /* 144 * Step 2: Implement the actual validity checks. 145 * 146 * Ideas taken from IntegerLib, code different. 147 */ 148 149 template <typename T, typename U, bool IsTSigned = std::is_signed_v<T>, 150 bool IsUSigned = std::is_signed_v<U>> 151 struct DoesRangeContainRange {}; 152 153 template <typename T, typename U, bool Signedness> 154 struct DoesRangeContainRange<T, U, Signedness, Signedness> { 155 static const bool value = sizeof(T) >= sizeof(U); 156 }; 157 158 template <typename T, typename U> 159 struct DoesRangeContainRange<T, U, true, false> { 160 static const bool value = sizeof(T) > sizeof(U); 161 }; 162 163 template <typename T, typename U> 164 struct DoesRangeContainRange<T, U, false, true> { 165 static const bool value = false; 166 }; 167 168 template <typename T, typename U, bool IsTSigned = std::is_signed_v<T>, 169 bool IsUSigned = std::is_signed_v<U>, 170 bool DoesTRangeContainURange = DoesRangeContainRange<T, U>::value> 171 struct IsInRangeImpl {}; 172 173 template <typename T, typename U, bool IsTSigned, bool IsUSigned> 174 struct IsInRangeImpl<T, U, IsTSigned, IsUSigned, true> { 175 static constexpr bool run(U) { return true; } 176 }; 177 178 template <typename T, typename U> 179 struct IsInRangeImpl<T, U, true, true, false> { 180 static constexpr bool run(U aX) { 181 return aX <= std::numeric_limits<T>::max() && 182 aX >= std::numeric_limits<T>::min(); 183 } 184 }; 185 186 template <typename T, typename U> 187 struct IsInRangeImpl<T, U, false, false, false> { 188 static constexpr bool run(U aX) { 189 return aX <= std::numeric_limits<T>::max(); 190 } 191 }; 192 193 template <typename T, typename U> 194 struct IsInRangeImpl<T, U, true, false, false> { 195 static constexpr bool run(U aX) { 196 return sizeof(T) > sizeof(U) || aX <= U(std::numeric_limits<T>::max()); 197 } 198 }; 199 200 template <typename T, typename U> 201 struct IsInRangeImpl<T, U, false, true, false> { 202 static constexpr bool run(U aX) { 203 return sizeof(T) >= sizeof(U) 204 ? aX >= 0 205 : aX >= 0 && aX <= U(std::numeric_limits<T>::max()); 206 } 207 }; 208 209 template <typename T, typename U> 210 constexpr bool IsInRange(U aX) { 211 return IsInRangeImpl<T, U>::run(aX); 212 } 213 214 template <typename T> 215 constexpr bool IsDivValid(T aX, T aY) { 216 // Keep in mind that in the signed case, min/-1 is invalid because 217 // abs(min)>max. 218 return aY != 0 && !(std::is_signed_v<T> && 219 aX == std::numeric_limits<T>::min() && aY == T(-1)); 220 } 221 222 template <typename T, bool IsTSigned = std::is_signed_v<T>> 223 struct IsModValidImpl; 224 225 template <typename T> 226 constexpr bool IsModValid(T aX, T aY) { 227 return IsModValidImpl<T>::run(aX, aY); 228 } 229 230 /* 231 * Mod is pretty simple. 232 * For now, let's just use the ANSI C definition: 233 * If aX or aY are negative, the results are implementation defined. 234 * Consider these invalid. 235 * Undefined for aY=0. 236 * The result will never exceed either aX or aY. 237 * 238 * Checking that aX>=0 is a warning when T is unsigned. 239 */ 240 241 template <typename T> 242 struct IsModValidImpl<T, false> { 243 static constexpr bool run(T aX, T aY) { return aY >= 1; } 244 }; 245 246 template <typename T> 247 struct IsModValidImpl<T, true> { 248 static constexpr bool run(T aX, T aY) { 249 if (aX < 0) { 250 return false; 251 } 252 return aY >= 1; 253 } 254 }; 255 256 template <typename T, bool IsSigned = std::is_signed_v<T>> 257 struct NegateImpl; 258 259 template <typename T> 260 struct NegateImpl<T, false> { 261 static constexpr CheckedInt<T> negate(const CheckedInt<T>& aVal) { 262 // Handle negation separately for signed/unsigned, for simpler code and to 263 // avoid an MSVC warning negating an unsigned value. 264 static_assert(detail::IsInRange<T>(0), "Integer type can't represent 0"); 265 return CheckedInt<T>(T(0), aVal.isValid() && aVal.mValue == 0); 266 } 267 }; 268 269 template <typename T> 270 struct NegateImpl<T, true> { 271 static constexpr CheckedInt<T> negate(const CheckedInt<T>& aVal) { 272 // Watch out for the min-value, which (with twos-complement) can't be 273 // negated as -min-value is then (max-value + 1). 274 if (!aVal.isValid() || aVal.mValue == std::numeric_limits<T>::min()) { 275 return CheckedInt<T>(aVal.mValue, false); 276 } 277 /* For some T, arithmetic ops automatically promote to a wider type, so 278 * explitly do the narrowing cast here. The narrowing cast is valid because 279 * we did the check for min value above. */ 280 return CheckedInt<T>(T(-aVal.mValue), true); 281 } 282 }; 283 284 } // namespace detail 285 286 /* 287 * Step 3: Now define the CheckedInt class. 288 */ 289 290 /** 291 * @class CheckedInt 292 * @brief Integer wrapper class checking for integer overflow and other errors 293 * @param T the integer type to wrap. Can be any type among the following: 294 * - any basic integer type such as |int| 295 * - any stdint type such as |int8_t| 296 * 297 * This class implements guarded integer arithmetic. Do a computation, check 298 * that isValid() returns true, you then have a guarantee that no problem, such 299 * as integer overflow, happened during this computation, and you can call 300 * value() to get the plain integer value. 301 * 302 * The arithmetic operators in this class are guaranteed not to raise a signal 303 * (e.g. in case of a division by zero). 304 * 305 * For example, suppose that you want to implement a function that computes 306 * (aX+aY)/aZ, that doesn't crash if aZ==0, and that reports on error (divide by 307 * zero or integer overflow). You could code it as follows: 308 @code 309 bool computeXPlusYOverZ(int aX, int aY, int aZ, int* aResult) 310 { 311 CheckedInt<int> checkedResult = (CheckedInt<int>(aX) + aY) / aZ; 312 if (checkedResult.isValid()) { 313 *aResult = checkedResult.value(); 314 return true; 315 } else { 316 return false; 317 } 318 } 319 @endcode 320 * 321 * Implicit conversion from plain integers to checked integers is allowed. The 322 * plain integer is checked to be in range before being casted to the 323 * destination type. This means that the following lines all compile, and the 324 * resulting CheckedInts are correctly detected as valid or invalid: 325 * @code 326 // 1 is of type int, is found to be in range for uint8_t, x is valid 327 CheckedInt<uint8_t> x(1); 328 // -1 is of type int, is found not to be in range for uint8_t, x is invalid 329 CheckedInt<uint8_t> x(-1); 330 // -1 is of type int, is found to be in range for int8_t, x is valid 331 CheckedInt<int8_t> x(-1); 332 // 1000 is of type int16_t, is found not to be in range for int8_t, 333 // x is invalid 334 CheckedInt<int8_t> x(int16_t(1000)); 335 // 3123456789 is of type uint32_t, is found not to be in range for int32_t, 336 // x is invalid 337 CheckedInt<int32_t> x(uint32_t(3123456789)); 338 * @endcode 339 * Implicit conversion from 340 * checked integers to plain integers is not allowed. As shown in the 341 * above example, to get the value of a checked integer as a normal integer, 342 * call value(). 343 * 344 * Arithmetic operations between checked and plain integers is allowed; the 345 * result type is the type of the checked integer. 346 * 347 * Checked integers of different types cannot be used in the same arithmetic 348 * expression. 349 * 350 * There are convenience typedefs for all stdint types, of the following form 351 * (these are just 2 examples): 352 @code 353 typedef CheckedInt<int32_t> CheckedInt32; 354 typedef CheckedInt<uint16_t> CheckedUint16; 355 @endcode 356 */ 357 template <typename T> 358 class CheckedInt { 359 protected: 360 T mValue; 361 bool mIsValid; 362 363 template <typename U> 364 constexpr CheckedInt(U aValue, bool aIsValid) 365 : mValue(aValue), mIsValid(aIsValid) { 366 static_assert(std::is_same_v<T, U>, 367 "this constructor must accept only T values"); 368 static_assert(detail::IsSupported<T>::value, 369 "This type is not supported by CheckedInt"); 370 } 371 372 friend struct detail::NegateImpl<T>; 373 374 public: 375 /** 376 * Constructs a checked integer with given @a value. The checked integer is 377 * initialized as valid or invalid depending on whether the @a value 378 * is in range. 379 * 380 * This constructor is not explicit. Instead, the type of its argument is a 381 * separate template parameter, ensuring that no conversion is performed 382 * before this constructor is actually called. As explained in the above 383 * documentation for class CheckedInt, this constructor checks that its 384 * argument is valid. 385 */ 386 template <typename U> 387 MOZ_IMPLICIT MOZ_NO_ARITHMETIC_EXPR_IN_ARGUMENT constexpr CheckedInt(U aValue) 388 : mValue(T(aValue)), mIsValid(detail::IsInRange<T>(aValue)) { 389 static_assert( 390 detail::IsSupported<T>::value && detail::IsSupported<U>::value, 391 "This type is not supported by CheckedInt"); 392 } 393 394 template <typename U> 395 friend class CheckedInt; 396 397 template <typename U> 398 constexpr CheckedInt<U> toChecked() const { 399 CheckedInt<U> ret(mValue); 400 ret.mIsValid = ret.mIsValid && mIsValid; 401 return ret; 402 } 403 404 /** Constructs a valid checked integer with initial value 0 */ 405 constexpr CheckedInt() : mValue(T(0)), mIsValid(true) { 406 static_assert(detail::IsSupported<T>::value, 407 "This type is not supported by CheckedInt"); 408 static_assert(detail::IsInRange<T>(0), "Integer type can't represent 0"); 409 } 410 411 /** @returns the actual value */ 412 constexpr T value() const { 413 MOZ_DIAGNOSTIC_ASSERT( 414 mIsValid, 415 "Invalid checked integer (division by zero or integer overflow)"); 416 return mValue; 417 } 418 419 /** 420 * @returns true if the checked integer is valid, i.e. is not the result 421 * of an invalid operation or of an operation involving an invalid checked 422 * integer 423 */ 424 constexpr bool isValid() const { return mIsValid; } 425 426 template <typename U> 427 friend constexpr CheckedInt<U> operator+(const CheckedInt<U>& aLhs, 428 const CheckedInt<U>& aRhs); 429 template <typename U> 430 constexpr CheckedInt& operator+=(U aRhs); 431 constexpr CheckedInt& operator+=(const CheckedInt<T>& aRhs); 432 433 template <typename U> 434 friend constexpr CheckedInt<U> operator-(const CheckedInt<U>& aLhs, 435 const CheckedInt<U>& aRhs); 436 template <typename U> 437 constexpr CheckedInt& operator-=(U aRhs); 438 constexpr CheckedInt& operator-=(const CheckedInt<T>& aRhs); 439 440 template <typename U> 441 friend constexpr CheckedInt<U> operator*(const CheckedInt<U>& aLhs, 442 const CheckedInt<U>& aRhs); 443 template <typename U> 444 constexpr CheckedInt& operator*=(U aRhs); 445 constexpr CheckedInt& operator*=(const CheckedInt<T>& aRhs); 446 447 template <typename U> 448 friend constexpr CheckedInt<U> operator/(const CheckedInt<U>& aLhs, 449 const CheckedInt<U>& aRhs); 450 template <typename U> 451 constexpr CheckedInt& operator/=(U aRhs); 452 constexpr CheckedInt& operator/=(const CheckedInt<T>& aRhs); 453 454 template <typename U> 455 friend constexpr CheckedInt<U> operator%(const CheckedInt<U>& aLhs, 456 const CheckedInt<U>& aRhs); 457 template <typename U> 458 constexpr CheckedInt& operator%=(U aRhs); 459 constexpr CheckedInt& operator%=(const CheckedInt<T>& aRhs); 460 461 constexpr CheckedInt operator-() const { 462 return detail::NegateImpl<T>::negate(*this); 463 } 464 465 /** 466 * @returns true if the left and right hand sides are valid 467 * and have the same value. 468 * 469 * Note that these semantics are the reason why we don't offer 470 * a operator!=. Indeed, we'd want to have a!=b be equivalent to !(a==b) 471 * but that would mean that whenever a or b is invalid, a!=b 472 * is always true, which would be very confusing. 473 * 474 * For similar reasons, operators <, >, <=, >= would be very tricky to 475 * specify, so we just avoid offering them. 476 * 477 * Notice that these == semantics are made more reasonable by these facts: 478 * 1. a==b implies equality at the raw data level 479 * (the converse is false, as a==b is never true among invalids) 480 * 2. This is similar to the behavior of IEEE floats, where a==b 481 * means that a and b have the same value *and* neither is NaN. 482 */ 483 constexpr bool operator==(const CheckedInt& aOther) const { 484 return mIsValid && aOther.mIsValid && mValue == aOther.mValue; 485 } 486 487 /** prefix ++ */ 488 constexpr CheckedInt& operator++() { 489 *this += 1; 490 return *this; 491 } 492 493 /** postfix ++ */ 494 constexpr CheckedInt operator++(int) { 495 CheckedInt tmp = *this; 496 *this += 1; 497 return tmp; 498 } 499 500 /** prefix -- */ 501 constexpr CheckedInt& operator--() { 502 *this -= 1; 503 return *this; 504 } 505 506 /** postfix -- */ 507 constexpr CheckedInt operator--(int) { 508 CheckedInt tmp = *this; 509 *this -= 1; 510 return tmp; 511 } 512 513 private: 514 /** 515 * The !=, <, <=, >, >= operators are disabled: 516 * see the comment on operator==. 517 */ 518 template <typename U> 519 bool operator!=(U aOther) const = delete; 520 template <typename U> 521 bool operator<(U aOther) const = delete; 522 template <typename U> 523 bool operator<=(U aOther) const = delete; 524 template <typename U> 525 bool operator>(U aOther) const = delete; 526 template <typename U> 527 bool operator>=(U aOther) const = delete; 528 }; 529 530 #define MOZ_CHECKEDINT_BASIC_BINARY_OPERATOR(NAME, OP) \ 531 template <typename T> \ 532 constexpr CheckedInt<T> operator OP(const CheckedInt<T>& aLhs, \ 533 const CheckedInt<T>& aRhs) { \ 534 if (!detail::Is##NAME##Valid(aLhs.mValue, aRhs.mValue)) { \ 535 static_assert(detail::IsInRange<T>(0), \ 536 "Integer type can't represent 0"); \ 537 return CheckedInt<T>(T(0), false); \ 538 } \ 539 /* For some T, arithmetic ops automatically promote to a wider type, so \ 540 * explicitly do the narrowing cast here. The narrowing cast is valid \ 541 * because we did the "Is##NAME##Valid" check above. */ \ 542 return CheckedInt<T>(T(aLhs.mValue OP aRhs.mValue), \ 543 aLhs.mIsValid && aRhs.mIsValid); \ 544 } 545 546 #define MOZ_CHECKEDINT_BASIC_BINARY_OPERATOR2(NAME, OP, FUN) \ 547 template <typename T> \ 548 constexpr CheckedInt<T> operator OP(const CheckedInt<T>& aLhs, \ 549 const CheckedInt<T>& aRhs) { \ 550 auto result = T{}; \ 551 if (MOZ_UNLIKELY(!FUN(aLhs.mValue, aRhs.mValue, &result))) { \ 552 static_assert(detail::IsInRange<T>(0), \ 553 "Integer type can't represent 0"); \ 554 return CheckedInt<T>(T(0), false); \ 555 } \ 556 return CheckedInt<T>(result, aLhs.mIsValid && aRhs.mIsValid); \ 557 } 558 MOZ_CHECKEDINT_BASIC_BINARY_OPERATOR2(Add, +, SafeAdd) 559 MOZ_CHECKEDINT_BASIC_BINARY_OPERATOR2(Sub, -, SafeSub) 560 MOZ_CHECKEDINT_BASIC_BINARY_OPERATOR2(Mul, *, SafeMul) 561 #undef MOZ_CHECKEDINT_BASIC_BINARY_OPERATOR2 562 563 MOZ_CHECKEDINT_BASIC_BINARY_OPERATOR(Div, /) 564 MOZ_CHECKEDINT_BASIC_BINARY_OPERATOR(Mod, %) 565 #undef MOZ_CHECKEDINT_BASIC_BINARY_OPERATOR 566 567 // Implement castToCheckedInt<T>(x), making sure that 568 // - it allows x to be either a CheckedInt<T> or any integer type 569 // that can be cast to T 570 // - if x is already a CheckedInt<T>, we just return a reference to it, 571 // instead of copying it (optimization) 572 573 namespace detail { 574 575 template <typename T, typename U> 576 struct CastToCheckedIntImpl { 577 typedef CheckedInt<T> ReturnType; 578 static constexpr CheckedInt<T> run(U aU) { return aU; } 579 }; 580 581 template <typename T> 582 struct CastToCheckedIntImpl<T, CheckedInt<T>> { 583 typedef const CheckedInt<T>& ReturnType; 584 static constexpr const CheckedInt<T>& run(const CheckedInt<T>& aU) { 585 return aU; 586 } 587 }; 588 589 } // namespace detail 590 591 template <typename T, typename U> 592 constexpr typename detail::CastToCheckedIntImpl<T, U>::ReturnType 593 castToCheckedInt(U aU) { 594 static_assert(detail::IsSupported<T>::value && detail::IsSupported<U>::value, 595 "This type is not supported by CheckedInt"); 596 return detail::CastToCheckedIntImpl<T, U>::run(aU); 597 } 598 599 #define MOZ_CHECKEDINT_CONVENIENCE_BINARY_OPERATORS(OP, COMPOUND_OP) \ 600 template <typename T> \ 601 template <typename U> \ 602 constexpr CheckedInt<T>& CheckedInt<T>::operator COMPOUND_OP(U aRhs) { \ 603 *this = *this OP castToCheckedInt<T>(aRhs); \ 604 return *this; \ 605 } \ 606 template <typename T> \ 607 constexpr CheckedInt<T>& CheckedInt<T>::operator COMPOUND_OP( \ 608 const CheckedInt<T>& aRhs) { \ 609 *this = *this OP aRhs; \ 610 return *this; \ 611 } \ 612 template <typename T, typename U> \ 613 constexpr CheckedInt<T> operator OP(const CheckedInt<T>& aLhs, U aRhs) { \ 614 return aLhs OP castToCheckedInt<T>(aRhs); \ 615 } \ 616 template <typename T, typename U> \ 617 constexpr CheckedInt<T> operator OP(U aLhs, const CheckedInt<T>& aRhs) { \ 618 return castToCheckedInt<T>(aLhs) OP aRhs; \ 619 } 620 621 MOZ_CHECKEDINT_CONVENIENCE_BINARY_OPERATORS(+, +=) 622 MOZ_CHECKEDINT_CONVENIENCE_BINARY_OPERATORS(*, *=) 623 MOZ_CHECKEDINT_CONVENIENCE_BINARY_OPERATORS(-, -=) 624 MOZ_CHECKEDINT_CONVENIENCE_BINARY_OPERATORS(/, /=) 625 MOZ_CHECKEDINT_CONVENIENCE_BINARY_OPERATORS(%, %=) 626 627 #undef MOZ_CHECKEDINT_CONVENIENCE_BINARY_OPERATORS 628 629 template <typename T, typename U> 630 constexpr bool operator==(const CheckedInt<T>& aLhs, U aRhs) { 631 return aLhs == castToCheckedInt<T>(aRhs); 632 } 633 634 template <typename T, typename U> 635 constexpr bool operator==(U aLhs, const CheckedInt<T>& aRhs) { 636 return castToCheckedInt<T>(aLhs) == aRhs; 637 } 638 639 // Convenience typedefs. 640 typedef CheckedInt<int8_t> CheckedInt8; 641 typedef CheckedInt<uint8_t> CheckedUint8; 642 typedef CheckedInt<int16_t> CheckedInt16; 643 typedef CheckedInt<uint16_t> CheckedUint16; 644 typedef CheckedInt<int32_t> CheckedInt32; 645 typedef CheckedInt<uint32_t> CheckedUint32; 646 typedef CheckedInt<int64_t> CheckedInt64; 647 typedef CheckedInt<uint64_t> CheckedUint64; 648 649 } // namespace mozilla 650 651 #endif /* mozilla_CheckedInt_h */