gh-156689: Fix out-of-bounds read in `PyAst_CheckMode()` for `mode='func_type'` (#156697)
diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst
index ab2a668..e30dadc 100644
--- a/Doc/library/ast.rst
+++ b/Doc/library/ast.rst
@@ -2254,7 +2254,7 @@
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 7d35fc4..92ef9c6 100644
--- a/Lib/test/test_ast/test_ast.py
+++ b/Lib/test/test_ast/test_ast.py
@@ -162,6 +162,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 0000000..8689973
--- /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 d538868..b2581e4 100755
--- a/Parser/asdl_c.py
+++ b/Parser/asdl_c.py
@@ -2122,22 +2122,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 f36072d..383384f 100644
--- a/Python/Python-ast.c
+++ b/Python/Python-ast.c
@@ -18549,22 +18549,25 @@
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;