]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-128632: fix segfault on nested __classdict__ type param (GH-128744) (#132085)
authorTomasz Pytel <tompytel@gmail.com>
Fri, 4 Apr 2025 15:23:40 +0000 (11:23 -0400)
committerGitHub <noreply@github.com>
Fri, 4 Apr 2025 15:23:40 +0000 (08:23 -0700)
(cherry picked from commit 891c61c1fa480928dd60cce8bbc8764630c95025)

Co-authored-by: Tomasz Pytel <tompytel@gmail.com>
Lib/test/test_syntax.py
Misc/NEWS.d/next/Core_and_Builtins/2025-01-11-20-11-28.gh-issue-128632.ryhnKs.rst [new file with mode: 0644]
Python/assemble.c
Python/symtable.c

index cdeb26adf34d8987919da264acb7dd1ffaa00f9f..68dceac97b53dc79bd848d89682ef8b7e4b5f46d 100644 (file)
@@ -2470,6 +2470,25 @@ if x:
 
         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 (file)
index 0000000..8cb23fc
--- /dev/null
@@ -0,0 +1,2 @@
+Disallow ``__classdict__`` as the name of a type parameter. Using this
+name would previously crash the interpreter in some circumstances.
index 671bc7caf17ba3629ded67dc62635d5b04f18969..98816aec78322764ad8b317e4a800a2410acaf9c 100644 (file)
@@ -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;
index 65dcf6746d8f69f262ded9ec382369b6e6dbb8ba..5322c5e9597c74ffd6ce88b0171950b57ba428bf 100644 (file)
@@ -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;