]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-143050: Correct PyLong_FromString() to use _PyLong_Negate() (GH-145901...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Tue, 31 Mar 2026 14:14:12 +0000 (16:14 +0200)
committerGitHub <noreply@github.com>
Tue, 31 Mar 2026 14:14:12 +0000 (16:14 +0200)
gh-143050: Correct PyLong_FromString() to use _PyLong_Negate() (GH-145901)

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().
(cherry picked from commit db5936c5b89aa19e04d63120e0cf5bbc73bf2420)

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

index ec84dc167a868cd15c80666df557668121c3af67..36d6d7d702b4d0d68271a73325e3225bda2beca2 100644 (file)
@@ -233,6 +233,20 @@ _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 = (op->long_value.lv_tag & IMMORTALITY_BIT_MASK) != 0;
+    assert(PyLong_CheckExact(op) || (!is_small_int));
+    assert(_Py_IsImmortal(op) || (!is_small_int));
+    assert((_PyLong_IsCompact(op)
+            && _PY_IS_SMALL_INT(_PyLong_CompactValue(op)))
+           || (!is_small_int));
+    return is_small_int;
+}
+
 static inline Py_ssize_t
 _PyLong_DigitCount(const PyLongObject *op)
 {
@@ -294,7 +308,9 @@ _PyLong_SetDigitCount(PyLongObject *op, Py_ssize_t size)
 #define NON_SIZE_MASK ~(uintptr_t)((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 d3156645eeec2d65b00339fb60b1291cbc2e145e..fc0454b71cb780de68ae38cc88518abe873153e2 100644 (file)
@@ -803,6 +803,16 @@ class LongTests(unittest.TestCase):
                 self.assertEqual(pylongwriter_create(negative, digits), num,
                                  (negative, digits))
 
+    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 0663c3781d426a23639b4ee4cc1071e8ba05ee6e..c996ed2424525997657b46ae22667b48760f0de2 100644 (file)
@@ -31,13 +31,13 @@ test_immortal_small_ints(PyObject *self, PyObject *Py_UNUSED(ignored))
     for (int i = -5; i <= 256; i++) {
         PyObject *obj = PyLong_FromLong(i);
         assert(verify_immortality(obj));
-        int has_int_immortal_bit = ((PyLongObject *)obj)->long_value.lv_tag & IMMORTALITY_BIT_MASK;
+        int has_int_immortal_bit = _PyLong_IsSmallInt((PyLongObject *)obj);
         assert(has_int_immortal_bit);
     }
     for (int i = 257; i <= 260; i++) {
         PyObject *obj = PyLong_FromLong(i);
         assert(obj);
-        int has_int_immortal_bit = ((PyLongObject *)obj)->long_value.lv_tag & IMMORTALITY_BIT_MASK;
+        int has_int_immortal_bit = _PyLong_IsSmallInt((PyLongObject *)obj);
         assert(!has_int_immortal_bit);
         Py_DECREF(obj);
     }
index 3d936cb92565c01e8f66017cd4c1227c419fb391..85e97861a05eefe6c0bb8ee22160eae3d875f80b 100644 (file)
@@ -3047,11 +3047,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;
@@ -3551,21 +3551,11 @@ long_richcompare(PyObject *self, PyObject *other, int op)
     Py_RETURN_RICHCOMPARE(result, 0, op);
 }
 
-static inline int
-/// Return 1 if the object is one of the immortal small ints
-_long_is_small_int(PyObject *op)
-{
-    PyLongObject *long_object = (PyLongObject *)op;
-    int is_small_int = (long_object->long_value.lv_tag & IMMORTALITY_BIT_MASK) != 0;
-    assert((!is_small_int) || PyLong_CheckExact(op));
-    return is_small_int;
-}
-
 void
 _PyLong_ExactDealloc(PyObject *self)
 {
     assert(PyLong_CheckExact(self));
-    if (_long_is_small_int(self)) {
+    if (_PyLong_IsSmallInt((PyLongObject *)self)) {
         // See PEP 683, section Accidental De-Immortalizing for details
         _Py_SetImmortal(self);
         return;
@@ -3580,7 +3570,7 @@ _PyLong_ExactDealloc(PyObject *self)
 static void
 long_dealloc(PyObject *self)
 {
-    if (_long_is_small_int(self)) {
+    if (_PyLong_IsSmallInt((PyLongObject *)self)) {
         /* This should never get called, but we also don't want to SEGV if
          * we accidentally decref small Ints out of existence. Instead,
          * since small Ints are immortal, re-set the reference count.