Skip to content

Commit 73b794e

Browse files
committed
gh-153569: tighten tokenizer iterator cleanup
1 parent 1881081 commit 73b794e

4 files changed

Lines changed: 51 additions & 28 deletions

File tree

Lib/test/test_tokenize.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2282,6 +2282,39 @@ def showwarning(*args, **kwargs):
22822282
):
22832283
next(iterator)
22842284

2285+
def test_exhausted_iterator_releases_readline(self):
2286+
class Readline:
2287+
def __init__(self):
2288+
self.lines = iter(["x\n", ""])
2289+
2290+
def __call__(self):
2291+
return next(self.lines)
2292+
2293+
readline = Readline()
2294+
readline_ref = weakref.ref(readline)
2295+
iterator = _tokenize.TokenizerIter(readline, extra_tokens=False)
2296+
del readline
2297+
2298+
for _ in iterator:
2299+
pass
2300+
support.gc_collect()
2301+
2302+
self.assertIsNone(readline_ref())
2303+
with self.assertRaises(StopIteration):
2304+
next(iterator)
2305+
2306+
def test_lone_carriage_return(self):
2307+
self.assertEqual(
2308+
list(tokenize.generate_tokens(StringIO("x\r").readline)),
2309+
[
2310+
tokenize.TokenInfo(token.NAME, "x", (1, 0), (1, 1), "x\r"),
2311+
tokenize.TokenInfo(
2312+
token.NEWLINE, "\r", (1, 1), (1, 3), "x\r"
2313+
),
2314+
tokenize.TokenInfo(token.ENDMARKER, "", (2, 0), (2, 0), ""),
2315+
],
2316+
)
2317+
22852318
def check_tokenize(self, s, expected):
22862319
# Format the tokens in s in a table format.
22872320
# The ENDMARKER and final NEWLINE are omitted.

Parser/tokenizer/api.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ _PyTok_TokenTextLocations(const PyTokenizer *tok, const _PyTok_Token *token,
117117
*end = token->end;
118118
return 0;
119119
}
120-
if (_PyTok_SourceLocation(
121-
&tok->source, token->span.start,
120+
if (_PyTok_CursorLocation(
121+
&tok->cursor, token->span.start,
122122
_PYTOK_AFFINITY_RIGHT, start) < 0) {
123123
return -1;
124124
}
@@ -127,8 +127,8 @@ _PyTok_TokenTextLocations(const PyTokenizer *tok, const _PyTok_Token *token,
127127
token->span.start == token->span.end
128128
? _PYTOK_AFFINITY_RIGHT
129129
: _PYTOK_AFFINITY_LEFT;
130-
return _PyTok_SourceLocation(
131-
&tok->source, token->span.end, end_affinity, end);
130+
return _PyTok_CursorLocation(
131+
&tok->cursor, token->span.end, end_affinity, end);
132132
}
133133

134134
static int

Parser/tokenizer/reader.c

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -727,19 +727,6 @@ tokenizer_from_string(const char *input, int utf8_only, int exec_input,
727727
return tok;
728728
}
729729

730-
static struct _PyTokenizer *
731-
tokenizer_from_plain_string(const char *input, int exec_input,
732-
int preserve_crlf)
733-
{
734-
return tokenizer_from_string(input, 0, exec_input, preserve_crlf);
735-
}
736-
737-
static struct _PyTokenizer *
738-
tokenizer_from_utf8(const char *input, int exec_input, int preserve_crlf)
739-
{
740-
return tokenizer_from_string(input, 1, exec_input, preserve_crlf);
741-
}
742-
743730
static struct _PyTokenizer *
744731
tokenizer_from_readline(PyObject *readline, const char *encoding)
745732
{
@@ -788,12 +775,12 @@ _PyTok_ReaderNew(const _PyTok_Config *config)
788775
{
789776
switch (config->kind) {
790777
case _PYTOK_SOURCE_STRING:
791-
return tokenizer_from_plain_string(
792-
config->source.string, config->exec_input,
778+
return tokenizer_from_string(
779+
config->source.string, 0, config->exec_input,
793780
config->preserve_crlf);
794781
case _PYTOK_SOURCE_UTF8:
795-
return tokenizer_from_utf8(
796-
config->source.string, config->exec_input,
782+
return tokenizer_from_string(
783+
config->source.string, 1, config->exec_input,
797784
config->preserve_crlf);
798785
case _PYTOK_SOURCE_FILE:
799786
return tokenizer_from_file(

Python/Python-tokenize.c

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,8 @@ tokenizeriter_next(PyObject *op)
211211
{
212212
tokenizeriterobject *it = (tokenizeriterobject*)op;
213213
PyObject* result = NULL;
214+
PyTokenizer *tok_to_free = NULL;
215+
PyObject *line_to_clear = NULL;
214216

215217
Py_BEGIN_CRITICAL_SECTION(it);
216218

@@ -279,17 +281,12 @@ tokenizeriter_next(PyObject *op)
279281
}
280282

281283
if (_PyTok_ExtraTokens(it->tok)) {
282-
// Necessary adjustments to match the original Python tokenize
283-
// implementation
284284
if (type > DEDENT && type < OP) {
285285
type = OP;
286286
}
287287
else if (type == NEWLINE) {
288-
Py_DECREF(str);
289-
if (token.flags & _PYTOK_IMPLICIT_NL) {
290-
str = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
291-
}
292-
else {
288+
if (!(token.flags & _PYTOK_IMPLICIT_NL)) {
289+
Py_DECREF(str);
293290
if (token_len > 0 && token_view[0] == '\r') {
294291
str = PyUnicode_FromString("\r\n");
295292
}
@@ -318,9 +315,15 @@ tokenizeriter_next(PyObject *op)
318315
_PyTok_TokenClear(&token);
319316
if (type == ENDMARKER) {
320317
it->done = 1;
318+
tok_to_free = it->tok;
319+
it->tok = NULL;
320+
line_to_clear = it->last_line;
321+
it->last_line = NULL;
321322
}
322323

323324
Py_END_CRITICAL_SECTION();
325+
_PyTok_Free(tok_to_free);
326+
Py_XDECREF(line_to_clear);
324327
return result;
325328
}
326329

0 commit comments

Comments
 (0)