Skip to content

Commit 900dc2b

Browse files
authored
[3.13] gh-128632: fix segfault on nested __classdict__ type param (GH-128744) (#132085)
(cherry picked from commit 891c61c) Co-authored-by: Tomasz Pytel <[email protected]>
1 parent d8986b7 commit 900dc2b

File tree

4 files changed

+53
-13
lines changed

4 files changed

+53
-13
lines changed

Lib/test/test_syntax.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2470,6 +2470,25 @@ def test_continuation_bad_indentation(self):
24702470

24712471
self.assertRaises(IndentationError, exec, code)
24722472

2473+
@support.cpython_only
2474+
def test_disallowed_type_param_names(self):
2475+
# See gh-128632
2476+
2477+
self._check_error(f"class A[__classdict__]: pass",
2478+
f"reserved name '__classdict__' cannot be used for type parameter")
2479+
self._check_error(f"def f[__classdict__](): pass",
2480+
f"reserved name '__classdict__' cannot be used for type parameter")
2481+
self._check_error(f"type T[__classdict__] = tuple[__classdict__]",
2482+
f"reserved name '__classdict__' cannot be used for type parameter")
2483+
2484+
# These compilations are here to make sure __class__, __classcell__ and __classdictcell__
2485+
# don't break in the future like __classdict__ did in this case.
2486+
for name in ('__class__', '__classcell__', '__classdictcell__'):
2487+
compile(f"""
2488+
class A:
2489+
class B[{name}]: pass
2490+
""", "<testcase>", mode="exec")
2491+
24732492
@support.cpython_only
24742493
def test_nested_named_except_blocks(self):
24752494
code = ""
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Disallow ``__classdict__`` as the name of a type parameter. Using this
2+
name would previously crash the interpreter in some circumstances.

Python/assemble.c

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ compute_localsplus_info(_PyCompile_CodeUnitMetadata *umd, int nlocalsplus,
503503
int nlocals = (int)PyDict_GET_SIZE(umd->u_varnames);
504504

505505
// This counter mirrors the fix done in fix_cell_offsets().
506-
int numdropped = 0;
506+
int numdropped = 0, cellvar_offset = -1;
507507
pos = 0;
508508
while (PyDict_Next(umd->u_cellvars, &pos, &k, &v)) {
509509
int has_name = PyDict_Contains(umd->u_varnames, k);
@@ -514,14 +514,14 @@ compute_localsplus_info(_PyCompile_CodeUnitMetadata *umd, int nlocalsplus,
514514
continue;
515515
}
516516

517-
int offset = PyLong_AsInt(v);
518-
if (offset == -1 && PyErr_Occurred()) {
517+
cellvar_offset = PyLong_AsInt(v);
518+
if (cellvar_offset == -1 && PyErr_Occurred()) {
519519
return ERROR;
520520
}
521-
assert(offset >= 0);
522-
offset += nlocals - numdropped;
523-
assert(offset < nlocalsplus);
524-
_Py_set_localsplus_info(offset, k, CO_FAST_CELL, names, kinds);
521+
assert(cellvar_offset >= 0);
522+
cellvar_offset += nlocals - numdropped;
523+
assert(cellvar_offset < nlocalsplus);
524+
_Py_set_localsplus_info(cellvar_offset, k, CO_FAST_CELL, names, kinds);
525525
}
526526

527527
pos = 0;
@@ -533,6 +533,10 @@ compute_localsplus_info(_PyCompile_CodeUnitMetadata *umd, int nlocalsplus,
533533
assert(offset >= 0);
534534
offset += nlocals - numdropped;
535535
assert(offset < nlocalsplus);
536+
/* XXX If the assertion below fails it is most likely because a freevar
537+
was added to u_freevars with the wrong index due to not taking into
538+
account cellvars already present, see gh-128632. */
539+
assert(offset > cellvar_offset);
536540
_Py_set_localsplus_info(offset, k, CO_FAST_FREE, names, kinds);
537541
}
538542
return SUCCESS;

Python/symtable.c

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2292,12 +2292,27 @@ symtable_visit_expr(struct symtable *st, expr_ty e)
22922292
static int
22932293
symtable_visit_type_param_bound_or_default(
22942294
struct symtable *st, expr_ty e, identifier name,
2295-
void *key, const char *ste_scope_info)
2295+
type_param_ty tp, const char *ste_scope_info)
22962296
{
2297+
if (_PyUnicode_Equal(name, &_Py_ID(__classdict__))) {
2298+
2299+
PyObject *error_msg = PyUnicode_FromFormat("reserved name '%U' cannot be "
2300+
"used for type parameter", name);
2301+
PyErr_SetObject(PyExc_SyntaxError, error_msg);
2302+
Py_DECREF(error_msg);
2303+
PyErr_RangedSyntaxLocationObject(st->st_filename,
2304+
tp->lineno,
2305+
tp->col_offset + 1,
2306+
tp->end_lineno,
2307+
tp->end_col_offset + 1);
2308+
return 0;
2309+
}
2310+
22972311
if (e) {
22982312
int is_in_class = st->st_cur->ste_can_see_class_scope;
2299-
if (!symtable_enter_block(st, name, TypeVariableBlock, key, LOCATION(e)))
2313+
if (!symtable_enter_block(st, name, TypeVariableBlock, (void *)tp, LOCATION(e))) {
23002314
return 0;
2315+
}
23012316

23022317
st->st_cur->ste_can_see_class_scope = is_in_class;
23032318
if (is_in_class && !symtable_add_def(st, &_Py_ID(__classdict__), USE, LOCATION(e))) {
@@ -2341,12 +2356,12 @@ symtable_visit_type_param(struct symtable *st, type_param_ty tp)
23412356
// The only requirement for the key is that it is unique and it matches the logic in
23422357
// compile.c where the scope is retrieved.
23432358
if (!symtable_visit_type_param_bound_or_default(st, tp->v.TypeVar.bound, tp->v.TypeVar.name,
2344-
(void *)tp, ste_scope_info)) {
2359+
tp, ste_scope_info)) {
23452360
VISIT_QUIT(st, 0);
23462361
}
23472362

23482363
if (!symtable_visit_type_param_bound_or_default(st, tp->v.TypeVar.default_value, tp->v.TypeVar.name,
2349-
(void *)((uintptr_t)tp + 1), "a TypeVar default")) {
2364+
(type_param_ty)((uintptr_t)tp + 1), "a TypeVar default")) {
23502365
VISIT_QUIT(st, 0);
23512366
}
23522367
break;
@@ -2356,7 +2371,7 @@ symtable_visit_type_param(struct symtable *st, type_param_ty tp)
23562371
}
23572372

23582373
if (!symtable_visit_type_param_bound_or_default(st, tp->v.TypeVarTuple.default_value, tp->v.TypeVarTuple.name,
2359-
(void *)tp, "a TypeVarTuple default")) {
2374+
tp, "a TypeVarTuple default")) {
23602375
VISIT_QUIT(st, 0);
23612376
}
23622377
break;
@@ -2366,7 +2381,7 @@ symtable_visit_type_param(struct symtable *st, type_param_ty tp)
23662381
}
23672382

23682383
if (!symtable_visit_type_param_bound_or_default(st, tp->v.ParamSpec.default_value, tp->v.ParamSpec.name,
2369-
(void *)tp, "a ParamSpec default")) {
2384+
tp, "a ParamSpec default")) {
23702385
VISIT_QUIT(st, 0);
23712386
}
23722387
break;

0 commit comments

Comments
 (0)