Skip to content

Commit b406b4f

Browse files
miss-islingtoneendebakptclaude
authored
[3.14] gh-150868: Fix sum() deallocating a float subclass as an exact float (GH-151063) (#161049)
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 78b7068 commit b406b4f

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
@@ -2080,6 +2080,31 @@ def __getitem__(self, index):
20802080
self.assertComplexesAreIdentical(sum([1.0, complex(1, -0.0)]),
20812081
complex(2, -0.0))
20822082

2083+
def test_sum_float_subclass(self):
2084+
# gh-151060: the C fast-sum loop must not free a float subclass with
2085+
# the exact-float deallocator. A generator keeps each item's only
2086+
# reference in sum(), so the specialized DECREF runs the deallocator.
2087+
class F(float):
2088+
count = 0
2089+
def __del__(self):
2090+
F.count += 1
2091+
2092+
self.assertEqual(sum((F(i) for i in range(5)), 1j), complex(10, 1))
2093+
self.assertEqual(F.count, 5)
2094+
2095+
def test_sum_int_subclass(self):
2096+
# gh-151060: likewise, the float and complex fast-sum loops must not
2097+
# free an int subclass with the exact-int deallocator.
2098+
class I(int):
2099+
count = 0
2100+
def __del__(self):
2101+
I.count += 1
2102+
2103+
self.assertEqual(sum((I(i) for i in range(5)), 1.0), 11.0)
2104+
self.assertEqual(I.count, 5)
2105+
self.assertEqual(sum((I(i) for i in range(5)), 1j), complex(10, 1))
2106+
self.assertEqual(I.count, 10)
2107+
20832108
@requires_IEEE_754
20842109
@unittest.skipIf(HAVE_DOUBLE_ROUNDING,
20852110
"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
@@ -2901,15 +2901,15 @@ builtin_sum_impl(PyObject *module, PyObject *iterable, PyObject *start)
29012901
_Py_DECREF_SPECIALIZED(item, _PyFloat_ExactDealloc);
29022902
continue;
29032903
}
2904-
if (PyLong_Check(item)) {
2904+
if (PyLong_CheckExact(item)) {
29052905
double value = PyLong_AsDouble(item);
29062906
if (value != -1.0 || !PyErr_Occurred()) {
29072907
re_sum = cs_add(re_sum, value);
2908-
Py_DECREF(item);
2908+
_Py_DECREF_SPECIALIZED(item, _PyLong_ExactDealloc);
29092909
continue;
29102910
}
29112911
else {
2912-
Py_DECREF(item);
2912+
_Py_DECREF_SPECIALIZED(item, _PyLong_ExactDealloc);
29132913
Py_DECREF(iter);
29142914
return NULL;
29152915
}
@@ -2953,20 +2953,20 @@ builtin_sum_impl(PyObject *module, PyObject *iterable, PyObject *start)
29532953
Py_DECREF(item);
29542954
continue;
29552955
}
2956-
if (PyLong_Check(item)) {
2956+
if (PyLong_CheckExact(item)) {
29572957
double value = PyLong_AsDouble(item);
29582958
if (value != -1.0 || !PyErr_Occurred()) {
29592959
re_sum = cs_add(re_sum, value);
2960-
Py_DECREF(item);
2960+
_Py_DECREF_SPECIALIZED(item, _PyLong_ExactDealloc);
29612961
continue;
29622962
}
29632963
else {
2964-
Py_DECREF(item);
2964+
_Py_DECREF_SPECIALIZED(item, _PyLong_ExactDealloc);
29652965
Py_DECREF(iter);
29662966
return NULL;
29672967
}
29682968
}
2969-
if (PyFloat_Check(item)) {
2969+
if (PyFloat_CheckExact(item)) {
29702970
double value = PyFloat_AS_DOUBLE(item);
29712971
re_sum = cs_add(re_sum, value);
29722972
_Py_DECREF_SPECIALIZED(item, _PyFloat_ExactDealloc);

0 commit comments

Comments
 (0)