From 2ca796c723421880c026854526ce51853913d21b Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Tue, 28 Nov 2023 22:41:51 -0500 Subject: [PATCH] Make it possible to disable overflow checks in signed integers If SUS_CHECK_INTEGER_OVERFLOW is defined to false then overflow checks will be removed in signed integers. Wrapping operations (as with unsigned integers) will be performed instead of Undefined Behaviour. --- sus/CMakeLists.txt | 2 + sus/num/__private/check_integer_overflow.h | 25 ++ sus/num/__private/intrinsics.h | 12 +- sus/num/__private/signed_integer_methods.inc | 272 +++++++++------- .../__private/unsigned_integer_methods.inc | 18 +- .../unsigned_integer_methods_impl.inc | 29 +- sus/num/i32_overflow_unittest.cc | 302 ++++++++++++++++++ sus/num/i32_unittest.cc | 2 +- sus/num/signed_integer.h | 141 +++++--- sus/num/types.h | 20 +- sus/num/unsigned_integer.h | 1 + 11 files changed, 638 insertions(+), 186 deletions(-) create mode 100644 sus/num/__private/check_integer_overflow.h create mode 100644 sus/num/i32_overflow_unittest.cc diff --git a/sus/CMakeLists.txt b/sus/CMakeLists.txt index 8f243e2d3..d30c615e8 100644 --- a/sus/CMakeLists.txt +++ b/sus/CMakeLists.txt @@ -157,6 +157,7 @@ target_sources(subspace PUBLIC "mem/size_of.h" "mem/swap.h" "mem/take.h" + "num/__private/check_integer_overflow.h" "num/__private/float_consts.inc" "num/__private/float_methods.inc" "num/__private/float_methods_impl.inc" @@ -298,6 +299,7 @@ if(${SUBSPACE_BUILD_TESTS}) "num/f64_unittest.cc" "num/i8_unittest.cc" "num/i16_unittest.cc" + "num/i32_overflow_unittest.cc" "num/i32_unittest.cc" "num/i64_unittest.cc" "num/isize_unittest.cc" diff --git a/sus/num/__private/check_integer_overflow.h b/sus/num/__private/check_integer_overflow.h new file mode 100644 index 000000000..988b6e2d0 --- /dev/null +++ b/sus/num/__private/check_integer_overflow.h @@ -0,0 +1,25 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// IWYU pragma: private +// IWYU pragma: friend "sus/.*" +#pragma once + +// SUS_CHECK_INTEGER_OVERFLOW can be defined to false to disable overflow checks +// in integer arithmetic and shifting operations. +#if !defined(SUS_CHECK_INTEGER_OVERFLOW) +#define SUS_CHECK_INTEGER_OVERFLOW true +#endif +static_assert(SUS_CHECK_INTEGER_OVERFLOW == false || + SUS_CHECK_INTEGER_OVERFLOW == true); diff --git a/sus/num/__private/intrinsics.h b/sus/num/__private/intrinsics.h index c33b67c10..e4cb54b97 100644 --- a/sus/num/__private/intrinsics.h +++ b/sus/num/__private/intrinsics.h @@ -459,6 +459,13 @@ sus_pure_const sus_always_inline constexpr T unchecked_shl( return static_cast(MathType{x} << y); } +template + requires(std::is_integral_v && std::is_signed_v) +sus_pure_const sus_always_inline constexpr T unchecked_shl( + T x, uint64_t y) noexcept { + return static_cast(MathType{x} << y); +} + template requires(std::is_integral_v && !std::is_signed_v) sus_pure_const sus_always_inline constexpr T unchecked_shr( @@ -1080,9 +1087,8 @@ sus_pure_const inline constexpr OverflowOut shl_with_overflow( const bool overflow = shift >= num_bits(); if (overflow) [[unlikely]] shift = shift & (unchecked_sub(num_bits(), uint32_t{1})); - return OverflowOut sus_clang_bug_56394(){ - .overflow = overflow, - .value = into_signed(unchecked_shl(into_unsigned(x), shift))}; + return OverflowOut sus_clang_bug_56394(){.overflow = overflow, + .value = unchecked_shl(x, shift)}; } template diff --git a/sus/num/__private/signed_integer_methods.inc b/sus/num/__private/signed_integer_methods.inc index 96f38f7e0..736e82025 100644 --- a/sus/num/__private/signed_integer_methods.inc +++ b/sus/num/__private/signed_integer_methods.inc @@ -536,41 +536,44 @@ sus_pure constexpr _self signum() const& noexcept { /// Satisfies the [`Eq`]($sus::cmp::Eq) concept for signed integers. /// #[doc.overloads=int.eq] -[[nodiscard]] sus_pure friend constexpr inline bool operator==( +[[nodiscard]] sus_pure_const friend constexpr inline bool operator==( _self l, _self r) noexcept = default; /// #[doc.overloads=int.eq] -[[nodiscard]] sus_pure friend constexpr inline bool operator==( +[[nodiscard]] sus_pure_const friend constexpr inline bool operator==( _self l, Signed auto r) noexcept { return l.primitive_value == r.primitive_value; } /// #[doc.overloads=int.eq] -[[nodiscard]] sus_pure friend constexpr inline bool operator==( +[[nodiscard]] sus_pure_const friend constexpr inline bool operator==( _self l, SignedPrimitiveInteger auto r) noexcept { return l.primitive_value == r; } /// Satisfies the [`StrongOrd`]($sus::cmp::StrongOrd) concept for signed /// integers. /// #[doc.overloads=int.strongord] -[[nodiscard]] sus_pure friend constexpr inline std::strong_ordering operator<=>( - _self l, _self r) noexcept { +[[nodiscard]] sus_pure_const friend constexpr inline std::strong_ordering +operator<=>(_self l, _self r) noexcept { return l.primitive_value <=> r.primitive_value; } /// #[doc.overloads=int.strongord] -[[nodiscard]] sus_pure friend constexpr inline std::strong_ordering operator<=>( - _self l, Signed auto r) noexcept { +[[nodiscard]] sus_pure_const friend constexpr inline std::strong_ordering +operator<=>(_self l, Signed auto r) noexcept { return l.primitive_value <=> r.primitive_value; } /// #[doc.overloads=int.strongord] -[[nodiscard]] sus_pure friend constexpr inline std::strong_ordering operator<=>( - _self l, SignedPrimitiveInteger auto r) noexcept { +[[nodiscard]] sus_pure_const friend constexpr inline std::strong_ordering +operator<=>(_self l, SignedPrimitiveInteger auto r) noexcept { return l.primitive_value <=> r; } /// Satisfies the [`Neg<@doc.self>`]($sus::num::Neg) concept. sus_pure constexpr inline _self operator-() const& noexcept { - // TODO: Allow opting out of all overflow checks? - ::sus::check(primitive_value != MIN_PRIMITIVE); - return __private::unchecked_neg(primitive_value); + if constexpr (SUS_CHECK_INTEGER_OVERFLOW) { + ::sus::check(primitive_value != MIN_PRIMITIVE); + return __private::unchecked_neg(primitive_value); + } else { + return wrapping_neg(); + } } /// Satisfies the [`BitNot<@doc.self>`]($sus::num::BitNot) concept. sus_pure constexpr inline _self operator~() const& noexcept { @@ -585,14 +588,21 @@ sus_pure constexpr inline _self operator~() const& noexcept { /// However enum class is excluded as they require an explicit conversion to an /// integer. /// +/// # Panics +/// This operator will panic on overflow when [overflow checks]( +/// $sus::num#overflow-behaviour) are enabled. +/// /// #[doc.overloads=signedint.+] [[nodiscard]] sus_pure_const friend constexpr inline _self operator+( _self l, _self r) noexcept { - const auto out = - __private::add_with_overflow(l.primitive_value, r.primitive_value); - // TODO: Allow opting out of all overflow checks? - ::sus::check(!out.overflow); - return out.value; + if constexpr (SUS_CHECK_INTEGER_OVERFLOW) { + const auto out = + __private::add_with_overflow(l.primitive_value, r.primitive_value); + ::sus::check(!out.overflow); + return out.value; + } else { + return l.wrapping_add(r); + } } /// #[doc.overloads=signedint.+] template U> @@ -625,14 +635,21 @@ friend constexpr inline _self operator+(U l, _self r) noexcept = delete; /// However enum class is excluded as they require an explicit conversion to an /// integer. /// +/// # Panics +/// This operator will panic on overflow when [overflow checks]( +/// $sus::num#overflow-behaviour) are enabled. +/// /// #[doc.overloads=signedint.-] -[[nodiscard]] sus_pure friend constexpr inline _self operator-( +[[nodiscard]] sus_pure_const friend constexpr inline _self operator-( _self l, _self r) noexcept { - const auto out = - __private::sub_with_overflow(l.primitive_value, r.primitive_value); - // TODO: Allow opting out of all overflow checks? - ::sus::check(!out.overflow); - return out.value; + if constexpr (SUS_CHECK_INTEGER_OVERFLOW) { + const auto out = + __private::sub_with_overflow(l.primitive_value, r.primitive_value); + ::sus::check(!out.overflow); + return out.value; + } else { + return l.wrapping_sub(r); + } } /// #[doc.overloads=signedint.-] template U> @@ -665,14 +682,21 @@ friend constexpr inline _self operator-(U l, _self r) noexcept = delete; /// However enum class is excluded as they require an explicit conversion to an /// integer. /// +/// # Panics +/// This operator will panic on overflow when [overflow checks]( +/// $sus::num#overflow-behaviour) are enabled. +/// /// #[doc.overloads=signedint.*] -[[nodiscard]] sus_pure friend constexpr inline _self operator*( +[[nodiscard]] sus_pure_const friend constexpr inline _self operator*( _self l, _self r) noexcept { - const auto out = - __private::mul_with_overflow(l.primitive_value, r.primitive_value); - // TODO: Allow opting out of all overflow checks? - ::sus::check(!out.overflow); - return out.value; + if constexpr (SUS_CHECK_INTEGER_OVERFLOW) { + const auto out = + __private::mul_with_overflow(l.primitive_value, r.primitive_value); + ::sus::check(!out.overflow); + return out.value; + } else { + return l.wrapping_mul(r); + } } /// #[doc.overloads=signedint.*] template U> @@ -705,13 +729,14 @@ friend constexpr inline _self operator*(U l, _self r) noexcept = delete; /// However enum class is excluded as they require an explicit conversion to an /// integer. /// +/// # Panics +/// This operator will panic when dividing by zero or when division will +/// overflow. +/// /// #[doc.overloads=signedint./] -[[nodiscard]] sus_pure friend constexpr inline _self operator/( +[[nodiscard]] sus_pure_const friend constexpr inline _self operator/( _self l, _self r) noexcept { - // TODO: Allow opting out of all overflow checks? - ::sus::check(r.primitive_value != 0); - // TODO: Allow opting out of all overflow checks? - ::sus::check(l.primitive_value != MIN_PRIMITIVE || r.primitive_value != -1); + ::sus::check(!__private::div_overflows(l.primitive_value, r.primitive_value)); return static_cast<_primitive>(l.primitive_value / r.primitive_value); } /// #[doc.overloads=signedint./] @@ -745,13 +770,14 @@ friend constexpr inline _self operator/(U l, _self r) noexcept = delete; /// However enum class is excluded as they require an explicit conversion to an /// integer. /// +/// # Panics +/// This operator will panic when dividing by zero or when division will +/// overflow. +/// /// #[doc.overloads=signedint.%] -[[nodiscard]] sus_pure friend constexpr inline _self operator%( +[[nodiscard]] sus_pure_const friend constexpr inline _self operator%( _self l, _self r) noexcept { - // TODO: Allow opting out of all overflow checks? - ::sus::check(r.primitive_value != 0); - // TODO: Allow opting out of all overflow checks? - ::sus::check(l.primitive_value != MIN_PRIMITIVE || r.primitive_value != -1); + ::sus::check(!__private::div_overflows(l.primitive_value, r.primitive_value)); return static_cast<_primitive>(l.primitive_value % r.primitive_value); } /// #[doc.overloads=signedint.%] @@ -787,7 +813,7 @@ friend constexpr inline _self operator%(U l, _self r) noexcept = delete; /// integer. /// /// #[doc.overloads=signedint.&] -[[nodiscard]] sus_pure friend constexpr inline _self operator&( +[[nodiscard]] sus_pure_const friend constexpr inline _self operator&( _self l, _self r) noexcept { return static_cast<_primitive>(l.primitive_value & r.primitive_value); } @@ -823,7 +849,7 @@ friend constexpr inline _self operator&(U l, _self r) noexcept = delete; /// integer. /// /// #[doc.overloads=signedint.|] -[[nodiscard]] sus_pure friend constexpr inline _self operator|( +[[nodiscard]] sus_pure_const friend constexpr inline _self operator|( _self l, _self r) noexcept { return static_cast<_primitive>(l.primitive_value | r.primitive_value); } @@ -859,7 +885,7 @@ friend constexpr inline _self operator|(U l, _self r) noexcept = delete; /// integer. /// /// #[doc.overloads=signedint.^] -[[nodiscard]] sus_pure friend constexpr inline _self operator^( +[[nodiscard]] sus_pure_const friend constexpr inline _self operator^( _self l, _self r) noexcept { return static_cast<_primitive>(l.primitive_value ^ r.primitive_value); } @@ -889,48 +915,71 @@ template friend constexpr inline _self operator^(U l, _self r) noexcept = delete; /// Satisfies the [`AddAssign<@doc.self>`]($sus::num::AddAssign) concept. +/// +/// # Panics +/// This operator will panic on overflow when [overflow checks]( +/// $sus::num#overflow-behaviour) are enabled. constexpr inline void operator+=(_self r) & noexcept { - const auto out = - __private::add_with_overflow(primitive_value, r.primitive_value); - // TODO: Allow opting out of all overflow checks? - ::sus::check(!out.overflow); - primitive_value = out.value; + if constexpr (SUS_CHECK_INTEGER_OVERFLOW) { + const auto out = + __private::add_with_overflow(primitive_value, r.primitive_value); + ::sus::check(!out.overflow); + primitive_value = out.value; + } else { + primitive_value = wrapping_add(r).primitive_value; + } } /// Satisfies the [`SubAssign<@doc.self>`]($sus::num::SubAssign) concept. +/// +/// # Panics +/// This operator will panic on overflow when [overflow checks]( +/// $sus::num#overflow-behaviour) are enabled. constexpr inline void operator-=(_self r) & noexcept { - const auto out = - __private::sub_with_overflow(primitive_value, r.primitive_value); - // TODO: Allow opting out of all overflow checks? - ::sus::check(!out.overflow); - primitive_value = out.value; + if constexpr (SUS_CHECK_INTEGER_OVERFLOW) { + const auto out = + __private::sub_with_overflow(primitive_value, r.primitive_value); + ::sus::check(!out.overflow); + primitive_value = out.value; + } else { + primitive_value = wrapping_sub(r).primitive_value; + } } /// Satisfies the [`MulAssign<@doc.self>`]($sus::num::MulAssign) concept. +/// +/// # Panics +/// This operator will panic on overflow when [overflow checks]( +/// $sus::num#overflow-behaviour) are enabled. constexpr inline void operator*=(_self r) & noexcept { - const auto out = - __private::mul_with_overflow(primitive_value, r.primitive_value); - // TODO: Allow opting out of all overflow checks? - ::sus::check(!out.overflow); - primitive_value = out.value; + if constexpr (SUS_CHECK_INTEGER_OVERFLOW) { + const auto out = + __private::mul_with_overflow(primitive_value, r.primitive_value); + ::sus::check(!out.overflow); + primitive_value = out.value; + } else { + primitive_value = wrapping_mul(r).primitive_value; + } } /// Satisfies the [`DivAssign<@doc.self>`]($sus::num::DivAssign) concept. +/// +/// # Panics +/// This operator will panic when dividing by zero or when division will +/// overflow. constexpr inline void operator/=(_self r) & noexcept { - // TODO: Allow opting out of all overflow checks? - ::sus::check(r.primitive_value != 0); - // TODO: Allow opting out of all overflow checks? - ::sus::check(primitive_value != MIN_PRIMITIVE || r.primitive_value != -1); + ::sus::check(!__private::div_overflows(primitive_value, r.primitive_value)); primitive_value /= r.primitive_value; } /// Satisfies the [`RemAssign<@doc.self>`]($sus::num::RemAssign) concept. constexpr inline void operator%=(_self r) & noexcept { - // TODO: Allow opting out of all overflow checks? - ::sus::check(r.primitive_value != 0); - // TODO: Allow opting out of all overflow checks? - ::sus::check(primitive_value != MIN_PRIMITIVE || r.primitive_value != -1); + ::sus::check(!__private::div_overflows(primitive_value, r.primitive_value)); primitive_value %= r.primitive_value; } /// Satisfies the [`BitAndAssign<@doc.self>`]($sus::num::BitAndAssign) /// concept. +/// +/// # Panics +/// This operator will panic when dividing by zero or when division will +/// overflow. constexpr inline void operator&=(_self r) & noexcept { primitive_value &= r.primitive_value; } @@ -944,44 +993,61 @@ constexpr inline void operator^=(_self r) & noexcept { primitive_value ^= r.primitive_value; } /// Satisfies the [`ShlAssign<@doc.self>`]($sus::num::ShlAssign) concept. +/// +/// # Panics +/// This operator will panic if the shift amount is not less than +/// [`@doc.self::BITS`]($sus::num::@doc.self::BITS) and [overflow checks]( +/// $sus::num#overflow-behaviour) are enabled. constexpr inline void operator<<=(u32 r) & noexcept { - const auto out = - __private::shl_with_overflow(primitive_value, r.primitive_value); - // TODO: Allow opting out of all overflow checks? - ::sus::check(!out.overflow); - primitive_value = out.value; + if constexpr (SUS_CHECK_INTEGER_OVERFLOW) { + const auto out = + __private::shl_with_overflow(primitive_value, r.primitive_value); + ::sus::check(!out.overflow); + primitive_value = out.value; + } else { + primitive_value = wrapping_shl(r).primitive_value; + } } /// Satisfies the [`ShrAssign<@doc.self>`]($sus::num::ShrAssign) concept. /// /// Performs sign extension, copying the sign bit to the right if its set. +/// +/// # Panics +/// This operator will panic if the shift amount is not less than +/// [`@doc.self::BITS`]($sus::num::@doc.self::BITS) and [overflow checks]( +/// $sus::num#overflow-behaviour) are enabled. constexpr inline void operator>>=(u32 r) & noexcept { - const auto out = - __private::shr_with_overflow(primitive_value, r.primitive_value); - // TODO: Allow opting out of all overflow checks? - ::sus::check(!out.overflow); - primitive_value = out.value; + if constexpr (SUS_CHECK_INTEGER_OVERFLOW) { + const auto out = + __private::shr_with_overflow(primitive_value, r.primitive_value); + ::sus::check(!out.overflow); + primitive_value = out.value; + } else { + primitive_value = wrapping_shr(r).primitive_value; + } } /// Computes the absolute value of itself. /// /// The absolute value of @doc.self::MIN cannot be represented as an @doc.self, -/// and attempting to calculate it will panic. +/// and attempting to calculate it will panic when [overflow checks]( +/// $sus::num#overflow-behaviour) are enabled. sus_pure constexpr inline _self abs() const& noexcept { - // TODO: Allow opting out of all overflow checks? - ::sus::check(primitive_value != MIN_PRIMITIVE); if (primitive_value >= 0) - return primitive_value; + return *this; else - return __private::unchecked_neg(primitive_value); + return -*this; } /// Checked absolute value. Computes [`abs`]( /// $sus::num::@doc.self::abs), returning `None` if the current /// value is [`MIN`]($sus::num::@doc.self::MIN). sus_pure constexpr Option<_self> checked_abs() const& noexcept { - if (primitive_value != MIN_PRIMITIVE) [[likely]] - return Option<_self>(abs()); - else + if (primitive_value >= 0) + return Option<_self>(*this); + else if (primitive_value != MIN_PRIMITIVE) + return Option<_self>(-*this); + else [[unlikely]] return Option<_self>(); } @@ -1166,7 +1232,6 @@ sus_pure constexpr Option<_self> checked_div(_self rhs) const& noexcept { /// This function will panic if `rhs` is 0. template > sus_pure constexpr Tuple overflowing_div(_self rhs) const& noexcept { - // TODO: Allow opting out of all overflow checks? ::sus::check(rhs.primitive_value != 0); if (__private::div_overflows_nonzero(::sus::marker::unsafe_fn, primitive_value, rhs.primitive_value)) @@ -1184,7 +1249,6 @@ sus_pure constexpr Tuple overflowing_div(_self rhs) const& noexcept { /// # Panics /// This function will panic if `rhs` is 0. sus_pure constexpr _self saturating_div(_self rhs) const& noexcept { - // TODO: Allow opting out of all overflow checks? ::sus::check(rhs.primitive_value != 0); if (__private::div_overflows_nonzero(::sus::marker::unsafe_fn, primitive_value, rhs.primitive_value)) @@ -1209,7 +1273,6 @@ sus_pure constexpr _self saturating_div(_self rhs) const& noexcept { /// # Panics /// This function will panic if `rhs` is 0. sus_pure constexpr _self wrapping_div(_self rhs) const& noexcept { - // TODO: Allow opting out of all overflow checks? ::sus::check(rhs.primitive_value != 0); if (__private::div_overflows_nonzero(::sus::marker::unsafe_fn, primitive_value, rhs.primitive_value)) @@ -1339,7 +1402,6 @@ sus_pure constexpr Option<_self> checked_rem(_self rhs) const& noexcept { /// This function will panic if `rhs` is 0. template > sus_pure constexpr Tuple overflowing_rem(_self rhs) const& noexcept { - // TODO: Allow opting out of all overflow checks? ::sus::check(rhs.primitive_value != 0); if (__private::div_overflows_nonzero(::sus::marker::unsafe_fn, primitive_value, rhs.primitive_value)) @@ -1358,7 +1420,6 @@ sus_pure constexpr Tuple overflowing_rem(_self rhs) const& noexcept { /// artifacts make `x % y` invalid for `MIN / -1` on a signed type (where `MIN` /// is the negative minimal value). In such a case, this function returns 0. sus_pure constexpr _self wrapping_rem(_self rhs) const& noexcept { - // TODO: Allow opting out of all overflow checks? ::sus::check(rhs.primitive_value != 0); if (__private::div_overflows_nonzero(::sus::marker::unsafe_fn, primitive_value, rhs.primitive_value)) @@ -1381,7 +1442,6 @@ sus_pure constexpr _self wrapping_rem(_self rhs) const& noexcept { /// # Panics /// This function will panic if `rhs` is 0 or the division results in overflow. sus_pure constexpr _self div_euclid(_self rhs) const& noexcept { - // TODO: Allow opting out of all overflow checks? ::sus::check(!__private::div_overflows(primitive_value, rhs.primitive_value)); return __private::div_euclid(::sus::marker::unsafe_fn, primitive_value, rhs.primitive_value); @@ -1411,7 +1471,6 @@ sus_pure constexpr Option<_self> checked_div_euclid(_self rhs) const& noexcept { /// This function will panic if `rhs` is 0. template > sus_pure constexpr Tuple overflowing_div_euclid(_self rhs) const& noexcept { - // TODO: Allow opting out of all overflow checks? ::sus::check(rhs.primitive_value != 0); if (__private::div_overflows_nonzero(::sus::marker::unsafe_fn, primitive_value, rhs.primitive_value)) @@ -1436,7 +1495,6 @@ sus_pure constexpr Tuple overflowing_div_euclid(_self rhs) const& noexcept { /// # Panics /// This function will panic if `rhs` is 0. sus_pure constexpr _self wrapping_div_euclid(_self rhs) const& noexcept { - // TODO: Allow opting out of all overflow checks? ::sus::check(rhs.primitive_value != 0); if (__private::div_overflows_nonzero(::sus::marker::unsafe_fn, primitive_value, rhs.primitive_value)) @@ -1457,7 +1515,6 @@ sus_pure constexpr _self wrapping_div_euclid(_self rhs) const& noexcept { /// # Panics /// This function will panic if `rhs` is 0 or the division results in overflow. sus_pure constexpr _self rem_euclid(_self rhs) const& noexcept { - // TODO: Allow opting out of all overflow checks? ::sus::check(!__private::div_overflows(primitive_value, rhs.primitive_value)); return __private::rem_euclid(::sus::marker::unsafe_fn, primitive_value, rhs.primitive_value); @@ -1487,7 +1544,6 @@ sus_pure constexpr Option<_self> checked_rem_euclid(_self rhs) const& noexcept { /// This function will panic if `rhs` is 0. template > sus_pure constexpr Tuple overflowing_rem_euclid(_self rhs) const& noexcept { - // TODO: Allow opting out of all overflow checks? ::sus::check(rhs.primitive_value != 0); if (__private::div_overflows_nonzero(::sus::marker::unsafe_fn, primitive_value, rhs.primitive_value)) @@ -1511,7 +1567,6 @@ sus_pure constexpr Tuple overflowing_rem_euclid(_self rhs) const& noexcept { /// # Panics /// This function will panic if `rhs` is 0. sus_pure constexpr _self wrapping_rem_euclid(_self rhs) const& noexcept { - // TODO: Allow opting out of all overflow checks? ::sus::check(rhs.primitive_value != 0); if (__private::div_overflows_nonzero(::sus::marker::unsafe_fn, primitive_value, rhs.primitive_value)) @@ -1525,7 +1580,7 @@ sus_pure constexpr _self wrapping_rem_euclid(_self rhs) const& noexcept { /// Checked shift left. Computes `self << rhs`, returning `None` if `rhs` is /// larger than or equal to the number of bits in `self`. -sus_pure constexpr Option<_self> checked_shl(u32 rhs) const& noexcept { +sus_pure constexpr Option<_self> checked_shl(u64 rhs) const& noexcept { const auto out = __private::shl_with_overflow(primitive_value, rhs.primitive_value); if (!out.overflow) [[likely]] @@ -1542,7 +1597,7 @@ sus_pure constexpr Option<_self> checked_shl(u32 rhs) const& noexcept { /// where `N` is the number of bits, and this value is then used to perform the /// shift. template > -sus_pure constexpr Tuple overflowing_shl(u32 rhs) const& noexcept { +sus_pure constexpr Tuple overflowing_shl(u64 rhs) const& noexcept { const auto out = __private::shl_with_overflow(primitive_value, rhs.primitive_value); return Tuple(out.value, out.overflow); @@ -1558,14 +1613,14 @@ sus_pure constexpr Tuple overflowing_shl(u32 rhs) const& noexcept { /// integer types all implement a // [`rotate_left`]($sus::num::@doc.self::rotate_left) function, which may be /// what you want instead. -sus_pure constexpr _self wrapping_shl(u32 rhs) const& noexcept { +sus_pure constexpr _self wrapping_shl(u64 rhs) const& noexcept { return __private::shl_with_overflow(primitive_value, rhs.primitive_value) .value; } /// Checked shift right. Computes `self >> rhs`, returning `None` if `rhs` is /// larger than or equal to the number of bits in `self`. -sus_pure constexpr Option<_self> checked_shr(u32 rhs) const& noexcept { +sus_pure constexpr Option<_self> checked_shr(u64 rhs) const& noexcept { const auto out = __private::shr_with_overflow(primitive_value, rhs.primitive_value); if (!out.overflow) [[likely]] @@ -1582,7 +1637,7 @@ sus_pure constexpr Option<_self> checked_shr(u32 rhs) const& noexcept { /// where `N` is the number of bits, and this value is then used to perform the /// shift. template > -sus_pure constexpr Tuple overflowing_shr(u32 rhs) const& noexcept { +sus_pure constexpr Tuple overflowing_shr(u64 rhs) const& noexcept { const auto out = __private::shr_with_overflow(primitive_value, rhs.primitive_value); return Tuple(out.value, out.overflow); @@ -1598,7 +1653,7 @@ sus_pure constexpr Tuple overflowing_shr(u32 rhs) const& noexcept { /// integer types all implement a /// [`rotate_right`]($sus::num::@doc.self::rotate_right) function, which may be /// what you want instead. -sus_pure constexpr _self wrapping_shr(u32 rhs) const& noexcept { +sus_pure constexpr _self wrapping_shr(u64 rhs) const& noexcept { return __private::shr_with_overflow(primitive_value, rhs.primitive_value) .value; } @@ -1767,8 +1822,9 @@ sus_pure constexpr _self swap_bytes() const& noexcept { sus_pure constexpr inline _self pow(u32 rhs) const& noexcept { const auto out = __private::pow_with_overflow(primitive_value, rhs.primitive_value); - // TODO: Allow opting out of all overflow checks? - ::sus::check(!out.overflow); + if constexpr (SUS_CHECK_INTEGER_OVERFLOW) { + ::sus::check(!out.overflow); + } return out.value; } @@ -1777,7 +1833,6 @@ sus_pure constexpr inline _self pow(u32 rhs) const& noexcept { sus_pure constexpr Option<_self> checked_pow(u32 rhs) const& noexcept { const auto out = __private::pow_with_overflow(primitive_value, rhs.primitive_value); - // TODO: Allow opting out of all overflow checks? if (!out.overflow) [[likely]] return Option<_self>(out.value); else @@ -1821,8 +1876,8 @@ sus_pure constexpr Option checked_log2() const& noexcept { /// # Panics /// When the number is zero or negative the function will panic. sus_pure constexpr u32 log2() const& noexcept { - // TODO: Allow opting out of all overflow checks? - return checked_log2().unwrap(); + return checked_log2().expect( + "argument of integer logarithm must be positive"); } /// Returns the base 10 logarithm of the number, rounded down. @@ -1841,8 +1896,8 @@ sus_pure constexpr Option checked_log10() const& noexcept { /// # Panics /// When the number is zero or negative the function will panic. sus_pure constexpr u32 log10() const& noexcept { - // TODO: Allow opting out of all overflow checks? - return checked_log10().unwrap(); + return checked_log10().expect( + "argument of integer logarithm must be positive"); } /// Returns the logarithm of the number with respect to an arbitrary base, @@ -1883,7 +1938,8 @@ sus_pure constexpr Option checked_log(_self base) const& noexcept { /// # Panics /// When the number is negative, zero, or if the `base` is not at least 2. sus_pure constexpr u32 log(_self base) const& noexcept { - return checked_log(base).unwrap(); + return checked_log(base).expect( + "argument of integer logarithm must be positive"); } /// Converts an integer from big endian to the target's endianness. diff --git a/sus/num/__private/unsigned_integer_methods.inc b/sus/num/__private/unsigned_integer_methods.inc index f4389ca6e..52e879752 100644 --- a/sus/num/__private/unsigned_integer_methods.inc +++ b/sus/num/__private/unsigned_integer_methods.inc @@ -1570,7 +1570,7 @@ sus_pure constexpr _self wrapping_rem_euclid(_self rhs) const& noexcept { /// Checked shift left. Computes `self << rhs`, returning `None` if `rhs` is /// larger than or equal to the number of bits in `self`. sus_pure constexpr ::sus::option::Option<_self> checked_shl( - u32 rhs) const& noexcept; + u64 rhs) const& noexcept; /// Shifts self left by `rhs` bits. /// @@ -1579,7 +1579,7 @@ sus_pure constexpr ::sus::option::Option<_self> checked_shl( /// bits. If the shift value is too large, then value is masked by `(N-1)` where /// `N` is the number of bits, and this value is then used to perform the shift. sus_pure constexpr ::sus::tuple_type::Tuple<_self, bool> overflowing_shl( - u32 rhs) const& noexcept; + u64 rhs) const& noexcept; /// Panic-free bitwise shift-left; yields `self << mask(rhs)`, where mask /// removes any high-order bits of `rhs` that would cause the shift to exceed @@ -1591,15 +1591,12 @@ sus_pure constexpr ::sus::tuple_type::Tuple<_self, bool> overflowing_shl( /// integer types all implement a [`rotate_left`]( /// $sus::num::@doc.self::rotate_left) function, which may be what you /// want instead. -sus_pure constexpr _self wrapping_shl(u32 rhs) const& noexcept { - return _self( - __private::shl_with_overflow(primitive_value, rhs.primitive_value).value); -} +sus_pure constexpr _self wrapping_shl(u64 rhs) const& noexcept; /// Checked shift right. Computes `self >> rhs`, returning `None` if `rhs` is /// larger than or equal to the number of bits in `self`. sus_pure constexpr ::sus::option::Option<_self> checked_shr( - u32 rhs) const& noexcept; + u64 rhs) const& noexcept; /// Shifts self right by `rhs` bits. /// @@ -1609,7 +1606,7 @@ sus_pure constexpr ::sus::option::Option<_self> checked_shr( /// where `N` is the number of bits, and this value is then used to perform the /// shift. sus_pure constexpr ::sus::tuple_type::Tuple<_self, bool> overflowing_shr( - u32 rhs) const& noexcept; + u64 rhs) const& noexcept; /// Panic-free bitwise shift-right; yields `self >> mask(rhs)`, where mask /// removes any high-order bits of `rhs` that would cause the shift to exceed @@ -1621,10 +1618,7 @@ sus_pure constexpr ::sus::tuple_type::Tuple<_self, bool> overflowing_shr( /// integer types all implement a [`rotate_right`]( /// $sus::num::@doc.self::rotate_right) function, which may be what you /// want instead. -sus_pure constexpr _self wrapping_shr(u32 rhs) const& noexcept { - return _self( - __private::shr_with_overflow(primitive_value, rhs.primitive_value).value); -} +sus_pure constexpr _self wrapping_shr(u64 rhs) const& noexcept; /// Checked integer subtraction. Computes `self - rhs`, returning `None` if /// overflow occurred. diff --git a/sus/num/__private/unsigned_integer_methods_impl.inc b/sus/num/__private/unsigned_integer_methods_impl.inc index c8ffba12d..bfa92d6cd 100644 --- a/sus/num/__private/unsigned_integer_methods_impl.inc +++ b/sus/num/__private/unsigned_integer_methods_impl.inc @@ -330,7 +330,7 @@ _self::overflowing_rem_euclid(_self rhs) const& noexcept { } sus_pure constexpr ::sus::option::Option<_self> _self::checked_shl( - u32 rhs) const& noexcept { + u64 rhs) const& noexcept { const auto out = __private::shl_with_overflow(primitive_value, rhs.primitive_value); if (!out.overflow) [[likely]] @@ -340,14 +340,19 @@ sus_pure constexpr ::sus::option::Option<_self> _self::checked_shl( } sus_pure constexpr ::sus::tuple_type::Tuple<_self, bool> _self::overflowing_shl( - u32 rhs) const& noexcept { + u64 rhs) const& noexcept { const auto out = __private::shl_with_overflow(primitive_value, rhs.primitive_value); return ::sus::tuple_type::Tuple<_self, bool>(_self(out.value), out.overflow); } +sus_pure constexpr _self _self::wrapping_shl(u64 rhs) const& noexcept { + return _self( + __private::shl_with_overflow(primitive_value, rhs.primitive_value).value); +} + sus_pure constexpr ::sus::option::Option<_self> _self::checked_shr( - u32 rhs) const& noexcept { + u64 rhs) const& noexcept { const auto out = __private::shr_with_overflow(primitive_value, rhs.primitive_value); if (!out.overflow) [[likely]] @@ -357,12 +362,17 @@ sus_pure constexpr ::sus::option::Option<_self> _self::checked_shr( } sus_pure constexpr ::sus::tuple_type::Tuple<_self, bool> _self::overflowing_shr( - u32 rhs) const& noexcept { + u64 rhs) const& noexcept { const auto out = __private::shr_with_overflow(primitive_value, rhs.primitive_value); return ::sus::tuple_type::Tuple<_self, bool>(_self(out.value), out.overflow); } +sus_pure constexpr _self _self::wrapping_shr(u64 rhs) const& noexcept { + return _self( + __private::shr_with_overflow(primitive_value, rhs.primitive_value).value); +} + sus_pure constexpr ::sus::option::Option<_self> _self::checked_sub( _self rhs) const& { const auto out = @@ -410,8 +420,8 @@ sus_pure constexpr ::sus::option::Option _self::checked_log2() } sus_pure constexpr u32 _self::log2() const& noexcept { - // TODO: Allow opting out of all overflow checks? - return checked_log2().unwrap(); + return checked_log2().expect( + "argument of integer logarithm must be positive"); } sus_pure constexpr ::sus::option::Option _self::checked_log10() @@ -425,8 +435,8 @@ sus_pure constexpr ::sus::option::Option _self::checked_log10() } sus_pure constexpr u32 _self::log10() const& noexcept { - // TODO: Allow opting out of all overflow checks? - return checked_log10().unwrap(); + return checked_log10().expect( + "argument of integer logarithm must be positive"); } sus_pure constexpr ::sus::option::Option _self::checked_log( @@ -446,7 +456,8 @@ sus_pure constexpr ::sus::option::Option _self::checked_log( } sus_pure constexpr u32 _self::log(_self base) const& noexcept { - return checked_log(base).unwrap(); + return checked_log(base).expect( + "argument of integer logarithm must be positive"); } sus_pure constexpr ::sus::option::Option<_self> diff --git a/sus/num/i32_overflow_unittest.cc b/sus/num/i32_overflow_unittest.cc new file mode 100644 index 000000000..31c234d71 --- /dev/null +++ b/sus/num/i32_overflow_unittest.cc @@ -0,0 +1,302 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#define SUS_CHECK_INTEGER_OVERFLOW false + +#include "googletest/include/gtest/gtest.h" +#include "sus/prelude.h" +#include "sus/test/ensure_use.h" + +namespace { + +using sus::test::ensure_use; + +TEST(i32OverflowDeathTest, Abs) { + EXPECT_EQ(i32::MIN.abs(), i32::MIN); +} + +TEST(i32Overflow, AddOverflow) { + EXPECT_EQ(i32::MAX + 1_i32, i32::MIN); + EXPECT_EQ(i32::MIN + -1_i32, i32::MAX); + + EXPECT_EQ(1_i16 + i32::MAX, i32::MIN); + EXPECT_EQ(i32::MAX + 1_i16, i32::MIN); +} + +// Division overflow still panics. +TEST(i32OverflowDeathTest, DivOverflow) { +#if GTEST_HAS_DEATH_TEST + EXPECT_DEATH( + { + auto x = i32::MAX / 0_i32; + ensure_use(&x); + }, + ""); + EXPECT_DEATH( + { + auto x = i32::MIN / -1_i32; + ensure_use(&x); + }, + ""); + + auto x = i32::MIN; + EXPECT_DEATH(x /= 0_i32, ""); + EXPECT_DEATH(x /= -1_i32, ""); +#endif +} + +TEST(i32OverflowDeathTest, OverflowingDivByZero) { +#if GTEST_HAS_DEATH_TEST + EXPECT_DEATH( + { + auto x = i32::MAX.overflowing_div(0_i32); + ensure_use(&x); + }, + ""); + +#endif +} + +TEST(i32OverflowDeathTest, SaturatingDivByZero) { +#if GTEST_HAS_DEATH_TEST + EXPECT_DEATH( + { + auto x = i32::MAX.saturating_div(0_i32); + ensure_use(&x); + }, + ""); + +#endif +} + +TEST(i32OverflowDeathTest, WrappingDivByZero) { +#if GTEST_HAS_DEATH_TEST + EXPECT_DEATH( + { + auto x = i32::MAX.wrapping_div(0_i32); + ensure_use(&x); + }, + ""); +#endif +} + +TEST(i32Overflow, MulOverflow) { + EXPECT_EQ(i32::MAX * 2_i32, -2); + EXPECT_EQ(i32::MAX * -2_i32, 2); +} + +TEST(i32OverflowDeathTest, NegOverflow) { // + EXPECT_EQ(-i32::MIN, i32::MIN); +} + +TEST(i32OverflowDeathTest, RemOverflow) { +#if GTEST_HAS_DEATH_TEST + EXPECT_DEATH( + { + auto x = i32::MAX % 0_i32; + ensure_use(&x); + }, + ""); + EXPECT_DEATH( + { + auto x = i32::MIN % -1_i32; + ensure_use(&x); + }, + ""); + + auto x = i32::MIN; + EXPECT_DEATH(x %= 0_i32, ""); + EXPECT_DEATH(x %= -1_i32, ""); +#endif +} + +TEST(i32OverflowDeathTest, OverflowingRemByZero) { +#if GTEST_HAS_DEATH_TEST + EXPECT_DEATH( + { + auto x = i32::MAX.overflowing_rem(0_i32); + ensure_use(&x); + }, + ""); +#endif +} + +TEST(i32OverflowDeathTest, WrappingRemByZero) { +#if GTEST_HAS_DEATH_TEST + EXPECT_DEATH( + { + auto x = i32::MAX.wrapping_rem(0_i32); + ensure_use(&x); + }, + ""); +#endif +} + +TEST(i32OverflowDeathTest, ShlOverflow) { EXPECT_EQ(1_i32 << 33_u32, 2); } + +TEST(i32OverflowDeathTest, ShrOverflow) { + EXPECT_EQ(i32::MAX >> 33_u32, i32::MAX >> 1_u32); +} + +TEST(i32Overflow, SubOverflow) { + EXPECT_EQ(i32::MIN - 1_i32, i32::MAX); + EXPECT_EQ(i32::MAX - -1_i32, i32::MIN); + + EXPECT_EQ(1_i16 - -i32::MAX, i32::MIN); + EXPECT_EQ(i32::MIN - 1_i16, i32::MAX); +} + +TEST(i32OverflowDeathTest, PowOverflow) { EXPECT_EQ((i32::MAX).pow(2_u32), 1); } + +TEST(i32OverflowDeathTest, Log2NonPositive) { +#if GTEST_HAS_DEATH_TEST + EXPECT_DEATH( + { + auto x = (0_i32).log2(); + ensure_use(&x); + }, + ""); + EXPECT_DEATH( + { + auto x = (-1_i32).log2(); + ensure_use(&x); + }, + ""); +#endif +} + +TEST(i32OverflowDeathTest, Log10NonPositive) { +#if GTEST_HAS_DEATH_TEST + EXPECT_DEATH( + { + auto x = (0_i32).log10(); + ensure_use(&x); + }, + ""); + EXPECT_DEATH( + { + auto x = (-1_i32).log10(); + ensure_use(&x); + }, + ""); +#endif +} + +TEST(i32OverflowDeathTest, LogNonPositive) { +#if GTEST_HAS_DEATH_TEST + EXPECT_DEATH( + { + auto x = (0_i32).log(10_i32); + ensure_use(&x); + }, + ""); + EXPECT_DEATH( + { + auto x = (2_i32).log(0_i32); + ensure_use(&x); + }, + ""); + EXPECT_DEATH( + { + auto x = (-1_i32).log(10_i32); + ensure_use(&x); + }, + ""); + EXPECT_DEATH( + { + auto x = (2_i32).log(-2_i32); + ensure_use(&x); + }, + ""); +#endif +} +TEST(i32OverflowDeathTest, DivEuclidOverflow) { +#if GTEST_HAS_DEATH_TEST + EXPECT_DEATH( + { + auto x = (7_i32).div_euclid(0_i32); + EXPECT_EQ(x, i32::MIN); + }, + ""); + EXPECT_DEATH( + { + auto x = (i32::MIN).div_euclid(-1_i32); + EXPECT_EQ(x, i32::MIN); + }, + ""); +#endif +} + +TEST(i32OverflowDeathTest, OverflowingDivEuclidDivByZero) { +#if GTEST_HAS_DEATH_TEST + EXPECT_DEATH( + { + auto x = (7_i32).overflowing_div_euclid(0_i32); + ensure_use(&x); + }, + ""); +#endif +} + +TEST(i32OverflowDeathTest, WrappingDivEuclidOverflow) { +#if GTEST_HAS_DEATH_TEST + EXPECT_DEATH( + { + auto x = (7_i32).wrapping_div_euclid(0_i32); + EXPECT_EQ(x, i32::MIN); + }, + ""); +#endif +} + +TEST(i32OverflowDeathTest, RemEuclidOverflow) { +#if GTEST_HAS_DEATH_TEST + EXPECT_DEATH( + { + auto x = (7_i32).rem_euclid(0_i32); + ensure_use(&x); + }, + ""); + EXPECT_DEATH( + { + auto x = (i32::MIN).rem_euclid(-1_i32); + ensure_use(&x); + }, + ""); +#endif +} + +TEST(i32OverflowDeathTest, OverflowingRemEuclidDivByZero) { +#if GTEST_HAS_DEATH_TEST + EXPECT_DEATH( + { + auto x = (7_i32).overflowing_rem_euclid(0_i32); + ensure_use(&x); + }, + ""); +#endif +} + +TEST(i32OverflowDeathTest, WrappingRemEuclidOverflow) { +#if GTEST_HAS_DEATH_TEST + EXPECT_DEATH( + { + auto x = (7_i32).wrapping_rem_euclid(0_i32); + ensure_use(&x); + }, + ""); +#endif +} + +} // namespace diff --git a/sus/num/i32_unittest.cc b/sus/num/i32_unittest.cc index a2718fc4e..b7670f391 100644 --- a/sus/num/i32_unittest.cc +++ b/sus/num/i32_unittest.cc @@ -672,7 +672,7 @@ TEST(i32DeathTest, Abs) { EXPECT_DEATH( { auto x = i32::MIN.abs(); - EXPECT_GT(x, 0); + ensure_use(&x); }, ""); #endif diff --git a/sus/num/signed_integer.h b/sus/num/signed_integer.h index d127f5791..ea2f88b1b 100644 --- a/sus/num/signed_integer.h +++ b/sus/num/signed_integer.h @@ -30,6 +30,7 @@ #include "sus/mem/move.h" #include "sus/mem/relocate.h" #include "sus/mem/size_of.h" +#include "sus/num/__private/check_integer_overflow.h" #include "sus/num/__private/int_log10.h" #include "sus/num/__private/intrinsics.h" #include "sus/num/__private/literals.h" @@ -222,14 +223,22 @@ constexpr inline P operator>>(P l, U r) noexcept = delete; /// Thus the bound is `std::convertible_to` (implicit conversion) instead of /// `sus::construct::From` (explicit conversion). /// +/// # Panics +/// This operator will panic if the shift amount is not less than +/// [`@doc.self::BITS`]($sus::num::@doc.self::BITS) and [overflow checks]( +/// $sus::num#overflow-behaviour) are enabled. +/// /// #[doc.overloads=signedint.<<] [[nodiscard]] sus_pure_const constexpr inline i8 operator<<( i8 l, std::convertible_to auto r) noexcept { - const auto out = - __private::shl_with_overflow(l.primitive_value, u64(r).primitive_value); - // TODO: Allow opting out of all overflow checks? - ::sus::check(!out.overflow); - return out.value; + if constexpr (SUS_CHECK_INTEGER_OVERFLOW) { + const auto out = + __private::shl_with_overflow(l.primitive_value, u64(r).primitive_value); + ::sus::check(!out.overflow); + return out.value; + } else { + return l.wrapping_shl(u64(r).primitive_value); + } } /// #[doc.overloads=signedint.<<] template @@ -239,14 +248,22 @@ constexpr inline i8 operator<<(i8 l, U r) noexcept = delete; /// /// Performs sign extension, copying the sign bit to the right if its set. /// +/// # Panics +/// This operator will panic if the shift amount is not less than +/// [`@doc.self::BITS`]($sus::num::@doc.self::BITS) and [overflow checks]( +/// $sus::num#overflow-behaviour) are enabled. +/// /// #[doc.overloads=signedint.>>] [[nodiscard]] sus_pure_const constexpr inline i8 operator>>( i8 l, std::convertible_to auto r) noexcept { - const auto out = - __private::shr_with_overflow(l.primitive_value, u64(r).primitive_value); - // TODO: Allow opting out of all overflow checks? - ::sus::check(!out.overflow); - return out.value; + if constexpr (SUS_CHECK_INTEGER_OVERFLOW) { + const auto out = + __private::shr_with_overflow(l.primitive_value, u64(r).primitive_value); + ::sus::check(!out.overflow); + return out.value; + } else { + return l.wrapping_shr(u64(r).primitive_value); + } } /// #[doc.overloads=signedint.>>] template @@ -255,11 +272,14 @@ constexpr inline i8 operator>>(i8 l, U r) noexcept = delete; /// #[doc.overloads=signedint.<<] [[nodiscard]] sus_pure_const constexpr inline i16 operator<<( i16 l, std::convertible_to auto r) noexcept { - const auto out = - __private::shl_with_overflow(l.primitive_value, u64(r).primitive_value); - // TODO: Allow opting out of all overflow checks? - ::sus::check(!out.overflow); - return out.value; + if constexpr (SUS_CHECK_INTEGER_OVERFLOW) { + const auto out = + __private::shl_with_overflow(l.primitive_value, u64(r).primitive_value); + ::sus::check(!out.overflow); + return out.value; + } else { + return l.wrapping_shl(u64(r).primitive_value); + } } /// #[doc.overloads=signedint.<<] template @@ -268,11 +288,14 @@ constexpr inline i16 operator<<(i16 l, U r) noexcept = delete; /// #[doc.overloads=signedint.>>] [[nodiscard]] sus_pure_const constexpr inline i16 operator>>( i16 l, std::convertible_to auto r) noexcept { - const auto out = - __private::shr_with_overflow(l.primitive_value, u64(r).primitive_value); - // TODO: Allow opting out of all overflow checks? - ::sus::check(!out.overflow); - return out.value; + if constexpr (SUS_CHECK_INTEGER_OVERFLOW) { + const auto out = + __private::shr_with_overflow(l.primitive_value, u64(r).primitive_value); + ::sus::check(!out.overflow); + return out.value; + } else { + return l.wrapping_shr(u64(r).primitive_value); + } } /// #[doc.overloads=signedint.>>] template @@ -281,11 +304,14 @@ constexpr inline i16 operator>>(i16 l, U r) noexcept = delete; /// #[doc.overloads=signedint.<<] [[nodiscard]] sus_pure_const constexpr inline i32 operator<<( i32 l, std::convertible_to auto r) noexcept { - const auto out = - __private::shl_with_overflow(l.primitive_value, u64(r).primitive_value); - // TODO: Allow opting out of all overflow checks? - ::sus::check(!out.overflow); - return out.value; + if constexpr (SUS_CHECK_INTEGER_OVERFLOW) { + const auto out = + __private::shl_with_overflow(l.primitive_value, u64(r).primitive_value); + ::sus::check(!out.overflow); + return out.value; + } else { + return l.wrapping_shl(u64(r).primitive_value); + } } /// #[doc.overloads=signedint.<<] template @@ -294,11 +320,14 @@ constexpr inline i32 operator<<(i32 l, U r) noexcept = delete; /// #[doc.overloads=signedint.>>] [[nodiscard]] sus_pure_const constexpr inline i32 operator>>( i32 l, std::convertible_to auto r) noexcept { - const auto out = - __private::shr_with_overflow(l.primitive_value, u64(r).primitive_value); - // TODO: Allow opting out of all overflow checks? - ::sus::check(!out.overflow); - return out.value; + if constexpr (SUS_CHECK_INTEGER_OVERFLOW) { + const auto out = + __private::shr_with_overflow(l.primitive_value, u64(r).primitive_value); + ::sus::check(!out.overflow); + return out.value; + } else { + return l.wrapping_shr(u64(r).primitive_value); + } } /// #[doc.overloads=signedint.>>] template @@ -307,11 +336,14 @@ constexpr inline i32 operator>>(i32 l, U r) noexcept = delete; /// #[doc.overloads=signedint.<<] [[nodiscard]] sus_pure_const constexpr inline i64 operator<<( i64 l, std::convertible_to auto r) noexcept { - const auto out = - __private::shl_with_overflow(l.primitive_value, u64(r).primitive_value); - // TODO: Allow opting out of all overflow checks? - ::sus::check(!out.overflow); - return out.value; + if constexpr (SUS_CHECK_INTEGER_OVERFLOW) { + const auto out = + __private::shl_with_overflow(l.primitive_value, u64(r).primitive_value); + ::sus::check(!out.overflow); + return out.value; + } else { + return l.wrapping_shl(u64(r).primitive_value); + } } /// #[doc.overloads=signedint.<<] template @@ -320,11 +352,14 @@ constexpr inline i64 operator<<(i64 l, U r) noexcept = delete; /// #[doc.overloads=signedint.>>] [[nodiscard]] sus_pure_const constexpr inline i64 operator>>( i64 l, std::convertible_to auto r) noexcept { - const auto out = - __private::shr_with_overflow(l.primitive_value, u64(r).primitive_value); - // TODO: Allow opting out of all overflow checks? - ::sus::check(!out.overflow); - return out.value; + if constexpr (SUS_CHECK_INTEGER_OVERFLOW) { + const auto out = + __private::shr_with_overflow(l.primitive_value, u64(r).primitive_value); + ::sus::check(!out.overflow); + return out.value; + } else { + return l.wrapping_shr(u64(r).primitive_value); + } } /// #[doc.overloads=signedint.>>] template @@ -333,11 +368,14 @@ constexpr inline i64 operator>>(i64 l, U r) noexcept = delete; /// #[doc.overloads=signedint.<<] [[nodiscard]] sus_pure_const constexpr inline isize operator<<( isize l, std::convertible_to auto r) noexcept { - const auto out = - __private::shl_with_overflow(l.primitive_value, u64(r).primitive_value); - // TODO: Allow opting out of all overflow checks? - ::sus::check(!out.overflow); - return out.value; + if constexpr (SUS_CHECK_INTEGER_OVERFLOW) { + const auto out = + __private::shl_with_overflow(l.primitive_value, u64(r).primitive_value); + ::sus::check(!out.overflow); + return out.value; + } else { + return l.wrapping_shl(u64(r).primitive_value); + } } /// #[doc.overloads=signedint.<<] template @@ -346,11 +384,14 @@ constexpr inline isize operator<<(isize l, U r) noexcept = delete; /// #[doc.overloads=signedint.>>] [[nodiscard]] sus_pure_const constexpr inline isize operator>>( isize l, std::convertible_to auto r) noexcept { - const auto out = - __private::shr_with_overflow(l.primitive_value, u64(r).primitive_value); - // TODO: Allow opting out of all overflow checks? - ::sus::check(!out.overflow); - return out.value; + if constexpr (SUS_CHECK_INTEGER_OVERFLOW) { + const auto out = + __private::shr_with_overflow(l.primitive_value, u64(r).primitive_value); + ::sus::check(!out.overflow); + return out.value; + } else { + return l.wrapping_shr(u64(r).primitive_value); + } } /// #[doc.overloads=signedint.>>] template diff --git a/sus/num/types.h b/sus/num/types.h index 00c72f49b..1e9acfbe2 100644 --- a/sus/num/types.h +++ b/sus/num/types.h @@ -39,9 +39,10 @@ namespace sus { /// The Subspace library numeric types can interoperate with primitive C++ /// types, but are safer than primitive C++ types and eliminate many classes of /// bugs that often lead to security vulnerabilities: -/// * Integer overflow is not allowed by default, and will -/// [`panic`]($sus::assertions::panic) to terminate the -/// program. Intentional overflow can be achieved through methods like +/// * Integer overflow is not allowed by default (see [Overflow behaviour]( +/// #overflow-behaviour), and will [`panic`]($sus::assertions::panic) +/// to terminate the program. +/// Intentional overflow can be achieved through methods like /// [`wrapping_add`]($sus::num::i32::wrapping_add) or /// [`saturating_mul`]($sus::num::i32::saturating_mul). The /// [`OverflowInteger`]($sus::num::OverflowInteger) type can be used for a @@ -66,6 +67,19 @@ namespace sus { /// [`pow`]($sus::num::i32::pow), [`log10`]($sus::num::i32::log10), or /// [`leading_ones`]($sus::num::i32::leading_ones). /// +/// # Overflow behaviour +/// +/// The default build configuration will panic on integer overflow in arithmetic +/// operations (`+`, `-`, `*`, `/`, etc). These checks can be disabled by +/// defining `SUS_CHECK_INTEGER_OVERFLOW` to false during compilation. Both +/// signed and unsigned integers will then overflow by performing wrapping +/// operations. There is no Undefined Behaviour with signed or unsigned integers +/// unless going through the unchecked operations explicitly, such as +/// [`unchecked_add`]($sus::num::i32::unchecked_add). +/// +/// Division by zero, or overflow in integer division will panic regardless of +/// whether overflow checks are enabled. +/// /// # Conversions /// /// To explicitly invoke a lossless conversion, use diff --git a/sus/num/unsigned_integer.h b/sus/num/unsigned_integer.h index 9a778e0a7..215981edf 100644 --- a/sus/num/unsigned_integer.h +++ b/sus/num/unsigned_integer.h @@ -36,6 +36,7 @@ #include "sus/num/__private/intrinsics.h" #include "sus/num/__private/literals.h" #include "sus/num/__private/primitive_type.h" +#include "sus/num/__private/check_integer_overflow.h" #include "sus/num/float_concepts.h" #include "sus/num/integer_concepts.h" #include "sus/num/try_from_int_error.h"