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