Skip to content

Commit 1b83f0c

Browse files
committed
py/objint_longlong: Add overflow checks to strtoll() result.
This relies on errno unfortunately, but there's no other way to disambiguate MAX_LONGLONG and an out of range value (apart from manually re-implementing strtoll). Includes some test workarounds to account for things which now overflow: - uctypes_array_load_store test was failing already, now won't parse. - all the ffi_int tests contain 64-bit unsigned values, that won't parse as long long. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <angus@redyak.com.au> Signed-off-by: Angus Gratton <angus@redyak.com.au>
1 parent 2da05bf commit 1b83f0c

File tree

3 files changed

+24
-5
lines changed

3 files changed

+24
-5
lines changed

py/objint_longlong.c

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
* THE SOFTWARE.
2626
*/
2727

28+
#include <errno.h>
2829
#include <stdlib.h>
2930
#include <string.h>
3031

@@ -265,19 +266,20 @@ mp_obj_t mp_obj_new_int_from_ll(long long val) {
265266
}
266267

267268
mp_obj_t mp_obj_new_int_from_ull(unsigned long long val) {
268-
// TODO raise an exception if the unsigned long long won't fit
269269
if (val >> (sizeof(unsigned long long) * 8 - 1) != 0) {
270270
mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("ulonglong too large"));
271271
}
272272
return mp_obj_new_int_from_ll(val);
273273
}
274274

275275
mp_obj_t mp_obj_new_int_from_str_len(const char **str, size_t len, bool neg, unsigned int base) {
276-
// TODO check overflow
277276
char temp_buf[65]; // Large enough to hold 64-bit base 2 str + NUL terminating byte
278277
const char *head = *str;
279278
char *endptr;
280-
long long parsed = strtoll(head, &endptr, base);
279+
long long parsed;
280+
281+
errno = 0;
282+
parsed = strtoll(head, &endptr, base);
281283

282284
// If 'str' is not properly terminated and has valid digits all through
283285
// and after the buffer, then strtoll will have walked off the end
@@ -290,18 +292,24 @@ mp_obj_t mp_obj_new_int_from_str_len(const char **str, size_t len, bool neg, uns
290292
if (len < sizeof(temp_buf)) {
291293
memcpy(temp_buf, head, len);
292294
temp_buf[len] = 0;
295+
errno = 0;
293296
parsed = strtoll(temp_buf, &endptr, base);
294-
assert(endptr == temp_buf + len); // The first strtoll() checked all digits are valid
297+
assert(errno != 0 || endptr == temp_buf + len); // The first strtoll() checked all digits are valid
295298
endptr = (char *)head + len;
296299
}
297300
// (if str doesn't fit in temp_buf, will fall through and fail below)
298301
}
299302

303+
if (errno != 0) {
304+
return mp_const_none; // Conversion failed, caller should raise OverflowError
305+
}
306+
300307
if (neg) {
301308
parsed *= -1;
302309
}
303310

304311
mp_obj_t result = mp_obj_new_int_from_ll(parsed);
312+
305313
// If endptr isn't (head + len) then string contained invalid chars, caller should fail
306314
*str = endptr;
307315
return result;

tests/extmod/uctypes_array_load_store.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66
print("SKIP")
77
raise SystemExit
88

9+
# 'int' needs to be able to represent UINT64 for this test
10+
try:
11+
int("FF" * 8, 16)
12+
except ValueError:
13+
print("SKIP")
14+
raise SystemExit
15+
916
N = 5
1017

1118
for endian in ("NATIVE", "LITTLE_ENDIAN", "BIG_ENDIAN"):

tests/run-tests.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -870,7 +870,11 @@ def run_one_test(test_file):
870870
or args.emit == "native"
871871
)
872872
is_endian = test_name.endswith("_endian")
873-
is_int_big = test_name.startswith("int_big") or test_name.endswith("_intbig")
873+
is_int_big = (
874+
test_name.startswith("int_big")
875+
or test_name.endswith("_intbig")
876+
or test_name.startswith("ffi_int") # these tests contain large integer literals
877+
)
874878
is_int_64 = test_name.startswith("int_64") or test_name.endswith("_int64")
875879
is_bytearray = test_name.startswith("bytearray") or test_name.endswith("_bytearray")
876880
is_set_type = test_name.startswith(("set_", "frozenset")) or test_name.endswith("_set")

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