Coverage Report

Created: 2026-09-03 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython3/Parser/pegen_errors.c
Line
Count
Source
1
#include <Python.h>
2
#include <errcode.h>
3
4
#include "pycore_pyerrors.h"      // _PyErr_ProgramDecodedTextObject()
5
#include "pycore_runtime.h"       // _Py_ID()
6
#include "pycore_tuple.h"         // _PyTuple_FromPair
7
#include "lexer/state.h"
8
#include "lexer/lexer.h"
9
#include "pegen.h"
10
11
// TOKENIZER ERRORS
12
13
static inline void
14
1.24k
raise_unclosed_parentheses_error(Parser *p) {
15
1.24k
       int error_lineno = p->tok->parenlinenostack[p->tok->level-1];
16
1.24k
       int error_col = p->tok->parencolstack[p->tok->level-1];
17
1.24k
       RAISE_ERROR_KNOWN_LOCATION(p, PyExc_SyntaxError,
18
1.24k
                                  error_lineno, error_col, error_lineno, -1,
19
1.24k
                                  "'%c' was never closed",
20
1.24k
                                  p->tok->parenstack[p->tok->level-1]);
21
1.24k
}
22
23
int
24
_Pypegen_tokenizer_error(Parser *p)
25
2.99k
{
26
2.99k
    if (PyErr_Occurred()) {
27
1.70k
        return -1;
28
1.70k
    }
29
30
1.28k
    const char *msg = NULL;
31
1.28k
    PyObject* errtype = PyExc_SyntaxError;
32
1.28k
    Py_ssize_t col_offset = -1;
33
1.28k
    p->error_indicator = 1;
34
1.28k
    switch (p->tok->done) {
35
0
        case E_TOKEN:
36
0
            msg = "invalid token";
37
0
            break;
38
1.16k
        case E_EOF:
39
1.16k
            if (p->tok->level) {
40
1.15k
                raise_unclosed_parentheses_error(p);
41
1.15k
            } else {
42
12
                RAISE_SYNTAX_ERROR("unexpected EOF while parsing");
43
12
            }
44
1.16k
            return -1;
45
1
        case E_DEDENT:
46
1
            RAISE_INDENTATION_ERROR("unindent does not match any outer indentation level");
47
1
            return -1;
48
0
        case E_INTR:
49
0
            if (!PyErr_Occurred()) {
50
0
                PyErr_SetNone(PyExc_KeyboardInterrupt);
51
0
            }
52
0
            return -1;
53
0
        case E_NOMEM:
54
0
            PyErr_NoMemory();
55
0
            return -1;
56
2
        case E_TABSPACE:
57
2
            errtype = PyExc_TabError;
58
2
            msg = "inconsistent use of tabs and spaces in indentation";
59
2
            break;
60
0
        case E_TOODEEP:
61
0
            errtype = PyExc_IndentationError;
62
0
            msg = "too many levels of indentation";
63
0
            break;
64
120
        case E_LINECONT: {
65
120
            col_offset = p->tok->cur - p->tok->buf - 1;
66
120
            msg = "unexpected character after line continuation character";
67
120
            break;
68
0
        }
69
0
        case E_COLUMNOVERFLOW:
70
0
            PyErr_SetString(PyExc_OverflowError,
71
0
                    "Parser column offset overflow - source line is too big");
72
0
            return -1;
73
0
        default:
74
0
            msg = "unknown parsing error";
75
1.28k
    }
76
77
122
    RAISE_ERROR_KNOWN_LOCATION(p, errtype, p->tok->lineno,
78
122
                               col_offset >= 0 ? col_offset : 0,
79
122
                               p->tok->lineno, -1, msg);
80
122
    return -1;
81
1.28k
}
82
83
int
84
_Pypegen_raise_decode_error(Parser *p)
85
881
{
86
881
    assert(PyErr_Occurred());
87
881
    const char *errtype = NULL;
88
881
    if (PyErr_ExceptionMatches(PyExc_UnicodeError)) {
89
861
        errtype = "unicode error";
90
861
    }
91
20
    else if (PyErr_ExceptionMatches(PyExc_ValueError)) {
92
8
        errtype = "value error";
93
8
    }
94
881
    if (errtype) {
95
869
        PyObject *type;
96
869
        PyObject *value;
97
869
        PyObject *tback;
98
869
        PyObject *errstr;
99
869
        PyErr_Fetch(&type, &value, &tback);
100
869
        errstr = PyObject_Str(value);
101
869
        if (errstr) {
102
869
            RAISE_SYNTAX_ERROR("(%s) %U", errtype, errstr);
103
869
            Py_DECREF(errstr);
104
869
        }
105
0
        else {
106
0
            PyErr_Clear();
107
0
            RAISE_SYNTAX_ERROR("(%s) unknown error", errtype);
108
0
        }
109
869
        Py_XDECREF(type);
110
869
        Py_XDECREF(value);
111
869
        Py_XDECREF(tback);
112
869
    }
113
114
881
    return -1;
115
881
}
116
117
static int
118
5.72k
_PyPegen_tokenize_full_source_to_check_for_errors(Parser *p) {
119
    // Tokenize the whole input to see if there are any tokenization
120
    // errors such as mismatching parentheses. These will get priority
121
    // over generic syntax errors only if the line number of the error is
122
    // before the one that we had for the generic error.
123
124
    // We don't want to tokenize to the end for interactive input
125
5.72k
    if (p->tok->prompt != NULL) {
126
0
        return 0;
127
0
    }
128
129
5.72k
    PyObject *type, *value, *traceback;
130
5.72k
    PyErr_Fetch(&type, &value, &traceback);
131
132
5.72k
    Token *current_token = p->known_err_token != NULL ? p->known_err_token : p->tokens[p->fill - 1];
133
5.72k
    Py_ssize_t current_err_line = current_token->lineno;
134
135
5.72k
    int ret = 0;
136
5.72k
    struct token new_token;
137
5.72k
    _PyToken_Init(&new_token);
138
139
178k
    for (;;) {
140
178k
        switch (_PyTokenizer_Get(p->tok, &new_token)) {
141
3.26k
            case ERRORTOKEN:
142
3.26k
                if (PyErr_Occurred()) {
143
1.62k
                    ret = -1;
144
1.62k
                    goto exit;
145
1.62k
                }
146
1.64k
                if (p->tok->level != 0) {
147
1.57k
                    int error_lineno = p->tok->parenlinenostack[p->tok->level-1];
148
1.57k
                    if (current_err_line > error_lineno) {
149
98
                        raise_unclosed_parentheses_error(p);
150
98
                        ret = -1;
151
98
                        goto exit;
152
98
                    }
153
1.57k
                }
154
1.54k
                break;
155
2.46k
            case ENDMARKER:
156
2.46k
                break;
157
172k
            default:
158
172k
                continue;
159
178k
        }
160
4.00k
        break;
161
178k
    }
162
163
164
5.72k
exit:
165
5.72k
    _PyToken_Free(&new_token);
166
    // If we're in an f-string, we want the syntax error in the expression part
167
    // to propagate, so that tokenizer errors (like expecting '}') that happen afterwards
168
    // do not swallow it.
169
5.72k
    if (PyErr_Occurred() && p->tok->tok_mode_stack_index <= 0) {
170
1.48k
        Py_XDECREF(value);
171
1.48k
        Py_XDECREF(type);
172
1.48k
        Py_XDECREF(traceback);
173
4.24k
    } else {
174
4.24k
        PyErr_Restore(type, value, traceback);
175
4.24k
    }
176
5.72k
    return ret;
177
5.72k
}
178
179
// PARSER ERRORS
180
181
void *
182
_PyPegen_raise_error(Parser *p, PyObject *errtype, int use_mark, const char *errmsg, ...)
183
2.46k
{
184
    // Bail out if we already have an error set.
185
2.46k
    if (p->error_indicator && PyErr_Occurred()) {
186
161
        return NULL;
187
161
    }
188
2.30k
    if (p->fill == 0) {
189
0
        va_list va;
190
0
        va_start(va, errmsg);
191
0
        _PyPegen_raise_error_known_location(p, errtype, 0, 0, 0, -1, errmsg, va);
192
0
        va_end(va);
193
0
        return NULL;
194
0
    }
195
2.30k
    if (use_mark && p->mark == p->fill && _PyPegen_fill_token(p) < 0) {
196
0
        p->error_indicator = 1;
197
0
        return NULL;
198
0
    }
199
2.30k
    Token *t = p->known_err_token != NULL
200
2.30k
                   ? p->known_err_token
201
2.30k
                   : p->tokens[use_mark ? p->mark : p->fill - 1];
202
2.30k
    Py_ssize_t col_offset;
203
2.30k
    Py_ssize_t end_col_offset = -1;
204
2.30k
    if (t->col_offset == -1) {
205
1.45k
        if (p->tok->cur == p->tok->buf) {
206
8
            col_offset = 0;
207
1.45k
        } else {
208
1.45k
            const char* start = p->tok->buf  ? p->tok->line_start : p->tok->buf;
209
1.45k
            col_offset = Py_SAFE_DOWNCAST(p->tok->cur - start, intptr_t, int);
210
1.45k
        }
211
1.45k
    } else {
212
845
        col_offset = t->col_offset + 1;
213
845
    }
214
215
2.30k
    if (t->end_col_offset != -1) {
216
845
        end_col_offset = t->end_col_offset + 1;
217
845
    }
218
219
2.30k
    va_list va;
220
2.30k
    va_start(va, errmsg);
221
2.30k
    _PyPegen_raise_error_known_location(p, errtype, t->lineno, col_offset, t->end_lineno, end_col_offset, errmsg, va);
222
2.30k
    va_end(va);
223
224
2.30k
    return NULL;
225
2.30k
}
226
227
static PyObject *
228
get_error_line_from_tokenizer_buffers(Parser *p, Py_ssize_t lineno)
229
295
{
230
    /* If the file descriptor is interactive, the source lines of the current
231
     * (multi-line) statement are stored in p->tok->interactive_src_start.
232
     * If not, we're parsing from a string, which means that the whole source
233
     * is stored in p->tok->str. */
234
295
    assert((p->tok->fp == NULL && p->tok->str != NULL) || p->tok->fp != NULL);
235
236
295
    char *cur_line = p->tok->fp_interactive ? p->tok->interactive_src_start : p->tok->str;
237
295
    if (cur_line == NULL) {
238
0
        assert(p->tok->fp_interactive);
239
        // We can reach this point if the tokenizer buffers for interactive source have not been
240
        // initialized because we failed to decode the original source with the given locale.
241
0
        return Py_GetConstant(Py_CONSTANT_EMPTY_STR);
242
0
    }
243
244
295
    Py_ssize_t relative_lineno = p->starting_lineno ? lineno - p->starting_lineno + 1 : lineno;
245
295
    const char* buf_end = p->tok->fp_interactive ? p->tok->interactive_src_end : p->tok->inp;
246
247
295
    if (buf_end < cur_line) {
248
0
        buf_end = cur_line + strlen(cur_line);
249
0
    }
250
251
7.88k
    for (int i = 0; i < relative_lineno - 1; i++) {
252
7.58k
        char *new_line = strchr(cur_line, '\n');
253
        // The assert is here for debug builds but the conditional that
254
        // follows is there so in release builds we do not crash at the cost
255
        // to report a potentially wrong line.
256
7.58k
        assert(new_line != NULL && new_line + 1 < buf_end);
257
7.58k
        if (new_line == NULL || new_line + 1 > buf_end) {
258
0
            break;
259
0
        }
260
7.58k
        cur_line = new_line + 1;
261
7.58k
    }
262
263
295
    char *next_newline;
264
295
    if ((next_newline = strchr(cur_line, '\n')) == NULL) { // This is the last line
265
6
        next_newline = cur_line + strlen(cur_line);
266
6
    }
267
295
    return PyUnicode_DecodeUTF8(cur_line, next_newline - cur_line, "replace");
268
295
}
269
270
void *
271
_PyPegen_raise_error_known_location(Parser *p, PyObject *errtype,
272
                                    Py_ssize_t lineno, Py_ssize_t col_offset,
273
                                    Py_ssize_t end_lineno, Py_ssize_t end_col_offset,
274
                                    const char *errmsg, va_list va)
