Coverage Report

Created: 2026-10-17 06:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/Parser/tokenizer/helpers.c
Line
Count
Source
1
#include "Python.h"
2
#include "errcode.h"
3
#include "pycore_runtime.h"       // _Py_ID()
4
#include "pycore_token.h"
5
#include "pycore_tuple.h"         // _PyTuple_FromPair
6
7
#include "../lexer/state.h"
8
9
10
/* ############## ERRORS ############## */
11
12
/* Convert a 1-based column in bytes into a 1-based column in characters.
13
   The line is UTF-8 encoded, so it is enough to skip continuation bytes. */
14
static int
15
byte_col_to_char_col(const char *line, int byte_col)
16
130
{
17
130
    int char_col = 1;
18
13.7k
    for (int i = 0; i < byte_col - 1; i++) {
19
13.6k
        if ((line[i] & 0xC0) != 0x80) {
20
12.9k
            char_col++;
21
12.9k
        }
22
13.6k
    }
23
130
    return char_col;
24
130
}
25
26
static int
27
_syntaxerror_range(struct tok_state *tok, const char *line,
28
                   Py_ssize_t cursor, int lineno, const char *format,
29
                   int col_offset, int end_col_offset,
30
                   va_list vargs)
31
3.04k
{
32
    // In release builds, we don't want to overwrite a previous error, but in debug builds we
33
    // want to fail if we are not doing it so we can fix it.
34
3.04k
    assert(tok->done != E_ERROR);
35
3.04k
    if (tok->done == E_ERROR) {
36
0
        return ERRORTOKEN;
37
0
    }
38
3.04k
    PyObject *errmsg, *errtext, *args;
39
3.04k
    errmsg = PyUnicode_FromFormatV(format, vargs);
40
3.04k
    if (!errmsg) {
41
0
        goto error;
42
0
    }
43
44
3.04k
    errtext = PyUnicode_DecodeUTF8(line, cursor,
45
3.04k
                                   "replace");
46
3.04k
    if (!errtext) {
47
0
        goto error;
48
0
    }
49
50
3.04k
    if (col_offset == -1) {
51
2.97k
        col_offset = (int)PyUnicode_GET_LENGTH(errtext);
52
2.97k
    }
53
68
    else if (col_offset > 0) {
54
62
        col_offset = byte_col_to_char_col(line, col_offset);
55
62
    }
56
3.04k
    if (end_col_offset == -1) {
57
2.97k
        end_col_offset = col_offset;
58
2.97k
    }
59
68
    else if (end_col_offset > 0) {
60
68
        end_col_offset = byte_col_to_char_col(line, end_col_offset);
61
68
    }
62
63
3.04k
    Py_ssize_t line_len = strcspn(line, "\n");
64
3.04k
    if (line_len != cursor) {
65
2.24k
        Py_DECREF(errtext);
66
2.24k
        errtext = PyUnicode_DecodeUTF8(line, line_len,
67
2.24k
                                       "replace");
68
2.24k
    }
69
3.04k
    if (!errtext) {
70
0
        goto error;
71
0
    }
72
73
3.04k
    args = Py_BuildValue("(O(OiiNii))", errmsg,
74
3.04k
                         tok->filename ? tok->filename : Py_None,
75
3.04k
                         lineno, col_offset, errtext,
76
3.04k
                         lineno, end_col_offset);
77
3.04k
    if (args) {
78
3.04k
        PyErr_SetObject(PyExc_SyntaxError, args);
79
3.04k
        Py_DECREF(args);
80
3.04k
    }
81
82
3.04k
error:
83
3.04k
    Py_XDECREF(errmsg);
84
3.04k
    tok->done = E_ERROR;
85
3.04k
    return ERRORTOKEN;
86
3.04k
}
87
88
int
89
_PyTokenizer_syntaxerror(struct tok_state *tok, const char *format, ...)
90
1.19k
{
91
    // These errors are cleaned on startup. Todo: Fix it.
92
1.19k
    va_list vargs;
93
1.19k
    va_start(vargs, format);
94
1.19k
    int ret = _syntaxerror_range(
95
1.19k
        tok, _PyTok_SourcePointer(&tok->source, tok->line_start),
96
1.19k
        tok->cur - tok->line_start, tok->lineno, format, -1, -1, vargs);
97
1.19k
    va_end(vargs);
98
1.19k
    return ret;
99
1.19k
}
100
101
int
102
_PyTokenizer_syntaxerror_known_range(struct tok_state *tok,
103
                        int col_offset, int end_col_offset,
104
                        const char *format, ...)
