Coverage Report

Created: 2026-10-17 06:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/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 "pegen.h"
8
#include "tokenizer/tokenizer.h"
9
10
// TOKENIZER ERRORS
11
12
static inline void
13
1.92k
raise_unclosed_parentheses_error(Parser *p) {
14
1.92k
       _PyTokenizer_Info info = _PyTokenizer_GetInfo(p->tok);
15
1.92k
       int error_lineno = info.delimiter_loc.lineno;
16
1.92k
       int error_col = info.delimiter_loc.byte_col;
17
1.92k
       RAISE_ERROR_KNOWN_LOCATION(p, PyExc_SyntaxError,
18
1.92k
                                  error_lineno, error_col, error_lineno, -1,
19
1.92k
                                  "'%c' was never closed",
20
1.92k
                                  info.delimiter);
21
1.92k
}
22
23
int
24
_Pypegen_tokenizer_error(Parser *p)
25
3.74k
{
26
3.74k
    if (PyErr_Occurred()) {
27
1.83k
        return -1;
28
1.83k
    }
29
30
1.91k
    const char *msg = NULL;
31
1.91k
    PyObject* errtype = PyExc_SyntaxError;
32
1.91k
    Py_ssize_t col_offset = -1;
33
1.91k
    p->error_indicator = 1;
34
1.91k
    _PyTokenizer_Info info = _PyTokenizer_GetInfo(p->tok);
35
1.91k
    switch (info.status) {
36
0
        case E_TOKEN:
37
0
            msg = "invalid token";
38
0
            break;
39
1.86k
        case E_EOF:
40
1.86k
            if (info.level) {
41
1.83k
                raise_unclosed_parentheses_error(p);
42
1.83k
            } else {
43
34
                RAISE_SYNTAX_ERROR("unexpected EOF while parsing");
44
34
            }
45
1.86k
            return -1;
46
6
        case E_DEDENT:
47
6
            RAISE_INDENTATION_ERROR("unindent does not match any outer indentation level");
48
6
            return -1;
49
0
        case E_INTR:
50
0
            if (!PyErr_Occurred()) {
51
0
                PyErr_SetNone(PyExc_KeyboardInterrupt);
52
0
            }
53
0
            return -1;
54
0
        case E_NOMEM:
55
0
            PyErr_NoMemory();
56
0
            return -1;
57
1
        case E_TABSPACE:
58
1
            errtype = PyExc_TabError;
59
1
            msg = "inconsistent use of tabs and spaces in indentation";
60
1
            break;
61
0
        case E_TOODEEP:
62
0
            errtype = PyExc_IndentationError;
63
0
            msg = "too many levels of indentation";
64
0
            break;
65
40
        case E_LINECONT: {
66
40
            col_offset = info.cursor - info.line_span.start - 1;
67
40
            msg = "unexpected character after line continuation character";
68
40
            break;
69
0
        }
70
0
        case E_COLUMNOVERFLOW:
71
0
            PyErr_SetString(PyExc_OverflowError,
72
0
                    "Parser column offset overflow - source line is too big");
73
0
            return -1;
74
0
        default:
75
0
            msg = "unknown parsing error";
76
1.91k
    }
77
78
41
    RAISE_ERROR_KNOWN_LOCATION(p, errtype, info.location.lineno,
79
41
                               col_offset >= 0 ? col_offset : 0,
80
41
                               info.location.lineno, -1, msg);
81
41
    return -1;
82
1.91k
}
83
84
int
85
_Pypegen_raise_decode_error(Parser *p)
86
125
{
87
125
    assert(PyErr_Occurred());
88
125
    const char *errtype = NULL;
89
125
    if (PyErr_ExceptionMatches(PyExc_UnicodeError)) {
90
112
        errtype = "unicode error";
91
112
    }
92
13
    else if (PyErr_ExceptionMatches(PyExc_ValueError)) {
93
5
        errtype = "value error";
94
5
    }
95
125
    if (errtype) {
96
117
        PyObject *type;
97
117
        PyObject *value;
98
117
        PyObject *tback;
99
117
        PyObject *errstr;
100
117
        PyErr_Fetch(&type, &value, &tback);
101
117
        errstr = PyObject_Str(value);
102
117
        if (errstr) {
103
117
            RAISE_SYNTAX_ERROR("(%s) %U", errtype, errstr);
104
117
            Py_DECREF(errstr);
105
117
        }
106
0
        else {
107
0
            PyErr_Clear();
108
0
            RAISE_SYNTAX_ERROR("(%s) unknown error", errtype);
109
0
        }
110
117
        Py_XDECREF(type);
111
117
        Py_XDECREF(value);
112
117
        Py_XDECREF(tback);
113
117
    }
114
115
125
    return -1;
116
125
}
117
118
static int
119
50.3k
_PyPegen_tokenize_full_source_to_check_for_errors(Parser *p) {
120
    // Tokenize the whole input to see if there are any tokenization
121
    // errors such as mismatching parentheses. These will get priority
122
    // over generic syntax errors only if the line number of the error is
123
    // before the one that we had for the generic error.
124
125
    // We don't want to tokenize to the end for interactive input
126
50.3k
    if (_PyTokenizer_IsInteractive(p->tok)) {
127
0
        return 0;
128
0
    }
129
130
50.3k
    PyObject *type, *value, *traceback;
131
50.3k
    PyErr_Fetch(&type, &value, &traceback);
132
133
50.3k
    Token *current_token = p->known_err_token != NULL ? p->known_err_token : p->tokens[p->fill - 1];
134
50.3k
    Py_ssize_t current_err_line = current_token->lineno;
135
136
50.3k
    int ret = 0;
137
50.3k
    struct token new_token;
138
50.3k
    _PyToken_Init(&new_token);
139
140
224k
    for (;;) {
141
224k
        _PyTokenizer_Get(p->tok, &new_token);
142
224k
        switch (new_token.type) {
143
2.92k
            case ERRORTOKEN: {
144
2.92k
                if (PyErr_Occurred()) {
145
663
                    ret = -1;
146
663
                    goto exit;
147
663
                }
148
2.26k
                _PyTokenizer_Info info = _PyTokenizer_GetInfo(p->tok);
149
2.26k
                if (info.level != 0) {
150
2.24k
                    int error_lineno = info.delimiter_loc.lineno;
151
2.24k
                    if (current_err_line > error_lineno) {
152
89
                        raise_unclosed_parentheses_error(p);
153
89
                        ret = -1;
154
89
                        goto exit;
155
89
                    }
156
2.24k
                }
157
2.17k
                break;
158
2.26k
            }
159
47.3k
            case ENDMARKER:
160
47.3k
                break;
161
173k
            default:
162
173k
                continue;
163
224k
        }
164
49.5k
        break;
165
224k
    }
166
167
168
50.3k
exit:
169
50.3k
    _PyToken_Free(&new_token);
170
50.3k
    _PyTokenizer_Info info = _PyTokenizer_GetInfo(p->tok);
171
    // Preserve expression errors over later formatted-string errors.
172
50.3k
    if (PyErr_Occurred() && !info.in_formatted_string) {
173
542
        Py_XDECREF(value);
174
542
        Py_XDECREF(type);
175
542
        Py_XDECREF(traceback);
176
49.7k
    } else {
177
49.7k
        PyErr_Restore(type, value, traceback);
178
49.7k
    }
179
50.3k
    return ret;
180
50.3k
}
181
182
// PARSER ERRORS
183
184
void *
185
_PyPegen_raise_error(Parser *p, PyObject *errtype, int use_mark, const char *errmsg, ...)
186
1.15k
{
187
    // Bail out if we already have an error set.
188
1.15k
    if (p->error_indicator && PyErr_Occurred()) {
189
256
        return NULL;
190
256
    }
191
896
    if (p->fill == 0) {
192
0
        va_list va;
193
0
        va_start(va, errmsg);
194
0
        _PyPegen_raise_error_known_location(p, errtype, 0, 0, 0, -1, errmsg, va);
195
0
        va_end(va);
196
0
        return NULL;
197
0
    }
198
896
    if (use_mark && p->mark == p->fill && _PyPegen_fill_token(p) < 0) {
199
0
        p->error_indicator = 1;
200
0
        return NULL;
201
0
    }
202
896
    Token *t = p->known_err_token != NULL
203
896
                   ? p->known_err_token
204
896
                   : p->tokens[use_mark ? p->mark : p->fill - 1];
205
896
    Py_ssize_t col_offset;
206
896
    Py_ssize_t end_col_offset = -1;
207
896
    if (t->col_offset == -1) {
208
204
        _PyTokenizer_Info info = _PyTokenizer_GetInfo(p->tok);
209
204
        if (info.diagnostic.location.lineno != 0) {
210
0
            col_offset = info.diagnostic.location.byte_col;
211
204
        } else if (info.cursor == info.input_span.start) {
212
3
            col_offset = 0;
213
201
        } else {
214
201
            col_offset = Py_SAFE_DOWNCAST(
215
201
                info.cursor - info.line_span.start, intptr_t, int);
216
201
        }
217
692
    } else {
218
692
        col_offset = t->col_offset + 1;
219
692
    }
220
221
896
    if (t->end_col_offset != -1) {
222
692
        end_col_offset = t->end_col_offset + 1;
223
692
    }
224
225
896
    va_list va;
226
896
    va_start(va, errmsg);
227
896
    _PyPegen_raise_error_known_location(p, errtype, t->lineno, col_offset, t->end_lineno, end_col_offset, errmsg, va);
228
896
    va_end(va);
229
230
896
    return NULL;
231
896
}
232
233
static PyObject *
234
get_error_line_from_source(Parser *p, Py_ssize_t lineno)
235
257
{
236
257
    if (_PyTokenizer_RetainedSource(p->tok) == NULL) {
237
0
        return Py_GetConstant(Py_CONSTANT_EMPTY_STR);
238
0
    }
239
257
    Py_ssize_t relative_lineno = p->starting_lineno ? lineno - p->starting_lineno + 1 : lineno;
240
257
    Py_ssize_t len;
241
257
    const char *line = _PyTokenizer_LineView(p->tok, relative_lineno, &len);
242
257
    return PyUnicode_DecodeUTF8(line, len, "replace");
243
257
}
244
245
void *
246
_PyPegen_raise_error_known_location(Parser *p, PyObject *errtype,
247
                                    Py_ssize_t lineno, Py_ssize_t col_offset,
248
                                    Py_ssize_t end_lineno, Py_ssize_t end_col_offset,
249
                                    const char *errmsg, va_list va)
