From 506f0db15ea04f918b4c4598a7ad5c86aaadda61 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Sun, 7 Jun 2026 22:22:31 +0200 Subject: [PATCH 1/2] 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) --- Lib/test/test_builtin.py | 12 ++++++++++++ .../2026-06-07-22-03-20.gh-issue-151060.tdexEE.rst | 2 ++ Python/bltinmodule.c | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-06-07-22-03-20.gh-issue-151060.tdexEE.rst diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 1d2c105ac047e18..38599cc3afb9c12 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -2229,6 +2229,18 @@ 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) + @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 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 d5129bf6a5a6bc0..fbdd637a3fcb78d 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -3046,7 +3046,7 @@ builtin_sum_impl(PyObject *module, PyObject *iterable, PyObject *start) 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); From 934a94f3cbc78a017ea40d713d723fd7a16b1c44 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Fri, 31 Jul 2026 13:58:48 +0200 Subject: [PATCH 2/2] 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 --- Lib/test/test_builtin.py | 13 +++++++++++++ Python/bltinmodule.c | 12 ++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 38599cc3afb9c12..60f61eb68212c80 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -2241,6 +2241,19 @@ def __del__(self): 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/Python/bltinmodule.c b/Python/bltinmodule.c index fbdd637a3fcb78d..6af21fbef6f2189 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -2981,15 +2981,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; } @@ -3033,15 +3033,15 @@ 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; }