275
9.82k
{
276
    // Bail out if we already have an error set.
277
9.82k
    if (p->error_indicator && PyErr_Occurred()) {
278
1.02k
        return NULL;
279
1.02k
    }
280
8.80k
    PyObject *value = NULL;
281
8.80k
    PyObject *errstr = NULL;
282
8.80k
    PyObject *error_line = NULL;
283
8.80k
    PyObject *tmp = NULL;
284
8.80k
    p->error_indicator = 1;
285
286
8.80k
    if (end_lineno == CURRENT_POS) {
287
29
        end_lineno = p->tok->lineno;
288
29
    }
289
8.80k
    if (end_col_offset == CURRENT_POS) {
290
29
        end_col_offset = p->tok->cur - p->tok->line_start;
291
29
    }
292
293
8.80k
    errstr = PyUnicode_FromFormatV(errmsg, va);
294
8.80k
    if (!errstr) {
295
0
        goto error;
296
0
    }
297
298
8.80k
    if (p->tok->fp_interactive && p->tok->interactive_src_start != NULL) {
299
0
        error_line = get_error_line_from_tokenizer_buffers(p, lineno);
300
0
    }
301
8.80k
    else if (p->start_rule == Py_file_input) {
302
4.72k
        error_line = _PyErr_ProgramDecodedTextObject(p->tok->filename,
303
4.72k
                                                     (int) lineno, p->tok->encoding);
304
4.72k
    }
305
306
8.80k
    if (!error_line) {
307
        /* PyErr_ProgramTextObject was not called or returned NULL. If it was not called,
308
           then we need to find the error line from some other source, because
309
           p->start_rule != Py_file_input. If it returned NULL, then it either unexpectedly
310
           failed or we're parsing from a string or the REPL. There's a third edge case where
311
           we're actually parsing from a file, which has an E_EOF SyntaxError and in that case
312
           `PyErr_ProgramTextObject` fails because lineno points to last_file_line + 1, which
313
           does not physically exist */
314
8.80k
        assert(p->tok->fp == NULL || p->tok->fp == stdin || p->tok->done == E_EOF);
315
316
8.80k
        if (p->tok->lineno <= lineno && p->tok->inp > p->tok->buf) {
317
8.50k
            Py_ssize_t size = p->tok->inp - p->tok->line_start;
318
8.50k
            error_line = PyUnicode_DecodeUTF8(p->tok->line_start, size, "replace");
319
8.50k
        }
320
295
        else if (p->tok->fp == NULL || p->tok->fp == stdin) {
321
295
            error_line = get_error_line_from_tokenizer_buffers(p, lineno);
322
295
        }
323
0
        else {
324
0
            error_line = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
325
0
        }
326
8.80k
        if (!error_line) {
327
0
            goto error;
328
0
        }
329
8.80k
    }
330
331
8.80k
    Py_ssize_t col_number = col_offset;
332
8.80k
    Py_ssize_t end_col_number = end_col_offset;
333
334
8.80k
    col_number = _PyPegen_byte_offset_to_character_offset(error_line, col_offset);
335
8.80k
    if (col_number < 0) {
336
0
        goto error;
337
0
    }
338
339
8.80k
    if (end_col_offset > 0) {
340
5.90k
        end_col_number = _PyPegen_byte_offset_to_character_offset(error_line, end_col_offset);
341
5.90k
        if (end_col_number < 0) {
342
0
            goto error;
343
0
        }
344
5.90k
    }
345
346
8.80k
    tmp = Py_BuildValue("(OnnNnn)", p->tok->filename, lineno, col_number, error_line, end_lineno, end_col_number);
347
8.80k
    if (!tmp) {
348
0
        goto error;
349
0
    }
350
8.80k
    value = _PyTuple_FromPair(errstr, tmp);
351
8.80k
    Py_DECREF(tmp);
352
8.80k
    if (!value) {
353
0
        goto error;
354
0
    }
355
8.80k
    PyErr_SetObject(errtype, value);
356
357
8.80k
    Py_DECREF(errstr);
358
8.80k
    Py_DECREF(value);
359
8.80k
    return NULL;
360
361
0
error:
362
0
    Py_XDECREF(errstr);
363
0
    Py_XDECREF(error_line);
364
0
    return NULL;
365
8.80k
}
366
367
void
368
9.07k
_Pypegen_set_syntax_error(Parser* p, Token* last_token) {
369
    // Existing syntax error
370
9.07k
    if (PyErr_Occurred()) {
371
        // Prioritize tokenizer errors to custom syntax errors raised
372
        // on the second phase only if the errors come from the parser.
373
4.95k
        int is_tok_ok = (p->tok->done == E_DONE || p->tok->done == E_OK);
374
4.95k
        if (is_tok_ok && PyErr_ExceptionMatches(PyExc_SyntaxError)) {
375
1.67k
            _PyPegen_tokenize_full_source_to_check_for_errors(p);
376
1.67k
        }
377
        // Propagate the existing syntax error.
378
4.95k
        return;
379
4.95k
    }
380
    // Initialization error
381
4.12k
    if (p->fill == 0) {
382
0
        RAISE_SYNTAX_ERROR("error at start before reading any input");
383
0
    }
384
    // Parser encountered EOF (End of File) unexpectedtly
385
4.12k
    if (last_token->type == ERRORTOKEN && p->tok->done == E_EOF) {
386
0
        if (p->tok->level) {
387
0
            raise_unclosed_parentheses_error(p);
388
0
        } else {
389
0
            RAISE_SYNTAX_ERROR("unexpected EOF while parsing");
390
0
        }
391
0
        return;
392
0
    }
393
    // Indentation error in the tokenizer
394
4.12k
    if (last_token->type == INDENT || last_token->type == DEDENT) {
395
65
        RAISE_INDENTATION_ERROR(last_token->type == INDENT ? "unexpected indent" : "unexpected unindent");
396
65
        return;
397
65
    }
398
    // Unknown error (generic case)
399
400
    // Use the last token we found on the first pass to avoid reporting
401
    // incorrect locations for generic syntax errors just because we reached
402
    // further away when trying to find specific syntax errors in the second
403
    // pass.
404
4.05k
    RAISE_SYNTAX_ERROR_KNOWN_LOCATION(last_token, "invalid syntax");
405
    // _PyPegen_tokenize_full_source_to_check_for_errors will override the existing
406
    // generic SyntaxError we just raised if errors are found.
407
4.05k
    _PyPegen_tokenize_full_source_to_check_for_errors(p);
408
4.05k
}
409
410
void
411
_Pypegen_stack_overflow(Parser *p)
412
54
{
413
54
    p->error_indicator = 1;
414
54
    PyErr_SetString(PyExc_MemoryError,
415
54
        "Parser stack overflowed - Python source too complex to parse");
416
54
}