Skip to content

Commit 33e346d

Browse files
committed
bpo-41088: Extend the AST Validator to validate all identifiers
1 parent 9e27bc0 commit 33e346d

3 files changed

Lines changed: 58 additions & 6 deletions

File tree

Lib/test/test_ast.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,28 @@ def to_tuple(t):
242242

243243
]
244244

245+
def _get_replaced_identifier_trees(identifier="True"):
246+
# Recursively search the given statements and replace
247+
# all 'x's with the given identifier
248+
for statement, name in [
249+
("def x(): pass", "FunctionDef"),
250+
("async def x(): pass", "AsyncFunctionDef"),
251+
("class x: pass", "ClassDef"),
252+
("from a import x", "ImportFrom"),
253+
("from a import b as x", "ImportFrom"),
254+
("from a import b, c, d as x", "ImportFrom"),
255+
("import x", "Import"),
256+
("import a, b, x", "Import"),
257+
("try: pass\nexcept A as x: pass", "ExceptHandler"),
258+
("try: pass\nexcept A as b: pass\nexcept B as x: pass\n", "ExceptHandler"),
259+
]:
260+
tree = ast.parse(statement)
261+
for node in ast.walk(tree):
262+
for field, value in ast.iter_fields(node):
263+
if value == "x":
264+
setattr(node, field, identifier)
265+
yield tree, name
266+
245267
# TODO: expr_context, slice, boolop, operator, unaryop, cmpop, comprehension
246268
# excepthandler, arguments, keywords, alias
247269

@@ -672,9 +694,14 @@ def test_constant_as_name(self):
672694
for constant in "True", "False", "None":
673695
expr = ast.Expression(ast.Name(constant, ast.Load()))
674696
ast.fix_missing_locations(expr)
675-
with self.assertRaisesRegex(ValueError, f"Name node can't be used with '{constant}' constant"):
697+
with self.assertRaisesRegex(ValueError, f"'Name' node can't be used with '{constant}' constant"):
676698
compile(expr, "<test>", "eval")
677699

700+
def test_constant_usage_in_identifier_fields(self):
701+
for tree, node_name in _get_replaced_identifier_trees(identifier="True"):
702+
with self.assertRaisesRegex(ValueError, f"'{node_name}' node can't be used with 'True' constant"):
703+
compile(tree, "<test>", "exec")
704+
678705

679706
class ASTHelpers_Test(unittest.TestCase):
680707
maxDiff = None
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
All identifiers that might crash the interpreter on a debug build is now
2+
validated in the ``PyAST_Validate`` interface (used by :func:`compile`).

Python/ast.c

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ static int validate_stmt(stmt_ty);
2121
static int validate_expr(expr_ty, expr_context_ty);
2222

