Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve _(Add|Multiply)_with_overflow_check #3678

Merged
merged 7 commits into from
May 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 4 additions & 65 deletions stl/inc/ranges
Original file line number Diff line number Diff line change
Expand Up @@ -9577,67 +9577,6 @@ namespace ranges {
_EXPORT_STD inline constexpr _Adjacent_transform_fn<2> pairwise_transform;
} // namespace views

template <_Integer_like _Int>
_NODISCARD constexpr bool _Add_with_overflow_check(const _Int _Left, const _Int _Right, _Int& _Out) {
#ifdef __clang__
if constexpr (integral<_Int>) {
return __builtin_add_overflow(_Left, _Right, &_Out);
} else
#endif // __clang__
{
if constexpr (!_Signed_integer_like<_Int>) {
_Out = static_cast<_Int>(_Left + _Right);
return _Out < _Left || _Out < _Right;
} else {
using _UInt = _Make_unsigned_like_t<_Int>;
_Out = static_cast<_Int>(static_cast<_UInt>(_Left) + static_cast<_UInt>(_Right));
return (_Left > 0 && _Right > 0 && _Out <= 0) || (_Left < 0 && _Right < 0 && _Out >= 0);
}
}
}

template <_Integer_like _Int>
_NODISCARD constexpr bool _Multiply_with_overflow_check(const _Int _Left, const _Int _Right, _Int& _Out) {
#ifdef __clang__
if constexpr (integral<_Int>) {
return __builtin_mul_overflow(_Left, _Right, &_Out);
} else
#endif // __clang__
{
if constexpr (!_Signed_integer_like<_Int>) {
_Out = static_cast<_Int>(_Left * _Right);
return _Left != 0 && _Right > (numeric_limits<_Int>::max)() / _Left;
} else {
// vvv Based on llvm::MulOverflow vvv
// https://github.com/llvm/llvm-project/blob/88e5206/llvm/include/llvm/Support/MathExtras.h#L725-L750
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
using _UInt = _Make_unsigned_like_t<_Int>;
const _UInt _ULeft = _Left < 0 ? (0 - static_cast<_UInt>(_Left)) : static_cast<_UInt>(_Left);
const _UInt _URight = _Right < 0 ? (0 - static_cast<_UInt>(_Right)) : static_cast<_UInt>(_Right);
const _UInt _UResult = static_cast<_UInt>(_ULeft * _URight);

const bool _Negative = (_Left < 0) != (_Right < 0);
_Out = static_cast<_Int>(_Negative ? (0 - _UResult) : _UResult);
if (_ULeft == 0 || _URight == 0) {
return false;
}

if (_Negative) {
return _ULeft > (static_cast<_UInt>((numeric_limits<_Int>::max)()) + _UInt{1}) / _URight;
} else {
return _ULeft > static_cast<_UInt>((numeric_limits<_Int>::max)()) / _URight;
}
// ^^^ Based on llvm::MulOverflow ^^^
}
}
}

