Skip to content

Commit c9b1bf3

Browse files
authored
gh-130139: always check ast node type in ast.parse() with ast input (#130140)
1 parent 2e8044a commit c9b1bf3

File tree

8 files changed

+65
-24
lines changed

8 files changed

+65
-24
lines changed

Doc/whatsnew/3.14.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,10 @@ ast
346346
* The ``repr()`` output for AST nodes now includes more information.
347347
(Contributed by Tomas R in :gh:`116022`.)
348348

349+
* :func:`ast.parse`, when called with an AST as input, now always verifies
350+
that the root node type is appropriate.
351+
(Contributed by Irit Katriel in :gh:`130139`.)
352+
349353

350354
calendar
351355
--------

Include/internal/pycore_ast.h

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_ast/test_ast.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@ def test_ast_validation(self):
131131
tree = ast.parse(snippet)
132132
compile(tree, '<string>', 'exec')
133133

134+
def test_parse_invalid_ast(self):
135+
# see gh-130139
136+
for optval in (-1, 0, 1, 2):
137+
self.assertRaises(TypeError, ast.parse, ast.Constant(42),
138+
optimize=optval)
139+
134140
def test_optimization_levels__debug__(self):
135141
cases = [(-1, '__debug__'), (0, '__debug__'), (1, False), (2, False)]
136142
for (optval, expected) in cases:

Lib/test/test_unparse.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -422,9 +422,9 @@ def test_docstrings(self):
422422
self.check_ast_roundtrip(f"'''{docstring}'''")
423423

424424
def test_constant_tuples(self):
425-
self.check_src_roundtrip(ast.Constant(value=(1,), kind=None), "(1,)")
425+
self.check_src_roundtrip(ast.Module([ast.Constant(value=(1,))]), "(1,)")
426426
self.check_src_roundtrip(
427-
ast.Constant(value=(1, 2, 3), kind=None), "(1, 2, 3)"
427+
ast.Module([ast.Constant(value=(1, 2, 3))]), "(1, 2, 3)"
428428
)
429429

430430
def test_function_type(self):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix bug where :func:`ast.parse` did not error on AST input which is not of the
2+
correct type, when called with optimize=False.

Parser/asdl_c.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2166,18 +2166,13 @@ class PartingShots(StaticVisitor):
21662166
}
21672167
21682168
/* mode is 0 for "exec", 1 for "eval" and 2 for "single" input */
2169-
mod_ty PyAST_obj2mod(PyObject* ast, PyArena* arena, int mode)
2169+
int PyAst_CheckMode(PyObject *ast, int mode)
21702170
{
21712171
const char * const req_name[] = {"Module", "Expression", "Interactive"};
2172-
int isinstance;
2173-
2174-
if (PySys_Audit("compile", "OO", ast, Py_None) < 0) {
2175-
return NULL;
2176-
}
21772172
21782173
struct ast_state *state = get_ast_state();
21792174
if (state == NULL) {
2180-
return NULL;
2175+
return -1;
21812176
}
21822177
21832178
PyObject *req_type[3];
@@ -2186,13 +2181,30 @@ class PartingShots(StaticVisitor):
21862181
req_type[2] = state->Interactive_type;
21872182
21882183
assert(0 <= mode && mode <= 2);
2189-
2190-
isinstance = PyObject_IsInstance(ast, req_type[mode]);
2191-
if (isinstance == -1)
2192-
return NULL;
2184+
int isinstance = PyObject_IsInstance(ast, req_type[mode]);
2185+
if (isinstance == -1) {
2186+
return -1;
2187+
}
21932188
if (!isinstance) {
21942189
PyErr_Format(PyExc_TypeError, "expected %s node, got %.400s",
21952190
req_name[mode], _PyType_Name(Py_TYPE(ast)));
2191+
return -1;
2192+
}
2193+
return 0;
2194+
}
2195+
2196+
mod_ty PyAST_obj2mod(PyObject* ast, PyArena* arena, int mode)
2197+
{
2198+
if (PySys_Audit("compile", "OO", ast, Py_None) < 0) {
2199+
return NULL;
2200+
}
2201+
2202+
struct ast_state *state = get_ast_state();
2203+
if (state == NULL) {
2204+
return NULL;
2205+
}
2206+
2207+
if (PyAst_CheckMode(ast, mode) < 0) {
21962208
return NULL;
21972209
}
21982210
@@ -2356,6 +2368,7 @@ def write_header(mod, metadata, f):
23562368
f.write(textwrap.dedent("""
23572369
23582370
PyObject* PyAST_mod2obj(mod_ty t);
2371+
int PyAst_CheckMode(PyObject *ast, int mode);
23592372
mod_ty PyAST_obj2mod(PyObject* ast, PyArena* arena, int mode);
23602373
int PyAST_Check(PyObject* obj);
23612374

Python/Python-ast.c

Lines changed: 23 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/bltinmodule.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,9 @@ builtin_compile_impl(PyObject *module, PyObject *source, PyObject *filename,
835835
goto error;
836836
if (is_ast) {
837837
if ((flags & PyCF_OPTIMIZED_AST) == PyCF_ONLY_AST) {
838+
if (PyAst_CheckMode(source, compile_mode) < 0) {
839+
goto error;
840+
}
838841
// return an un-optimized AST
839842
result = Py_NewRef(source);
840843
}

0 commit comments

Comments
 (0)