Skip to content

Commit 600a445

Browse files
committed
gh-153569: bound tokenizer input storage
1 parent f5dbcba commit 600a445

8 files changed

Lines changed: 208 additions & 104 deletions

File tree

Lib/test/test_tokenize.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2427,6 +2427,31 @@ def test_stop_iteration_skips_encoded_readline_codec_lookup(self):
24272427
(token.ENDMARKER, "", (1, 0), (1, 0), ""),
24282428
)
24292429

2430+
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)
2454+
24302455
def test_extra_tokens_relaxes_lexer_errors(self):
24312456
cases = [
24322457
(

Parser/lexer/buffer.c

Lines changed: 32 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,45 @@
11
#include "Python.h"
2-
#include "errcode.h"
3-
2+
#include "buffer.h"
43
#include "state.h"
54

6-
/* Traverse and remember all f-string buffers, in order to be able to restore
7-
them after reallocating tok->buf */
85
void
9-
_PyLexer_remember_fstring_buffers(struct tok_state *tok)
6+
_PyLexer_SnapshotBuffer(struct tok_state *tok, const char *base,
7+
_PyLexer_BufferSnapshot *snapshot)
108
{
11-
int index;
12-
tokenizer_mode *mode;
13-
14-
for (index = tok->tok_mode_stack_index; index >= 0; --index) {
15-
mode = &(tok->tok_mode_stack[index]);
9+
snapshot->buf = tok->buf - base;
10+
snapshot->cur = tok->cur - tok->buf;
11+
snapshot->inp = tok->inp - tok->buf;
12+
snapshot->start = tok->start == NULL ? -1 : tok->start - tok->buf;
13+
snapshot->line_start = tok->line_start == NULL
14+
? -1 : tok->line_start - tok->buf;
15+
snapshot->multi_line_start = tok->multi_line_start == NULL
16+
? -1 : tok->multi_line_start - tok->buf;
17+
for (int index = tok->tok_mode_stack_index; index >= 0; --index) {
18+
tokenizer_mode *mode = &tok->tok_mode_stack[index];
1619
mode->start_offset = mode->start == NULL ? -1 : mode->start - tok->buf;
17-
mode->multi_line_start_offset = mode->multi_line_start == NULL ? -1 : mode->multi_line_start - tok->buf;
20+
mode->multi_line_start_offset = mode->multi_line_start == NULL
21+
? -1 : mode->multi_line_start - tok->buf;
1822
}
1923
}
2024

21-
/* Traverse and restore all f-string buffers after reallocating tok->buf */
2225
void
23-
_PyLexer_restore_fstring_buffers(struct tok_state *tok)
24-
{
25-
int index;
26-
tokenizer_mode *mode;
27-
28-
for (index = tok->tok_mode_stack_index; index >= 0; --index) {
29-
mode = &(tok->tok_mode_stack[index]);
30-
mode->start = mode->start_offset < 0 ? NULL : tok->buf + mode->start_offset;
31-
mode->multi_line_start = mode->multi_line_start_offset < 0 ? NULL : tok->buf + mode->multi_line_start_offset;
32-
}
33-
}
34-
35-
int
36-
_PyLexer_tok_reserve_buf(struct tok_state *tok, Py_ssize_t size)
26+
_PyLexer_RestoreBuffer(struct tok_state *tok, char *base,
27+
const _PyLexer_BufferSnapshot *snapshot)
3728
{
38-
Py_ssize_t cur = tok->cur - tok->buf;
39-
Py_ssize_t oldsize = tok->inp - tok->buf;
40-
Py_ssize_t newsize = oldsize + Py_MAX(size, oldsize >> 1);
41-
if (newsize > tok->end - tok->buf) {
42-
char *newbuf = tok->buf;
43-
Py_ssize_t start = tok->start == NULL ? -1 : tok->start - tok->buf;
44-
Py_ssize_t line_start = tok->start == NULL ? -1 : tok->line_start - tok->buf;
45-
Py_ssize_t multi_line_start = tok->multi_line_start - tok->buf;
46-
_PyLexer_remember_fstring_buffers(tok);
47-
newbuf = (char *)PyMem_Realloc(newbuf, newsize);
48-
if (newbuf == NULL) {
49-
tok->done = E_NOMEM;
50-
return 0;
51-
}
52-
tok->buf = newbuf;
53-
tok->cur = tok->buf + cur;
54-
tok->inp = tok->buf + oldsize;
55-
tok->end = tok->buf + newsize;
56-
tok->start = start < 0 ? NULL : tok->buf + start;
57-
tok->line_start = line_start < 0 ? NULL : tok->buf + line_start;
58-
tok->multi_line_start = multi_line_start < 0 ? NULL : tok->buf + multi_line_start;
59-
_PyLexer_restore_fstring_buffers(tok);
29+
tok->buf = base + snapshot->buf;
30+
tok->cur = tok->buf + snapshot->cur;
31+
tok->inp = tok->buf + snapshot->inp;
32+
tok->start = snapshot->start < 0
33+
? NULL : tok->buf + snapshot->start;
34+
tok->line_start = snapshot->line_start < 0
35+
? NULL : tok->buf + snapshot->line_start;
36+
tok->multi_line_start = snapshot->multi_line_start < 0
37+
? NULL : tok->buf + snapshot->multi_line_start;
38+
for (int index = tok->tok_mode_stack_index; index >= 0; --index) {
39+
tokenizer_mode *mode = &tok->tok_mode_stack[index];
40+
mode->start = mode->start_offset < 0
41+
? NULL : tok->buf + mode->start_offset;
42+
mode->multi_line_start = mode->multi_line_start_offset < 0
43+
? NULL : tok->buf + mode->multi_line_start_offset;
6044
}
61-
return 1;
6245
}

Parser/lexer/buffer.h

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,20 @@
33

44
#include "pyport.h"
55

6-
void _PyLexer_remember_fstring_buffers(struct tok_state *tok);
7-
void _PyLexer_restore_fstring_buffers(struct tok_state *tok);
8-
int _PyLexer_tok_reserve_buf(struct tok_state *tok, Py_ssize_t size);
6+
struct tok_state;
7+
8+
typedef struct {
9+
Py_ssize_t buf;
10+
Py_ssize_t cur;
11+
Py_ssize_t inp;
12+
Py_ssize_t start;
13+
Py_ssize_t line_start;
14+
Py_ssize_t multi_line_start;
15+
} _PyLexer_BufferSnapshot;
16+
17+
void _PyLexer_SnapshotBuffer(
18+
struct tok_state *, const char *, _PyLexer_BufferSnapshot *);
19+
void _PyLexer_RestoreBuffer(
20+
struct tok_state *, char *, const _PyLexer_BufferSnapshot *);
921

1022
#endif

Parser/lexer/state.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ _PyTokenizer_tok_new(void)
2626
tok->interactive_src_start = NULL;
2727
tok->interactive_src_end = NULL;
2828
tok->start = NULL;
29-
tok->end = NULL;
3029
tok->done = E_OK;
3130
tok->fp = NULL;
3231
tok->tabsize = TABSIZE;
@@ -52,6 +51,7 @@ _PyTokenizer_tok_new(void)
5251
tok->comment_newline = 0;
5352
tok->implicit_newline = 0;
5453
_PyTok_SourceInit(&tok->source);
54+
_PyTok_CursorInit(&tok->reader_cursor, &tok->source);
5555
tok->reader = NULL;
5656
tok->tok_mode_stack[0] = (tokenizer_mode){.kind =TOK_REGULAR_MODE, .quote='\0', .quote_size = 0, .in_debug=0};
5757
tok->tok_mode_stack_index = 0;

Parser/lexer/state.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#define _PY_LEXER_H_
33

44
#include "object.h"
5-
#include "../tokenizer/source.h"
5+
#include "../tokenizer/cursor.h"
66

77
#define MAXINDENT 100 /* Max indentation level */
88
#define MAXLEVEL 200 /* Max parentheses level */
@@ -67,15 +67,15 @@ typedef struct _tokenizer_mode {
6767

6868
/* Tokenizer state */
6969
struct tok_state {
70-
/* Input state; buf <= cur <= inp <= end */
70+
/* Input state; buf <= cur <= inp */
7171
/* NB an entire line is held in the buffer */
7272
char *buf; /* Input buffer, or NULL; malloc'ed if fp != NULL or readline != NULL */
7373
char *cur; /* Next character in buffer */
7474
char *inp; /* End of data in buffer */
75+
_PyTok_Off buf_offset; /* Logical offset of buf[0]. */
7576
int fp_interactive; /* If the file descriptor is interactive */
7677
char *interactive_src_start; /* The start of the source parsed so far in interactive mode */
7778
char *interactive_src_end; /* The end of the source parsed so far in interactive mode */
78-
const char *end; /* End of input buffer if buf != NULL */
7979
const char *start; /* Start of current token if not NULL */
8080
int done; /* E_OK normally, E_EOF at EOF, otherwise error code */
8181
/* NB If done != E_OK, cur must be == inp!!! */
@@ -110,6 +110,7 @@ struct tok_state {
110110
char* str; /* Source string being tokenized (if tokenizing from a string)*/
111111

112112
_PyTok_SourceText source;
113+
_PyTok_Cursor reader_cursor;
113114
struct _PyTok_Reader *reader;
114115

115116
int type_comments; /* Whether to look for type comments */

Parser/lexer/string.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ _PyLexer_update_ftstring_expr(struct tok_state *tok, char cur)
125125
{
126126
assert(tok->cur != NULL);
127127

128-
Py_ssize_t size = strlen(tok->cur);
128+
Py_ssize_t size = cur == 0
129+
? tok->inp - tok->cur : (Py_ssize_t)strlen(tok->cur);
129130
tokenizer_mode *tok_mode = TOK_GET_MODE(tok);
130131

131132
switch (cur) {
@@ -142,7 +143,8 @@ _PyLexer_update_ftstring_expr(struct tok_state *tok, char cur)
142143
goto error;
143144
}
144145
tok_mode->last_expr_buffer = new_buffer;
145-
strncpy(tok_mode->last_expr_buffer + tok_mode->last_expr_size, tok->cur, size);
146+
memcpy(tok_mode->last_expr_buffer + tok_mode->last_expr_size,
147+
tok->cur, size);
146148
tok_mode->last_expr_size += size;
147149
break;
148150
case '{':
@@ -155,7 +157,7 @@ _PyLexer_update_ftstring_expr(struct tok_state *tok, char cur)
155157
}
156158
tok_mode->last_expr_size = size;
157159
tok_mode->last_expr_end = -1;
158-
strncpy(tok_mode->last_expr_buffer, tok->cur, size);
160+
memcpy(tok_mode->last_expr_buffer, tok->cur, size);
159161
break;
160162
case '}':
161163
case '!':

0 commit comments

Comments
 (0)