From b5732028d52332784b3b8cca95b3447c2658ab0d Mon Sep 17 00:00:00 2001 From: Tomasz Pytel Date: Fri, 4 Apr 2025 09:23:35 -0400 Subject: [PATCH 1/2] [3.12] gh-128632: fix segfault on nested __classdict__ type param (GH-128744) (cherry picked from commit 891c61c1fa480928dd60cce8bbc8764630c95025) Co-authored-by: Tomasz Pytel --- Lib/test/test_syntax.py | 19 +++++++++++++++ ...-01-11-20-11-28.gh-issue-128632.ryhnKs.rst | 2 ++ Python/assemble.c | 16 ++++++++----- Python/symtable.c | 24 +++++++++++++++++++ 4 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-01-11-20-11-28.gh-issue-128632.ryhnKs.rst diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 099667c73561519..36a7f5a873dc8f8 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -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 + """, "", mode="exec") + @support.cpython_only def test_nested_named_except_blocks(self): code = "" diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-01-11-20-11-28.gh-issue-128632.ryhnKs.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-11-20-11-28.gh-issue-128632.ryhnKs.rst new file mode 100644 index 000000000000000..8cb23fc2d9e78e5 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-11-20-11-28.gh-issue-128632.ryhnKs.rst @@ -0,0 +1,2 @@ +Disallow ``__classdict__`` as the name of a type parameter. Using this +name would previously crash the interpreter in some circumstances. diff --git a/Python/assemble.c b/Python/assemble.c index 4aa922848f23aa5..f6efd0585e8342d 100644 --- a/Python/assemble.c +++ b/Python/assemble.c @@ -467,7 +467,7 @@ compute_localsplus_info(_PyCompile_CodeUnitMetadata *umd, int nlocalsplus, int nlocals = (int)PyDict_GET_SIZE(umd->u_varnames); // This counter mirrors the fix done in fix_cell_offsets(). - int numdropped = 0; + int numdropped = 0, cellvar_offset = -1; pos = 0; while (PyDict_Next(umd->u_cellvars, &pos, &k, &v)) { if (PyDict_GetItem(umd->u_varnames, k) != NULL) { @@ -475,11 +475,11 @@ compute_localsplus_info(_PyCompile_CodeUnitMetadata *umd, int nlocalsplus, numdropped += 1; continue; } - int offset = (int)PyLong_AS_LONG(v); - assert(offset >= 0); - offset += nlocals - numdropped; - assert(offset < nlocalsplus); - _Py_set_localsplus_info(offset, k, CO_FAST_CELL, names, kinds); + int cellvar_offset = (int)PyLong_AS_LONG(v); + assert(cellvar_offset >= 0); + cellvar_offset += nlocals - numdropped; + assert(cellvar_offset < nlocalsplus); + _Py_set_localsplus_info(cellvar_offset, k, CO_FAST_CELL, names, kinds); } pos = 0; @@ -488,6 +488,10 @@ compute_localsplus_info(_PyCompile_CodeUnitMetadata *umd, int nlocalsplus, assert(offset >= 0); offset += nlocals - numdropped; assert(offset < nlocalsplus); + /* XXX If the assertion below fails it is most likely because a freevar + was added to u_freevars with the wrong index due to not taking into + account cellvars already present, see gh-128632. */ + assert(offset > cellvar_offset); _Py_set_localsplus_info(offset, k, CO_FAST_FREE, names, kinds); } } diff --git a/Python/symtable.c b/Python/symtable.c index f99ca4fdd06fa27..da17485acdd4117 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -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) { @@ -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) { @@ -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; From c1b9e8891243cd9cfefd98f016a9fbc53d940af9 Mon Sep 17 00:00:00 2001 From: Tomasz Pytel Date: Fri, 4 Apr 2025 11:32:13 -0400 Subject: [PATCH 2/2] undo changes to assemble.c --- Python/assemble.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/Python/assemble.c b/Python/assemble.c index f6efd0585e8342d..4aa922848f23aa5 100644 --- a/Python/assemble.c +++ b/Python/assemble.c @@ -467,7 +467,7 @@ compute_localsplus_info(_PyCompile_CodeUnitMetadata *umd, int nlocalsplus, int nlocals = (int)PyDict_GET_SIZE(umd->u_varnames); // This counter mirrors the fix done in fix_cell_offsets(). - int numdropped = 0, cellvar_offset = -1; + int numdropped = 0; pos = 0; while (PyDict_Next(umd->u_cellvars, &pos, &k, &v)) { if (PyDict_GetItem(umd->u_varnames, k) != NULL) { @@ -475,11 +475,11 @@ compute_localsplus_info(_PyCompile_CodeUnitMetadata *umd, int nlocalsplus, numdropped += 1; continue; } - int cellvar_offset = (int)PyLong_AS_LONG(v); - assert(cellvar_offset >= 0); - cellvar_offset += nlocals - numdropped; - assert(cellvar_offset < nlocalsplus); - _Py_set_localsplus_info(cellvar_offset, k, CO_FAST_CELL, names, kinds); + int offset = (int)PyLong_AS_LONG(v); + assert(offset >= 0); + offset += nlocals - numdropped; + assert(offset < nlocalsplus); + _Py_set_localsplus_info(offset, k, CO_FAST_CELL, names, kinds); } pos = 0; @@ -488,10 +488,6 @@ compute_localsplus_info(_PyCompile_CodeUnitMetadata *umd, int nlocalsplus, assert(offset >= 0); offset += nlocals - numdropped; assert(offset < nlocalsplus); - /* XXX If the assertion below fails it is most likely because a freevar - was added to u_freevars with the wrong index due to not taking into - account cellvars already present, see gh-128632. */ - assert(offset > cellvar_offset); _Py_set_localsplus_info(offset, k, CO_FAST_FREE, names, kinds); } }