250
53.1k
{
251
    // Bail out if we already have an error set.
252
53.1k
    if (p->error_indicator && PyErr_Occurred()) {
253
628
        return NULL;
254
628
    }
255
52.4k
    PyObject *value = NULL;
256
52.4k
    PyObject *errstr = NULL;
257
52.4k
    PyObject *error_line = NULL;
258
52.4k
    PyObject *tmp = NULL;
259
52.4k
    p->error_indicator = 1;
260
52.4k
    _PyTokenizer_Info info = _PyTokenizer_GetInfo(p->tok);
261
52.4k
    _PyTok_Loc location = info.diagnostic.location.lineno != 0
262
52.4k
        ? info.diagnostic.location : info.location;
263
52.4k
    _PyTok_Span text_span = info.diagnostic.location.lineno != 0
264
52.4k
        ? info.diagnostic.text_span : info.line_span;
265
266
52.4k
    if (end_lineno == CURRENT_POS) {
267
30
        end_lineno = location.lineno;
268
30
    }
269
52.4k
    if (end_col_offset == CURRENT_POS) {
270
30
        end_col_offset = location.byte_col;
271
30
    }
272
273
52.4k
    errstr = PyUnicode_FromFormatV(errmsg, va);
274
52.4k
    if (!errstr) {
275
0
        goto error;
276
0
    }
277
278
52.4k
    if (info.is_interactive && _PyTokenizer_RetainedSource(p->tok) != NULL) {
279
0
        error_line = get_error_line_from_source(p, lineno);
280
0
    }
281
52.4k
    else if (p->start_rule == Py_file_input) {
282
52.4k
        error_line = _PyErr_ProgramDecodedTextObject(info.filename,
283
52.4k
                                                     (int) lineno, info.encoding);
284
52.4k
    }
285
286
52.4k
    if (!error_line) {
287
        /* PyErr_ProgramTextObject was not called or returned NULL. If it was not called,
288
           then we need to find the error line from some other source, because
289
           p->start_rule != Py_file_input. If it returned NULL, then it either unexpectedly
290
           failed or we're parsing from a string or the REPL. There's a third edge case where
291
           we're actually parsing from a file, which has an E_EOF SyntaxError and in that case
292
           `PyErr_ProgramTextObject` fails because lineno points to last_file_line + 1, which
293
           does not physically exist */
294
52.4k
        assert(!info.is_file || info.status == E_EOF);
295
296
52.4k
        if (location.lineno <= lineno &&
297
52.2k
                info.input_span.end > info.input_span.start) {
298
52.2k
            Py_ssize_t size;
299
52.2k
            const char *line = _PyTokenizer_SpanView(
300
52.2k
                p->tok, text_span, &size);
301
52.2k
            error_line = PyUnicode_DecodeUTF8(line, size, "replace");
302
52.2k
        }
303
257
        else if (!info.is_file) {
304
257
            error_line = get_error_line_from_source(p, lineno);
305
257
        }
306
0
        else {
307
0
            error_line = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
308
0
        }
309
52.4k
        if (!error_line) {
310
0
            goto error;
311
0
        }
312
52.4k
    }
313
314
52.4k
    Py_ssize_t col_number = col_offset;
315
52.4k
    Py_ssize_t end_col_number = end_col_offset;
316
317
52.4k
    col_number = _PyPegen_byte_offset_to_character_offset(error_line, col_offset);
318
52.4k
    if (col_number < 0) {
319
0
        goto error;
320
0
    }
321
322
52.4k
    if (end_col_offset > 0) {
323
50.2k
        end_col_number = _PyPegen_byte_offset_to_character_offset(error_line, end_col_offset);
324
50.2k
        if (end_col_number < 0) {
325
0
            goto error;
326
0
        }
327
50.2k
    }
328
329
52.4k
    tmp = Py_BuildValue("(OnnNnn)", info.filename, lineno, col_number, error_line, end_lineno, end_col_number);
330
52.4k
    error_line = NULL;
331
52.4k
    if (!tmp) {
332
0
        goto error;
333
0
    }
334
52.4k
    value = _PyTuple_FromPair(errstr, tmp);
335
52.4k
    Py_DECREF(tmp);
336
52.4k
    if (!value) {
337
0
        goto error;
338
0
    }
339
52.4k
    PyErr_SetObject(errtype, value);
340
341
52.4k
    Py_DECREF(errstr);
342
52.4k
    Py_DECREF(value);
343
52.4k
    return NULL;
344
345
0
error:
346
0
    Py_XDECREF(errstr);
347
0
    Py_XDECREF(error_line);
348
0
    return NULL;
349
52.4k
}
350
351
void
352
54.2k
_Pypegen_set_syntax_error(Parser* p, Token* last_token) {
353
54.2k
    _PyTokenizer_Info info = _PyTokenizer_GetInfo(p->tok);
354
    // Existing syntax error
355
54.2k
    if (PyErr_Occurred()) {
356
        // Prioritize tokenizer errors to custom syntax errors raised
357
        // on the second phase only if the errors come from the parser.
358
5.75k
        int is_tok_ok = (info.status == E_DONE || info.status == E_OK);
359
5.75k
        if (is_tok_ok && PyErr_ExceptionMatches(PyExc_SyntaxError)) {
360
1.85k
            _PyPegen_tokenize_full_source_to_check_for_errors(p);
361
1.85k
        }
362
        // Propagate the existing syntax error.
363
5.75k
        return;
364
5.75k
    }
365
    // Initialization error
366
48.5k
    if (p->fill == 0) {
367
0
        RAISE_SYNTAX_ERROR("error at start before reading any input");
368
0
    }
369
    // Parser encountered EOF (End of File) unexpectedtly
370
48.5k
    if (last_token->type == ERRORTOKEN && info.status == E_EOF) {
371
0
        if (info.level) {
372
0
            raise_unclosed_parentheses_error(p);
373
0
        } else {
374
0
            RAISE_SYNTAX_ERROR("unexpected EOF while parsing");
375
0
        }
376
0
        return;
377
0
    }
378
    // Indentation error in the tokenizer
379
48.5k
    if (last_token->type == INDENT || last_token->type == DEDENT) {
380
57
        RAISE_INDENTATION_ERROR(last_token->type == INDENT ? "unexpected indent" : "unexpected unindent");
381
57
        return;
382
57
    }
383
    // Unknown error (generic case)
384
385
    // Use the last token we found on the first pass to avoid reporting
386
    // incorrect locations for generic syntax errors just because we reached
387
    // further away when trying to find specific syntax errors in the second
388
    // pass.
389
48.4k
    RAISE_SYNTAX_ERROR_KNOWN_LOCATION(last_token, "invalid syntax");
390
    // _PyPegen_tokenize_full_source_to_check_for_errors will override the existing
391
    // generic SyntaxError we just raised if errors are found.
392
48.4k
    _PyPegen_tokenize_full_source_to_check_for_errors(p);
393
48.4k
}
394
395
void
396
_Pypegen_stack_overflow(Parser *p)
397
53
{
398
53
    p->error_indicator = 1;
399
53
    PyErr_SetString(PyExc_MemoryError,
400
53
        "Parser stack overflowed - Python source too complex to parse");
401
53
}