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]
