Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix interpreter crash when a string literal contains a null character.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this NEWS entry is filed under gh-issue-96670, but this PR fixes gh-97556. Can we rename it so the changelog points to the right issue?

22 changes: 14 additions & 8 deletions Parser/string_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,18 @@ int
_PyPegen_parsestr(Parser *p, int *bytesmode, int *rawmode, PyObject **result,
const char **fstr, Py_ssize_t *fstrlen, Token *t)
{
const char *s = PyBytes_AsString(t->bytes);
if (s == NULL) {
char *s;
Py_ssize_t len;

if (PyBytes_AsStringAndSize(t->bytes, &s, &len)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we reject this in the tokenizer instead? Otherwise files with NUL bytes in a string literal become valid source, while compile() still rejects NUL bytes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a crash fix but it lands without a regression test. I know a .py file with a null byte is blocked by gh-96670, but can we still add a regression test somehow, e.g. compiling a bytes source with an embedded null? We really want to keep this from regressing.

return -1;
}

if (len > INT_MAX) {
PyErr_SetString(PyExc_OverflowError, "string to parse is too long");
return -1;
}

size_t len;
int quote = Py_CHARMASK(*s);
int fmode = 0;
*bytesmode = 0;
Expand All @@ -184,17 +190,21 @@ _PyPegen_parsestr(Parser *p, int *bytesmode, int *rawmode, PyObject **result,
while (!*bytesmode || !*rawmode) {
if (quote == 'b' || quote == 'B') {
quote =(unsigned char)*++s;
len--;
*bytesmode = 1;
}
else if (quote == 'u' || quote == 'U') {
quote = (unsigned char)*++s;
len--;
}
else if (quote == 'r' || quote == 'R') {
quote = (unsigned char)*++s;
len--;
*rawmode = 1;
}
else if (quote == 'f' || quote == 'F') {
quote = (unsigned char)*++s;
len--;
fmode = 1;
}
else {
Expand All @@ -220,11 +230,7 @@ _PyPegen_parsestr(Parser *p, int *bytesmode, int *rawmode, PyObject **result,
}
/* Skip the leading quote char. */
s++;
len = strlen(s);
if (len > INT_MAX) {
PyErr_SetString(PyExc_OverflowError, "string to parse is too long");
return -1;
}
len--;
if (s[--len] != quote) {
/* Last quote char must match the first. */
PyErr_BadInternalCall();
Expand Down