Skip to content

Commit b5addc5

Browse files
committed
gh-153569: enforce tokenizer buffer view lifetimes
1 parent f134e81 commit b5addc5

3 files changed

Lines changed: 53 additions & 27 deletions

File tree

Lib/test/test_repl.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,22 @@ def test_lexer_buffer_realloc_with_null_start(self):
198198
self.assertEqual(p.returncode, 0)
199199
self.assertIn(long_value, output)
200200

201+
@cpython_only
202+
def test_multiline_fstring_source_reallocation(self):
203+
long_line = " " * 9000 + "+ 2"
204+
user_input = (
205+
'value = f"""{(\n'
206+
'1\n'
207+
f'{long_line}\n'
208+
')}"""\n'
209+
'print(value)\n'
210+
)
211+
p = spawn_repl()
212+
p.stdin.write(user_input)
213+
output = kill_python(p)
214+
self.assertEqual(p.returncode, 0)
215+
self.assertIn(">>> 3\n>>> ", output)
216+
201217
def test_close_stdin(self):
202218
user_input = dedent('''
203219
import os

Lib/test/test_tokenize.py

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2428,29 +2428,28 @@ def test_stop_iteration_skips_encoded_readline_codec_lookup(self):
24282428
)
24292429

24302430
def test_fstring_offsets_survive_buffer_reallocation(self):
2431-
padding = " " * 9000
2432-
expression_line = ")=:>{2}}\n"
2433-
physical_lines = [
2434-
'f"""\n',
2435-
"{(\n",
2436-
padding + "1\n",
2437-
expression_line,
2438-
'"""\n',
2439-
]
2440-
source = "".join(physical_lines)
2441-
chunks = iter([
2442-
"".join(physical_lines[:2]),
2443-
"".join(physical_lines[2:4]),
2444-
physical_lines[4],
2445-
"",
2446-
])
2447-
2448-
expected = self._get_tokens(source, extra_tokens=True)
2449-
tokens = list(tokenize._generate_tokens_from_c_tokenizer(
2450-
chunks.__next__,
2451-
extra_tokens=True,
2452-
))
2453-
self.assertEqual(tokens, expected)
2431+
for prefix in ("f", "t"):
2432+
for extra_tokens in (False, True):
2433+
with self.subTest(prefix=prefix, extra_tokens=extra_tokens):
2434+
physical_lines = [
2435+
prefix + '"""\n',
2436+
"{(\n",
2437+
" " * 9000 + "1\n",
2438+
")=:>{2}}\n",
2439+
'"""\n',
2440+
]
2441+
source = "".join(physical_lines)
2442+
chunks = iter([
2443+
"".join(physical_lines[:2]),
2444+
"".join(physical_lines[2:4]),
2445+
physical_lines[4],
2446+
"",
2447+
])
2448+
expected = self._get_tokens(
2449+
source, extra_tokens=extra_tokens)
2450+
tokens = list(tokenize._generate_tokens_from_c_tokenizer(
2451+
chunks.__next__, extra_tokens=extra_tokens))
2452+
self.assertEqual(tokens, expected)
24542453

24552454
def test_extra_tokens_relaxes_lexer_errors(self):
24562455
cases = [

Parser/tokenizer/reader.c

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,23 @@ reserve_input_buffer(struct tok_state *tok, Py_ssize_t needed)
7878
assert(tok->inp - tok->buf <= reader->input_buffer_cap);
7979
_PyLexer_BufferPointers pointers;
8080
_PyLexer_SaveBufferPointers(tok, tok->buf, &pointers);
81+
#ifdef Py_DEBUG
82+
char *buffer = NULL;
83+
Py_ssize_t capacity = reader->input_buffer_cap;
84+
if (reserve_buffer(&buffer, &capacity, needed) < 0) {
85+
return -1;
86+
}
87+
memcpy(buffer, tok->buf, (size_t)(tok->inp - tok->buf) + 1);
88+
memset(tok->buf, 0xDD, reader->input_buffer_cap);
89+
PyMem_Free(tok->buf);
90+
tok->buf = buffer;
91+
reader->input_buffer_cap = capacity;
92+
#else
8193
if (reserve_buffer(
8294
&tok->buf, &reader->input_buffer_cap, needed) < 0) {
8395
return -1;
8496
}
97+
#endif
8598
_PyLexer_RestoreBufferPointers(tok, tok->buf, &pointers);
8699
return 0;
87100
}
@@ -629,10 +642,8 @@ _PyTok_ReaderUnderflow(struct tok_state *tok)
629642
*tok->inp = '\0';
630643
}
631644
else if (!prepared) {
632-
int source_will_grow =
633-
chunk.len > tok->source.cap - tok->source.len - 1;
634645
_PyLexer_BufferPointers pointers;
635-
if (!reset_buffer && source_will_grow) {
646+
if (!reset_buffer) {
636647
_PyLexer_SaveBufferPointers(
637648
tok, tok->source.bytes, &pointers);
638649
}
@@ -653,7 +664,7 @@ _PyTok_ReaderUnderflow(struct tok_state *tok)
653664
tok->start = NULL;
654665
tok->multi_line_start = NULL;
655666
}
656-
else if (source_will_grow) {
667+
else {
657668
_PyLexer_RestoreBufferPointers(
658669
tok, tok->source.bytes, &pointers);
659670
}

0 commit comments

Comments
 (0)