Skip to content

Commit 47c249a

Browse files
eendebakptclaude
andauthored
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) <noreply@anthropic.com> * 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 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e7acd6a commit 47c249a

3 files changed

Lines changed: 34 additions & 7 deletions

File tree

‎Lib/test/test_builtin.py‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2252,6 +2252,31 @@ def __getitem__(self, index):
22522252
self.assertComplexesAreIdentical(sum([1.0, complex(1, -0.0)]),
22532253
complex(2, -0.0))
22542254

2255+
def test_sum_float_subclass(self):
2256+
# gh-151060: the C fast-sum loop must not free a float subclass with
2257+
# the exact-float deallocator. A generator keeps each item's only
2258+
# reference in sum(), so the specialized DECREF runs the deallocator.
2259+
class F(float):
2260+
count = 0
2261+
def __del__(self):
2262+
F.count += 1
2263+
2264+
self.assertEqual(sum((F(i) for i in range(5)), 1j), complex(10, 1))
2265+
self.assertEqual(F.count, 5)
2266+
2267+
def test_sum_int_subclass(self):
2268+
# gh-151060: likewise, the float and complex fast-sum loops must not
2269+
# free an int subclass with the exact-int deallocator.
2270+
class I(int):
2271+
count = 0
2272+
def __del__(self):
2273+
I.count += 1
2274+
2275+
self.assertEqual(sum((I(i) for i in range(5)), 1.0), 11.0)
2276+
self.assertEqual(I.count, 5)
2277+
self.assertEqual(sum((I(i) for i in range(5)), 1j), complex(10, 1))
2278+
self.assertEqual(I.count, 10)
2279+
22552280
@requires_IEEE_754
22562281
@skip_if_double_rounding
22572282
@support.cpython_only # Other implementations may choose a different algorithm
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a crash or memory corruption in :func:`sum` when summing a
2+
:class:`float` subclass instance into a complex running total.

‎Python/bltinmodule.c‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2949,15 +2949,15 @@ builtin_sum_impl(PyObject *module, PyObject *iterable, PyObject *start)
29492949
_Py_DECREF_SPECIALIZED(item, _PyFloat_ExactDealloc);
29502950
continue;
29512951
}
2952-
if (PyLong_Check(item)) {
2952+
if (PyLong_CheckExact(item)) {
29532953
double value = PyLong_AsDouble(item);
29542954
if (value != -1.0 || !PyErr_Occurred()) {
29552955
re_sum = cs_add(re_sum, value);
2956-
Py_DECREF(item);
2956+
_Py_DECREF_SPECIALIZED(item, _PyLong_ExactDealloc);
29572957
continue;
29582958
}
29592959
else {
2960-
Py_DECREF(item);
2960+
_Py_DECREF_SPECIALIZED(item, _PyLong_ExactDealloc);
29612961
Py_DECREF(iter);
29622962
return NULL;
29632963
}
@@ -3001,20 +3001,20 @@ builtin_sum_impl(PyObject *module, PyObject *iterable, PyObject *start)
30013001
Py_DECREF(item);
30023002
continue;
30033003
}
3004-
if (PyLong_Check(item)) {
3004+
if (PyLong_CheckExact(item)) {
30053005
double value = PyLong_AsDouble(item);
30063006
if (value != -1.0 || !PyErr_Occurred()) {
30073007
re_sum = cs_add(re_sum, value);
3008-
Py_DECREF(item);
3008+
_Py_DECREF_SPECIALIZED(item, _PyLong_ExactDealloc);
30093009
continue;
30103010
}
30113011
else {
3012-
Py_DECREF(item);
3012+
_Py_DECREF_SPECIALIZED(item, _PyLong_ExactDealloc);
30133013
Py_DECREF(iter);
30143014
return NULL;
30153015
}
30163016
}
3017-
if (PyFloat_Check(item)) {
3017+
if (PyFloat_CheckExact(item)) {
30183018
double value = PyFloat_AS_DOUBLE(item);
30193019
re_sum = cs_add(re_sum, value);
30203020
_Py_DECREF_SPECIALIZED(item, _PyFloat_ExactDealloc);

0 commit comments

Comments
 (0)