Skip to content

Commit c391d1a

Browse files
miss-islingtoneendebakptclaude
authored
[3.15] gh-150868: Fix sum() deallocating a float subclass as an exact float (GH-151063) (#161048)
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. * 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. --------- (cherry picked from commit 47c249a) Co-authored-by: Pieter Eendebak <pieter.eendebak@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7b426f9 commit c391d1a

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
@unittest.skipIf(HAVE_DOUBLE_ROUNDING,
22572282
"sum accuracy not guaranteed on machines with double rounding")
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
@@ -2987,15 +2987,15 @@ builtin_sum_impl(PyObject *module, PyObject *iterable, PyObject *start)
29872987
_Py_DECREF_SPECIALIZED(item, _PyFloat_ExactDealloc);
29882988
continue;
29892989
}
2990-
if (PyLong_Check(item)) {
2990+
if (PyLong_CheckExact(item)) {
29912991
double value = PyLong_AsDouble(item);
29922992
if (value != -1.0 || !PyErr_Occurred()) {
29932993
re_sum = cs_add(re_sum, value);
2994-
Py_DECREF(item);
2994+
_Py_DECREF_SPECIALIZED(item, _PyLong_ExactDealloc);
29952995
continue;
29962996
}
29972997
else {
2998-
Py_DECREF(item);
2998+
_Py_DECREF_SPECIALIZED(item, _PyLong_ExactDealloc);
29992999
Py_DECREF(iter);
30003000
return NULL;
30013001
}
@@ -3039,20 +3039,20 @@ builtin_sum_impl(PyObject *module, PyObject *iterable, PyObject *start)
30393039
Py_DECREF(item);
30403040
continue;
30413041
}
3042-
if (PyLong_Check(item)) {
3042+
if (PyLong_CheckExact(item)) {
30433043
double value = PyLong_AsDouble(item);
30443044
if (value != -1.0 || !PyErr_Occurred()) {
30453045
re_sum = cs_add(re_sum, value);
3046-
Py_DECREF(item);
3046+
_Py_DECREF_SPECIALIZED(item, _PyLong_ExactDealloc);
30473047
continue;
30483048
}
30493049
else {
3050-
Py_DECREF(item);
3050+
_Py_DECREF_SPECIALIZED(item, _PyLong_ExactDealloc);
30513051
Py_DECREF(iter);
30523052
return NULL;
30533053
}
30543054
}
3055-
if (PyFloat_Check(item)) {
3055+
if (PyFloat_CheckExact(item)) {
30563056
double value = PyFloat_AS_DOUBLE(item);
30573057
re_sum = cs_add(re_sum, value);
30583058
_Py_DECREF_SPECIALIZED(item, _PyFloat_ExactDealloc);

0 commit comments

Comments
 (0)