https://github.com/python/cpython/commit/47c249adf3bc2ae3264a7373f7f1f5e7c4d84955
commit: 47c249adf3bc2ae3264a7373f7f1f5e7c4d84955
branch: main
author: Pieter Eendebak <[email protected]>
committer: eendebakpt <[email protected]>
date: 2026-10-31T23:43:11Z
summary:

gh-150868: Fix sum() deallocating a float subclass as an exact float (#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.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* 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.

Co-Authored-By: Claude Fable 5 <[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 294355698c8683..501f482570ac32 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
     @skip_if_double_rounding
     @support.cpython_only    # Other implementations may choose a different 
algorithm
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 00000000000000..1efde8a536c093
--- /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 40471af7186ae3..5413e57754667f 100644
--- a/Python/bltinmodule.c
+++ b/Python/bltinmodule.c
@@ -2949,15 +2949,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;
                 }
@@ -3001,20 +3001,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