https://github.com/python/cpython/commit/47e21752b550f1715ec25547fd326fac8d675a96
commit: 47e21752b550f1715ec25547fd326fac8d675a96
branch: main
author: Batuhan Taskaya <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-13T08:28:05+03:00
summary:
gh-85260: Extend the AST Validator to validate all identifiers (GH-21069)
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2020-06-23-13-59-37.gh-issue-85260.o_LJ76.rst
M Lib/test/test_ast/test_ast.py
M Python/ast.c
diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py
index 28ac6c6fcbccc1f..7d35fc4ef7c3644 100644
--- a/Lib/test/test_ast/test_ast.py
+++ b/Lib/test/test_ast/test_ast.py
@@ -983,6 +983,34 @@ def test_constant_as_name(self):
with self.assertRaisesRegex(ValueError, f"identifier field can't
represent '{constant}' constant"):
compile(expr, "<test>", "eval")
+ def test_constant_in_identifier_fields(self):
+ # gh-85260: an identifier field holding a constant name used to
+ # crash the compiler
+ for statement in [
+ "def x(): pass",
+ "async def x(): pass",
+ "class x: pass",
+ "from a import x",
+ "from a import b as x",
+ "from a import b, c, d as x",
+ "import x",
+ "import a, b, x",
+ "try: pass\nexcept A as x: pass",
+ "try: pass\nexcept A as b: pass\nexcept B as x: pass\n",
+ ]:
+ for constant in "True", "False", "None":
+ with self.subTest(statement=statement, constant=constant):
+ tree = ast.parse(statement)
+ for node in ast.walk(tree):
+ for field, value in ast.iter_fields(node):
+ if value == "x":
+ setattr(node, field, constant)
+ with self.assertRaisesRegex(
+ ValueError,
+ f"identifier field can't represent "
+ f"'{constant}' constant"):
+ compile(tree, "<test>", "exec")
+
def test_constant_as_unicode_name(self):
constants = [
("True", b"Tru\xe1\xb5\x89"),
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2020-06-23-13-59-37.gh-issue-85260.o_LJ76.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2020-06-23-13-59-37.gh-issue-85260.o_LJ76.rst
new file mode 100644
index 000000000000000..d156efe4ca24144
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2020-06-23-13-59-37.gh-issue-85260.o_LJ76.rst
@@ -0,0 +1,4 @@
+:func:`compile` now raises :exc:`ValueError` instead of crashing on a debug
+build if an identifier field of an AST node (such as the name of a function,
+a class, an imported module or a caught exception) is ``"None"``, ``"True"``
+or ``"False"``.
diff --git a/Python/ast.c b/Python/ast.c
index 4cfa2ff559a5f7d..f625c59fe5bffc3 100644
--- a/Python/ast.c
+++ b/Python/ast.c
@@ -710,6 +710,23 @@ _validate_nonempty_seq(asdl_seq *seq, const char *what,
const char *owner)
}
#define validate_nonempty_seq(seq, what, owner)
_validate_nonempty_seq((asdl_seq*)seq, what, owner)
+static int
+validate_import_names(asdl_alias_seq *seq, const char *what, const char *owner)
+{
+ if (!validate_nonempty_seq(seq, what, owner)) {
+ return 0;
+ }
+ Py_ssize_t n = asdl_seq_LEN(seq);
+ for (Py_ssize_t i = 0; i < n; i++) {
+ alias_ty alias = asdl_seq_GET(seq, i);
+ if (!validate_name(alias->name) ||
+ (alias->asname && !validate_name(alias->asname))) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
static int
validate_assignlist(asdl_expr_seq *targets, expr_context_ty ctx)
{
@@ -735,6 +752,7 @@ validate_stmt(stmt_ty stmt)
switch (stmt->kind) {
case FunctionDef_kind:
ret = validate_body(stmt->v.FunctionDef.body, "FunctionDef") &&
+ validate_name(stmt->v.FunctionDef.name) &&
validate_type_params(stmt->v.FunctionDef.type_params) &&
validate_arguments(stmt->v.FunctionDef.args) &&
validate_exprs(stmt->v.FunctionDef.decorator_list, Load, 0) &&
@@ -743,6 +761,7 @@ validate_stmt(stmt_ty stmt)
break;
case ClassDef_kind:
ret = validate_body(stmt->v.ClassDef.body, "ClassDef") &&
+ validate_name(stmt->v.ClassDef.name) &&
validate_type_params(stmt->v.ClassDef.type_params) &&
validate_exprs(stmt->v.ClassDef.bases, Load, 0) &&
validate_keywords(stmt->v.ClassDef.keywords) &&
@@ -873,6 +892,8 @@ validate_stmt(stmt_ty stmt)
VALIDATE_POSITIONS(handler);
if ((handler->v.ExceptHandler.type &&
!validate_expr(handler->v.ExceptHandler.type, Load)) ||
+ (handler->v.ExceptHandler.name &&
+ !validate_name(handler->v.ExceptHandler.name)) ||
!validate_body(handler->v.ExceptHandler.body, "ExceptHandler"))
return 0;
}
@@ -911,14 +932,14 @@ validate_stmt(stmt_ty stmt)
(!stmt->v.Assert.msg || validate_expr(stmt->v.Assert.msg, Load));
break;
case Import_kind:
- ret = validate_nonempty_seq(stmt->v.Import.names, "names", "Import");
+ ret = validate_import_names(stmt->v.Import.names, "names", "Import");
break;
case ImportFrom_kind:
if (stmt->v.ImportFrom.level < 0) {
PyErr_SetString(PyExc_ValueError, "Negative ImportFrom level");
return 0;
}
- ret = validate_nonempty_seq(stmt->v.ImportFrom.names, "names",
"ImportFrom");
+ ret = validate_import_names(stmt->v.ImportFrom.names, "names",
"ImportFrom");
break;
case Global_kind:
ret = validate_nonempty_seq(stmt->v.Global.names, "names", "Global");
@@ -931,6 +952,7 @@ validate_stmt(stmt_ty stmt)
break;
case AsyncFunctionDef_kind:
ret = validate_body(stmt->v.AsyncFunctionDef.body, "AsyncFunctionDef")
&&
+ validate_name(stmt->v.AsyncFunctionDef.name) &&
validate_type_params(stmt->v.AsyncFunctionDef.type_params) &&
validate_arguments(stmt->v.AsyncFunctionDef.args) &&
validate_exprs(stmt->v.AsyncFunctionDef.decorator_list, Load, 0) &&
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]