Skip to content

Commit 5505010

Browse files
committed
gh-153569: complete tokenizer validation coverage
1 parent 595b345 commit 5505010

7 files changed

Lines changed: 113 additions & 15 deletions

File tree

Lib/test/test_tokenize.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2434,6 +2434,23 @@ def test_multiline_readline_chunk(self):
24342434
))
24352435
self.assertEqual(tokens, expected)
24362436

2437+
def test_large_multiline_readline_chunk(self):
2438+
comment = "#" + "x" * (1024 * 1024)
2439+
lines = iter([(comment + "\npass\n").encode(), b""])
2440+
tokens = list(tokenize._generate_tokens_from_c_tokenizer(
2441+
lines.__next__, extra_tokens=True, encoding="utf-8"
2442+
))
2443+
self.assertEqual(tokens[0].string, comment)
2444+
self.assertEqual(tokens[0].start, (1, 0))
2445+
self.assertEqual(tokens[0].end, (1, len(comment)))
2446+
self.assertEqual(
2447+
tokens[2],
2448+
tokenize.TokenInfo(
2449+
token.NAME, "pass", (2, 0), (2, 4), "pass\n"
2450+
),
2451+
)
2452+
self.assertEqual(tokens[-1].type, token.ENDMARKER)
2453+
24372454
def test_multiline_readline_chunk_with_unterminated_tail(self):
24382455
readline = mock.Mock(side_effect=["x\nz", ""])
24392456
iterator = _tokenize.TokenizerIter(readline, extra_tokens=True)
@@ -3874,6 +3891,12 @@ def test_newline_at_the_end_of_buffer(self):
38743891
file_name = make_script(temp_dir, 'foo', test_script)
38753892
run_test_script(file_name)
38763893

3894+
def test_large_file_reader_buffer(self):
3895+
test_script = "#" + "x" * (1024 * 1024) + "\npass\n"
3896+
with os_helper.temp_dir() as temp_dir:
3897+
file_name = make_script(temp_dir, "large_reader", test_script)
3898+
run_test_script(file_name)
3899+
38773900

38783901
@support.force_not_colorized_test_class
38793902
class CommandLineTest(unittest.TestCase):
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# delimiters and operators
2+
"("
3+
")"
4+
"["
5+
"]"
6+
"{"
7+
"}"
8+
":"
9+
"!"
10+
"="
11+
":="
12+
13+
# strings and interpolation
14+
"f'"
15+
"f\""
16+
"t'"
17+
"t\""
18+
"'''"
19+
"\"\"\""
20+
"!r"
21+
"!s"
22+
"!a"
23+
24+
# reader and decoder boundaries
25+
"# coding: utf-8\n"
26+
"# coding: latin-1\n"
27+
"\\\n"
28+
"\r\n"
29+
"\xc3\xa9"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
A# coding: utf-8
2+
é = 1
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Gf'{value:{width}.{precision}f}'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Ct'{value=!r:{width}}'

Modules/_xxtestfuzz/fuzzer.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,7 @@ static int fuzz_pycompile(const char* data, size_t size) {
594594
}
595595

596596
#define MAX_TOKENIZER_TEST_SIZE 16384
597+
#define MAX_TOKENIZER_FILE_TEST_SIZE (2 * 1024 * 1024)
597598

