GeezerVison is showing github.com — gh-81548: Deprecate octal escape sequences with value larger than 0o3… · python/cpython@3483299
File treeExpand file treeCollapse file tree Open diff view settings Misc/NEWS.d/next/Core and Builtins
Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: Doc/reference/lexical_analysis.rst +5Lines changed: 5 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -596,6 +596,11 @@ Notes: | 596 | 596 | (1) | 597 | 597 | As in Standard C, up to three octal digits are accepted. | 598 | 598 |
| | 599 | + .. versionchanged:: 3.11 | | 600 | + Octal escapes with value larger than ``0o377`` produce a :exc:`DeprecationWarning`. | | 601 | + In a future Python version they will be a :exc:`SyntaxWarning` and | | 602 | + eventually a :exc:`SyntaxError`. | | 603 | + | 599 | 604 | (2) | 600 | 605 | Unlike in Standard C, exactly two hex digits are required. | 601 | 606 |
| |
Collapse file Copy file name to clipboardExpand all lines: Doc/whatsnew/3.11.rst +6Lines changed: 6 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1055,6 +1055,12 @@ CPython bytecode changes | 1055 | 1055 | Deprecated | 1056 | 1056 | ========== | 1057 | 1057 |
| | 1058 | +* Octal escapes with value larger than ``0o377`` now produce | | 1059 | + a :exc:`DeprecationWarning`. | | 1060 | + In a future Python version they will be a :exc:`SyntaxWarning` and | | 1061 | + eventually a :exc:`SyntaxError`. | | 1062 | + (Contributed by Serhiy Storchaka in :issue:`81548`.) | | 1063 | + | 1058 | 1064 | * The :mod:`lib2to3` package and ``2to3`` tool are now deprecated and may not | 1059 | 1065 | be able to parse Python 3.10 or newer. See the :pep:`617` (New PEG parser for | 1060 | 1066 | CPython). (Contributed by Victor Stinner in :issue:`40360`.) | |
Collapse file Copy file name to clipboardExpand all lines: Lib/test/test_codecs.py +6-1Lines changed: 6 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -1193,7 +1193,6 @@ def test_escape(self): | 1193 | 1193 | check(br"[\418]", b"[!8]") | 1194 | 1194 | check(br"[\101]", b"[A]") | 1195 | 1195 | check(br"[\1010]", b"[A0]") | 1196 | | - check(br"[\501]", b"[A]") | 1197 | 1196 | check(br"[\x41]", b"[A]") | 1198 | 1197 | check(br"[\x410]", b"[A0]") | 1199 | 1198 | for i in range(97, 123): | @@ -1209,6 +1208,9 @@ def test_escape(self): | 1209 | 1208 | check(br"\9", b"\\9") | 1210 | 1209 | with self.assertWarns(DeprecationWarning): | 1211 | 1210 | check(b"\\\xfa", b"\\\xfa") | | 1211 | + for i in range(0o400, 0o1000): | | 1212 | + with self.assertWarns(DeprecationWarning): | | 1213 | + check(rb'\%o' % i, bytes([i & 0o377])) | 1212 | 1214 |
| 1213 | 1215 | def test_errors(self): | 1214 | 1216 | decode = codecs.escape_decode | @@ -2435,6 +2437,9 @@ def test_escape_decode(self): | 2435 | 2437 | check(br"\9", "\\9") | 2436 | 2438 | with self.assertWarns(DeprecationWarning): | 2437 | 2439 | check(b"\\\xfa", "\\\xfa") | | 2440 | + for i in range(0o400, 0o1000): | | 2441 | + with self.assertWarns(DeprecationWarning): | | 2442 | + check(rb'\%o' % i, chr(i)) | 2438 | 2443 |
| 2439 | 2444 | def test_decode_errors(self): | 2440 | 2445 | decode = codecs.unicode_escape_decode | |
Collapse file Copy file name to clipboardExpand all lines: Lib/test/test_string_literals.py +53Lines changed: 53 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -116,6 +116,7 @@ def test_eval_str_invalid_escape(self): | 116 | 116 | warnings.simplefilter('always', category=DeprecationWarning) | 117 | 117 | eval("'''\n\\z'''") | 118 | 118 | self.assertEqual(len(w), 1) | | 119 | + self.assertEqual(str(w[0].message), r"invalid escape sequence '\z'") | 119 | 120 | self.assertEqual(w[0].filename, '<string>') | 120 | 121 | self.assertEqual(w[0].lineno, 1) | 121 | 122 |
| @@ -125,6 +126,32 @@ def test_eval_str_invalid_escape(self): | 125 | 126 | eval("'''\n\\z'''") | 126 | 127 | exc = cm.exception | 127 | 128 | self.assertEqual(w, []) | | 129 | + self.assertEqual(exc.msg, r"invalid escape sequence '\z'") | | 130 | + self.assertEqual(exc.filename, '<string>') | | 131 | + self.assertEqual(exc.lineno, 1) | | 132 | + self.assertEqual(exc.offset, 1) | | 133 | + | | 134 | + def test_eval_str_invalid_octal_escape(self): | | 135 | + for i in range(0o400, 0o1000): | | 136 | + with self.assertWarns(DeprecationWarning): | | 137 | + self.assertEqual(eval(r"'\%o'" % i), chr(i)) | | 138 | + | | 139 | + with warnings.catch_warnings(record=True) as w: | | 140 | + warnings.simplefilter('always', category=DeprecationWarning) | | 141 | + eval("'''\n\\407'''") | | 142 | + self.assertEqual(len(w), 1) | | 143 | + self.assertEqual(str(w[0].message), | | 144 | + r"invalid octal escape sequence '\407'") | | 145 | + self.assertEqual(w[0].filename, '<string>') | | 146 | + self.assertEqual(w[0].lineno, 1) | | 147 | + | | 148 | + with warnings.catch_warnings(record=True) as w: | | 149 | + warnings.simplefilter('error', category=DeprecationWarning) | | 150 | + with self.assertRaises(SyntaxError) as cm: | | 151 | + eval("'''\n\\407'''") | | 152 | + exc = cm.exception | | 153 | + self.assertEqual(w, []) | | 154 | + self.assertEqual(exc.msg, r"invalid octal escape sequence '\407'") | 128 | 155 | self.assertEqual(exc.filename, '<string>') | 129 | 156 | self.assertEqual(exc.lineno, 1) | 130 | 157 | self.assertEqual(exc.offset, 1) | @@ -166,6 +193,7 @@ def test_eval_bytes_invalid_escape(self): | 166 | 193 | warnings.simplefilter('always', category=DeprecationWarning) | 167 | 194 | eval("b'''\n\\z'''") | 168 | 195 | self.assertEqual(len(w), 1) | | 196 | + self.assertEqual(str(w[0].message), r"invalid escape sequence '\z'") | 169 | 197 | self.assertEqual(w[0].filename, '<string>') | 170 | 198 | self.assertEqual(w[0].lineno, 1) | 171 | 199 |
| @@ -175,6 +203,31 @@ def test_eval_bytes_invalid_escape(self): | 175 | 203 | eval("b'''\n\\z'''") | 176 | 204 | exc = cm.exception | 177 | 205 | self.assertEqual(w, []) | | 206 | + self.assertEqual(exc.msg, r"invalid escape sequence '\z'") | | 207 | + self.assertEqual(exc.filename, '<string>') | | 208 | + self.assertEqual(exc.lineno, 1) | | 209 | + | | 210 | + def test_eval_bytes_invalid_octal_escape(self): | | 211 | + for i in range(0o400, 0o1000): | | 212 | + with self.assertWarns(DeprecationWarning): | | 213 | + self.assertEqual(eval(r"b'\%o'" % i), bytes([i & 0o377])) | | 214 | + | | 215 | + with warnings.catch_warnings(record=True) as w: | | 216 | + warnings.simplefilter('always', category=DeprecationWarning) | | 217 | + eval("b'''\n\\407'''") | | 218 | + self.assertEqual(len(w), 1) | | 219 | + self.assertEqual(str(w[0].message), | | 220 | + r"invalid octal escape sequence '\407'") | | 221 | + self.assertEqual(w[0].filename, '<string>') | | 222 | + self.assertEqual(w[0].lineno, 1) | | 223 | + | | 224 | + with warnings.catch_warnings(record=True) as w: | | 225 | + warnings.simplefilter('error', category=DeprecationWarning) | | 226 | + with self.assertRaises(SyntaxError) as cm: | | 227 | + eval("b'''\n\\407'''") | | 228 | + exc = cm.exception | | 229 | + self.assertEqual(w, []) | | 230 | + self.assertEqual(exc.msg, r"invalid octal escape sequence '\407'") | 178 | 231 | self.assertEqual(exc.filename, '<string>') | 179 | 232 | self.assertEqual(exc.lineno, 1) | 180 | 233 |
| |
Collapse file Copy file name to clipboard +3Lines changed: 3 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +Octal escapes with value larger than ``0o377`` now produce a | | 2 | +:exc:`DeprecationWarning`. In a future Python version they will be a | | 3 | +:exc:`SyntaxWarning` and eventually a :exc:`SyntaxError`. |
Collapse file Copy file name to clipboardExpand all lines: Objects/bytesobject.c +24-5Lines changed: 24 additions & 5 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1113,6 +1113,12 @@ PyObject *_PyBytes_DecodeEscape(const char *s, | 1113 | 1113 | if (s < end && '0' <= *s && *s <= '7') | 1114 | 1114 | c = (c<<3) + *s++ - '0'; | 1115 | 1115 | } | | 1116 | + if (c > 0377) { | | 1117 | + if (*first_invalid_escape == NULL) { | | 1118 | + *first_invalid_escape = s-3; /* Back up 3 chars, since we've | | 1119 | + already incremented s. */ | | 1120 | + } | | 1121 | + } | 1116 | 1122 | *p++ = c; | 1117 | 1123 | break; | 1118 | 1124 | case 'x': | @@ -1179,11 +1185,24 @@ PyObject *PyBytes_DecodeEscape(const char *s, | 1179 | 1185 | if (result == NULL) | 1180 | 1186 | return NULL; | 1181 | 1187 | if (first_invalid_escape != NULL) { | 1182 | | - if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, | 1183 | | - "invalid escape sequence '\\%c'", | 1184 | | - (unsigned char)*first_invalid_escape) < 0) { | 1185 | | - Py_DECREF(result); | 1186 | | - return NULL; | | 1188 | + unsigned char c = *first_invalid_escape; | | 1189 | + if ('4' <= c && c <= '7') { | | 1190 | + if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, | | 1191 | + "invalid octal escape sequence '\\%.3s'", | | 1192 | + first_invalid_escape) < 0) | | 1193 | + { | | 1194 | + Py_DECREF(result); | | 1195 | + return NULL; | | 1196 | + } | | 1197 | + } | | 1198 | + else { | | 1199 | + if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, | | 1200 | + "invalid escape sequence '\\%c'", | | 1201 | + c) < 0) | | 1202 | + { | | 1203 | + Py_DECREF(result); | | 1204 | + return NULL; | | 1205 | + } | 1187 | 1206 | } | 1188 | 1207 | } | 1189 | 1208 | return result; | |
Collapse file Copy file name to clipboardExpand all lines: Objects/unicodeobject.c +24-5Lines changed: 24 additions & 5 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -6404,6 +6404,12 @@ _PyUnicode_DecodeUnicodeEscapeInternal(const char *s, | 6404 | 6404 | ch = (ch<<3) + *s++ - '0'; | 6405 | 6405 | } | 6406 | 6406 | } | | 6407 | + if (ch > 0377) { | | 6408 | + if (*first_invalid_escape == NULL) { | | 6409 | + *first_invalid_escape = s-3; /* Back up 3 chars, since we've | | 6410 | + already incremented s. */ | | 6411 | + } | | 6412 | + } | 6407 | 6413 | WRITE_CHAR(ch); | 6408 | 6414 | continue; | 6409 | 6415 |
| @@ -6554,11 +6560,24 @@ _PyUnicode_DecodeUnicodeEscapeStateful(const char *s, | 6554 | 6560 | if (result == NULL) | 6555 | 6561 | return NULL; | 6556 | 6562 | if (first_invalid_escape != NULL) { | 6557 | | - if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, | 6558 | | - "invalid escape sequence '\\%c'", | 6559 | | - (unsigned char)*first_invalid_escape) < 0) { | 6560 | | - Py_DECREF(result); | 6561 | | - return NULL; | | 6563 | + unsigned char c = *first_invalid_escape; | | 6564 | + if ('4' <= c && c <= '7') { | | 6565 | + if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, | | 6566 | + "invalid octal escape sequence '\\%.3s'", | | 6567 | + first_invalid_escape) < 0) | | 6568 | + { | | 6569 | + Py_DECREF(result); | | 6570 | + return NULL; | | 6571 | + } | | 6572 | + } | | 6573 | + else { | | 6574 | + if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, | | 6575 | + "invalid escape sequence '\\%c'", | | 6576 | + c) < 0) | | 6577 | + { | | 6578 | + Py_DECREF(result); | | 6579 | + return NULL; | | 6580 | + } | 6562 | 6581 | } | 6563 | 6582 | } | 6564 | 6583 | return result; | |
Collapse file Copy file name to clipboardExpand all lines: Parser/string_parser.c +18-7Lines changed: 18 additions & 7 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 9 | 9 | //// STRING HANDLING FUNCTIONS //// | 10 | 10 |
| 11 | 11 | static int | 12 | | -warn_invalid_escape_sequence(Parser *p, unsigned char first_invalid_escape_char, Token *t) | | 12 | +warn_invalid_escape_sequence(Parser *p, const char *first_invalid_escape, Token *t) | 13 | 13 | { | | 14 | + unsigned char c = *first_invalid_escape; | | 15 | + int octal = ('4' <= c && c <= '7'); | 14 | 16 | PyObject *msg = | 15 | | - PyUnicode_FromFormat("invalid escape sequence '\\%c'", first_invalid_escape_char); | | 17 | + octal | | 18 | + ? PyUnicode_FromFormat("invalid octal escape sequence '\\%.3s'", | | 19 | + first_invalid_escape) | | 20 | + : PyUnicode_FromFormat("invalid escape sequence '\\%c'", c); | 16 | 21 | if (msg == NULL) { | 17 | 22 | return -1; | 18 | 23 | } | @@ -27,7 +32,13 @@ warn_invalid_escape_sequence(Parser *p, unsigned char first_invalid_escape_char, | 27 | 32 | since _PyPegen_raise_error uses p->tokens[p->fill - 1] for the | 28 | 33 | error location, if p->known_err_token is not set. */ | 29 | 34 | p->known_err_token = t; | 30 | | - RAISE_SYNTAX_ERROR("invalid escape sequence '\\%c'", first_invalid_escape_char); | | 35 | + if (octal) { | | 36 | + RAISE_SYNTAX_ERROR("invalid octal escape sequence '\\%.3s'", | | 37 | + first_invalid_escape); | | 38 | + } | | 39 | + else { | | 40 | + RAISE_SYNTAX_ERROR("invalid escape sequence '\\%c'", c); | | 41 | + } | 31 | 42 | } | 32 | 43 | Py_DECREF(msg); | 33 | 44 | return -1; | @@ -118,7 +129,7 @@ decode_unicode_with_escapes(Parser *parser, const char *s, size_t len, Token *t) | 118 | 129 | v = _PyUnicode_DecodeUnicodeEscapeInternal(s, len, NULL, NULL, &first_invalid_escape); | 119 | 130 |
| 120 | 131 | if (v != NULL && first_invalid_escape != NULL) { | 121 | | - if (warn_invalid_escape_sequence(parser, *first_invalid_escape, t) < 0) { | | 132 | + if (warn_invalid_escape_sequence(parser, first_invalid_escape, t) < 0) { | 122 | 133 | /* We have not decref u before because first_invalid_escape points | 123 | 134 | inside u. */ | 124 | 135 | Py_XDECREF(u); | @@ -140,7 +151,7 @@ decode_bytes_with_escapes(Parser *p, const char *s, Py_ssize_t len, Token *t) | 140 | 151 | } | 141 | 152 |
| 142 | 153 | if (first_invalid_escape != NULL) { | 143 | | - if (warn_invalid_escape_sequence(p, *first_invalid_escape, t) < 0) { | | 154 | + if (warn_invalid_escape_sequence(p, first_invalid_escape, t) < 0) { | 144 | 155 | Py_DECREF(result); | 145 | 156 | return NULL; | 146 | 157 | } | @@ -357,7 +368,7 @@ fstring_compile_expr(Parser *p, const char *expr_start, const char *expr_end, | 357 | 368 | break; | 358 | 369 | } | 359 | 370 | } | 360 | | - | | 371 | + | 361 | 372 | if (s == expr_end) { | 362 | 373 | if (*expr_end == '!' || *expr_end == ':' || *expr_end == '=') { | 363 | 374 | RAISE_SYNTAX_ERROR("f-string: expression required before '%c'", *expr_end); | @@ -465,7 +476,7 @@ fstring_find_literal(Parser *p, const char **str, const char *end, int raw, | 465 | 476 | decode_unicode_with_escapes(). */ | 466 | 477 | continue; | 467 | 478 | } | 468 | | - if (ch == '{' && warn_invalid_escape_sequence(p, ch, t) < 0) { | | 479 | + if (ch == '{' && warn_invalid_escape_sequence(p, s-1, t) < 0) { | 469 | 480 | return -1; | 470 | 481 | } | 471 | 482 | } | |
|