Coverage Report

Created: 2026-10-17 06:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/Python/Python-tokenize.c
Line
Count
Source
1
#include "Python.h"
2
#include "errcode.h"
3
#include "internal/pycore_critical_section.h"   // Py_BEGIN_CRITICAL_SECTION
4
#include "../Parser/tokenizer/tokenizer.h"
5
#include "../Parser/pegen.h"                    // _PyPegen_byte_offset_to_character_offset()
6
7
static struct PyModuleDef _tokenizemodule;
8
9
typedef struct {
10
    PyTypeObject *TokenizerIter;
11
} tokenize_state;
12
13
static tokenize_state *
14
722
get_tokenize_state(PyObject *module) {
15
722
    return (tokenize_state *)PyModule_GetState(module);
16
722
}
17
18
#define _tokenize_get_state_by_type(type) \
19
    get_tokenize_state(PyType_GetModuleByDef(type, &_tokenizemodule))
20
21
#include "pycore_runtime.h"
22
#include "clinic/Python-tokenize.c.h"
23
24
/*[clinic input]
25
module _tokenizer
26
class _tokenizer.tokenizeriter "tokenizeriterobject *" "_tokenize_get_state_by_type(type)->TokenizerIter"
27
[clinic start generated code]*/
28
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=96d98ee2fef7a8bc]*/
29
30
typedef struct
31
{
32
    PyObject_HEAD struct tok_state *tok;
33
    int done;
34
    int extra_tokens;
35
36
    /* Needed to cache line for performance */
37
    PyObject *last_line;
38
    Py_ssize_t last_lineno;
39
    Py_ssize_t byte_col_offset_diff;
40
} tokenizeriterobject;
41
42
/*[clinic input]
43
@classmethod
44
_tokenizer.tokenizeriter.__new__ as tokenizeriter_new
45
46
    readline: object
47
    /
48
    *
49
    extra_tokens: bool
50
    encoding: str(c_default="NULL") = 'utf-8'
51
[clinic start generated code]*/
52
53
static PyObject *
54
tokenizeriter_new_impl(PyTypeObject *type, PyObject *readline,
55
                       int extra_tokens, const char *encoding)
56
/*[clinic end generated code: output=7501a1211683ce16 input=f7dddf8a613ae8bd]*/
57
20
{
58
20
    struct tok_state *tok = _PyTokenizer_FromReadline(readline, encoding);
59
20
    if (tok == NULL) {
60
0
        return NULL;
61
0
    }
62
20
    _Py_DECLARE_STR(anon_string, "<string>");
63
20
    _PyTokenizer_SetContext(tok, &_Py_STR(anon_string), NULL);
64
20
    tokenizeriterobject *self = (tokenizeriterobject *)type->tp_alloc(type, 0);
65
20
    if (self == NULL) {
66
0
        _PyTokenizer_Free(tok);
67
0
        return NULL;
68
0
    }
69
20
    self->tok = tok;
70
20
    _PyTokenizer_SetOptions(self->tok, extra_tokens, 0);
71
20
    self->extra_tokens = extra_tokens;
72
20
    self->done = 0;
73
74
20
    self->last_line = NULL;
75
20
    self->byte_col_offset_diff = 0;
76
20
    self->last_lineno = 0;
77
78
20
    return (PyObject *)self;
79
20
}
80
81
static void
82
_tokenizer_error(tokenizeriterobject *it)
83
0
{
84
0
    _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(it);
85
0
    assert(!PyErr_Occurred());
86
87
0
    const char *msg = NULL;
88
0
    PyObject* errtype = PyExc_SyntaxError;
89
0
    struct tok_state *tok = it->tok;
90
0
    _PyTokenizer_Info info = _PyTokenizer_GetInfo(tok);
91
0
    switch (info.status) {
92
0
        case E_TOKEN:
93
0
            msg = "invalid token";
94
0
            break;
95
0
        case E_EOF:
96
0
            PyErr_SetString(PyExc_SyntaxError, "unexpected EOF in multi-line statement");
97
0
            PyErr_SyntaxLocationObject(
98
0
                info.filename, info.location.lineno,
99
0
                (int)Py_MAX(0, info.input_span.end - info.input_span.start));
100
0
            return;
101
0
        case E_DEDENT:
102
0
            msg = "unindent does not match any outer indentation level";
103
0
            errtype = PyExc_IndentationError;
104
0
            break;
105
0
        case E_INTR:
106
0
            PyErr_SetNone(PyExc_KeyboardInterrupt);
107
0
            return;
108
0
        case E_NOMEM:
109
0
            PyErr_NoMemory();
110
0
            return;
111
0
        case E_TABSPACE:
112
0
            errtype = PyExc_TabError;
113
0
            msg = "inconsistent use of tabs and spaces in indentation";
114
0
            break;
115
0
        case E_TOODEEP:
116
0
            errtype = PyExc_IndentationError;
117
0
            msg = "too many levels of indentation";
118
0
            break;
119
0
        case E_LINECONT: {
120
0
            msg = "unexpected character after line continuation character";
121
0
            break;
122
0
        }
123
0
        default:
124
0
            msg = "unknown tokenization error";
125
0
    }
126
127
0
    PyObject* error_line = NULL;
128
0
    PyObject* value = NULL;
129
130
0
    Py_ssize_t input_size;
131
0
    const char *input = _PyTokenizer_SpanView(
132
0
        tok, info.input_span, &input_size);
133
0
    Py_ssize_t size = input_size;
134
0
    assert(input[size-1] == '\n');
135
0
    size -= 1; // Remove the newline character from the end of the line
136
0
    error_line = PyUnicode_DecodeUTF8(input, size, "replace");
137
0
    if (!error_line) {
138
0
        goto exit;
139
0
    }
140
141
0
    Py_ssize_t offset = _PyPegen_byte_offset_to_character_offset(error_line, input_size);
142
0
    if (offset == -1) {
143
0
        goto exit;
144
0
    }
145
0
    value = Py_BuildValue("(s(OinOOO))", msg, info.filename,
146
0
                          info.location.lineno, offset, error_line,
147
0
                          Py_None, Py_None);
148
0
    if (!value) {
149
0
        goto exit;
150
0
    }
151
152
0
    PyErr_SetObject(errtype, value);
153
154
0
exit:
155
0
    Py_XDECREF(error_line);
156
0
    Py_XDECREF(value);
157
0
}
158
159
static PyObject *
160
_get_current_line(tokenizeriterobject *it, int current_lineno,
161
                  const _PyToken_View *view, int *line_changed)
