Skip to content

Commit 01af34a

Browse files
vstinnerskirpichev
andauthored
[3.13] gh-143050: Correct PyLong_FromString() to use _PyLong_Negate() (#145901) (#147437)
The long_from_string_base() might return a small integer, when the _pylong.py is used to do conversion. Hence, we must be careful here to not smash it "small int" bit by using the _PyLong_FlipSign(). Co-authored-by: Victor Stinner <vstinner@python.org> (cherry picked from commit db5936c) Co-authored-by: Sergey B Kirpichev <skirpichev@gmail.com>
1 parent fc1c644 commit 01af34a

File tree

4 files changed

+56
-16
lines changed

4 files changed

+56
-16
lines changed

Include/internal/pycore_long.h

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ extern void _PyLong_FiniTypes(PyInterpreterState *interp);
6363
# error "_PY_NSMALLPOSINTS must be greater than or equal to 257"
6464
#endif
6565

66+
#define _PY_IS_SMALL_INT(val) \
67+
(-_PY_NSMALLNEGINTS <= (val) && (val) < _PY_NSMALLPOSINTS)
68+
6669
// Return a reference to the immortal zero singleton.
6770
// The function cannot return NULL.
6871
static inline PyObject* _PyLong_GetZero(void)
@@ -224,6 +227,25 @@ _PyLong_IsPositive(const PyLongObject *op)
224227
return (op->long_value.lv_tag & SIGN_MASK) == 0;
225228
}
226229

230+
/* Return true if the argument is a small int */
231+
static inline bool
232+
_PyLong_IsSmallInt(const PyLongObject *op)
233+
{
234+
assert(PyLong_Check(op));
235+
bool is_small_int = false;
236+
if (_PyLong_IsCompact(op)) {
237+
Py_ssize_t value = _PyLong_CompactValue(op);
238+
if (_PY_IS_SMALL_INT(value)) {
239+
PyLongObject *small_obj;
240+
small_obj = &_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS + value];
241+
is_small_int = (op == small_obj);
242+
}
243+
}
244+
assert(PyLong_CheckExact(op) || (!is_small_int));
245+
assert(_Py_IsImmortal(op) || (!is_small_int));
246+
return is_small_int;
247+
}
248+
227249
static inline Py_ssize_t
228250
_PyLong_DigitCount(const PyLongObject *op)
229251
{
@@ -284,7 +306,9 @@ _PyLong_SetDigitCount(PyLongObject *op, Py_ssize_t size)
284306
#define NON_SIZE_MASK ~((1 << NON_SIZE_BITS) - 1)
285307

286308
static inline void
287-
_PyLong_FlipSign(PyLongObject *op) {
309+
_PyLong_FlipSign(PyLongObject *op)
310+
{
311+
assert(!_PyLong_IsSmallInt(op));
288312
unsigned int flipped_sign = 2 - (op->long_value.lv_tag & SIGN_MASK);
289313
op->long_value.lv_tag &= NON_SIZE_MASK;
290314
op->long_value.lv_tag |= flipped_sign;

Lib/test/test_capi/test_long.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,16 @@ def test_long_fromnativebytes(self):
614614
self.assertEqual(expect_u, fromnativebytes(v_be, n, 4, 1),
615615
f"PyLong_FromNativeBytes(buffer, {n}, <big|unsigned>)")
616616

617+
def test_bug_143050(self):
618+
with support.adjust_int_max_str_digits(0):
619+
# Bug coming from using _pylong.int_from_string(), that
620+
# currently requires > 6000 decimal digits.
621+
int('-' + '0' * 7000, 10)
622+
_testcapi.test_immortal_small_ints()
623+
# Test also nonzero small int
624+
int('-' + '0' * 7000 + '123', 10)
625+
_testcapi.test_immortal_small_ints()
626+
617627

618628
if __name__ == "__main__":
619629
unittest.main()

Modules/_testcapi/immortal.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#include "parts.h"
22

3+
#define Py_BUILD_CORE
4+
#include "internal/pycore_long.h" // _PyLong_IsSmallInt()
5+
36
int verify_immortality(PyObject *object)
47
{
58
assert(_Py_IsImmortal(object));
@@ -26,7 +29,17 @@ static PyObject *
2629
test_immortal_small_ints(PyObject *self, PyObject *Py_UNUSED(ignored))
2730
{
2831
for (int i = -5; i <= 256; i++) {
29-
assert(verify_immortality(PyLong_FromLong(i)));
32+
PyObject *obj = PyLong_FromLong(i);
33+
assert(verify_immortality(obj));
34+
int is_small_int = _PyLong_IsSmallInt((PyLongObject *)obj);
35+
assert(is_small_int);
36+
}
37+
for (int i = 257; i <= 260; i++) {
38+
PyObject *obj = PyLong_FromLong(i);
39+
assert(obj);
40+
int is_small_int = _PyLong_IsSmallInt((PyLongObject *)obj);
41+
assert(!is_small_int);
42+
Py_DECREF(obj);
3043
}
3144
Py_RETURN_NONE;
3245
}

Objects/longobject.c

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class int "PyObject *" "&PyLong_Type"
2222

2323
#define medium_value(x) ((stwodigits)_PyLong_CompactValue(x))
2424

25-
#define IS_SMALL_INT(ival) (-_PY_NSMALLNEGINTS <= (ival) && (ival) < _PY_NSMALLPOSINTS)
25+
#define IS_SMALL_INT(ival) _PY_IS_SMALL_INT(ival)
2626
#define IS_SMALL_UINT(ival) ((ival) < _PY_NSMALLPOSINTS)
2727

2828
#define _MAX_STR_DIGITS_ERROR_FMT_TO_INT "Exceeds the limit (%d digits) for integer string conversion: value has %zd digits; use sys.set_int_max_str_digits() to increase the limit"
@@ -3057,11 +3057,11 @@ PyLong_FromString(const char *str, char **pend, int base)
30573057
}
30583058

30593059
/* Set sign and normalize */
3060-
if (sign < 0) {
3061-
_PyLong_FlipSign(z);
3062-
}
30633060
long_normalize(z);
30643061
z = maybe_small_long(z);
3062+
if (sign < 0) {
3063+
_PyLong_Negate(&z);
3064+
}
30653065

30663066
if (pend != NULL) {
30673067
*pend = (char *)str;
@@ -3587,16 +3587,9 @@ long_dealloc(PyObject *self)
35873587
* we accidentally decref small Ints out of existence. Instead,
35883588
* since small Ints are immortal, re-set the reference count.
35893589
*/
3590-
PyLongObject *pylong = (PyLongObject*)self;
3591-
if (pylong && _PyLong_IsCompact(pylong)) {
3592-
stwodigits ival = medium_value(pylong);
3593-
if (IS_SMALL_INT(ival)) {
3594-
PyLongObject *small_pylong = (PyLongObject *)get_small_int((sdigit)ival);
3595-
if (pylong == small_pylong) {
3596-
_Py_SetImmortal(self);
3597-
return;
3598-
}
3599-
}
3590+
if (_PyLong_IsSmallInt((PyLongObject*)self)) {
3591+
_Py_SetImmortal(self);
3592+
return;
36003593
}
36013594
Py_TYPE(self)->tp_free(self);
36023595
}

0 commit comments

Comments
 (0)