105
62
{
106
62
    va_list vargs;
107
62
    va_start(vargs, format);
108
62
    int ret = _syntaxerror_range(
109
62
        tok, _PyTok_SourcePointer(&tok->source, tok->line_start),
110
62
        tok->cur - tok->line_start, tok->lineno,
111
62
        format, col_offset, end_col_offset, vargs);
112
62
    va_end(vargs);
113
62
    return ret;
114
62
}
115
116
int
117
_PyTokenizer_syntaxerror_at(struct tok_state *tok, const char *line,
118
                             Py_ssize_t cursor, int lineno,
119
                             int col_offset, int end_col_offset,
120
                             const char *format, ...)
121
1.79k
{
122
1.79k
    va_list vargs;
123
1.79k
    va_start(vargs, format);
124
1.79k
    int ret = _syntaxerror_range(tok, line, cursor, lineno, format,
125
1.79k
                                col_offset, end_col_offset, vargs);
126
1.79k
    va_end(vargs);
127
1.79k
    return ret;
128
1.79k
}
129
130
int
131
_PyTokenizer_indenterror(struct tok_state *tok)
132
3
{
133
3
    tok->done = E_TABSPACE;
134
3
    tok->cur = tok->inp;
135
3
    return ERRORTOKEN;
136
3
}
137
138
int
139
_PyTokenizer_warn_invalid_escape_sequence(struct tok_state *tok, int first_invalid_escape_char)
140
1.12k
{
141
1.12k
    PyObject *msg = PyUnicode_FromFormat(
142
1.12k
        "\"\\%c\" is an invalid escape sequence. "
143
1.12k
        "Such sequences will not work in the future. "
144
1.12k
        "Did you mean \"\\\\%c\"? A raw string is also an option.",
145
1.12k
        (char) first_invalid_escape_char,
146
1.12k
        (char) first_invalid_escape_char
147
1.12k
    );
148
149
1.12k
    if (msg == NULL) {
150
0
        return -1;
151
0
    }
152
153
1.12k
    if (PyErr_WarnExplicitObject(PyExc_SyntaxWarning, msg, tok->filename,
154
1.12k
                                 tok->lineno, tok->module, NULL) < 0) {
155
0
        Py_DECREF(msg);
156
157
0
        if (PyErr_ExceptionMatches(PyExc_SyntaxWarning)) {
158
            /* Replace the SyntaxWarning exception with a SyntaxError
159
               to get a more accurate error report */
160
0
            PyErr_Clear();
161
162
0
            return _PyTokenizer_syntaxerror(tok,
163
0
                "\"\\%c\" is an invalid escape sequence. "
164
0
                "Did you mean \"\\\\%c\"? A raw string is also an option.",
165
0
                (char) first_invalid_escape_char,
166
0
                (char) first_invalid_escape_char);
167
0
        }
168
169
0
        return -1;
170
0
    }
171
172
1.12k
    Py_DECREF(msg);
173
1.12k
    return 0;
174
1.12k
}
175
176
void
177
_PyTokenizer_raise_init_error(PyObject *filename)
178
2.52k
{
179
2.52k
    if (!(PyErr_ExceptionMatches(PyExc_LookupError)
180
2.39k
          || PyErr_ExceptionMatches(PyExc_SyntaxError)
181
1.84k
          || PyErr_ExceptionMatches(PyExc_ValueError)
182
53
          || PyErr_ExceptionMatches(PyExc_UnicodeDecodeError))) {
183
53
        return;
184
53
    }
185
2.47k
    PyObject *errstr = NULL;
186
2.47k
    PyObject *tuple = NULL;
187
2.47k
    PyObject *type;
188
2.47k
    PyObject *value;
189
2.47k
    PyObject *tback;
190
2.47k
    PyErr_Fetch(&type, &value, &tback);
191
2.47k
    if (PyErr_GivenExceptionMatches(value, PyExc_SyntaxError)) {
192
547
        if (PyObject_SetAttr(value, &_Py_ID(filename), filename)) {
193
0
            goto error;
194
0
        }
195
547
        PyErr_Restore(type, value, tback);
196
547
        return;
197
547
    }
198
1.92k
    errstr = PyObject_Str(value);
199
1.92k
    if (!errstr) {
200
0
        goto error;
201
0
    }
202
203
1.92k
    PyObject *tmp = Py_BuildValue("(OiiO)", filename, 0, -1, Py_None);
204
1.92k
    if (!tmp) {
205
0
        goto error;
206
0
    }
207
208
1.92k
    tuple = _PyTuple_FromPair(errstr, tmp);
209
1.92k
    Py_DECREF(tmp);
210
1.92k
    if (!tuple) {
211
0
        goto error;
212
0
    }
213
1.92k
    PyErr_SetObject(PyExc_SyntaxError, tuple);
214
215
1.92k
error:
216
1.92k
    Py_XDECREF(type);
217
1.92k
    Py_XDECREF(value);
218
1.92k
    Py_XDECREF(tback);
219
1.92k
    Py_XDECREF(errstr);
220
1.92k
    Py_XDECREF(tuple);
221
1.92k
}
222
223
int
224
_PyTokenizer_parser_warn(struct tok_state *tok, PyObject *category, const char *format, ...)
225
9.72k
{
226
9.72k
    PyObject *errmsg;
227
9.72k
    va_list vargs;
228
9.72k
    va_start(vargs, format);
229
9.72k
    errmsg = PyUnicode_FromFormatV(format, vargs);
230
9.72k
    va_end(vargs);
231
9.72k
    if (!errmsg) {
232
0
        goto error;
233
0
    }
234
235
9.72k
    if (PyErr_WarnExplicitObject(category, errmsg, tok->filename,
236
9.72k
                                 tok->lineno, tok->module, NULL) < 0) {
237
0
        if (PyErr_ExceptionMatches(category)) {
238
            /* Replace the DeprecationWarning exception with a SyntaxError
239
               to get a more accurate error report */
240
0
            PyErr_Clear();
241
0
            _PyTokenizer_syntaxerror(tok, "%U", errmsg);
242
0
        }
243
0
        goto error;
244
0
    }
245
9.72k
    Py_DECREF(errmsg);
246
9.72k
    return 0;
247
248
0
error:
249
0
    Py_XDECREF(errmsg);
250
0
    tok->done = E_ERROR;
251
0
    return -1;
252
9.72k
}
253
254
255
/* Check whether the characters at s start a valid
256
   UTF-8 sequence. Return the number of characters forming
257
   the sequence if yes, 0 if not.  The special cases match
258
   those in stringlib/codecs.h:utf8_decode.
259
*/
260
static int
261
valid_utf8(const unsigned char* s)
262
4.42M
{
263
4.42M
    int expected = 0;
264
4.42M
    int length;
265
4.42M
    if (*s < 0x80) {
266
        /* single-byte code */
267
4.41M
        return 1;
268
4.41M
    }
269
8.19k
    else if (*s < 0xE0) {
270
        /* \xC2\x80-\xDF\xBF -- 0080-07FF */
271
4.06k
        if (*s < 0xC2) {
272
            /* invalid sequence
273
               \x80-\xBF -- continuation byte
274
               \xC0-\xC1 -- fake 0000-007F */
275
145
            return 0;
276
145
        }
277
3.92k
        expected = 1;
278
3.92k
    }
279
4.13k
    else if (*s < 0xF0) {
280
        /* \xE0\xA0\x80-\xEF\xBF\xBF -- 0800-FFFF */
281
1.23k
        if (*s == 0xE0 && *(s + 1) < 0xA0) {
282
            /* invalid sequence
283
               \xE0\x80\x80-\xE0\x9F\xBF -- fake 0000-0800 */
284
15
            return 0;
285
15
        }
286
1.22k
        else if (*s == 0xED && *(s + 1) >= 0xA0) {
287
            /* Decoding UTF-8 sequences in range \xED\xA0\x80-\xED\xBF\xBF
288
               will result in surrogates in range D800-DFFF. Surrogates are
289
               not valid UTF-8 so they are rejected.
290
               See https://www.unicode.org/versions/Unicode5.2.0/ch03.pdf
291
               (table 3-7) and http://www.rfc-editor.org/rfc/rfc3629.txt */
292
18
            return 0;
293
18
        }
294
1.20k
        expected = 2;
295
1.20k
    }
296
2.89k
    else if (*s < 0xF5) {
297
        /* \xF0\x90\x80\x80-\xF4\x8F\xBF\xBF -- 10000-10FFFF */
298
2.86k
        if (*(s + 1) < 0x90 ? *s == 0xF0 : *s == 0xF4) {
299
            /* invalid sequence -- one of:
300
               \xF0\x80\x80\x80-\xF0\x8F\xBF\xBF -- fake 0000-FFFF
301
               \xF4\x90\x80\x80- -- 110000- overflow */
302
31
            return 0;
303
31
        }
304
2.83k
        expected = 3;
305
2.83k
    }
306
32
    else {
307
        /* invalid start byte */
308
32
        return 0;
309
32
    }
310
7.95k
    length = expected + 1;
311
22.2k
    for (int i = 1; i <= expected; i++) {
312
14.5k
        if (s[i] < 0x80 || s[i] >= 0xC0) {
313
300
            return 0;
314
300
        }
315
14.5k
    }
316
7.65k
    return length;
317
7.95k
}
318
319
int
320
_PyTokenizer_ensure_utf8(const char *line, struct tok_state *tok, int lineno)
321
17.2k
{
322
17.2k
    const char *badchar = NULL;
323
17.2k
    const char *c;
324
17.2k
    int length;
325
17.2k
    const char *line_start = line;
326
4.43M
    for (c = line; *c; c += length) {
327
4.42M
        if (!(length = valid_utf8((const unsigned char *)c))) {
328
541
            badchar = c;
329
541
            break;
330
541
        }
331
4.42M
        if (*c == '\n') {
332
198k
            lineno++;
333
198k
            line_start = c + 1;
334
198k
        }
335
4.42M
    }
336
17.2k
    if (badchar) {
337
541
        _PyTokenizer_syntaxerror_at(
338
541
                tok, line_start, badchar - line_start + 1, lineno,
339
541
                -1, -1,
340
541
                "Non-UTF-8 code starting with '\\x%.2x'"
341
541
                "%s%V on line %i, "
342
541
                "but no encoding declared; "
343
541
                "see https://peps.python.org/pep-0263/ for details",
344
541
                (unsigned char)*badchar,
345
541
                tok->filename ? " in file " : "", tok->filename, "",
346
541
                lineno);
347
541
        return 0;
348
541
    }
349
16.6k
    return 1;
350
17.2k
}
351
352
353
/* ############## DEBUGGING STUFF ############## */
354
355
#ifdef Py_DEBUG
356
void
357
_PyTokenizer_print_escape(FILE *f, const char *s, Py_ssize_t size)
358
{
359
    if (s == NULL) {
360
        fputs("NULL", f);
361
        return;
362
    }
363
    putc('"', f);
364
    while (size-- > 0) {
365
        unsigned char c = *s++;
366
        switch (c) {
367
            case '\n': fputs("\\n", f); break;
368
            case '\r': fputs("\\r", f); break;
369
            case '\t': fputs("\\t", f); break;
370
            case '\f': fputs("\\f", f); break;
371
            case '\'': fputs("\\'", f); break;
372
            case '"': fputs("\\\"", f); break;
373
            default:
374
                if (0x20 <= c && c <= 0x7f)
375
                    putc(c, f);
376
                else
377
                    fprintf(f, "\\x%02x", c);
378
        }
379
    }
380
    putc('"', f);
381
}
382
#endif