Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 31584e3

Browse files
committed
Issue #17278: Fix a crash in heapq.heappush() and heapq.heappop() when the list is being resized concurrently.
2 parents b1efa53 + 44d5214 commit 31584e3

3 files changed

Lines changed: 63 additions & 6 deletions

File tree

Lib/test/test_heapq.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,16 @@ def L(seqn):
319319
return chain(map(lambda x:x, R(Ig(G(seqn)))))
320320

321321

322+
class SideEffectLT:
323+
def __init__(self, value, heap):
324+
self.value = value
325+
self.heap = heap
326+
327+
def __lt__(self, other):
328+
self.heap[:] = []
329+
return self.value < other.value
330+
331+
322332
class TestErrorHandling:
323333

324334
def test_non_sequence(self):
@@ -369,6 +379,22 @@ def test_iterable_args(self):
369379
self.assertRaises(TypeError, f, 2, N(s))
370380
self.assertRaises(ZeroDivisionError, f, 2, E(s))
371381

382+
# Issue #17278: the heap may change size while it's being walked.
383+
384+
def test_heappush_mutating_heap(self):
385+
heap = []
386+
heap.extend(SideEffectLT(i, heap) for i in range(200))
387+
# Python version raises IndexError, C version RuntimeError
388+
with self.assertRaises((IndexError, RuntimeError)):
389+
self.module.heappush(heap, SideEffectLT(5, heap))
390+
391+
def test_heappop_mutating_heap(self):
392+
heap = []
393+
heap.extend(SideEffectLT(i, heap) for i in range(200))
394+
# Python version raises IndexError, C version RuntimeError
395+
with self.assertRaises((IndexError, RuntimeError)):
396+
self.module.heappop(heap)
397+
372398

373399
class TestErrorHandlingPython(TestErrorHandling, TestCase):
374400
module = py_heapq

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,9 @@ Core and Builtins
193193
Library
194194
-------
195195

196+
- Issue #17278: Fix a crash in heapq.heappush() and heapq.heappop() when
197+
the list is being resized concurrently.
198+
196199
- Issue #16962: Use getdents64 instead of the obsolete getdents syscall
197200
in the subprocess module on Linux.
198201

Modules/_heapqmodule.c

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ annotated by François Pinard, and converted to C by Raymond Hettinger.
1111
static int
1212
_siftdown(PyListObject *heap, Py_ssize_t startpos, Py_ssize_t pos)
1313
{
14-
PyObject *newitem, *parent;
14+
PyObject *newitem, *parent, *olditem;
1515
int cmp;
1616
Py_ssize_t parentpos;
17+
Py_ssize_t size;
1718

1819
assert(PyList_Check(heap));
19-
if (pos >= PyList_GET_SIZE(heap)) {
20+
size = PyList_GET_SIZE(heap);
21+
if (pos >= size) {
2022
PyErr_SetString(PyExc_IndexError, "index out of range");
2123
return -1;
2224
}
@@ -33,12 +35,24 @@ _siftdown(PyListObject *heap, Py_ssize_t startpos, Py_ssize_t pos)
3335
Py_DECREF(newitem);
3436
return -1;
3537
}
38+
if (size != PyList_GET_SIZE(heap)) {
39+
Py_DECREF(newitem);
40+
PyErr_SetString(PyExc_RuntimeError,
41+
"list changed size during iteration");
42+
return -1;
43+
}
3644
if (cmp == 0)
3745
break;
3846
Py_INCREF(parent);
39-
Py_DECREF(PyList_GET_ITEM(heap, pos));
47+
olditem = PyList_GET_ITEM(heap, pos);
4048
PyList_SET_ITEM(heap, pos, parent);
49+
Py_DECREF(olditem);
4150
pos = parentpos;
51+
if (size != PyList_GET_SIZE(heap)) {
52+
PyErr_SetString(PyExc_RuntimeError,
53+
"list changed size during iteration");
54+
return -1;
55+
}
4256
}
4357
Py_DECREF(PyList_GET_ITEM(heap, pos));
4458
PyList_SET_ITEM(heap, pos, newitem);
@@ -50,10 +64,12 @@ _siftup(PyListObject *heap, Py_ssize_t pos)
5064
{
5165
Py_ssize_t startpos, endpos, childpos, rightpos;
5266
int cmp;
53-
PyObject *newitem, *tmp;
67+
PyObject *newitem, *tmp, *olditem;
68+
Py_ssize_t size;
5469

5570
assert(PyList_Check(heap));
56-
endpos = PyList_GET_SIZE(heap);
71+
size = PyList_GET_SIZE(heap);
72+
endpos = size;
5773
startpos = pos;
5874
if (pos >= endpos) {
5975
PyErr_SetString(PyExc_IndexError, "index out of range");
@@ -79,13 +95,25 @@ _siftup(PyListObject *heap, Py_ssize_t pos)
7995
if (cmp == 0)
8096
childpos = rightpos;
8197
}
98+
if (size != PyList_GET_SIZE(heap)) {
99+
Py_DECREF(newitem);
100+
PyErr_SetString(PyExc_RuntimeError,
101+
"list changed size during iteration");
102+
return -1;
103+
}
82104
/* Move the smaller child up. */
83105
tmp = PyList_GET_ITEM(heap, childpos);
84106
Py_INCREF(tmp);
85-
Py_DECREF(PyList_GET_ITEM(heap, pos));
107+
olditem = PyList_GET_ITEM(heap, pos);
86108
PyList_SET_ITEM(heap, pos, tmp);
109+
Py_DECREF(olditem);
87110
pos = childpos;
88111
childpos = 2*pos + 1;
112+
if (size != PyList_GET_SIZE(heap)) {
113+
PyErr_SetString(PyExc_RuntimeError,
114+
"list changed size during iteration");
115+
return -1;
116+
}
89117
}
90118

91119
/* The leaf at pos is empty now. Put newitem there, and and bubble

0 commit comments

Comments
 (0)