gh-130139: always check ast node type in ast.parse() with ast input (#130140)

This commit is contained in:
Irit Katriel 2025-02-16 13:32:39 +00:00 committed by GitHub
parent 2e8044a4f7
commit c9b1bf302c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 73 additions and 32 deletions

View file

@ -346,6 +346,10 @@ ast
* The ``repr()`` output for AST nodes now includes more information.
(Contributed by Tomas R in :gh:`116022`.)
* :func:`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
--------

View file

@ -907,6 +907,7 @@ type_param_ty _PyAST_TypeVarTuple(identifier name, expr_ty default_value, int
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);

View file

@ -131,6 +131,12 @@ class AST_Tests(unittest.TestCase):
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:

View file

@ -422,9 +422,9 @@ class UnparseTestCase(ASTTestCase):
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):

View file

@ -0,0 +1,2 @@
Fix bug where :func:`ast.parse` did not error on AST input which is not of the
correct type, when called with optimize=False.

View file

@ -2166,11 +2166,35 @@ PyObject* PyAST_mod2obj(mod_ty t)
}
/* 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;
struct ast_state *state = get_ast_state();
if (state == NULL) {
return -1;
}
PyObject *req_type[3];
req_type[0] = state->Module_type;
req_type[1] = state->Expression_type;
req_type[2] = state->Interactive_type;
assert(0 <= mode && mode <= 2);
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;
}
@ -2180,19 +2204,7 @@ mod_ty PyAST_obj2mod(PyObject* ast, PyArena* arena, int mode)
return NULL;
}
PyObject *req_type[3];
req_type[0] = state->Module_type;
req_type[1] = state->Expression_type;
req_type[2] = state->Interactive_type;
assert(0 <= mode && mode <= 2);
isinstance = PyObject_IsInstance(ast, req_type[mode]);
if (isinstance == -1)
return NULL;
if (!isinstance) {
PyErr_Format(PyExc_TypeError, "expected %s node, got %.400s",
req_name[mode], _PyType_Name(Py_TYPE(ast)));
if (PyAst_CheckMode(ast, mode) < 0) {
return NULL;
}
@ -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);

42
Python/Python-ast.c generated
View file

@ -18161,11 +18161,35 @@ PyObject* PyAST_mod2obj(mod_ty t)
}
/* 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;
struct ast_state *state = get_ast_state();
if (state == NULL) {
return -1;
}
PyObject *req_type[3];
req_type[0] = state->Module_type;
req_type[1] = state->Expression_type;
req_type[2] = state->Interactive_type;
assert(0 <= mode && mode <= 2);
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;
}
@ -18175,19 +18199,7 @@ mod_ty PyAST_obj2mod(PyObject* ast, PyArena* arena, int mode)
return NULL;
}
PyObject *req_type[3];
req_type[0] = state->Module_type;
req_type[1] = state->Expression_type;
req_type[2] = state->Interactive_type;
assert(0 <= mode && mode <= 2);
isinstance = PyObject_IsInstance(ast, req_type[mode]);
if (isinstance == -1)
return NULL;
if (!isinstance) {
PyErr_Format(PyExc_TypeError, "expected %s node, got %.400s",
req_name[mode], _PyType_Name(Py_TYPE(ast)));
if (PyAst_CheckMode(ast, mode) < 0) {
return NULL;
}

View file

@ -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);
}