Coverage Report

Created: 2026-10-31 06:21

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