Skip to content

Commit

Permalink
pythongh-130139: always check ast node type in ast.parse() with ast i…
Browse files Browse the repository at this point in the history
…nput
  • Loading branch information
iritkatriel committed Feb 15, 2025
1 parent e094420 commit 117152f
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 24 deletions.
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,10 @@ ast
* The ``repr()`` output for AST nodes now includes more information.
(Contributed by Tomas R in :gh:`116022`.)

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


calendar
--------
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_ast.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Lib/test/test_ast/test_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ def test_ast_validation(self):
tree = ast.parse(snippet)
compile(tree, '<string>', 'exec')

def test_parse_invalid_ast(self):
# see gh-130139
for optval in (-1, 0, 1, 2):
self.assertRaises(TypeError, ast.parse, ast.Constant(42),
optimize=optval)

def test_optimization_levels__debug__(self):
cases = [(-1, '__debug__'), (0, '__debug__'), (1, False), (2, False)]
for (optval, expected) in cases:
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_unparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,9 +422,9 @@ def test_docstrings(self):
self.check_ast_roundtrip(f"'''{docstring}'''")

def test_constant_tuples(self):
self.check_src_roundtrip(ast.Constant(value=(1,), kind=None), "(1,)")
self.check_src_roundtrip(ast.Module([ast.Constant(value=(1,))]), "(1,)")
self.check_src_roundtrip(
ast.Constant(value=(1, 2, 3), kind=None), "(1, 2, 3)"
ast.Module([ast.Constant(value=(1, 2, 3))]), "(1, 2, 3)"
)

def test_function_type(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix bug where ``ast.parse`` did not error on AST input which is not of the
correct type, when called with optimize=False.
35 changes: 24 additions & 11 deletions Parser/asdl_c.py
Original file line number Diff line number Diff line change
Expand Up @@ -2166,18 +2166,13 @@ class PartingShots(StaticVisitor):
}
/* mode is 0 for "exec", 1 for "eval" and 2 for "single" input */
mod_ty PyAST_obj2mod(PyObject* ast, PyArena* arena, int mode)
int PyAst_CheckMode(PyObject *ast, int mode)
{
const char * const req_name[] = {"Module", "Expression", "Interactive"};
int isinstance;
if (PySys_Audit("compile", "OO", ast, Py_None) < 0) {
return NULL;
}
struct ast_state *state = get_ast_state();
if (state == NULL) {
return NULL;
return -1;
}
PyObject *req_type[3];
Expand All @@ -2186,13 +2181,30 @@ class PartingShots(StaticVisitor):
req_type[2] = state->Interactive_type;
assert(0 <= mode && mode <= 2);
isinstance = PyObject_IsInstance(ast, req_type[mode]);
if (isinstance == -1)
return NULL;
int isinstance = PyObject_IsInstance(ast, req_type[mode]);
if (isinstance == -1) {
return -1;
}
if (!isinstance) {
PyErr_Format(PyExc_TypeError, "expected %s node, got %.400s",
req_name[mode], _PyType_Name(Py_TYPE(ast)));
return -1;
}
return 0;
}
mod_ty PyAST_obj2mod(PyObject* ast, PyArena* arena, int mode)
{
if (PySys_Audit("compile", "OO", ast, Py_None) < 0) {
return NULL;
}
struct ast_state *state = get_ast_state();
if (state == NULL) {
return NULL;
}
if (PyAst_CheckMode(ast, mode) < 0) {
return NULL;
}
Expand Down Expand Up @@ -2356,6 +2368,7 @@ def write_header(mod, metadata, f):
f.write(textwrap.dedent("""
PyObject* PyAST_mod2obj(mod_ty t);
int PyAst_CheckMode(PyObject *ast, int mode);
mod_ty PyAST_obj2mod(PyObject* ast, PyArena* arena, int mode);
int PyAST_Check(PyObject* obj);
Expand Down
34 changes: 23 additions & 11 deletions Python/Python-ast.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Python/bltinmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,9 @@ builtin_compile_impl(PyObject *module, PyObject *source, PyObject *filename,
goto error;
if (is_ast) {
if ((flags & PyCF_OPTIMIZED_AST) == PyCF_ONLY_AST) {
if (PyAst_CheckMode(source, compile_mode) < 0) {
goto error;
}
// return an un-optimized AST
result = Py_NewRef(source);
}
Expand Down

0 comments on commit 117152f

Please sign in to comment.