2323
static int
24-
validate_name(PyObject *name)
24+
validate_name(const char* owner, PyObject *name)
2525
{
2626
assert(PyUnicode_Check(name));
2727
static const char * const forbidden[] = {
@@ -32,7 +32,8 @@ validate_name(PyObject *name)
3232
};
3333
for (int i = 0; forbidden[i] != NULL; i++) {
3434
if (_PyUnicode_EqualToASCIIString(name, forbidden[i])) {
35-
PyErr_Format(PyExc_ValueError, "Name node can't be used with '%s' constant", forbidden[i]);
35+
PyErr_Format(PyExc_ValueError, "'%s' node can't be used with '%s' constant",
36+
owner, forbidden[i]);
3637
return 0;
3738
}
3839
}
@@ -191,7 +192,7 @@ validate_expr(expr_ty exp, expr_context_ty ctx)
191192
actual_ctx = exp->v.Starred.ctx;
192193
break;
193194
case Name_kind:
194-
if (!validate_name(exp->v.Name.id)) {
195+
if (!validate_name("Name", exp->v.Name.id)) {
195196
return 0;
196197
}
197198
actual_ctx = exp->v.Name.ctx;
@@ -332,6 +333,23 @@ validate_nonempty_seq(asdl_seq *seq, const char *what, const char *owner)
332333
return 0;
333334
}
334335

336+
static int
337+
validate_import_names(asdl_seq *seq, const char *what, const char *owner)
338+
{
339+
if (!validate_nonempty_seq(seq, what, owner)) {
340+
return 0;
341+
}
342+
Py_ssize_t i, n = asdl_seq_LEN(seq);
343+
for (i = 0; i < n; i++) {
344+
alias_ty alias = (alias_ty)asdl_seq_GET(seq, i);
345+
if (!validate_name(owner, alias->name) ||
346+
(alias->asname && !validate_name(owner, alias->asname))) {
347+
return 0;
348+
}
349+
}
350+
return 1;
351+
}
352+
335353
static int
336354
validate_assignlist(asdl_seq *targets, expr_context_ty ctx)
337355
{
@@ -352,12 +370,14 @@ validate_stmt(stmt_ty stmt)
352370
switch (stmt->kind) {
353371
case FunctionDef_kind:
354372
return validate_body(stmt->v.FunctionDef.body, "FunctionDef") &&
373+
validate_name("FunctionDef", stmt->v.FunctionDef.name) &&
355374
validate_arguments(stmt->v.FunctionDef.args) &&
356375
validate_exprs(stmt->v.FunctionDef.decorator_list, Load, 0) &&
357376
(!stmt->v.FunctionDef.returns ||
358377
validate_expr(stmt->v.FunctionDef.returns, Load));
359378
case ClassDef_kind:
360379
return validate_body(stmt->v.ClassDef.body, "ClassDef") &&
380+
validate_name("ClassDef", stmt->v.ClassDef.name) &&
361381
validate_exprs(stmt->v.ClassDef.bases, Load, 0) &&
362382
validate_keywords(stmt->v.ClassDef.keywords) &&
363383
validate_exprs(stmt->v.ClassDef.decorator_list, Load, 0);
@@ -447,6 +467,8 @@ validate_stmt(stmt_ty stmt)
447467
excepthandler_ty handler = asdl_seq_GET(stmt->v.Try.handlers, i);
448468
if ((handler->v.ExceptHandler.type &&
449469
!validate_expr(handler->v.ExceptHandler.type, Load)) ||
470+
(handler->v.ExceptHandler.name &&
471+
!validate_name("ExceptHandler", handler->v.ExceptHandler.name)) ||
450472
!validate_body(handler->v.ExceptHandler.body, "ExceptHandler"))
451473
return 0;
452474
}
@@ -458,13 +480,13 @@ validate_stmt(stmt_ty stmt)
458480
return validate_expr(stmt->v.Assert.test, Load) &&
459481
(!stmt->v.Assert.msg || validate_expr(stmt->v.Assert.msg, Load));
460482
case Import_kind:
461-
return validate_nonempty_seq(stmt->v.Import.names, "names", "Import");
483+
return validate_import_names(stmt->v.Import.names, "names", "Import");
462484
case ImportFrom_kind:
463485
if (stmt->v.ImportFrom.level < 0) {
464486
PyErr_SetString(PyExc_ValueError, "Negative ImportFrom level");
465487
return 0;
466488
}
467-
return validate_nonempty_seq(stmt->v.ImportFrom.names, "names", "ImportFrom");
489+
return validate_import_names(stmt->v.ImportFrom.names, "names", "ImportFrom");
468490
case Global_kind:
469491
return validate_nonempty_seq(stmt->v.Global.names, "names", "Global");
470492
case Nonlocal_kind:
@@ -473,6 +495,7 @@ validate_stmt(stmt_ty stmt)
473495
return validate_expr(stmt->v.Expr.value, Load);
474496
case AsyncFunctionDef_kind:
475497
return validate_body(stmt->v.AsyncFunctionDef.body, "AsyncFunctionDef") &&
498+
validate_name("AsyncFunctionDef", stmt->v.AsyncFunctionDef.name) &&
476499
validate_arguments(stmt->v.AsyncFunctionDef.args) &&
477500
validate_exprs(stmt->v.AsyncFunctionDef.decorator_list, Load, 0) &&
478501
(!stmt->v.AsyncFunctionDef.returns ||

0 commit comments

Comments
 (0)