Skip to content

Commit caac5d8

Browse files
redsun82Copilot
andcommitted
Widen escape round-trip test to all printable ASCII neighbours
The test claimed exhaustive coverage but checked seven suffix characters and no prefix at all. Cover every printable ASCII neighbour on both sides of each escape shape, rendering `"` and `\` as Rust would. 2280 cases, 24ms. Also state the NUL case as the single literal `"\01"`, since the `+` notation read as concatenation of two separate literals. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent abf5a0d commit caac5d8

2 files changed

Lines changed: 29 additions & 20 deletions

File tree

python/extractor/semmle/python/parser/tsg_parser.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ def read_tsg_python_output(path, logger):
179179
# variation selector, U+200D zero width joiner and combining accents -- are rendered as `\u{...}`,
180180
# a syntax Python does not know at all;
181181
# - NUL is rendered as `\0`, which Python reads as the start of an *octal* escape, silently
182-
# swallowing up to two more digits (`"\0" + "1"` would decode to `\x01`).
182+
# swallowing up to two more digits (NUL followed by `1` is emitted as `"\01"`, which decodes
183+
# to `\x01`).
183184
# Everything else Rust emits (`\t`, `\r`, `\n`, `\\`, `\"`, and unescaped characters) is read back
184185
# identically by `literal_eval`, as verified exhaustively over every Unicode scalar value.
185186
_RUST_ESCAPE = re.compile(r"\\(?:u\{([0-9a-fA-F]{1,6})\}|.)", re.DOTALL)

python/extractor/tests/test_tsg_parser.py

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,26 +37,34 @@ def test_nul_is_not_left_as_an_octal_escape(self):
3737
self.assertEqual(rust_to_python_escapes(r'"\01"'), r'"\x001"')
3838

3939
def test_every_escape_shape_round_trips(self):
40-
# Rust's `Debug for str` only ever emits these shapes. Checked exhaustively against every
41-
# Unicode scalar value, each also paired with every printable ASCII neighbour.
42-
for rendered, expected in [
43-
(r'"\0"', "\x00"),
44-
(r'"\t"', "\t"),
45-
(r'"\n"', "\n"),
46-
(r'"\r"', "\r"),
47-
(r'"\\"', "\\"),
48-
(r'"\""', '"'),
49-
(r'"\u{1}"', "\u0001"),
50-
(r'"\u{1f}"', "\u001f"),
51-
(r'"\u{300}"', "\u0300"),
52-
(r'"\u{fe0f}"', "\ufe0f"),
53-
(r'"\u{e0100}"', "\U000e0100"),
54-
(r'"\u{10fffe}"', "\U0010fffe"),
40+
# Rust's `Debug for str` only ever emits these escape shapes. Check that each round-trips
41+
# with every printable ASCII neighbour before and after it.
42+
for escape_shape, expected in [
43+
(r'\0', "\x00"),
44+
(r'\t', "\t"),
45+
(r'\n', "\n"),
46+
(r'\r', "\r"),
47+
(r'\\', "\\"),
48+
(r'\"', '"'),
49+
(r'\u{1}', "\u0001"),
50+
(r'\u{1f}', "\u001f"),
51+
(r'\u{300}', "\u0300"),
52+
(r'\u{fe0f}', "\ufe0f"),
53+
(r'\u{e0100}', "\U000e0100"),
54+
(r'\u{10fffe}', "\U0010fffe"),
5555
]:
56-
for neighbour in ["", "0", "7", "9", "f", "u", "{"]:
57-
with self.subTest(rendered=rendered, neighbour=neighbour):
58-
text = rendered[:-1] + neighbour + '"'
59-
self.assertEqual(literal_eval(rust_to_python_escapes(text)), expected + neighbour)
56+
for neighbour in map(chr, range(0x20, 0x7F)):
57+
rendered_neighbour = {"\\": r"\\", '"': r'\"'}.get(neighbour, neighbour)
58+
for position, text, expected_value in [
59+
("before", '"' + rendered_neighbour + escape_shape + '"', neighbour + expected),
60+
("after", '"' + escape_shape + rendered_neighbour + '"', expected + neighbour),
61+
]:
62+
with self.subTest(
63+
escape_shape=escape_shape,
64+
neighbour=neighbour,
65+
position=position,
66+
):
67+
self.assertEqual(literal_eval(rust_to_python_escapes(text)), expected_value)
6068

6169
def test_evaluate_string_on_reported_value(self):
6270
# The exact value from https://github.com/github/codeql/issues/22435 that used to raise

0 commit comments

Comments
 (0)