/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 | } |