Skip to content

Commit 93d5c48

Browse files
[3.13] gh-141370: Fix undefined behavior when using Py_ABS() (GH-141548) (#142304)
(cherry picked from commit 706fdda) Co-authored-by: Sergey B Kirpichev <skirpichev@gmail.com>
1 parent 9303573 commit 93d5c48

File tree

5 files changed

+42
-2
lines changed

5 files changed

+42
-2
lines changed

Include/pymacro.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@
3131

3232
/* Absolute value of the number x */
3333
#define Py_ABS(x) ((x) < 0 ? -(x) : (x))
34+
/* Safer implementation that avoids an undefined behavior for the minimal
35+
value of the signed integer type if its absolute value is larger than
36+
the maximal value of the signed integer type (in the two's complement
37+
representations, which is common).
38+
*/
39+
#define _Py_ABS_CAST(T, x) ((x) >= 0 ? ((T) (x)) : ((T) (((T) -((x) + 1)) + 1u)))
3440

3541
#define _Py_XSTRINGIFY(x) #x
3642

Lib/test/test_bytes.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,17 @@ def test_hex_separator_basics(self):
515515
self.assertEqual(three_bytes.hex(':', 2), 'b9:01ef')
516516
self.assertEqual(three_bytes.hex(':', 1), 'b9:01:ef')
517517
self.assertEqual(three_bytes.hex('*', -2), 'b901*ef')
518+
self.assertEqual(three_bytes.hex(sep=':', bytes_per_sep=2), 'b9:01ef')
519+
self.assertEqual(three_bytes.hex(sep='*', bytes_per_sep=-2), 'b901*ef')
520+
for bytes_per_sep in 3, -3, 2**31-1, -(2**31-1):
521+
with self.subTest(bytes_per_sep=bytes_per_sep):
522+
self.assertEqual(three_bytes.hex(':', bytes_per_sep), 'b901ef')
523+
for bytes_per_sep in 2**31, -2**31, 2**1000, -2**1000:
524+
with self.subTest(bytes_per_sep=bytes_per_sep):
525+
try:
526+
self.assertEqual(three_bytes.hex(':', bytes_per_sep), 'b901ef')
527+
except OverflowError:
528+
pass
518529

519530
value = b'{s\005\000\000\000worldi\002\000\000\000s\005\000\000\000helloi\001\000\000\0000'
520531
self.assertEqual(value.hex('.', 8), '7b7305000000776f.726c646902000000.730500000068656c.6c6f690100000030')

Lib/test/test_marshal.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ def test_ints(self):
3636
for expected in (-n, n):
3737
self.helper(expected)
3838
n = n >> 1
39+
n = 1 << 100
40+
while n:
41+
for expected in (-n, -n+1, n-1, n):
42+
self.helper(expected)
43+
n = n >> 1
3944

4045
def test_int64(self):
4146
# Simulate int marshaling with TYPE_INT64.

Lib/test/test_memoryview.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,25 @@ def test_memoryview_hex(self):
545545
m2 = m1[::-1]
546546
self.assertEqual(m2.hex(), '30' * 200000)
547547

548+
def test_memoryview_hex_separator(self):
549+
x = bytes(range(97, 102))
550+
m1 = memoryview(x)
551+
m2 = m1[::-1]
552+
self.assertEqual(m2.hex(':'), '65:64:63:62:61')
553+
self.assertEqual(m2.hex(':', 2), '65:6463:6261')
554+
self.assertEqual(m2.hex(':', -2), '6564:6362:61')
555+
self.assertEqual(m2.hex(sep=':', bytes_per_sep=2), '65:6463:6261')
556+
self.assertEqual(m2.hex(sep=':', bytes_per_sep=-2), '6564:6362:61')
557+
for bytes_per_sep in 5, -5, 2**31-1, -(2**31-1):
558+
with self.subTest(bytes_per_sep=bytes_per_sep):
559+
self.assertEqual(m2.hex(':', bytes_per_sep), '6564636261')
560+
for bytes_per_sep in 2**31, -2**31, 2**1000, -2**1000:
561+
with self.subTest(bytes_per_sep=bytes_per_sep):
562+
try:
563+
self.assertEqual(m2.hex(':', bytes_per_sep), '6564636261')
564+
except OverflowError:
565+
pass
566+
548567
def test_copy(self):
549568
m = memoryview(b'abc')
550569
with self.assertRaises(TypeError):

Python/pystrhex.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@ static PyObject *_Py_strhex_impl(const char* argbuf, const Py_ssize_t arglen,
4242
else {
4343
bytes_per_sep_group = 0;
4444
}
45-
46-
unsigned int abs_bytes_per_sep = Py_ABS(bytes_per_sep_group);
45+
unsigned int abs_bytes_per_sep = _Py_ABS_CAST(unsigned int, bytes_per_sep_group);
4746
Py_ssize_t resultlen = 0;
4847
if (bytes_per_sep_group && arglen > 0) {
4948
/* How many sep characters we'll be inserting. */

0 commit comments

Comments
 (0)