template <bool _Const, class _First, class... _Rest>
concept _Cartesian_product_is_random_access = (random_access_range<_Maybe_const<_Const, _First>> && ...
&& (random_access_range<_Maybe_const<_Const, _Rest>>
Expand Down Expand Up @@ -9863,8 +9802,8 @@ namespace ranges {
const auto _Size =
static_cast<_Difference_type<_Const>>(_RANGES size(_STD get<_Index>(_Parent->_Bases)));
[[maybe_unused]] const bool _Overflow =
_Multiply_with_overflow_check(_Size, _Distance_from<_Index - 1>(_Tpl), _Result)
|| _Add_with_overflow_check(_Result, _Diff, _Result);
_Mul_overflow(_Size, _Distance_from<_Index - 1>(_Tpl), _Result)
|| _Add_overflow(_Result, _Diff, _Result);
#if _ITERATOR_DEBUG_LEVEL != 0
_STL_VERIFY(!_Overflow, "Scaled-sum cannot be represented by difference_type (N4928 "
"[range.cartesian.iterator]/8).");
Expand Down Expand Up @@ -10121,7 +10060,7 @@ namespace ranges {
#if _CONTAINER_DEBUG_LEVEL > 0
_Size_type<false> _Product{1};
const bool _Overflow =
(_Multiply_with_overflow_check(
(_Mul_overflow(
_Product, static_cast<_Size_type<false>>(_RANGES size(_STD get<_Indices>(_Bases))), _Product)
|| ...);
_STL_VERIFY(!_Overflow, "Size of cartesian product cannot be represented by size type (N4928 "
Expand All @@ -10141,7 +10080,7 @@ namespace ranges {
#if _CONTAINER_DEBUG_LEVEL > 0
_Size_type<true> _Product{1};
const bool _Overflow =
(_Multiply_with_overflow_check(
(_Mul_overflow(
_Product, static_cast<_Size_type<true>>(_RANGES size(_STD get<_Indices>(_Bases))), _Product)
|| ...);
_STL_VERIFY(!_Overflow, "Size of cartesian product cannot be represented by size type (N4928 "
Expand Down
72 changes: 72 additions & 0 deletions stl/inc/xutility
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
#include <cstdlib>
#include <cstring>

#if _HAS_CXX23
#include <limits>
JMazurkiewicz marked this conversation as resolved.
Show resolved Hide resolved
#endif // _HAS_CXX23

#pragma pack(push, _CRT_PACKING)
#pragma warning(push, _STL_WARNING_LEVEL)
#pragma warning(disable : _STL_DISABLED_WARNINGS)
Expand Down Expand Up @@ -7280,6 +7284,74 @@ _NODISCARD constexpr bool _Is_finite(const _Ty _Xx) { // constexpr isfinite()
_EXPORT_STD struct monostate {};
#endif // _HAS_CXX17

#if _HAS_CXX23 && defined(__cpp_lib_concepts) // TRANSITION, GH-395
template <_Integer_like _Int>
_NODISCARD constexpr bool _Add_overflow(const _Int _Left, const _Int _Right, _Int& _Out) {
#ifdef __clang__
if constexpr (integral<_Int>) {
return __builtin_add_overflow(_Left, _Right, &_Out);
} else
#endif // __clang__
{
if constexpr (!_Signed_integer_like<_Int>) {
_Out = static_cast<_Int>(_Left + _Right);
return _Out < _Left || _Out < _Right;
} else {
using _UInt = _Make_unsigned_like_t<_Int>;
_Out = static_cast<_Int>(static_cast<_UInt>(_Left) + static_cast<_UInt>(_Right));
return (_Left > 0 && _Right > 0 && _Out <= 0) || (_Left < 0 && _Right < 0 && _Out >= 0);
}
}
}

template <_Integer_like _Int>
_NODISCARD constexpr bool _Mul_overflow(const _Int _Left, const _Int _Right, _Int& _Out) {
#ifdef __clang__
if constexpr (integral<_Int>) {
return __builtin_mul_overflow(_Left, _Right, &_Out);
} else
#endif // __clang__
{
if constexpr (!_Signed_integer_like<_Int>) {
const bool _Overflow = _Left != 0 && _Right > (numeric_limits<_Int>::max)() / _Left;
if (!_Overflow) {
_Out = static_cast<_Int>(_Left * _Right);
}
return _Overflow;
} else {
// vvv Based on llvm::MulOverflow vvv
// https://github.com/llvm/llvm-project/blob/88e5206/llvm/include/llvm/Support/MathExtras.h#L725-L750
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
using _UInt = _Make_unsigned_like_t<_Int>;
const _UInt _ULeft =
static_cast<_UInt>(_Left < 0 ? (0 - static_cast<_UInt>(_Left)) : static_cast<_UInt>(_Left));
const _UInt _URight =
static_cast<_UInt>(_Right < 0 ? (0 - static_cast<_UInt>(_Right)) : static_cast<_UInt>(_Right));
const _UInt _UResult = static_cast<_UInt>(_ULeft * _URight);

const bool _Negative = (_Left < 0) != (_Right < 0);
_Out = static_cast<_Int>(_Negative ? (0 - _UResult) : _UResult);
if (_ULeft == 0 || _URight == 0) {
return false;
}

if (_Negative) {
return _ULeft > (static_cast<_UInt>((numeric_limits<_Int>::max)()) + _UInt{1}) / _URight;
} else {
return _ULeft > static_cast<_UInt>((numeric_limits<_Int>::max)()) / _URight;
}
// ^^^ Based on llvm::MulOverflow ^^^
}
}
}
#endif // _HAS_CXX23 && defined(__cpp_lib_concepts)

_STD_END
#pragma pop_macro("new")
_STL_RESTORE_CLANG_WARNINGS
Expand Down
1 change: 1 addition & 0 deletions tests/std/test.lst
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,7 @@ tests\P2321R2_views_adjacent_transform
tests\P2321R2_views_zip
tests\P2321R2_views_zip_transform
tests\P2322R6_ranges_alg_fold
tests\P2374R4_checked_arithmetic_operations
tests\P2374R4_views_cartesian_product
tests\P2374R4_views_cartesian_product_death
tests\P2387R3_bind_back
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

RUNALL_INCLUDE ..\concepts_latest_matrix.lst
90 changes: 90 additions & 0 deletions tests/std/tests/P2374R4_checked_arithmetic_operations/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <cassert>
#include <cstdint>
#include <limits>
#include <ranges>
#include <utility>

using namespace std;

// Check MSVC-STL internal machinery

template <class T>
constexpr void check_add_overflow() {
constexpr bool is_T_signed = numeric_limits<T>::is_signed;
T out;
const T minval = numeric_limits<T>::min();
const T maxval = numeric_limits<T>::max();

assert(!_Add_overflow(T{0}, T{0}, out) && out == T{0});
assert(!_Add_overflow(T{1}, T{1}, out) && out == T{2});
assert(!_Add_overflow(minval, T{0}, out) && out == minval);
assert(!_Add_overflow(maxval, T{0}, out) && out == maxval);
assert(!_Add_overflow(minval, maxval, out) && out == static_cast<T>(-1));
assert(_Add_overflow(maxval, T{1}, out));
assert(_Add_overflow(static_cast<T>(maxval / 2), maxval, out));
assert(!_Add_overflow(static_cast<T>(maxval / 2), static_cast<T>(maxval / 2), out)
&& out == static_cast<T>(maxval - 1));
assert(_Add_overflow(maxval, maxval, out));

if constexpr (is_T_signed) {
assert(!_Add_overflow(T{1}, T{-1}, out) && out == T{0});
assert(_Add_overflow(minval, T{-1}, out));
assert(_Add_overflow(minval, minval, out));
assert(_Add_overflow(T{100}, T{28}, out) == (sizeof(T) == 1));
} else {
assert(_Add_overflow(T{200}, T{56}, out) == (sizeof(T) == 1));
}
}

template <class T>
constexpr void check_mul_overflow() {
constexpr bool is_T_signed = numeric_limits<T>::is_signed;
T out;
const T minval = numeric_limits<T>::min();
const T maxval = numeric_limits<T>::max();

assert(!_Mul_overflow(T{0}, T{0}, out) && out == T{0});
assert(!_Mul_overflow(T{1}, T{1}, out) && out == T{1});
assert(!_Mul_overflow(minval, T{0}, out) && out == T{0});
assert(!_Mul_overflow(minval, T{1}, out) && out == minval);
assert(!_Mul_overflow(maxval, T{0}, out) && out == T{0});
assert(!_Mul_overflow(maxval, T{1}, out) && out == maxval);
assert(_Mul_overflow(maxval, maxval, out));
assert(_Mul_overflow(minval, maxval, out) == is_T_signed);
assert(_Mul_overflow(minval, minval, out) == is_T_signed);
assert(!_Mul_overflow(static_cast<T>(maxval / 2), T{2}, out) && out == static_cast<T>(maxval - 1));
assert(_Mul_overflow(T{0x77}, T{0x78}, out) == (sizeof(T) == 1));

if constexpr (is_T_signed) {
assert(_Mul_overflow(minval, T{-1}, out));
assert(!_Mul_overflow(maxval, T{-1}, out) && out == minval + 1);
}
}

template <class T>
constexpr void check_add_and_mul() {
check_add_overflow<T>();
check_mul_overflow<T>();
}

constexpr bool test() {
check_add_and_mul<int8_t>();
check_add_and_mul<uint8_t>();
check_add_and_mul<int16_t>();
check_add_and_mul<uint16_t>();
check_add_and_mul<int32_t>();
check_add_and_mul<uint32_t>();
check_add_and_mul<int64_t>();
check_add_and_mul<uint64_t>();
check_add_and_mul<_Signed128>();
check_add_and_mul<_Unsigned128>();
return true;
}

int main() {
static_assert(test());
test();
}