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

Skip to content

Commit 44d5214

Browse files
committed
Issue #17278: Fix a crash in heapq.heappush() and heapq.heappop() when the list is being resized concurrently.
1 parent aaef344 commit 44d5214

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(TestCase):
323333
module = None
324334

@@ -370,6 +380,22 @@ def test_iterable_args(self):
370380
self.assertRaises(TypeError, f, 2, N(s))
371381
self.assertRaises(ZeroDivisionError, f, 2, E(s))
372382

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

374400
class TestErrorHandlingPython(TestErrorHandling):
375401
module = py_heapq

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,9 @@ Core and Builtins
233233
Library
234234
-------
235235

236+
- Issue #17278: Fix a crash in heapq.heappush() and heapq.heappop() when
237+
the list is being resized concurrently.
238+
236239
- Issue #17018: Make Process.join() retry if os.waitpid() fails with EINTR.
237240

238241
- Issue #14720: sqlite3: Convert datetime microseconds correctly.

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)