Skip to content

Commit a2d4281

Browse files
authored
gh-112087: Make __sizeof__ and listiter_{len, next} to be threadsafe (gh-114843)
1 parent 4b2d178 commit a2d4281

File tree

7 files changed

+80
-69
lines changed

7 files changed

+80
-69
lines changed

Lib/test/support/__init__.py

+15-12
Original file line numberDiff line numberDiff line change
@@ -1727,19 +1727,22 @@ def _check_tracemalloc():
17271727

17281728

17291729
def check_free_after_iterating(test, iter, cls, args=()):
1730-
class A(cls):
1731-
def __del__(self):
1732-
nonlocal done
1733-
done = True
1734-
try:
1735-
next(it)
1736-
except StopIteration:
1737-
pass
1738-
17391730
done = False
1740-
it = iter(A(*args))
1741-
# Issue 26494: Shouldn't crash
1742-
test.assertRaises(StopIteration, next, it)
1731+
def wrapper():
1732+
class A(cls):
1733+
def __del__(self):
1734+
nonlocal done
1735+
done = True
1736+
try:
1737+
next(it)
1738+
except StopIteration:
1739+
pass
1740+
1741+
it = iter(A(*args))
1742+
# Issue 26494: Shouldn't crash
1743+
test.assertRaises(StopIteration, next, it)
1744+
1745+
wrapper()
17431746
# The sequence should be deallocated just after the end of iterating
17441747
gc_collect()
17451748
test.assertTrue(done)

Lib/test/test_iter.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ def __eq__(self, other):
302302
# listiter_reduce_general
303303
self.assertEqual(
304304
run("reversed", orig["reversed"](list(range(8)))),
305-
(iter, ([],))
305+
(reversed, ([],))
306306
)
307307

308308
for case in types:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
For an empty reverse iterator for list will be reduced to :func:`reversed`.
2+
Patch by Donghee Na

Objects/listobject.c

+52-50
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ class list "PyListObject *" "&PyList_Type"
2020

2121
_Py_DECLARE_STR(list_err, "list index out of range");
2222

23+
#ifdef Py_GIL_DISABLED
24+
# define LOAD_SSIZE(value) _Py_atomic_load_ssize_relaxed(&value)
25+
# define STORE_SSIZE(value, new_value) _Py_atomic_store_ssize_relaxed(&value, new_value)
26+
#else
27+
# define LOAD_SSIZE(value) value
28+
# define STORE_SSIZE(value, new_value) value = new_value
29+
#endif
30+
2331
#ifdef WITH_FREELISTS
2432
static struct _Py_list_freelist *
2533
get_list_freelist(void)
@@ -2971,7 +2979,8 @@ list___sizeof___impl(PyListObject *self)
29712979
/*[clinic end generated code: output=3417541f95f9a53e input=b8030a5d5ce8a187]*/
29722980
{
29732981
size_t res = _PyObject_SIZE(Py_TYPE(self));
2974-
res += (size_t)self->allocated * sizeof(void*);
2982+
Py_ssize_t allocated = LOAD_SSIZE(self->allocated);
2983+
res += (size_t)allocated * sizeof(void*);
29752984
return PyLong_FromSize_t(res);
29762985
}
29772986

@@ -3373,33 +3382,34 @@ static PyObject *
33733382
listiter_next(PyObject *self)
33743383
{
33753384
_PyListIterObject *it = (_PyListIterObject *)self;
3376-
PyListObject *seq;
3377-
PyObject *item;
3378-
3379-
assert(it != NULL);
3380-
seq = it->it_seq;
3381-
if (seq == NULL)
3385+
Py_ssize_t index = LOAD_SSIZE(it->it_index);
3386+
if (index < 0) {
33823387
return NULL;
3383-
assert(PyList_Check(seq));
3384-
3385-
if (it->it_index < PyList_GET_SIZE(seq)) {
3386-
item = PyList_GET_ITEM(seq, it->it_index);
3387-
++it->it_index;
3388-
return Py_NewRef(item);
33893388
}
33903389

3391-
it->it_seq = NULL;
3392-
Py_DECREF(seq);
3393-
return NULL;
3390+
PyObject *item = list_get_item_ref(it->it_seq, index);
3391+
if (item == NULL) {
3392+
// out-of-bounds
3393+
STORE_SSIZE(it->it_index, -1);
3394+
#ifndef Py_GIL_DISABLED
3395+
PyListObject *seq = it->it_seq;
3396+
it->it_seq = NULL;
3397+
Py_DECREF(seq);
3398+
#endif
3399+
return NULL;
3400+
}
3401+
STORE_SSIZE(it->it_index, index + 1);
3402+
return item;
33943403
}
33953404

33963405
static PyObject *
33973406
listiter_len(PyObject *self, PyObject *Py_UNUSED(ignored))
33983407
{
3408+
assert(self != NULL);
33993409
_PyListIterObject *it = (_PyListIterObject *)self;
3400-
Py_ssize_t len;
3401-
if (it->it_seq) {
3402-
len = PyList_GET_SIZE(it->it_seq) - it->it_index;
3410+
Py_ssize_t index = LOAD_SSIZE(it->it_index);
3411+
if (index >= 0) {
3412+
Py_ssize_t len = PyList_GET_SIZE(it->it_seq) - index;
34033413
if (len >= 0)
34043414
return PyLong_FromSsize_t(len);
34053415
}
@@ -3420,8 +3430,8 @@ listiter_setstate(PyObject *self, PyObject *state)
34203430
if (index == -1 && PyErr_Occurred())
34213431
return NULL;
34223432
if (it->it_seq != NULL) {
3423-
if (index < 0)
3424-
index = 0;
3433+
if (index < -1)
3434+
index = -1;
34253435
else if (index > PyList_GET_SIZE(it->it_seq))
34263436
index = PyList_GET_SIZE(it->it_seq); /* iterator exhausted */
34273437
it->it_index = index;
@@ -3526,34 +3536,33 @@ static PyObject *
35263536
listreviter_next(PyObject *self)
35273537
{
35283538
listreviterobject *it = (listreviterobject *)self;
3529-
PyObject *item;
3530-
Py_ssize_t index;
3531-
PyListObject *seq;
3532-
35333539
assert(it != NULL);
3534-
seq = it->it_seq;
3535-
if (seq == NULL) {
3536-
return NULL;
3537-
}
3540+
PyListObject *seq = it->it_seq;
35383541
assert(PyList_Check(seq));
35393542

3540-
index = it->it_index;
3541-
if (index>=0 && index < PyList_GET_SIZE(seq)) {
3542-
item = PyList_GET_ITEM(seq, index);
3543-
it->it_index--;
3544-
return Py_NewRef(item);
3543+
Py_ssize_t index = LOAD_SSIZE(it->it_index);
3544+
if (index < 0) {
3545+
return NULL;
3546+
}
3547+
PyObject *item = list_get_item_ref(seq, index);
3548+
if (item != NULL) {
3549+
STORE_SSIZE(it->it_index, index - 1);
3550+
return item;
35453551
}
3546-
it->it_index = -1;
3552+
STORE_SSIZE(it->it_index, -1);
3553+
#ifndef Py_GIL_DISABLED
35473554
it->it_seq = NULL;
35483555
Py_DECREF(seq);
3556+
#endif
35493557
return NULL;
35503558
}
35513559

35523560
static PyObject *
35533561
listreviter_len(PyObject *self, PyObject *Py_UNUSED(ignored))
35543562
{
35553563
listreviterobject *it = (listreviterobject *)self;
3556-
Py_ssize_t len = it->it_index + 1;
3564+
Py_ssize_t index = LOAD_SSIZE(it->it_index);
3565+
Py_ssize_t len = index + 1;
35573566
if (it->it_seq == NULL || PyList_GET_SIZE(it->it_seq) < len)
35583567
len = 0;
35593568
return PyLong_FromSsize_t(len);
@@ -3588,36 +3597,29 @@ static PyObject *
35883597
listiter_reduce_general(void *_it, int forward)
35893598
{
35903599
PyObject *list;
3600+
PyObject *iter;
35913601

35923602
/* _PyEval_GetBuiltin can invoke arbitrary code,
35933603
* call must be before access of iterator pointers.
35943604
* see issue #101765 */
35953605

35963606
/* the objects are not the same, index is of different types! */
35973607
if (forward) {
3598-
PyObject *iter = _PyEval_GetBuiltin(&_Py_ID(iter));
3599-
if (!iter) {
3600-
return NULL;
3601-
}
3608+
iter = _PyEval_GetBuiltin(&_Py_ID(iter));
36023609
_PyListIterObject *it = (_PyListIterObject *)_it;
3603-
if (it->it_seq) {
3610+
if (it->it_index >= 0) {
36043611
return Py_BuildValue("N(O)n", iter, it->it_seq, it->it_index);
36053612
}
3606-
Py_DECREF(iter);
36073613
} else {
3608-
PyObject *reversed = _PyEval_GetBuiltin(&_Py_ID(reversed));
3609-
if (!reversed) {
3610-
return NULL;
3611-
}
3614+
iter = _PyEval_GetBuiltin(&_Py_ID(reversed));
36123615
listreviterobject *it = (listreviterobject *)_it;
3613-
if (it->it_seq) {
3614-
return Py_BuildValue("N(O)n", reversed, it->it_seq, it->it_index);
3616+
if (it->it_index >= 0) {
3617+
return Py_BuildValue("N(O)n", iter, it->it_seq, it->it_index);
36153618
}
3616-
Py_DECREF(reversed);
36173619
}
36183620
/* empty iterator, create an empty list */
36193621
list = PyList_New(0);
36203622
if (list == NULL)
36213623
return NULL;
3622-
return Py_BuildValue("N(N)", _PyEval_GetBuiltin(&_Py_ID(iter)), list);
3624+
return Py_BuildValue("N(N)", iter, list);
36233625
}

Python/bytecodes.c

+5-3
Original file line numberDiff line numberDiff line change
@@ -2606,11 +2606,14 @@ dummy_func(
26062606
assert(Py_TYPE(iter) == &PyListIter_Type);
26072607
STAT_INC(FOR_ITER, hit);
26082608
PyListObject *seq = it->it_seq;
2609-
if (seq == NULL || it->it_index >= PyList_GET_SIZE(seq)) {
2609+
if ((size_t)it->it_index >= (size_t)PyList_GET_SIZE(seq)) {
2610+
it->it_index = -1;
2611+
#ifndef Py_GIL_DISABLED
26102612
if (seq != NULL) {
26112613
it->it_seq = NULL;
26122614
Py_DECREF(seq);
26132615
}
2616+
#endif
26142617
Py_DECREF(iter);
26152618
STACK_SHRINK(1);
26162619
/* Jump forward oparg, then skip following END_FOR and POP_TOP instructions */
@@ -2624,8 +2627,7 @@ dummy_func(
26242627
_PyListIterObject *it = (_PyListIterObject *)iter;
26252628
assert(Py_TYPE(iter) == &PyListIter_Type);
26262629
PyListObject *seq = it->it_seq;
2627-
DEOPT_IF(seq == NULL);
2628-
DEOPT_IF(it->it_index >= PyList_GET_SIZE(seq));
2630+
DEOPT_IF((size_t)it->it_index >= (size_t)PyList_GET_SIZE(seq));
26292631
}
26302632

26312633
op(_ITER_NEXT_LIST, (iter -- iter, next)) {

Python/executor_cases.c.h

+1-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/generated_cases.c.h

+4-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)