From d96a2c5709fd196351ee66e23c9b8d5d42253845 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Sun, 1 Nov 2026 00:43:11 +0100 Subject: [PATCH] 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. Co-Authored-By: Claude Opus 4.8 (1M context) * 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 --------- (cherry picked from commit 47c249adf3bc2ae3264a7373f7f1f5e7c4d84955) Co-authored-by: Pieter Eendebak Co-authored-by: Claude Opus 4.8 (1M context) --- Lib/test/test_builtin.py | 25 +++++++++++++++++++ ...-06-07-22-03-20.gh-issue-151060.tdexEE.rst | 2 ++ Python/bltinmodule.c | 14 +++++------ 3 files changed, 34 insertions(+), 7 deletions(-) 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 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);