Skip to content

[3.12] gh-128632: fix segfault on nested __classdict__ type param (GH… #132090

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Lib/test/test_syntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -2244,6 +2244,25 @@ def test_continuation_bad_indentation(self):

self.assertRaises(IndentationError, exec, code)

@support.cpython_only
def test_disallowed_type_param_names(self):
# See gh-128632

self._check_error(f"class A[__classdict__]: pass",
f"reserved name '__classdict__' cannot be used for type parameter")
self._check_error(f"def f[__classdict__](): pass",
f"reserved name '__classdict__' cannot be used for type parameter")
self._check_error(f"type T[__classdict__] = tuple[__classdict__]",
f"reserved name '__classdict__' cannot be used for type parameter")

# These compilations are here to make sure __class__, __classcell__ and __classdictcell__
# don't break in the future like __classdict__ did in this case.
for name in ('__class__', '__classcell__', '__classdictcell__'):
compile(f"""
class A:
class B[{name}]: pass
""", "<testcase>", mode="exec")

@support.cpython_only
def test_nested_named_except_blocks(self):
code = ""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Disallow ``__classdict__`` as the name of a type parameter. Using this
name would previously crash the interpreter in some circumstances.
24 changes: 24 additions & 0 deletions Python/symtable.c
Original file line number Diff line number Diff line change
Expand Up @@ -2191,6 +2191,24 @@ symtable_visit_expr(struct symtable *st, expr_ty e)
VISIT_QUIT(st, 1);
}

static int
symtable_visit_type_param_check_reserved_name(struct symtable *st, type_param_ty tp, identifier name)
{
if (_PyUnicode_Equal(name, &_Py_ID(__classdict__))) {
PyObject *error_msg = PyUnicode_FromFormat("reserved name '%U' cannot be "
"used for type parameter", name);
PyErr_SetObject(PyExc_SyntaxError, error_msg);
Py_DECREF(error_msg);
PyErr_RangedSyntaxLocationObject(st->st_filename,
tp->lineno,
tp->col_offset + 1,
tp->end_lineno,
tp->end_col_offset + 1);
return 0;
}
return 1;
}

static int
symtable_visit_type_param(struct symtable *st, type_param_ty tp)
{
Expand All @@ -2201,6 +2219,8 @@ symtable_visit_type_param(struct symtable *st, type_param_ty tp)
}
switch(tp->kind) {
case TypeVar_kind:
if (!symtable_visit_type_param_check_reserved_name(st, tp, tp->v.TypeVar.name))
VISIT_QUIT(st, 0);
if (!symtable_add_def(st, tp->v.TypeVar.name, DEF_TYPE_PARAM | DEF_LOCAL, LOCATION(tp)))
VISIT_QUIT(st, 0);
if (tp->v.TypeVar.bound) {
Expand All @@ -2219,10 +2239,14 @@ symtable_visit_type_param(struct symtable *st, type_param_ty tp)
}
break;
case TypeVarTuple_kind:
if (!symtable_visit_type_param_check_reserved_name(st, tp, tp->v.TypeVarTuple.name))
VISIT_QUIT(st, 0);
if (!symtable_add_def(st, tp->v.TypeVarTuple.name, DEF_TYPE_PARAM | DEF_LOCAL, LOCATION(tp)))
VISIT_QUIT(st, 0);
break;
case ParamSpec_kind:
if (!symtable_visit_type_param_check_reserved_name(st, tp, tp->v.ParamSpec.name))
VISIT_QUIT(st, 0);
if (!symtable_add_def(st, tp->v.ParamSpec.name, DEF_TYPE_PARAM | DEF_LOCAL, LOCATION(tp)))
VISIT_QUIT(st, 0);
break;
Expand Down
Loading