Skip to content

Commit 516aa02

Browse files
projectgusdpgeorge
authored andcommitted
py/objint_longlong: Add arithmetic overflow checks.
Long long big integer support now raises an exception on overflow rather than returning an undefined result. Also adds an error when shifting by a negative value. The new arithmetic checks are added in the misc.h header. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <angus@redyak.com.au>
1 parent d07f103 commit 516aa02

File tree

4 files changed

+154
-22
lines changed

4 files changed

+154
-22
lines changed

py/misc.h

Lines changed: 98 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,15 @@
3333
#include <stdbool.h>
3434
#include <stdint.h>
3535
#include <stddef.h>
36+
#include <limits.h>
3637

3738
typedef unsigned char byte;
3839
typedef unsigned int uint;
3940

41+
#ifndef __has_builtin
42+
#define __has_builtin(x) (0)
43+
#endif
44+
4045
/** generic ops *************************************************/
4146

4247
#ifndef MIN
@@ -374,26 +379,23 @@ static inline bool mp_check(bool value) {
374379
static inline uint32_t mp_popcount(uint32_t x) {
375380
return __popcnt(x);
376381
}
377-
#else
382+
#else // _MSC_VER
378383
#define mp_clz(x) __builtin_clz(x)
379384
#define mp_clzl(x) __builtin_clzl(x)
380385
#define mp_clzll(x) __builtin_clzll(x)
381386
#define mp_ctz(x) __builtin_ctz(x)
382387
#define mp_check(x) (x)
383-
#if defined __has_builtin
384388
#if __has_builtin(__builtin_popcount)
385389
#define mp_popcount(x) __builtin_popcount(x)
386-
#endif
387-
#endif
388-
#if !defined(mp_popcount)
390+
#else
389391
static inline uint32_t mp_popcount(uint32_t x) {
390392
x = x - ((x >> 1) & 0x55555555);
391393
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
392394
x = (x + (x >> 4)) & 0x0F0F0F0F;
393395
return (x * 0x01010101) >> 24;
394396
}
395-
#endif
396-
#endif
397+
#endif // __has_builtin(__builtin_popcount)
398+
#endif // _MSC_VER
397399

398400
#define MP_FIT_UNSIGNED(bits, value) (((value) & (~0U << (bits))) == 0)
399401
#define MP_FIT_SIGNED(bits, value) \
@@ -426,4 +428,93 @@ static inline uint32_t mp_clz_mpi(mp_int_t x) {
426428
#endif
427429
}
428430

431+
// Overflow-checked operations for long long
432+
433+
// Integer overflow builtins were added to GCC 5, but __has_builtin only in GCC 10
434+
//
435+
// Note that the builtins has a defined result when overflow occurs, whereas the custom
436+
// functions below don't update the result if an overflow would occur (to avoid UB).
437+
#define MP_GCC_HAS_BUILTIN_OVERFLOW (__GNUC__ >= 5)
438+
439+
#if __has_builtin(__builtin_umulll_overflow) || MP_GCC_HAS_BUILTIN_OVERFLOW
440+
#define mp_mul_ull_overflow __builtin_umulll_overflow
441+
#else
442+
inline static bool mp_mul_ull_overflow(unsigned long long int x, unsigned long long int y, unsigned long long int *res) {
443+
if (y > 0 && x > (ULLONG_MAX / y)) {
444+
return true; // overflow
445+
}
446+
*res = x * y;
447+
return false;
448+
}
449+
#endif
450+
451+
#if __has_builtin(__builtin_smulll_overflow) || MP_GCC_HAS_BUILTIN_OVERFLOW
452+
#define mp_mul_ll_overflow __builtin_smulll_overflow
453+
#else
454+
inline static bool mp_mul_ll_overflow(long long int x, long long int y, long long int *res) {
455+
bool overflow;
456+
457+
// Check for multiply overflow; see CERT INT32-C
458+
if (x > 0) { // x is positive
459+
if (y > 0) { // x and y are positive
460+
overflow = (x > (LLONG_MAX / y));
461+
} else { // x positive, y nonpositive
462+
overflow = (y < (LLONG_MIN / x));
463+
} // x positive, y nonpositive
464+
} else { // x is nonpositive
465+
if (y > 0) { // x is nonpositive, y is positive
466+
overflow = (x < (LLONG_MIN / y));
467+
} else { // x and y are nonpositive
468+
overflow = (x != 0 && y < (LLONG_MAX / x));
469+
} // End if x and y are nonpositive
470+
} // End if x is nonpositive
471+
472+
if (!overflow) {
473+
*res = x * y;
474+
}
475+
476+
return overflow;
477+
}
478+
#endif
479+
480+
#if __has_builtin(__builtin_saddll_overflow) || MP_GCC_HAS_BUILTIN_OVERFLOW
481+
#define mp_add_ll_overflow __builtin_saddll_overflow
482+
#else
483+
inline static bool mp_add_ll_overflow(long long int lhs, long long int rhs, long long int *res) {
484+
bool overflow;
485+
486+
if (rhs > 0) {
487+
overflow = (lhs > LLONG_MAX - rhs);
488+
} else {
489+
overflow = (lhs < LLONG_MIN - rhs);
490+
}
491+
492+
if (!overflow) {
493+
*res = lhs + rhs;
494+
}
495+
496+
return overflow;
497+
}
498+
#endif
499+
500+
#if __has_builtin(__builtin_ssubll_overflow) || MP_GCC_HAS_BUILTIN_OVERFLOW
501+
#define mp_sub_ll_overflow __builtin_ssubll_overflow
502+
#else
503+
inline static bool mp_sub_ll_overflow(long long int lhs, long long int rhs, long long int *res) {
504+
bool overflow;
505+
506+
if (rhs > 0) {
507+
overflow = (lhs < LLONG_MIN + rhs);
508+
} else {
509+
overflow = (lhs > LLONG_MAX + rhs);
510+
}
511+
512+
if (!overflow) {
513+
*res = lhs - rhs;
514+
}
515+
516+
return overflow;
517+
}
518+
#endif
519+
429520
#endif // MICROPY_INCLUDED_PY_MISC_H

py/objint_longlong.c

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "py/smallint.h"
3232
#include "py/objint.h"
3333
#include "py/runtime.h"
34+
#include "py/misc.h"
3435

3536
#if MICROPY_PY_BUILTINS_FLOAT
3637
#include <math.h>
@@ -43,6 +44,10 @@
4344
const mp_obj_int_t mp_sys_maxsize_obj = {{&mp_type_int}, MP_SSIZE_MAX};
4445
#endif
4546

47+
static void raise_long_long_overflow(void) {
48+
mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("result overflows long long storage"));
49+
}
50+
4651
mp_obj_t mp_obj_int_from_bytes_impl(bool big_endian, size_t len, const byte *buf) {
4752
int delta = 1;
4853
if (!big_endian) {
@@ -120,7 +125,6 @@ mp_obj_t mp_obj_int_unary_op(mp_unary_op_t op, mp_obj_t o_in) {
120125
// small int if the value fits without truncation
121126
case MP_UNARY_OP_HASH:
122127
return MP_OBJ_NEW_SMALL_INT((mp_int_t)o->val);
123-
124128
case MP_UNARY_OP_POSITIVE:
125129
return o_in;
126130
case MP_UNARY_OP_NEGATIVE:
@@ -147,6 +151,8 @@ mp_obj_t mp_obj_int_unary_op(mp_unary_op_t op, mp_obj_t o_in) {
147151
mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) {
148152
long long lhs_val;
149153
long long rhs_val;
154+
bool overflow = false;
155+
long long result;
150156

151157
if (mp_obj_is_small_int(lhs_in)) {
152158
lhs_val = MP_OBJ_SMALL_INT_VALUE(lhs_in);
@@ -167,13 +173,16 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i
167173
switch (op) {
168174
case MP_BINARY_OP_ADD:
169175
case MP_BINARY_OP_INPLACE_ADD:
170-
return mp_obj_new_int_from_ll(lhs_val + rhs_val);
176+
overflow = mp_add_ll_overflow(lhs_val, rhs_val, &result);
177+
break;
171178
case MP_BINARY_OP_SUBTRACT:
172179
case MP_BINARY_OP_INPLACE_SUBTRACT:
173-
return mp_obj_new_int_from_ll(lhs_val - rhs_val);
180+
overflow = mp_sub_ll_overflow(lhs_val, rhs_val, &result);
181+
break;
174182
case MP_BINARY_OP_MULTIPLY:
175183
case MP_BINARY_OP_INPLACE_MULTIPLY:
176-
return mp_obj_new_int_from_ll(lhs_val * rhs_val);
184+
overflow = mp_mul_ll_overflow(lhs_val, rhs_val, &result);
185+
break;
177186
case MP_BINARY_OP_FLOOR_DIVIDE:
178187
case MP_BINARY_OP_INPLACE_FLOOR_DIVIDE:
179188
if (rhs_val == 0) {
@@ -199,9 +208,21 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i
199208

200209
case MP_BINARY_OP_LSHIFT:
201210
case MP_BINARY_OP_INPLACE_LSHIFT:
202-
return mp_obj_new_int_from_ll(lhs_val << (int)rhs_val);
211+
if ((int)rhs_val < 0) {
212+
// negative shift not allowed
213+
mp_raise_ValueError(MP_ERROR_TEXT("negative shift count"));
214+
}
215+
result = lhs_val << (int)rhs_val;
216+
// Left-shifting of negative values is implementation defined in C, but assume compiler
217+
// will give us typical 2s complement behaviour unless the value overflows
218+
overflow = rhs_val > 0 && ((lhs_val >= 0 && result < lhs_val) || (lhs_val < 0 && result > lhs_val));
219+
break;
203220
case MP_BINARY_OP_RSHIFT:
204221
case MP_BINARY_OP_INPLACE_RSHIFT:
222+
if ((int)rhs_val < 0) {
223+
// negative shift not allowed
224+
mp_raise_ValueError(MP_ERROR_TEXT("negative shift count"));
225+
}
205226
return mp_obj_new_int_from_ll(lhs_val >> (int)rhs_val);
206227

207228
case MP_BINARY_OP_POWER:
@@ -213,18 +234,18 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i
213234
mp_raise_ValueError(MP_ERROR_TEXT("negative power with no float support"));
214235
#endif
215236
}
216-
long long ans = 1;
217-
while (rhs_val > 0) {
237+
result = 1;
238+
while (rhs_val > 0 && !overflow) {
218239
if (rhs_val & 1) {
219-
ans *= lhs_val;
240+
overflow = mp_mul_ll_overflow(result, lhs_val, &result);
220241
}
221-
if (rhs_val == 1) {
242+
if (rhs_val == 1 || overflow) {
222243
break;
223244
}
224245
rhs_val /= 2;
225-
lhs_val *= lhs_val;
246+
overflow = mp_mul_ll_overflow(lhs_val, lhs_val, &lhs_val);
226247
}
227-
return mp_obj_new_int_from_ll(ans);
248+
break;
228249
}
229250

230251
case MP_BINARY_OP_LESS:
@@ -242,6 +263,12 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i
242263
return MP_OBJ_NULL; // op not supported
243264
}
244265

266+
if (overflow) {
267+
raise_long_long_overflow();
268+
}
269+
270+
return mp_obj_new_int_from_ll(result);
271+
245272
zero_division:
246273
mp_raise_msg(&mp_type_ZeroDivisionError, MP_ERROR_TEXT("divide by zero"));
247274
}
@@ -267,7 +294,7 @@ mp_obj_t mp_obj_new_int_from_ll(long long val) {
267294
mp_obj_t mp_obj_new_int_from_ull(unsigned long long val) {
268295
// TODO raise an exception if the unsigned long long won't fit
269296
if (val >> (sizeof(unsigned long long) * 8 - 1) != 0) {
270-
mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("ulonglong too large"));
297+
raise_long_long_overflow();
271298
}
272299
return mp_obj_new_int_from_ll(val);
273300
}

tests/basics/int_64_basics.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,21 @@
117117
# sys.maxsize is a constant bigint, so test it's compatible with dynamic ones
118118
import sys
119119
if hasattr(sys, "maxsize"):
120-
print(sys.maxsize + 1 - 1 == sys.maxsize)
120+
print(sys.maxsize - 1 + 1 == sys.maxsize)
121121
else:
122122
print(True) # No maxsize property in this config
123123

124124
# test extraction of big int value via mp_obj_get_int_maybe
125125
x = 1 << 62
126126
print('a' * (x + 4 - x))
127+
128+
# negative shifts are invalid
129+
try:
130+
print((1 << 48) >> -4)
131+
except ValueError as e:
132+
print(e)
133+
134+
try:
135+
print((1 << 48) << -6)
136+
except ValueError as e:
137+
print(e)

tests/extmod/uctypes_addressof.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,8 @@
1212
print(uctypes.addressof(uctypes.bytearray_at(1 << i, 8)))
1313

1414
# Test address that is bigger than the greatest small-int but still within the address range.
15-
large_addr = maxsize + 1
16-
print(uctypes.addressof(uctypes.bytearray_at(large_addr, 8)) == large_addr)
15+
try:
16+
large_addr = maxsize + 1
17+
print(uctypes.addressof(uctypes.bytearray_at(large_addr, 8)) == large_addr)
18+
except OverflowError:
19+
print(True) # systems with 64-bit bigints will overflow on the above operation

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy