Coverage Report

Created: 2026-10-31 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/Objects/iterobject.c
Line
Count
Source
1
/* Iterator objects */
2
3
#include "Python.h"
4
#include "pycore_abstract.h"      // _PyObject_HasLen()
5
#include "pycore_call.h"          // _PyObject_CallNoArgs()
6
#include "pycore_ceval.h"         // _PyEval_GetBuiltin()
7
#include "pycore_genobject.h"     // _PyCoro_GetAwaitableIter()
8
#include "pycore_iterobject.h"    // _PyCallIter_NewEx()
9
#include "pycore_object.h"        // _PyObject_GC_TRACK()
10
#include "pycore_pyatomic_ft_wrappers.h"  // FT_ATOMIC_LOAD_SSIZE_RELAXED()
11
#include "pycore_pyerrors.h"      // _PyErr_FormatFromCause()
12
#include "pycore_pystate.h"       // _PyThreadState_GET()
13
14
15
typedef struct {
16
    PyObject_HEAD
17
    Py_ssize_t it_index;  /* -1 when iterator is exhausted */
18
    PyObject *it_seq; /* Set to NULL when iterator is exhausted
19
                         (in the default build) */
20
} seqiterobject;
21
22
PyObject *
23
PySeqIter_New(PyObject *seq)
24
1.72M
{
25
1.72M
    seqiterobject *it;
26
27
1.72M
    if (!PySequence_Check(seq)) {
28
0
        PyErr_BadInternalCall();
29
0
        return NULL;
30
0
    }
31
1.72M
    it = PyObject_GC_New(seqiterobject, &PySeqIter_Type);
32
1.72M
    if (it == NULL)
33
0
        return NULL;
34
1.72M
    it->it_index = 0;
35
1.72M
    it->it_seq = Py_NewRef(seq);
36
1.72M
    _PyObject_GC_TRACK(it);
37
1.72M
    return (PyObject *)it;
38
1.72M
}
39
40
static void
41
iter_dealloc(PyObject *op)
42
1.72M
{
43
1.72M
    seqiterobject *it = (seqiterobject*)op;
44
1.72M
    _PyObject_GC_UNTRACK(it);
45
1.72M
    Py_XDECREF(it->it_seq);
46
1.72M
    PyObject_GC_Del(it);
47
1.72M
}
48
49
static int
50
iter_traverse(PyObject *op, visitproc visit, void *arg)
51
2.02k
{
52
2.02k
    seqiterobject *it = (seqiterobject*)op;
53
2.02k
    Py_VISIT(it->it_seq);
54
2.02k
    return 0;
55
2.02k
}
56
57
static PyObject *
58
iter_iternext(PyObject *iterator)
59
9.37M
{
60
9.37M
    seqiterobject *it;
61
9.37M
    PyObject *seq;
62
9.37M
    PyObject *result;
63
64
9.37M
    assert(PySeqIter_Check(iterator));
65
9.37M
    it = (seqiterobject *)iterator;
66
9.37M
    Py_ssize_t index = FT_ATOMIC_LOAD_SSIZE_RELAXED(it->it_index);
67
9.37M
    if (index < 0)
68
0
        return NULL;
69
9.37M
    seq = it->it_seq;
70
9.37M
#ifndef Py_GIL_DISABLED
71
9.37M
    if (seq == NULL)
72
0
        return NULL;
73
9.37M
#endif
74
9.37M
    if (index == PY_SSIZE_T_MAX) {
75
0
        PyErr_SetString(PyExc_OverflowError,
76
0
                        "iter index too large");
77
0
        return NULL;
78
0
    }
79
80
9.37M
    result = PySequence_GetItem(seq, index);
81
9.37M
    if (result != NULL) {
82
        /* PySequence_GetItem() can exhaust the iterator re-entrantly.
83
         * Preserve the exhaustion sentinel if it is observed.  Concurrent
84
         * exhaustion can still race with the store, but remains memory-safe
85
         * because the sequence stays alive. */
86
7.64M
        if (FT_ATOMIC_LOAD_SSIZE_RELAXED(it->it_index) >= 0) {
87
7.64M
            FT_ATOMIC_STORE_SSIZE_RELAXED(it->it_index, index + 1);
88
7.64M
        }
89
7.64M
        return result;
90
7.64M
    }
91
1.72M
    if (PyErr_ExceptionMatches(PyExc_IndexError) ||
92
0
        PyErr_ExceptionMatches(PyExc_StopIteration))
93
1.72M
    {
94
        /* Mark the iterator exhausted before anything that can run
95
         * arbitrary code. */
96
1.72M
        FT_ATOMIC_STORE_SSIZE_RELAXED(it->it_index, -1);
97
1.72M
#ifndef Py_GIL_DISABLED
98
1.72M
        Py_CLEAR(it->it_seq);
99
1.72M
#endif
100
1.72M
        PyErr_Clear();
101
1.72M
    }
102
1.72M
    return NULL;
103
9.37M
}
104
105
static PyObject *
106
iter_len(PyObject *op, PyObject *Py_UNUSED(ignored))
107
6.62k
{
108
6.62k
    seqiterobject *it = (seqiterobject*)op;
109
6.62k
    Py_ssize_t seqsize, len;
110
111
6.62k
    Py_ssize_t index = FT_ATOMIC_LOAD_SSIZE_RELAXED(it->it_index);
112
6.62k
    if (index >= 0 && it->it_seq != NULL) {
113
6.62k
        if (_PyObject_HasLen(it->it_seq)) {
114
6.62k
            seqsize = PySequence_Size(it->it_seq);
115
6.62k
            if (seqsize == -1)
116
0
                return NULL;
117
6.62k
        }
118
0
        else {
119
0
            Py_RETURN_NOTIMPLEMENTED;
120
0
        }
121
6.62k
        len = seqsize - index;
122
6.62k
        if (len >= 0)
123
6.62k
            return PyLong_FromSsize_t(len);
124
6.62k
    }
125
0
    return PyLong_FromLong(0);
126
6.62k
}
127
128
PyDoc_STRVAR(length_hint_doc, "Private method returning an estimate of len(list(it)).");
129
130
static PyObject *
131
iter_reduce(PyObject *op, PyObject *Py_UNUSED(ignored))
132
0
{
133
0
    seqiterobject *it = (seqiterobject*)op;
134
0
    PyObject *iter = _PyEval_GetBuiltin(&_Py_ID(iter));
135
136
    /* _PyEval_GetBuiltin can invoke arbitrary code,
137
     * call must be before access of iterator pointers.
138
     * see issue #101765 */
139
140
0
    Py_ssize_t index = FT_ATOMIC_LOAD_SSIZE_RELAXED(it->it_index);
141
0
    if (index >= 0 && it->it_seq != NULL)
142
0
        return Py_BuildValue("N(O)n", iter, it->it_seq, index);
143
0
    else
144
0
        return Py_BuildValue("N(())", iter);
145
0
}
146
147
PyDoc_STRVAR(reduce_doc, "Return state information for pickling.");
148
149
static PyObject *
150
iter_setstate(PyObject *op, PyObject *state)
151
0
{
152
0
    seqiterobject *it = (seqiterobject*)op;
153
0
    Py_ssize_t index = PyLong_AsSsize_t(state);
154
0
    if (index == -1 && PyErr_Occurred())
155
0
        return NULL;
156
0
    if (index < 0)
157
0
        index = 0;
158
0
    if (it->it_seq && FT_ATOMIC_LOAD_SSIZE_RELAXED(it->it_index) >= 0) {
159
0
        FT_ATOMIC_STORE_SSIZE_RELAXED(it->it_index, index);
160
0
    }
161
0
    Py_RETURN_NONE;
162
0
}
163
164
PyDoc_STRVAR(setstate_doc, "Set state information for unpickling.");
165
166
static PyMethodDef seqiter_methods[] = {
167
    {"__length_hint__", iter_len, METH_NOARGS, length_hint_doc},
168
    {"__reduce__", iter_reduce, METH_NOARGS, reduce_doc},
169
    {"__setstate__", iter_setstate, METH_O, setstate_doc},
170
    {NULL,              NULL}           /* sentinel */
171
};
172
173
PyTypeObject PySeqIter_Type = {
174
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
175
    "iterator",                                 /* tp_name */
176
    sizeof(seqiterobject),                      /* tp_basicsize */
177
    0,                                          /* tp_itemsize */
178
    /* methods */
179
    iter_dealloc,                               /* tp_dealloc */
180
    0,                                          /* tp_vectorcall_offset */
181
    0,                                          /* tp_getattr */
182
    0,                                          /* tp_setattr */
183
    0,                                          /* tp_as_async */
184
    0,                                          /* tp_repr */
185
    0,                                          /* tp_as_number */
186
    0,                                          /* tp_as_sequence */
187
    0,                                          /* tp_as_mapping */
188
    0,                                          /* tp_hash */
189
    0,                                          /* tp_call */
190
    0,                                          /* tp_str */
191
    PyObject_GenericGetAttr,                    /* tp_getattro */
192
    0,                                          /* tp_setattro */
193
    0,                                          /* tp_as_buffer */
194
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,    /* tp_flags */
195
    0,                                          /* tp_doc */
196
    iter_traverse,                              /* tp_traverse */
197
    0,                                          /* tp_clear */
198
    0,                                          /* tp_richcompare */
199
    0,                                          /* tp_weaklistoffset */
200
    PyObject_SelfIter,                          /* tp_iter */
201
    iter_iternext,                              /* tp_iternext */
202
    seqiter_methods,                            /* tp_methods */
203
    0,                                          /* tp_members */
204
};
205
206
/* -------------------------------------- */
207
208
typedef struct {
209
    PyObject_HEAD
210
    PyObject *it_callable;  /* set to NULL when the iterator is exhausted */
211
    PyObject *it_sentinel;  /* can be NULL, and is when exhausted */
212
    PyObject *it_stop_exc;  /* never NULL */
213
} calliterobject;
214
215
PyObject *
216
_PyCallIter_NewEx(PyObject *callable, PyObject *sentinel, PyObject *stop_exc)
217
376k
{
218
376k
    calliterobject *it;
219
376k
    if (stop_exc == NULL) {
220
376k
        stop_exc = PyExc_StopIteration;
221
376k
    }
222
0
    else if (_PyEval_CheckExceptTypeValid(_PyThreadState_GET(), stop_exc) < 0) {
223
0
        return NULL;
224
0
    }
225
376k
    it = PyObject_GC_New(calliterobject, &PyCallIter_Type);
226
376k
    if (it == NULL)
227
0
        return NULL;
228
376k
    it->it_callable = Py_NewRef(callable);
229
376k
    it->it_sentinel = Py_XNewRef(sentinel);
230
376k
    it->it_stop_exc = Py_NewRef(stop_exc);
231
376k
    _PyObject_GC_TRACK(it);
232
376k
    return (PyObject *)it;
233
376k
}
234
235
PyObject *
236
PyCallIter_New(PyObject *callable, PyObject *sentinel)
237
376k
{
238
376k
    return _PyCallIter_NewEx(callable, sentinel, NULL);
239
376k
}
240
241
static void
242
calliter_exhaust(calliterobject *it)
243
376k
{
244
376k
    Py_CLEAR(it->it_callable);
245
376k
    Py_CLEAR(it->it_sentinel);
246
376k
}
247
248
static void
249
calliter_dealloc(PyObject *op)
250
376k
{
251
376k
    calliterobject *it = (calliterobject*)op;
252
376k
    _PyObject_GC_UNTRACK(it);
253
376k
    Py_XDECREF(it->it_callable);
254
376k
    Py_XDECREF(it->it_sentinel);
255
376k
    Py_XDECREF(it->it_stop_exc);
256
376k
    PyObject_GC_Del(it);
257
376k
}
258
259
static int
260
calliter_traverse(PyObject *op, visitproc visit, void *arg)
261
1.16k
{
262
1.16k
    calliterobject *it = (calliterobject*)op;
263
1.16k
    Py_VISIT(it->it_callable);
264
1.16k
    Py_VISIT(it->it_sentinel);
265
1.16k
    Py_VISIT(it->it_stop_exc);
266
1.16k
    return 0;
267
1.16k
}
268
269
static PyObject *
270
calliter_iternext(PyObject *op)
271
3.81M
{
272
3.81M
    calliterobject *it = (calliterobject*)op;
273
3.81M
    PyObject *result;
274
275
3.81M
    if (it->it_callable == NULL) {
276
0
        return NULL;
277
0
    }
278
279
3.81M
    result = _PyObject_CallNoArgs(it->it_callable);
280
    /* The call can exhaust the iterator re-entrantly. */
281
3.81M
    if (result != NULL && it->it_callable != NULL) {
282
3.81M
        if (it->it_sentinel == NULL) {
283
0
            return result; /* Common case, fast path */
284
0
        }
285
3.81M
        int ok = PyObject_RichCompareBool(it->it_sentinel, result, Py_EQ);
286
3.81M
        if (ok == 0) {
287
3.43M
            return result; /* Common case, fast path */
288
3.43M
        }
289
290
376k
        if (ok > 0) {
291
376k
            calliter_exhaust(it);
292
376k
        }
293
376k
    }
294
0
    else if (PyErr_ExceptionMatches(it->it_stop_exc)) {
295
0
        PyErr_Clear();
296
0
        calliter_exhaust(it);
297
0
    }
298
0
    else if (PyErr_ExceptionMatches(PyExc_StopIteration)) {
299
        /* It would be mistaken for the end of the iteration (see PEP 479). */
300
0
        _PyErr_FormatFromCause(PyExc_RuntimeError,
301
0
                               "callable raised StopIteration");
302
0
    }
303
376k
    Py_XDECREF(result);
304
376k
    return NULL;
305
3.81M
}
306
307
static PyObject *
308
calliter_reduce(PyObject *op, PyObject *Py_UNUSED(ignored))
309
0
{
310
0
    calliterobject *it = (calliterobject*)op;
311
0
    PyObject *iter = _PyEval_GetBuiltin(&_Py_ID(iter));
312
313
    /* _PyEval_GetBuiltin can invoke arbitrary code,
314
     * call must be before access of iterator pointers.
315
     * see issue #101765 */
316
317
0
    if (it->it_callable == NULL) {
318
0
        return Py_BuildValue("N(())", iter);
319
0
    }
320
    /* Only the sentinel can be passed as an argument of iter(), so other
321
       attributes are restored from the state (see calliter_setstate()). */
322
0
    if (it->it_sentinel == NULL) {
323
0
        return Py_BuildValue("N(OO)(()O)", iter, it->it_callable, Py_None,
324
0
                             it->it_stop_exc);
325
0
    }
326
0
    else if (it->it_stop_exc == PyExc_StopIteration) {
327
0
        return Py_BuildValue("N(OO)", iter, it->it_callable, it->it_sentinel);
328
0
    }
329
0
    else {
330
0
        return Py_BuildValue("N(OO)((O)O)", iter, it->it_callable, Py_None,
331
0
                             it->it_sentinel, it->it_stop_exc);
332
0
    }
333
0
}
334
335
static PyObject *
336
calliter_setstate(PyObject *op, PyObject *state)
337
0
{
338
0
    calliterobject *it = (calliterobject*)op;
339
0
    PyObject *sentinel, *stop_exc;
340
341
0
    if (!PyTuple_Check(state) || PyTuple_GET_SIZE(state) != 2) {
342
0
        goto error;
343
0
    }
344
0
    sentinel = PyTuple_GET_ITEM(state, 0);
345
0
    stop_exc = PyTuple_GET_ITEM(state, 1);
346
0
    if (!PyTuple_Check(sentinel) || PyTuple_GET_SIZE(sentinel) > 1) {
347
0
        goto error;
348
0
    }
349
0
    if (_PyEval_CheckExceptTypeValid(_PyThreadState_GET(), stop_exc) < 0) {
350
0
        return NULL;
351
0
    }
352
0
    if (it->it_callable != NULL) {
353
0
        Py_XSETREF(it->it_sentinel,
354
0
                   PyTuple_GET_SIZE(sentinel) ?
355
0
                   Py_NewRef(PyTuple_GET_ITEM(sentinel, 0)) : NULL);
356
0
        Py_SETREF(it->it_stop_exc, Py_NewRef(stop_exc));
357
0
    }
358
0
    Py_RETURN_NONE;
359
360
0
error:
361
0
    PyErr_SetString(PyExc_TypeError, "invalid state for callable_iterator");
362
    return NULL;
363
0
}
364
365
static PyMethodDef calliter_methods[] = {
366
    {"__reduce__", calliter_reduce, METH_NOARGS, reduce_doc},
367
    {"__setstate__", calliter_setstate, METH_O, setstate_doc},
368
    {NULL,              NULL}           /* sentinel */
369
};
370
371
PyTypeObject PyCallIter_Type = {
372
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
373
    "callable_iterator",                        /* tp_name */
374
    sizeof(calliterobject),                     /* tp_basicsize */
375
    0,                                          /* tp_itemsize */
376
    /* methods */
377
    calliter_dealloc,                           /* tp_dealloc */
378
    0,                                          /* tp_vectorcall_offset */
379
    0,                                          /* tp_getattr */
380
    0,                                          /* tp_setattr */
381
    0,                                          /* tp_as_async */
382
    0,                                          /* tp_repr */
383
    0,                                          /* tp_as_number */
384
    0,                                          /* tp_as_sequence */
385
    0,                                          /* tp_as_mapping */
386
    0,                                          /* tp_hash */
387
    0,                                          /* tp_call */
388
    0,                                          /* tp_str */
389
    PyObject_GenericGetAttr,                    /* tp_getattro */
390
    0,                                          /* tp_setattro */
391
    0,                                          /* tp_as_buffer */
392
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,    /* tp_flags */
393
    0,                                          /* tp_doc */
394
    calliter_traverse,                          /* tp_traverse */
395
    0,                                          /* tp_clear */
396
    0,                                          /* tp_richcompare */
397
    0,                                          /* tp_weaklistoffset */
398
    PyObject_SelfIter,                          /* tp_iter */
399
    calliter_iternext,                          /* tp_iternext */
400
    calliter_methods,                           /* tp_methods */
401
};