162
2.04k
{
163
2.04k
    _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(it);
164
2.04k
    if (current_lineno != it->last_lineno) {
165
320
        Py_ssize_t size;
166
320
        const char *line_start = _PyTokenizer_SpanView(
167
320
            it->tok, view->line_span, &size);
168
320
        if (size > 0 && view->implicit_newline) {
169
4
            size--;
170
4
        }
171
320
        Py_XDECREF(it->last_line);
172
320
        it->last_line = PyUnicode_DecodeUTF8(line_start, size, "replace");
173
320
        it->byte_col_offset_diff = 0;
174
320
    }
175
1.72k
    else {
176
1.72k
        *line_changed = 0;
177
1.72k
    }
178
2.04k
    return it->last_line;
179
2.04k
}
180
181
static int
182
_get_col_offsets(tokenizeriterobject *it, const struct token *token,
183
                 const _PyToken_View *view, PyObject *line, int line_changed,
184
                 Py_ssize_t *col_offset, Py_ssize_t *end_col_offset)
185
2.06k
{
186
2.06k
    _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(it);
187
2.06k
    _PyTok_Off token_start = token->span.start;
188
2.06k
    _PyTok_Off token_end = token->span.end;
189
2.06k
    Py_ssize_t lineno = token->start_loc.lineno;
190
2.06k
    Py_ssize_t end_lineno = token->end_loc.lineno;
191
2.06k
    Py_ssize_t byte_offset = -1;
192
2.06k
    if (token_start >= view->line_span.start) {
193
2.05k
        byte_offset = token_start - view->line_span.start;
194
2.05k
        if (line_changed) {
195
332
            *col_offset = _PyPegen_byte_offset_to_character_offset_line(line, 0, byte_offset);
196
332
            if (*col_offset < 0) {
197
0
                return -1;
198
0
            }
199
332
            it->byte_col_offset_diff = byte_offset - *col_offset;
200
332
        }
201
1.72k
        else {
202
1.72k
            *col_offset = byte_offset - it->byte_col_offset_diff;
203
1.72k
        }
204
2.05k
    }
205
206
2.06k
    if (token_end >= view->end_line_start) {
207
2.05k
        Py_ssize_t end_byte_offset = token_end - view->end_line_start;
208
2.05k
        if (lineno == end_lineno) {
209
            // Avoid rescanning the prefix of a very long line.
210
2.05k
            Py_ssize_t token_col_offset = _PyPegen_byte_offset_to_character_offset_line(line, byte_offset, end_byte_offset);
211
2.05k
            if (token_col_offset < 0) {
212
0
                return -1;
213
0
            }
214
2.05k
            *end_col_offset = *col_offset + token_col_offset;
215
2.05k
            it->byte_col_offset_diff += token_end - token_start - token_col_offset;
216
2.05k
        }
217
4
        else {
218
4
            *end_col_offset = _PyPegen_byte_offset_to_character_offset_line(
219
4
                line, view->end_line_start - view->line_span.start,
220
4
                token_end - view->line_span.start);
221
4
            if (*end_col_offset < 0) {
222
0
                return -1;
223
0
            }
224
4
            it->byte_col_offset_diff += end_byte_offset - *end_col_offset;
225
4
        }
226
2.05k
    }
227
2.06k
    it->last_lineno = lineno;
228
2.06k
    return 0;
229
2.06k
}
230
231
static PyObject *
232
tokenizeriter_next(PyObject *op)
233
2.06k
{
234
2.06k
    tokenizeriterobject *it = (tokenizeriterobject*)op;
235
2.06k
    PyObject* result = NULL;
236
237
2.06k
    Py_BEGIN_CRITICAL_SECTION(it);
238
239
2.06k
    if (it->done) {
240
4
        PyErr_SetString(PyExc_StopIteration, "EOF");
241
4
        goto unlock;
242
4
    }
243
244
2.06k
    struct token token;
245
2.06k
    _PyToken_Init(&token);
246
247
2.06k
    _PyTokenizer_Get(it->tok, &token);
248
2.06k
    int type = token.type;
249
2.06k
    if (type == ERRORTOKEN) {
250
0
        if(!PyErr_Occurred()) {
251
0
            _tokenizer_error(it);
252
0
            assert(PyErr_Occurred());
253
0
        }
254
0
        goto exit;
255
0
    }
256
2.06k
    _PyToken_View view;
257
2.06k
    _PyToken_GetView(it->tok, &token, &view);
258
2.06k
    PyObject *str = PyUnicode_FromStringAndSize(view.text, view.length);
259
2.06k
    if (str == NULL) {
260
0
        goto exit;
261
0
    }
262
263
2.06k
    int is_trailing_token = type == ENDMARKER || (type == DEDENT && view.at_eof);
264
265
2.06k
    PyObject* line = NULL;
266
2.06k
    int line_changed = 1;
267
2.06k
    if (it->extra_tokens && is_trailing_token) {
268
20
        line = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
269
2.04k
    } else {
270
2.04k
        line = _get_current_line(
271
2.04k
            it, token.end_loc.lineno, &view, &line_changed);
272
2.04k
    }
273
2.06k
    if (line == NULL) {
274
0
        Py_DECREF(str);
275
0
        goto exit;
276
0
    }
277
278
2.06k
    Py_ssize_t lineno = token.start_loc.lineno;
279
2.06k
    Py_ssize_t end_lineno = token.end_loc.lineno;
280
2.06k
    Py_ssize_t col_offset = -1;
281
2.06k
    Py_ssize_t end_col_offset = -1;
282
2.06k
    if (_get_col_offsets(it, &token, &view, line,
283
2.06k
                         line_changed, &col_offset, &end_col_offset) < 0) {
284
0
        Py_DECREF(str);
285
0
        goto exit;
286
0
    }
287
288
2.06k
    if (it->extra_tokens) {
289
2.06k
        if (is_trailing_token) {
290
20
            lineno = end_lineno = lineno + 1;
291
20
            col_offset = end_col_offset = 0;
292
20
        }
293
        // Necessary adjustments to match the original Python tokenize
294
        // implementation
295
2.06k
        if (type > DEDENT && type < OP) {
296
796
            type = OP;
297
796
        }
298
1.26k
        else if (type == NEWLINE) {
299
164
            if (!view.implicit_newline) {
300
160
                Py_DECREF(str);
301
160
                if (view.text[0] == '\r') {
302
0
                    str = PyUnicode_FromString("\r\n");
303
160
                } else {
304
160
                    str = PyUnicode_FromString("\n");
305
160
                }
306
160
            }
307
164
            end_col_offset++;
308
164
        }
309
1.10k
        else if (type == NL) {
310
140
            if (view.implicit_newline) {
311
0
                Py_DECREF(str);
312
0
                str = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
313
0
            }
314
140
        }
315
316
2.06k
        if (str == NULL) {
317
0
            goto exit;
318
0
        }
319
2.06k
    }
320
321
2.06k
    result = Py_BuildValue("(iN(nn)(nn)O)", type, str, lineno, col_offset, end_lineno, end_col_offset, line);
322
2.06k
exit:
323
2.06k
    _PyToken_Free(&token);
324
2.06k
    if (type == ENDMARKER) {
325
8
        it->done = 1;
326
8
    }
327
328
2.06k
unlock:;
329
2.06k
    Py_END_CRITICAL_SECTION();
330
2.06k
    return result;
331
2.06k
}
332
333
static void
334
tokenizeriter_dealloc(PyObject *op)
335
20
{
336
20
    tokenizeriterobject *it = (tokenizeriterobject*)op;
337
20
    PyTypeObject *tp = Py_TYPE(it);
338
20
    PyObject_GC_UnTrack(it);
339
20
    Py_XDECREF(it->last_line);
340
20
    _PyTokenizer_Free(it->tok);
341
20
    tp->tp_free(it);
342
20
    Py_DECREF(tp);
343
20
}
344
345
static int
346
tokenizeriter_traverse(PyObject *op, visitproc visit, void *arg)
347
0
{
348
0
    tokenizeriterobject *it = (tokenizeriterobject *)op;
349
0
    Py_VISIT(Py_TYPE(it));
350
0
    Py_VISIT(it->last_line);
351
0
    return _PyTokenizer_Traverse(it->tok, visit, arg);
352
0
}
353
354
static PyType_Slot tokenizeriter_slots[] = {
355
    {Py_tp_new, tokenizeriter_new},
356
    {Py_tp_dealloc, tokenizeriter_dealloc},
357
    {Py_tp_traverse, tokenizeriter_traverse},
358
    {Py_tp_getattro, PyObject_GenericGetAttr},
359
    {Py_tp_iter, PyObject_SelfIter},
360
    {Py_tp_iternext, tokenizeriter_next},
361
    {0, NULL},
362
};
363
364
static PyType_Spec tokenizeriter_spec = {
365
    .name = "_tokenize.TokenizerIter",
366
    .basicsize = sizeof(tokenizeriterobject),
367
    .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC),
