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