https://github.com/python/cpython/commit/c391d1a764d864af222fded9fbaab587b685d839
commit: c391d1a764d864af222fded9fbaab587b685d839
branch: 3.15
author: Miss Islington (bot) <[email protected]>
committer: eendebakpt <[email protected]>
date: 2026-11-01T21:27:29+01:00
summary:

[3.15] gh-150868: Fix sum() deallocating a float subclass as an exact float 
(GH-151063) (#161048)

gh-150868: Fix sum() deallocating a float subclass as an exact float (GH-151063)

* gh-151060: Fix sum() deallocating a float subclass as an exact float

The complex-total fast path in builtin_sum_impl() guarded with
PyFloat_Check() (which accepts subclasses) but freed the item with
_Py_DECREF_SPECIALIZED(item, _PyFloat_ExactDealloc), which assumes an
exact float. A float subclass instance reaching refcount 0 in that loop
aborts on an assertion in debug builds, and in release builds skips the
subclass tp_dealloc (type refcount leak, __del__ never runs, subclass
object pushed onto the float freelist).

Use PyFloat_CheckExact() instead, matching the float-total branch, so
subclass instances fall through to the generic PyNumber_Add() path.



* gh-150868: Also use exact-type checks for ints in the fast sum() loops

Per review: use PyLong_CheckExact with the specialized exact-int
deallocator in the float and complex fast-sum loops, so int subclasses
take the generic path.



---------
(cherry picked from commit 47c249adf3bc2ae3264a7373f7f1f5e7c4d84955)

Co-authored-by: Pieter Eendebak <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-06-07-22-03-20.gh-issue-151060.tdexEE.rst
M Lib/test/test_builtin.py
M Python/bltinmodule.c

diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py
index 0b98c08bdfba373..d7c1e080063cac6 100644
--- a/Lib/test/test_builtin.py
+++ b/Lib/test/test_builtin.py
@@ -2252,6 +2252,31 @@ def __getitem__(self, index):
         self.assertComplexesAreIdentical(sum([1.0, complex(1, -0.0)]),
                                          complex(2, -0.0))
 
+    def test_sum_float_subclass(self):
+        # gh-151060: the C fast-sum loop must not free a float subclass with
+        # the exact-float deallocator.  A generator keeps each item's only
+        # reference in sum(), so the specialized DECREF runs the deallocator.
+        class F(float):
+            count = 0
+            def __del__(self):
+                F.count += 1
+
+        self.assertEqual(sum((F(i) for i in range(5)), 1j), complex(10, 1))
+        self.assertEqual(F.count, 5)
+
+    def test_sum_int_subclass(self):
+        # gh-151060: likewise, the float and complex fast-sum loops must not
+        # free an int subclass with the exact-int deallocator.
+        class I(int):
+            count = 0
+            def __del__(self):
+                I.count += 1
+
+        self.assertEqual(sum((I(i) for i in range(5)), 1.0), 11.0)
+        self.assertEqual(I.count, 5)
+        self.assertEqual(sum((I(i) for i in range(5)), 1j), complex(10, 1))
+        self.assertEqual(I.count, 10)
+
     @requires_IEEE_754
     @unittest.skipIf(HAVE_DOUBLE_ROUNDING,
                          "sum accuracy not guaranteed on machines with double 
rounding")
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-07-22-03-20.gh-issue-151060.tdexEE.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-07-22-03-20.gh-issue-151060.tdexEE.rst
new file mode 100644
index 000000000000000..1efde8a536c0939
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-07-22-03-20.gh-issue-151060.tdexEE.rst
@@ -0,0 +1,2 @@
+Fix a crash or memory corruption in :func:`sum` when summing a
+:class:`float` subclass instance into a complex running total.
diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c
index cbe59c8883d5a57..a90cd60f636f4e3 100644
--- a/Python/bltinmodule.c
+++ b/Python/bltinmodule.c
@@ -2987,15 +2987,15 @@ builtin_sum_impl(PyObject *module, PyObject *iterable, 
PyObject *start)
                 _Py_DECREF_SPECIALIZED(item, _PyFloat_ExactDealloc);
                 continue;
             }
-            if (PyLong_Check(item)) {
+            if (PyLong_CheckExact(item)) {
                 double value = PyLong_AsDouble(item);
                 if (value != -1.0 || !PyErr_Occurred()) {
                     re_sum = cs_add(re_sum, value);
-                    Py_DECREF(item);
+                    _Py_DECREF_SPECIALIZED(item, _PyLong_ExactDealloc);
                     continue;
                 }
                 else {
-                    Py_DECREF(item);
+                    _Py_DECREF_SPECIALIZED(item, _PyLong_ExactDealloc);
                     Py_DECREF(iter);
                     return NULL;
                 }
@@ -3039,20 +3039,20 @@ builtin_sum_impl(PyObject *module, PyObject *iterable, 
PyObject *start)
                 Py_DECREF(item);
                 continue;
             }
-            if (PyLong_Check(item)) {
+            if (PyLong_CheckExact(item)) {
                 double value = PyLong_AsDouble(item);
                 if (value != -1.0 || !PyErr_Occurred()) {
                     re_sum = cs_add(re_sum, value);
-                    Py_DECREF(item);
+                    _Py_DECREF_SPECIALIZED(item, _PyLong_ExactDealloc);
                     continue;
                 }
                 else {
-                    Py_DECREF(item);
+                    _Py_DECREF_SPECIALIZED(item, _PyLong_ExactDealloc);
                     Py_DECREF(iter);
                     return NULL;
                 }
             }
-            if (PyFloat_Check(item)) {
+            if (PyFloat_CheckExact(item)) {
                 double value = PyFloat_AS_DOUBLE(item);
                 re_sum = cs_add(re_sum, value);
                 _Py_DECREF_SPECIALIZED(item, _PyFloat_ExactDealloc);

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to