598599
static PyObject *tokenizer_iter_type;
599600
static PyObject *stringio_type;
@@ -717,11 +718,21 @@ fuzz_tokenizer_file(const char *data, size_t size, unsigned char options)
717718
static int
718719
fuzz_tokenizer(const char *data, size_t size)
719720
{
720-
if (size < 1 || size > MAX_TOKENIZER_TEST_SIZE) {
721+
if (size < 1) {
721722
return 0;
722723
}
723724

724725
unsigned char options = (unsigned char)data[0];
726+
#ifdef FUZZ_TOKENIZER_FILE
727+
size_t max_size = options & 0x04
728+
? MAX_TOKENIZER_FILE_TEST_SIZE
729+
: MAX_TOKENIZER_TEST_SIZE;
730+
#else
731+
size_t max_size = MAX_TOKENIZER_TEST_SIZE;
732+
#endif
733+
if (size > max_size) {
734+
return 0;
735+
}
725736
data++;
726737
size--;
727738

Tools/tokenizer/difftest.py

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,35 @@ def positive_float(value):
4242

4343

4444
def exception_result(exc, *, locations=True):
45+
message = getattr(exc, "msg", None)
46+
token_location = None
47+
if message is None and isinstance(exc, tokenize.TokenError):
48+
if (
49+
len(exc.args) == 2
50+
and isinstance(exc.args[1], tuple)
51+
and len(exc.args[1]) == 2
52+
):
53+
message, token_location = exc.args
54+
elif exc.args:
55+
message = exc.args[0]
56+
if message is None:
57+
message = str(exc)
4558
result = {
4659
"type": type(exc).__name__,
47-
"message": getattr(exc, "msg", str(exc)),
60+
"message": message,
4861
}
4962
if locations:
5063
result.update({
51-
"lineno": getattr(exc, "lineno", None),
52-
"offset": getattr(exc, "offset", None),
64+
"lineno": (
65+
token_location[0]
66+
if token_location is not None
67+
else getattr(exc, "lineno", None)
68+
),
69+
"offset": (
70+
token_location[1]
71+
if token_location is not None
72+
else getattr(exc, "offset", None)
73+
),
5374
"end_lineno": getattr(exc, "end_lineno", None),
5475
"end_offset": getattr(exc, "end_offset", None),
5576
"text": getattr(exc, "text", None),
@@ -70,7 +91,9 @@ def warning_results(caught):
7091
]
7192

7293

73-
def tokenizer_iter_result(data, extra_tokens, *, encoded=False, batched=False):
94+
def tokenizer_iter_result(
95+
data, extra_tokens, *, encoded=False, batched=False, semantic=False
96+
):
7497
tokens = []
7598
with warnings.catch_warnings(record=True) as caught:
7699
warnings.simplefilter("always")
@@ -102,7 +125,7 @@ def readline():
102125
iterator = _tokenize.TokenizerIter(readline, **kwargs)
103126
tokens.extend(iterator)
104127
except Exception as exc:
105-
error = exception_result(exc)
128+
error = exception_result(exc, locations=not semantic)
106129
else:
107130
error = None
108131
return {
@@ -112,14 +135,14 @@ def readline():
112135
}
113136

114137

115-
def tokenize_result(data):
138+
def tokenize_result(data, semantic):
116139
tokens = []
117140
with warnings.catch_warnings(record=True) as caught:
118141
warnings.simplefilter("always")
119142
try:
120143
tokens.extend(tokenize.tokenize(io.BytesIO(data).readline))
121144
except Exception as exc:
122-
error = exception_result(exc)
145+
error = exception_result(exc, locations=not semantic)
123146
else:
124147
error = None
125148
return {
@@ -180,26 +203,34 @@ def case_result(case, batched, semantic):
180203
data = Path(case["path"]).read_bytes()
181204
else:
182205
data = base64.b64decode(case["data"], validate=True)
183-
parser_tokens = tokenizer_iter_result(data, False)
184-
extra_tokens = tokenizer_iter_result(data, True)
185-
encoded_tokens = tokenizer_iter_result(data, False, encoded=True)
206+
parser_tokens = tokenizer_iter_result(data, False, semantic=semantic)
207+
extra_tokens = tokenizer_iter_result(data, True, semantic=semantic)
208+
encoded_tokens = tokenizer_iter_result(
209+
data, False, encoded=True, semantic=semantic
210+
)
186211
return {
187212
"name": case["name"],
188213
"parser": compile_result(data, semantic),
189-
"tokenize": tokenize_result(data),
214+
"tokenize": tokenize_result(data, semantic),
190215
"parser_tokens": parser_tokens,
191216
"extra_tokens": extra_tokens,
192217
"encoded_tokens": encoded_tokens,
193218
"batched_tokens": (
194-
tokenizer_iter_result(data, False, batched=True)
219+
tokenizer_iter_result(
220+
data, False, batched=True, semantic=semantic
221+
)
195222
if batched else parser_tokens
196223
),
197224
"batched_extra_tokens": (
198-
tokenizer_iter_result(data, True, batched=True)
225+
tokenizer_iter_result(
226+
data, True, batched=True, semantic=semantic
227+
)
199228
if batched else extra_tokens
200229
),
201230
"batched_encoded_tokens": (
202-
tokenizer_iter_result(data, False, encoded=True, batched=True)
231+
tokenizer_iter_result(
232+
data, False, encoded=True, batched=True, semantic=semantic
233+
)
203234
if batched else encoded_tokens
204235
),
205236
}

0 commit comments

Comments
 (0)