diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index 5338d407ca22596..85fc2b9b0232a88 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -2257,7 +2257,7 @@ and classes for traversing abstract syntax trees: In addition, if ``mode`` is ``'func_type'``, the input syntax is modified to correspond to :pep:`484` "signature type comments", - e.g. ``(str, int) -> List[str]``. + for example ``(str, int) -> List[str]``. Setting ``feature_version`` to a tuple ``(major, minor)`` will result in a "best-effort" attempt to parse using that Python version's grammar. diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py index 5d5967cd9148e65..94ad8f85b17a24c 100644 --- a/Lib/test/test_ast/test_ast.py +++ b/Lib/test/test_ast/test_ast.py @@ -152,6 +152,15 @@ def test_parse_invalid_ast(self): self.assertRaises(TypeError, ast.parse, ast.Constant(42), optimize=optval) + def test_parse_ast_func_type(self): + # see gh-156689 + tree = ast.parse('(int, str) -> bool', mode='func_type') + self.assertEqual(ast.dump(ast.parse(tree, mode='func_type')), + ast.dump(tree)) + self.assertRaises(TypeError, ast.parse, ast.Constant(42), + mode='func_type') + self.assertRaises(TypeError, ast.parse, tree, mode='exec') + def test_optimization_levels__debug__(self): cases = [(-1, '__debug__'), (0, '__debug__'), (1, False), (2, False)] for (optval, expected) in cases: diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-31-10-45-11.gh-issue-156689.Jq3xNv.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-31-10-45-11.gh-issue-156689.Jq3xNv.rst new file mode 100644 index 000000000000000..868997320b2f7cb --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-31-10-45-11.gh-issue-156689.Jq3xNv.rst @@ -0,0 +1,2 @@ +Fix an out-of-bounds read in :func:`compile` and :func:`ast.parse` when an AST +object is passed with ``mode='func_type'``. diff --git a/Parser/asdl_c.py b/Parser/asdl_c.py index 5ad20d49fa4b310..de202aaec8cbbb2 100755 --- a/Parser/asdl_c.py +++ b/Parser/asdl_c.py @@ -2169,22 +2169,25 @@ class PartingShots(StaticVisitor): return result; } -/* mode is 0 for "exec", 1 for "eval" and 2 for "single" input */ +/* mode is 0 for "exec", 1 for "eval", 2 for "single" and 3 for "func_type" + input */ int PyAst_CheckMode(PyObject *ast, int mode) { - const char * const req_name[] = {"Module", "Expression", "Interactive"}; + const char * const req_name[] = {"Module", "Expression", "Interactive", + "FunctionType"}; struct ast_state *state = get_ast_state(); if (state == NULL) { return -1; } - PyObject *req_type[3]; + PyObject *req_type[4]; req_type[0] = state->Module_type; req_type[1] = state->Expression_type; req_type[2] = state->Interactive_type; + req_type[3] = state->FunctionType_type; - assert(0 <= mode && mode <= 2); + assert(0 <= mode && mode <= 3); int isinstance = PyObject_IsInstance(ast, req_type[mode]); if (isinstance == -1) { return -1; diff --git a/Python/Python-ast.c b/Python/Python-ast.c index 1cc88dc179e120d..2dd2f6778d14749 100644 --- a/Python/Python-ast.c +++ b/Python/Python-ast.c @@ -18423,22 +18423,25 @@ PyObject* PyAST_mod2obj(mod_ty t) return result; } -/* mode is 0 for "exec", 1 for "eval" and 2 for "single" input */ +/* mode is 0 for "exec", 1 for "eval", 2 for "single" and 3 for "func_type" + input */ int PyAst_CheckMode(PyObject *ast, int mode) { - const char * const req_name[] = {"Module", "Expression", "Interactive"}; + const char * const req_name[] = {"Module", "Expression", "Interactive", + "FunctionType"}; struct ast_state *state = get_ast_state(); if (state == NULL) { return -1; } - PyObject *req_type[3]; + PyObject *req_type[4]; req_type[0] = state->Module_type; req_type[1] = state->Expression_type; req_type[2] = state->Interactive_type; + req_type[3] = state->FunctionType_type; - assert(0 <= mode && mode <= 2); + assert(0 <= mode && mode <= 3); int isinstance = PyObject_IsInstance(ast, req_type[mode]); if (isinstance == -1) { return -1;