https://github.com/python/cpython/commit/900dc2b0340fdd77314b69780dbe1397067b2104
commit: 900dc2b0340fdd77314b69780dbe1397067b2104
branch: 3.13
author: Tomasz Pytel <tompy...@gmail.com>
committer: JelleZijlstra <jelle.zijls...@gmail.com>
date: 2025-04-04T08:23:40-07:00
summary:

[3.13] gh-128632: fix segfault on nested __classdict__ type param (GH-128744) 
(#132085)

(cherry picked from commit 891c61c1fa480928dd60cce8bbc8764630c95025)

Co-authored-by: Tomasz Pytel <tompy...@gmail.com>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2025-01-11-20-11-28.gh-issue-128632.ryhnKs.rst
M Lib/test/test_syntax.py
M Python/assemble.c
M Python/symtable.c

diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py
index cdeb26adf34d89..68dceac97b53dc 100644
--- a/Lib/test/test_syntax.py
+++ b/Lib/test/test_syntax.py
@@ -2470,6 +2470,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 = ""
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 00000000000000..8cb23fc2d9e78e
--- /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 671bc7caf17ba3..98816aec783227 100644
--- a/Python/assemble.c
+++ b/Python/assemble.c
@@ -503,7 +503,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)) {
         int has_name = PyDict_Contains(umd->u_varnames, k);
@@ -514,14 +514,14 @@ compute_localsplus_info(_PyCompile_CodeUnitMetadata *umd, 
int nlocalsplus,
             continue;
         }
 
-        int offset = PyLong_AsInt(v);
-        if (offset == -1 && PyErr_Occurred()) {
+        cellvar_offset = PyLong_AsInt(v);
+        if (cellvar_offset == -1 && PyErr_Occurred()) {
             return ERROR;
         }
-        assert(offset >= 0);
-        offset += nlocals - numdropped;
-        assert(offset < nlocalsplus);
-        _Py_set_localsplus_info(offset, k, CO_FAST_CELL, names, kinds);
+        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;
@@ -533,6 +533,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);
     }
     return SUCCESS;
diff --git a/Python/symtable.c b/Python/symtable.c
index 65dcf6746d8f69..5322c5e9597c74 100644
--- a/Python/symtable.c
+++ b/Python/symtable.c
@@ -2292,12 +2292,27 @@ symtable_visit_expr(struct symtable *st, expr_ty e)
 static int
 symtable_visit_type_param_bound_or_default(
     struct symtable *st, expr_ty e, identifier name,
-    void *key, const char *ste_scope_info)
+    type_param_ty tp, const char *ste_scope_info)
 {
+    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;
+    }
+
     if (e) {
         int is_in_class = st->st_cur->ste_can_see_class_scope;
-        if (!symtable_enter_block(st, name, TypeVariableBlock, key, 
LOCATION(e)))
+        if (!symtable_enter_block(st, name, TypeVariableBlock, (void *)tp, 
LOCATION(e))) {
             return 0;
+        }
 
         st->st_cur->ste_can_see_class_scope = is_in_class;
         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)
         // The only requirement for the key is that it is unique and it 
matches the logic in
         // compile.c where the scope is retrieved.
         if (!symtable_visit_type_param_bound_or_default(st, 
tp->v.TypeVar.bound, tp->v.TypeVar.name,
-                                                        (void *)tp, 
ste_scope_info)) {
+                                                        tp, ste_scope_info)) {
             VISIT_QUIT(st, 0);
         }
 
         if (!symtable_visit_type_param_bound_or_default(st, 
tp->v.TypeVar.default_value, tp->v.TypeVar.name,
-                                                        (void *)((uintptr_t)tp 
+ 1), "a TypeVar default")) {
+                                                        
(type_param_ty)((uintptr_t)tp + 1), "a TypeVar default")) {
             VISIT_QUIT(st, 0);
         }
         break;
@@ -2356,7 +2371,7 @@ symtable_visit_type_param(struct symtable *st, 
type_param_ty tp)
         }
 
         if (!symtable_visit_type_param_bound_or_default(st, 
tp->v.TypeVarTuple.default_value, tp->v.TypeVarTuple.name,
-                                                        (void *)tp, "a 
TypeVarTuple default")) {
+                                                        tp, "a TypeVarTuple 
default")) {
             VISIT_QUIT(st, 0);
         }
         break;
@@ -2366,7 +2381,7 @@ symtable_visit_type_param(struct symtable *st, 
type_param_ty tp)
         }
 
         if (!symtable_visit_type_param_bound_or_default(st, 
tp->v.ParamSpec.default_value, tp->v.ParamSpec.name,
-                                                        (void *)tp, "a 
ParamSpec default")) {
+                                                        tp, "a ParamSpec 
default")) {
             VISIT_QUIT(st, 0);
         }
         break;

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to