Coverage Report

Created: 2026-09-01 06:32

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