368
    .slots = tokenizeriter_slots,
369
};
370
371
static int
372
tokenizemodule_exec(PyObject *m)
373
6
{
374
6
    tokenize_state *state = get_tokenize_state(m);
375
6
    if (state == NULL) {
376
0
        return -1;
377
0
    }
378
379
6
    state->TokenizerIter = (PyTypeObject *)PyType_FromModuleAndSpec(m, &tokenizeriter_spec, NULL);
380
6
    if (state->TokenizerIter == NULL) {
381
0
        return -1;
382
0
    }
383
6
    if (PyModule_AddType(m, state->TokenizerIter) < 0) {
384
0
        return -1;
385
0
    }
386
387
6
    return 0;
388
6
}
389
390
static PyMethodDef tokenize_methods[] = {
391
    {NULL, NULL, 0, NULL} /* Sentinel */
392
};
393
394
static PyModuleDef_Slot tokenizemodule_slots[] = {
395
    _Py_ABI_SLOT,
396
    {Py_mod_exec, tokenizemodule_exec},
397
    {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
398
    {Py_mod_gil, Py_MOD_GIL_NOT_USED},
399
    {0, NULL}
400
};
401
402
static int
403
tokenizemodule_traverse(PyObject *m, visitproc visit, void *arg)
404
716
{
405
716
    tokenize_state *state = get_tokenize_state(m);
406
716
    Py_VISIT(state->TokenizerIter);
407
716
    return 0;
408
716
}
409
410
static int
411
tokenizemodule_clear(PyObject *m)
412
0
{
413
0
    tokenize_state *state = get_tokenize_state(m);
414
0
    Py_CLEAR(state->TokenizerIter);
415
0
    return 0;
416
0
}
417
418
static void
419
tokenizemodule_free(void *m)
420
0
{
421
0
    tokenizemodule_clear((PyObject *)m);
422
0
}
423
424
static struct PyModuleDef _tokenizemodule = {
425
    PyModuleDef_HEAD_INIT,
426
    .m_name = "_tokenize",
427
    .m_size = sizeof(tokenize_state),
428
    .m_slots = tokenizemodule_slots,
429
    .m_methods = tokenize_methods,
430
    .m_traverse = tokenizemodule_traverse,
431
    .m_clear = tokenizemodule_clear,
432
    .m_free = tokenizemodule_free,
433
};
434
435
PyMODINIT_FUNC
436
PyInit__tokenize(void)
437
6
{
438
6
    return PyModuleDef_Init(&_tokenizemodule);
439
6
}