diff --git a/Include/internal/pycore_long.h b/Include/internal/pycore_long.h index ff7d9afc03a4f2..43685cfc11cbb6 100644 --- a/Include/internal/pycore_long.h +++ b/Include/internal/pycore_long.h @@ -63,6 +63,9 @@ extern void _PyLong_FiniTypes(PyInterpreterState *interp); # error "_PY_NSMALLPOSINTS must be greater than or equal to 257" #endif +#define _PY_IS_SMALL_INT(val) \ + (-_PY_NSMALLNEGINTS <= (val) && (val) < _PY_NSMALLPOSINTS) + // Return a reference to the immortal zero singleton. // The function cannot return NULL. static inline PyObject* _PyLong_GetZero(void) @@ -224,6 +227,25 @@ _PyLong_IsPositive(const PyLongObject *op) return (op->long_value.lv_tag & SIGN_MASK) == 0; } +/* Return true if the argument is a small int */ +static inline bool +_PyLong_IsSmallInt(const PyLongObject *op) +{ + assert(PyLong_Check(op)); + bool is_small_int = false; + if (_PyLong_IsCompact(op)) { + Py_ssize_t value = _PyLong_CompactValue(op); + if (_PY_IS_SMALL_INT(value)) { + PyLongObject *small_obj; + small_obj = &_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS + value]; + is_small_int = (op == small_obj); + } + } + assert(PyLong_CheckExact(op) || (!is_small_int)); + assert(_Py_IsImmortal(op) || (!is_small_int)); + return is_small_int; +} + static inline Py_ssize_t _PyLong_DigitCount(const PyLongObject *op) { @@ -284,7 +306,9 @@ _PyLong_SetDigitCount(PyLongObject *op, Py_ssize_t size) #define NON_SIZE_MASK ~((1 << NON_SIZE_BITS) - 1) static inline void -_PyLong_FlipSign(PyLongObject *op) { +_PyLong_FlipSign(PyLongObject *op) +{ + assert(!_PyLong_IsSmallInt(op)); unsigned int flipped_sign = 2 - (op->long_value.lv_tag & SIGN_MASK); op->long_value.lv_tag &= NON_SIZE_MASK; op->long_value.lv_tag |= flipped_sign; diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py index 841de107a02821..cb3c7e7e328038 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -614,6 +614,16 @@ def test_long_fromnativebytes(self): self.assertEqual(expect_u, fromnativebytes(v_be, n, 4, 1), f"PyLong_FromNativeBytes(buffer, {n}, )") + def test_bug_143050(self): + with support.adjust_int_max_str_digits(0): + # Bug coming from using _pylong.int_from_string(), that + # currently requires > 6000 decimal digits. + int('-' + '0' * 7000, 10) + _testcapi.test_immortal_small_ints() + # Test also nonzero small int + int('-' + '0' * 7000 + '123', 10) + _testcapi.test_immortal_small_ints() + if __name__ == "__main__": unittest.main() diff --git a/Modules/_testcapi/immortal.c b/Modules/_testcapi/immortal.c index 9f81389811c645..c14a8a148fecb4 100644 --- a/Modules/_testcapi/immortal.c +++ b/Modules/_testcapi/immortal.c @@ -1,5 +1,8 @@ #include "parts.h" +#define Py_BUILD_CORE +#include "internal/pycore_long.h" // _PyLong_IsSmallInt() + int verify_immortality(PyObject *object) { assert(_Py_IsImmortal(object)); @@ -26,7 +29,17 @@ static PyObject * test_immortal_small_ints(PyObject *self, PyObject *Py_UNUSED(ignored)) { for (int i = -5; i <= 256; i++) { - assert(verify_immortality(PyLong_FromLong(i))); + PyObject *obj = PyLong_FromLong(i); + assert(verify_immortality(obj)); + int is_small_int = _PyLong_IsSmallInt((PyLongObject *)obj); + assert(is_small_int); + } + for (int i = 257; i <= 260; i++) { + PyObject *obj = PyLong_FromLong(i); + assert(obj); + int is_small_int = _PyLong_IsSmallInt((PyLongObject *)obj); + assert(!is_small_int); + Py_DECREF(obj); } Py_RETURN_NONE; } diff --git a/Objects/longobject.c b/Objects/longobject.c index 06d9ae4742ffc8..4942c57f1ac980 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -22,7 +22,7 @@ class int "PyObject *" "&PyLong_Type" #define medium_value(x) ((stwodigits)_PyLong_CompactValue(x)) -#define IS_SMALL_INT(ival) (-_PY_NSMALLNEGINTS <= (ival) && (ival) < _PY_NSMALLPOSINTS) +#define IS_SMALL_INT(ival) _PY_IS_SMALL_INT(ival) #define IS_SMALL_UINT(ival) ((ival) < _PY_NSMALLPOSINTS) #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) } /* Set sign and normalize */ - if (sign < 0) { - _PyLong_FlipSign(z); - } long_normalize(z); z = maybe_small_long(z); + if (sign < 0) { + _PyLong_Negate(&z); + } if (pend != NULL) { *pend = (char *)str; @@ -3587,16 +3587,9 @@ long_dealloc(PyObject *self) * we accidentally decref small Ints out of existence. Instead, * since small Ints are immortal, re-set the reference count. */ - PyLongObject *pylong = (PyLongObject*)self; - if (pylong && _PyLong_IsCompact(pylong)) { - stwodigits ival = medium_value(pylong); - if (IS_SMALL_INT(ival)) { - PyLongObject *small_pylong = (PyLongObject *)get_small_int((sdigit)ival); - if (pylong == small_pylong) { - _Py_SetImmortal(self); - return; - } - } + if (_PyLong_IsSmallInt((PyLongObject*)self)) { + _Py_SetImmortal(self); + return; } Py_TYPE(self)->tp_free(self); }