Skip to content

Commit a5fa5ee

Browse files
committed
Merge branch 'gh47926-safe-arith' into exp-csv-fuzz-safe-arith
2 parents 23582e7 + b9b1245 commit a5fa5ee

File tree

6 files changed

+3754
-1109
lines changed

6 files changed

+3754
-1109
lines changed

cpp/src/arrow/util/int_util_overflow.h

Lines changed: 105 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -25,62 +25,130 @@
2525
#include "arrow/util/macros.h"
2626
#include "arrow/util/visibility.h"
2727

28-
// "safe-math.h" includes <intsafe.h> from the Windows headers.
29-
#include "arrow/vendored/portable-snippets/safe-math.h"
30-
// clang-format off (avoid include reordering)
31-
// clang-format on
28+
#include "arrow/vendored/safeint/safe_math.h"
3229

3330
namespace arrow {
3431
namespace internal {
3532

33+
// static inline bool check_add_int32_int32(int32_t a, int32_t b, int32_t* ret)
34+
3635
// Define functions AddWithOverflow, SubtractWithOverflow, MultiplyWithOverflow
3736
// with the signature `bool(T u, T v, T* out)` where T is an integer type.
3837
// On overflow, these functions return true. Otherwise, false is returned
3938
// and `out` is updated with the result of the operation.
4039

41-
#define OP_WITH_OVERFLOW(_func_name, _psnip_op, _type, _psnip_type) \
42-
[[nodiscard]] static inline bool _func_name(_type u, _type v, _type* out) { \
43-
return !psnip_safe_##_psnip_type##_##_psnip_op(out, u, v); \
40+
#define SAFE_INT_OP_WITH_OVERFLOW(_func_name, _op_name, _c_type, _type) \
41+
[[nodiscard]] static inline bool _func_name(_c_type u, _c_type v, _c_type* out) { \
42+
return !check_##_op_name##_##_type##_##_type(u, v, out); \
4443
}
4544

46-
#define OPS_WITH_OVERFLOW(_func_name, _psnip_op) \
47-
OP_WITH_OVERFLOW(_func_name, _psnip_op, int8_t, int8) \
48-
OP_WITH_OVERFLOW(_func_name, _psnip_op, int16_t, int16) \
49-
OP_WITH_OVERFLOW(_func_name, _psnip_op, int32_t, int32) \
50-
OP_WITH_OVERFLOW(_func_name, _psnip_op, int64_t, int64) \
51-
OP_WITH_OVERFLOW(_func_name, _psnip_op, uint8_t, uint8) \
52-
OP_WITH_OVERFLOW(_func_name, _psnip_op, uint16_t, uint16) \
53-
OP_WITH_OVERFLOW(_func_name, _psnip_op, uint32_t, uint32) \
54-
OP_WITH_OVERFLOW(_func_name, _psnip_op, uint64_t, uint64)
45+
#define SAFE_INT_OPS_WITH_OVERFLOW(_func_name, _psnip_op) \
46+
SAFE_INT_OP_WITH_OVERFLOW(_func_name, _psnip_op, int32_t, int32) \
47+
SAFE_INT_OP_WITH_OVERFLOW(_func_name, _psnip_op, int64_t, int64) \
48+
SAFE_INT_OP_WITH_OVERFLOW(_func_name, _psnip_op, uint32_t, uint32) \
49+
SAFE_INT_OP_WITH_OVERFLOW(_func_name, _psnip_op, uint64_t, uint64)
50+
51+
SAFE_INT_OPS_WITH_OVERFLOW(SafeIntAddWithOverflow, add)
52+
SAFE_INT_OPS_WITH_OVERFLOW(SafeIntSubtractWithOverflow, sub)
53+
SAFE_INT_OPS_WITH_OVERFLOW(SafeIntMultiplyWithOverflow, mul)
54+
SAFE_INT_OPS_WITH_OVERFLOW(SafeIntDivideWithOverflow, div)
55+
56+
#undef SAFE_INT_OP_WITH_OVERFLOW
57+
#undef SAFE_INT_OPS_WITH_OVERFLOW
58+
59+
template <typename Int, typename SignedRet, typename UnsignedRet>
60+
using transformed_int_t =
61+
std::conditional_t<std::is_signed_v<Int>, SignedRet, UnsignedRet>;
62+
63+
template <typename Int>
64+
using upscaled_int32_t = transformed_int_t<Int, int32_t, uint32_t>;
65+
66+
// TODO use builtins on clang/gcc
67+
68+
template <typename Int>
69+
[[nodiscard]] bool AddWithOverflowGeneric(Int u, Int v, Int* out) {
70+
if constexpr (sizeof(Int) < 4) {
71+
auto r =
72+
static_cast<upscaled_int32_t<Int>>(u) + static_cast<upscaled_int32_t<Int>>(v);
73+
*out = static_cast<Int>(r);
74+
return r != *out;
75+
} else {
76+
return SafeIntAddWithOverflow(u, v, out);
77+
}
78+
}
5579

56-
OPS_WITH_OVERFLOW(AddWithOverflow, add)
57-
OPS_WITH_OVERFLOW(SubtractWithOverflow, sub)
58-
OPS_WITH_OVERFLOW(MultiplyWithOverflow, mul)
59-
OPS_WITH_OVERFLOW(DivideWithOverflow, div)
80+
template <typename Int>
81+
[[nodiscard]] bool SubtractWithOverflowGeneric(Int u, Int v, Int* out) {
82+
if constexpr (sizeof(Int) < 4) {
83+
auto r =
84+
static_cast<upscaled_int32_t<Int>>(u) - static_cast<upscaled_int32_t<Int>>(v);
85+
*out = static_cast<Int>(r);
86+
return r != *out;
87+
} else {
88+
return SafeIntSubtractWithOverflow(u, v, out);
89+
}
90+
}
6091

61-
#undef OP_WITH_OVERFLOW
62-
#undef OPS_WITH_OVERFLOW
92+
template <typename Int>
93+
[[nodiscard]] bool MultiplyWithOverflowGeneric(Int u, Int v, Int* out) {
94+
if constexpr (sizeof(Int) < 4) {
95+
auto r =
96+
static_cast<upscaled_int32_t<Int>>(u) * static_cast<upscaled_int32_t<Int>>(v);
97+
*out = static_cast<Int>(r);
98+
return r != *out;
99+
} else {
100+
return SafeIntMultiplyWithOverflow(u, v, out);
101+
}
102+
}
63103

64-
// Define function NegateWithOverflow with the signature `bool(T u, T* out)`
65-
// where T is a signed integer type. On overflow, these functions return true.
66-
// Otherwise, false is returned and `out` is updated with the result of the
67-
// operation.
104+
template <typename Int>
105+
[[nodiscard]] bool DivideWithOverflowGeneric(Int u, Int v, Int* out) {
106+
if constexpr (sizeof(Int) < 4) {
107+
using UpscaledInt = upscaled_int32_t<Int>;
108+
UpscaledInt r;
109+
bool error = SafeIntDivideWithOverflow(UpscaledInt{u}, UpscaledInt{v}, &r);
110+
*out = static_cast<Int>(r);
111+
return error || r != *out;
112+
} else {
113+
return SafeIntDivideWithOverflow(u, v, out);
114+
}
115+
}
68116

69-
#define UNARY_OP_WITH_OVERFLOW(_func_name, _psnip_op, _type, _psnip_type) \
70-
[[nodiscard]] static inline bool _func_name(_type u, _type* out) { \
71-
return !psnip_safe_##_psnip_type##_##_psnip_op(out, u); \
117+
// Define non-generic versions of the above so as to benefit from automatic
118+
// integer conversion, to allow for mixed-type calls such as
119+
// AddWithOverflow(int32_t, int64_t, int64_t*).
120+
121+
#define NON_GENERIC_OP_WITH_OVERFLOW(_func_name, _c_type) \
122+
[[nodiscard]] inline bool _func_name(_c_type u, _c_type v, _c_type* out) { \
123+
return _func_name##Generic(u, v, out); \
72124
}
73125

74-
#define SIGNED_UNARY_OPS_WITH_OVERFLOW(_func_name, _psnip_op) \
75-
UNARY_OP_WITH_OVERFLOW(_func_name, _psnip_op, int8_t, int8) \
76-
UNARY_OP_WITH_OVERFLOW(_func_name, _psnip_op, int16_t, int16) \
77-
UNARY_OP_WITH_OVERFLOW(_func_name, _psnip_op, int32_t, int32) \
78-
UNARY_OP_WITH_OVERFLOW(_func_name, _psnip_op, int64_t, int64)
126+
#define NON_GENERIC_OPS_WITH_OVERFLOW(_func_name) \
127+
NON_GENERIC_OP_WITH_OVERFLOW(_func_name, int8_t) \
128+
NON_GENERIC_OP_WITH_OVERFLOW(_func_name, uint8_t) \
129+
NON_GENERIC_OP_WITH_OVERFLOW(_func_name, int16_t) \
130+
NON_GENERIC_OP_WITH_OVERFLOW(_func_name, uint16_t) \
131+
NON_GENERIC_OP_WITH_OVERFLOW(_func_name, int32_t) \
132+
NON_GENERIC_OP_WITH_OVERFLOW(_func_name, uint32_t) \
133+
NON_GENERIC_OP_WITH_OVERFLOW(_func_name, int64_t) \
134+
NON_GENERIC_OP_WITH_OVERFLOW(_func_name, uint64_t)
135+
136+
NON_GENERIC_OPS_WITH_OVERFLOW(AddWithOverflow)
137+
NON_GENERIC_OPS_WITH_OVERFLOW(SubtractWithOverflow)
138+
NON_GENERIC_OPS_WITH_OVERFLOW(MultiplyWithOverflow)
139+
NON_GENERIC_OPS_WITH_OVERFLOW(DivideWithOverflow)
79140

80-
SIGNED_UNARY_OPS_WITH_OVERFLOW(NegateWithOverflow, neg)
141+
#undef NON_GENERIC_OPS_WITH_OVERFLOW
142+
#undef NON_GENERIC_OP_WITH_OVERFLOW
81143

82-
#undef UNARY_OP_WITH_OVERFLOW
83-
#undef SIGNED_UNARY_OPS_WITH_OVERFLOW
144+
// Define function NegateWithOverflow with the signature `bool(T u, T* out)`
145+
// where T is a signed integer type. On overflow, these functions return true.
146+
// Otherwise, false is returned and `out` is updated with the result of the
147+
// operation.
148+
template <typename Int>
149+
[[nodiscard]] bool NegateWithOverflow(Int v, Int* out) {
150+
return SubtractWithOverflow(Int{}, v, out);
151+
}
84152

85153
/// Signed addition with well-defined behaviour on overflow (as unsigned)
86154
template <typename SignedInt>

0 commit comments

Comments
 (0)