]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-143050: Correct PyLong_FromString() to use _PyLong_Negate() (#145901) ...
authorVictor Stinner <vstinner@python.org>
Tue, 31 Mar 2026 15:06:38 +0000 (17:06 +0200)
committerGitHub <noreply@github.com>
Tue, 31 Mar 2026 15:06:38 +0000 (17:06 +0200)
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 db5936c5b89aa19e04d63120e0cf5bbc73bf2420)

Co-authored-by: Sergey B Kirpichev <skirpichev@gmail.com>
Include/internal/pycore_long.h
Lib/test/test_capi/test_long.py
Modules/_testcapi/immortal.c
Objects/longobject.c

index ff7d9afc03a4f24ea4602560b49c68fc7b2273cb..43685cfc11cbb602b5f1a20da5e7205195626537 100644 (file)
@@ -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;
index 841de107a02821202a02a9eb4d62b2b0c29bf124..cb3c7e7e328038ec6b28ece317ef0577157fe852 100644 (file)
@@ -614,6 +614,16 @@ class LongTests(unittest.TestCase):
                 self.assertEqual(expect_u, fromnativebytes(v_be, n, 4, 1),
                     f"PyLong_FromNativeBytes(buffer, {n}, <big|unsigned>)")
 
+    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()
index 9f81389811c645614d8216045caabe2b0221c9ae..c14a8a148fecb47003d3353125c0e47e1f9061d9 100644 (file)
@@ -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;
 }
index 06d9ae4742ffc84b79f045fae04cf8ccf7716193..4942c57f1ac98072c4ce92e1bd9a1400f82e56fb 100644 (file)
@@ -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);
 }