/src/cpython3/Parser/tokenizer/helpers.c
Line | Count | Source |
1 | | #include "Python.h" |
2 | | #include "errcode.h" |
3 | | #include "pycore_runtime.h" // _Py_ID() |
4 | | #include "pycore_token.h" |
5 | | #include "pycore_tuple.h" // _PyTuple_FromPair |
6 | | |
7 | | #include "../lexer/state.h" |
8 | | |
9 | | |
10 | | /* ############## ERRORS ############## */ |
11 | | |
12 | | static int |
13 | | _syntaxerror_range(struct tok_state *tok, const char *format, |
14 | | int col_offset, int end_col_offset, |
15 | | va_list vargs) |
16 | 3.27k | { |
17 | | // In release builds, we don't want to overwrite a previous error, but in debug builds we |
18 | | // want to fail if we are not doing it so we can fix it. |
19 | 3.27k | assert(tok->done != E_ERROR); |
20 | 3.27k | if (tok->done == E_ERROR) { |
21 | 0 | return ERRORTOKEN; |
22 | 0 | } |
23 | 3.27k | PyObject *errmsg, *errtext, *args; |
24 | 3.27k | errmsg = PyUnicode_FromFormatV(format, vargs); |
25 | 3.27k | if (!errmsg) { |
26 | 0 | goto error; |
27 | 0 | } |
28 | | |
29 | 3.27k | errtext = PyUnicode_DecodeUTF8(tok->line_start, tok->cur - tok->line_start, |
30 | 3.27k | "replace"); |
31 | 3.27k | if (!errtext) { |
32 | 0 | goto error; |
33 | 0 | } |
34 | | |
35 | 3.27k | if (col_offset == -1) { |
36 | 2.93k | col_offset = (int)PyUnicode_GET_LENGTH(errtext); |
37 | 2.93k | } |
38 | 3.27k | if (end_col_offset == -1) { |
39 | 2.93k | end_col_offset = col_offset; |
40 | 2.93k | } |
41 | | |
42 | 3.27k | Py_ssize_t line_len = strcspn(tok->line_start, "\n"); |
43 | 3.27k | if (line_len != tok->cur - tok->line_start) { |
44 | 2.70k | Py_DECREF(errtext); |
45 | 2.70k | errtext = PyUnicode_DecodeUTF8(tok->line_start, line_len, |
46 | 2.70k | "replace"); |
47 | 2.70k | } |
48 | 3.27k | if (!errtext) { |
49 | 0 | goto error; |
50 | 0 | } |
51 | | |
52 | 3.27k | args = Py_BuildValue("(O(OiiNii))", errmsg, |
53 | 3.27k | tok->filename ? tok->filename : Py_None, |
54 | 3.27k | tok->lineno, col_offset, errtext, |
55 | 3.27k | tok->lineno, end_col_offset); |
56 | 3.27k | if (args) { |
57 | 3.27k | PyErr_SetObject(PyExc_SyntaxError, args); |
58 | 3.27k | Py_DECREF(args); |
59 | 3.27k | } |
60 | | |
61 | 3.27k | error: |
62 | 3.27k | Py_XDECREF(errmsg); |
63 | 3.27k | tok->done = E_ERROR; |
64 | 3.27k | return ERRORTOKEN; |
65 | 3.27k | } |
66 | | |
67 | | int |
68 | | _PyTokenizer_syntaxerror(struct tok_state *tok, const char *format, ...) |
69 | 2.93k | { |
70 | | // These errors are cleaned on startup. Todo: Fix it. |
71 | 2.93k | va_list vargs; |
72 | 2.93k | va_start(vargs, format); |
73 | 2.93k | int ret = _syntaxerror_range(tok, format, -1, -1, vargs); |
74 | 2.93k | va_end(vargs); |
75 | 2.93k | return ret; |
76 | 2.93k | } |
77 | | |
78 | | int |
79 | | _PyTokenizer_syntaxerror_known_range(struct tok_state *tok, |
80 | | int col_offset, int end_col_offset, |
81 | | const char *format, ...) |
82 | 338 | { |
83 | 338 | va_list vargs; |
84 | 338 | va_start(vargs, format); |
85 | 338 | int ret = _syntaxerror_range(tok, format, col_offset, end_col_offset, vargs); |
86 | 338 | va_end(vargs); |
87 | 338 | return ret; |
88 | 338 | } |
89 | | |
90 | | int |
91 | | _PyTokenizer_indenterror(struct tok_state *tok) |
92 | 5 | { |
93 | 5 | tok->done = E_TABSPACE; |
94 | 5 | tok->cur = tok->inp; |
95 | 5 | return ERRORTOKEN; |
96 | 5 | } |
97 | | |
98 | | int |
99 | | _PyTokenizer_warn_invalid_escape_sequence(struct tok_state *tok, int first_invalid_escape_char) |
100 | 3.95k | { |
101 | 3.95k | if (!tok->report_warnings) { |
102 | 0 | return 0; |
103 | 0 | } |
104 | | |
105 | 3.95k | PyObject *msg = PyUnicode_FromFormat( |
106 | 3.95k | "\"\\%c\" is an invalid escape sequence. " |
107 | 3.95k | "Such sequences will not work in the future. " |
108 | 3.95k | "Did you mean \"\\\\%c\"? A raw string is also an option.", |
109 | 3.95k | (char) first_invalid_escape_char, |
110 | 3.95k | (char) first_invalid_escape_char |
111 | 3.95k | ); |
112 | | |
113 | 3.95k | if (msg == NULL) { |
114 | 0 | return -1; |
115 | 0 | } |
116 | | |
117 | 3.95k | if (PyErr_WarnExplicitObject(PyExc_SyntaxWarning, msg, tok->filename, |
118 | 3.95k | tok->lineno, tok->module, NULL) < 0) { |
119 | 0 | Py_DECREF(msg); |
120 | |
|
121 | 0 | if (PyErr_ExceptionMatches(PyExc_SyntaxWarning)) { |
122 | | /* Replace the SyntaxWarning exception with a SyntaxError |
123 | | to get a more accurate error report */ |
124 | 0 | PyErr_Clear(); |
125 | |
|
126 | 0 | return _PyTokenizer_syntaxerror(tok, |
127 | 0 | "\"\\%c\" is an invalid escape sequence. " |
128 | 0 | "Did you mean \"\\\\%c\"? A raw string is also an option.", |
129 | 0 | (char) first_invalid_escape_char, |
130 | 0 | (char) first_invalid_escape_char); |
131 | 0 | } |
132 | | |
133 | 0 | return -1; |
134 | 0 | } |
135 | | |
136 | 3.95k | Py_DECREF(msg); |
137 | 3.95k | return 0; |
138 | 3.95k | } |
139 | | |
140 | | void |
141 | | _PyTokenizer_raise_init_error(PyObject *filename) |
142 | 1.90k | { |
143 | 1.90k | if (!(PyErr_ExceptionMatches(PyExc_LookupError) |
144 | 1.67k | || PyErr_ExceptionMatches(PyExc_SyntaxError) |
145 | 1.43k | || PyErr_ExceptionMatches(PyExc_ValueError) |
146 | 44 | || PyErr_ExceptionMatches(PyExc_UnicodeDecodeError))) { |
147 | 44 | return; |
148 | 44 | } |
149 | 1.86k | PyObject *errstr = NULL; |
150 | 1.86k | PyObject *tuple = NULL; |
151 | 1.86k | PyObject *type; |
152 | 1.86k | PyObject *value; |
153 | 1.86k | PyObject *tback; |
154 | 1.86k | PyErr_Fetch(&type, &value, &tback); |
155 | 1.86k | if (PyErr_GivenExceptionMatches(value, PyExc_SyntaxError)) { |
156 | 240 | if (PyObject_SetAttr(value, &_Py_ID(filename), filename)) { |
157 | 0 | goto error; |
158 | 0 | } |
159 | 240 | PyErr_Restore(type, value, tback); |
160 | 240 | return; |
161 | 240 | } |
162 | 1.62k | errstr = PyObject_Str(value); |
163 | 1.62k | if (!errstr) { |
164 | 0 | goto error; |
165 | 0 | } |
166 | | |
167 | 1.62k | PyObject *tmp = Py_BuildValue("(OiiO)", filename, 0, -1, Py_None); |
168 | 1.62k | if (!tmp) { |
169 | 0 | goto error; |
170 | 0 | } |
171 | | |
172 | 1.62k | tuple = _PyTuple_FromPair(errstr, tmp); |
173 | 1.62k | Py_DECREF(tmp); |
174 | 1.62k | if (!tuple) { |
175 | 0 | goto error; |
176 | 0 | } |
177 | 1.62k | PyErr_SetObject(PyExc_SyntaxError, tuple); |
178 | | |
179 | 1.62k | error: |
180 | 1.62k | Py_XDECREF(type); |
181 | 1.62k | Py_XDECREF(value); |
182 | 1.62k | Py_XDECREF(tback); |
183 | 1.62k | Py_XDECREF(errstr); |
184 | 1.62k | Py_XDECREF(tuple); |
185 | 1.62k | } |
186 | | |
187 | | int |
188 | | _PyTokenizer_parser_warn(struct tok_state *tok, PyObject *category, const char *format, ...) |
189 | 31.7k | { |
190 | 31.7k | if (!tok->report_warnings) { |
191 | 0 | return 0; |
192 | 0 | } |
193 | | |
194 | 31.7k | PyObject *errmsg; |
195 | 31.7k | va_list vargs; |
196 | 31.7k | va_start(vargs, format); |
197 | 31.7k | errmsg = PyUnicode_FromFormatV(format, vargs); |
198 | 31.7k | va_end(vargs); |
199 | 31.7k | if (!errmsg) { |
200 | 0 | goto error; |
201 | 0 | } |
202 | | |
203 | 31.7k | if (PyErr_WarnExplicitObject(category, errmsg, tok->filename, |
204 | 31.7k | tok->lineno, tok->module, NULL) < 0) { |
205 | 0 | if (PyErr_ExceptionMatches(category)) { |
206 | | /* Replace the DeprecationWarning exception with a SyntaxError |
207 | | to get a more accurate error report */ |
208 | 0 | PyErr_Clear(); |
209 | 0 | _PyTokenizer_syntaxerror(tok, "%U", errmsg); |
210 | 0 | } |
211 | 0 | goto error; |
212 | 0 | } |
213 | 31.7k | Py_DECREF(errmsg); |
214 | 31.7k | return 0; |
215 | | |
216 | 0 | error: |
217 | 0 | Py_XDECREF(errmsg); |
218 | 0 | tok->done = E_ERROR; |
219 | 0 | return -1; |
220 | 31.7k | } |
221 | | |
222 | | |
223 | | /* Check whether the characters at s start a valid |
224 | | UTF-8 sequence. Return the number of characters forming |
225 | | the sequence if yes, 0 if not. The special cases match |
226 | | those in stringlib/codecs.h:utf8_decode. |
227 | | */ |
228 | | static int |
229 | | valid_utf8(const unsigned char* s) |
230 | 5.48M | { |
231 | 5.48M | int expected = 0; |
232 | 5.48M | int length; |
233 | 5.48M | if (*s < 0x80) { |
234 | | /* single-byte code */ |
235 | 5.46M | return 1; |
236 | 5.46M | } |
237 | 12.7k | else if (*s < 0xE0) { |
238 | | /* \xC2\x80-\xDF\xBF -- 0080-07FF */ |
239 | 8.90k | if (*s < 0xC2) { |
240 | | /* invalid sequence |
241 | | \x80-\xBF -- continuation byte |
242 | | \xC0-\xC1 -- fake 0000-007F */ |
243 | 95 | return 0; |
244 | 95 | } |
245 | 8.81k | expected = 1; |
246 | 8.81k | } |
247 | 3.81k | else if (*s < 0xF0) { |
248 | | /* \xE0\xA0\x80-\xEF\xBF\xBF -- 0800-FFFF */ |
249 | 1.98k | if (*s == 0xE0 && *(s + 1) < 0xA0) { |
250 | | /* invalid sequence |
251 | | \xE0\x80\x80-\xE0\x9F\xBF -- fake 0000-0800 */ |
252 | 9 | return 0; |
253 | 9 | } |
254 | 1.97k | else if (*s == 0xED && *(s + 1) >= 0xA0) { |
255 | | /* Decoding UTF-8 sequences in range \xED\xA0\x80-\xED\xBF\xBF |
256 | | will result in surrogates in range D800-DFFF. Surrogates are |
257 | | not valid UTF-8 so they are rejected. |
258 | | See https://www.unicode.org/versions/Unicode5.2.0/ch03.pdf |
259 | | (table 3-7) and http://www.rfc-editor.org/rfc/rfc3629.txt */ |
260 | 8 | return 0; |
261 | 8 | } |
262 | 1.96k | expected = 2; |
263 | 1.96k | } |
264 | 1.83k | else if (*s < 0xF5) { |
265 | | /* \xF0\x90\x80\x80-\xF4\x8F\xBF\xBF -- 10000-10FFFF */ |
266 | 1.82k | if (*(s + 1) < 0x90 ? *s == 0xF0 : *s == 0xF4) { |
267 | | /* invalid sequence -- one of: |
268 | | \xF0\x80\x80\x80-\xF0\x8F\xBF\xBF -- fake 0000-FFFF |
269 | | \xF4\x90\x80\x80- -- 110000- overflow */ |
270 | 13 | return 0; |
271 | 13 | } |
272 | 1.81k | expected = 3; |
273 | 1.81k | } |
274 | 13 | else { |
275 | | /* invalid start byte */ |
276 | 13 | return 0; |
277 | 13 | } |
278 | 12.5k | length = expected + 1; |
279 | 30.5k | for (int i = 1; i <= expected; i++) { |
280 | 18.0k | if (s[i] < 0x80 || s[i] >= 0xC0) { |
281 | 100 | return 0; |
282 | 100 | } |
283 | 18.0k | } |
284 | 12.4k | return length; |
285 | 12.5k | } |
286 | | |
287 | | int |
288 | | _PyTokenizer_ensure_utf8(const char *line, struct tok_state *tok, int lineno) |
289 | 8.70k | { |
290 | 8.70k | const char *badchar = NULL; |
291 | 8.70k | const char *c; |
292 | 8.70k | int length; |
293 | 8.70k | int col_offset = 0; |
294 | 8.70k | const char *line_start = line; |
295 | 5.49M | for (c = line; *c; c += length) { |
296 | 5.48M | if (!(length = valid_utf8((const unsigned char *)c))) { |
297 | 238 | badchar = c; |
298 | 238 | break; |
299 | 238 | } |
300 | 5.48M | col_offset++; |
301 | 5.48M | if (*c == '\n') { |
302 | 158k | lineno++; |
303 | 158k | col_offset = 0; |
304 | 158k | line_start = c + 1; |
305 | 158k | } |
306 | 5.48M | } |
307 | 8.70k | if (badchar) { |
308 | 238 | tok->lineno = lineno; |
309 | 238 | tok->line_start = line_start; |
310 | 238 | tok->cur = (char *)badchar; |
311 | 238 | _PyTokenizer_syntaxerror_known_range(tok, |
312 | 238 | col_offset + 1, col_offset + 1, |
313 | 238 | "Non-UTF-8 code starting with '\\x%.2x'" |
314 | 238 | "%s%V on line %i, " |
315 | 238 | "but no encoding declared; " |
316 | 238 | "see https://peps.python.org/pep-0263/ for details", |
317 | 238 | (unsigned char)*badchar, |
318 | 238 | tok->filename ? " in file " : "", tok->filename, "", |
319 | 238 | lineno); |
320 | 238 | return 0; |
321 | 238 | } |
322 | 8.46k | return 1; |
323 | 8.70k | } |
324 | | |
325 | | |
326 | | /* ############## DEBUGGING STUFF ############## */ |
327 | | |
328 | | #ifdef Py_DEBUG |
329 | | void |
330 | | _PyTokenizer_print_escape(FILE *f, const char *s, Py_ssize_t size) |
331 | | { |
332 | | if (s == NULL) { |
333 | | fputs("NULL", f); |
334 | | return; |
335 | | } |
336 | | putc('"', f); |
337 | | while (size-- > 0) { |
338 | | unsigned char c = *s++; |
339 | | switch (c) { |
340 | | case '\n': fputs("\\n", f); break; |
341 | | case '\r': fputs("\\r", f); break; |
342 | | case '\t': fputs("\\t", f); break; |
343 | | case '\f': fputs("\\f", f); break; |
344 | | case '\'': fputs("\\'", f); break; |
345 | | case '"': fputs("\\\"", f); break; |
346 | | default: |
347 | | if (0x20 <= c && c <= 0x7f) |
348 | | putc(c, f); |
349 | | else |
350 | | fprintf(f, "\\x%02x", c); |
351 | | } |
352 | | } |
353 | | putc('"', f); |
354 | | } |
355 | | |
356 | | void |
357 | | _PyTokenizer_tok_dump(int type, char *start, char *end) |
358 | | { |
359 | | fprintf(stderr, "%s", _PyParser_TokenNames[type]); |
360 | | if (type == NAME || type == NUMBER || type == STRING || type == OP) |
361 | | fprintf(stderr, "(%.*s)", (int)(end - start), start); |
362 | | } |
363 | | #endif |