]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-128632: fix segfault on nested __classdict__ type param (#128744)
authorTomasz Pytel <tompytel@gmail.com>
Fri, 4 Apr 2025 13:23:35 +0000 (09:23 -0400)
committerGitHub <noreply@github.com>
Fri, 4 Apr 2025 13:23:35 +0000 (06:23 -0700)
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 02d10c1961e28de6dda456b979d8229ebe729a9a..c5408b37fe5629d675c73f877df1832eb39cb1d6 100644 (file)
@@ -2690,6 +2690,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 6fdc0c86c0fe39487ddcbec490a31469d8eb7395..5efaf0d84a1f3185e90e483bb535969fcf0f1227 100644 (file)
@@ -516,7 +516,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);
@@ -527,14 +527,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;
@@ -546,6 +546,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 748bd447dab0ad91953ee4d48e0ecc8a2737c0ec..35c9a0e295c60c6e6450c0a9e289190cbbc9f0e0 100644 (file)
@@ -2569,11 +2569,21 @@ 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);
+        SET_ERROR_LOCATION(st->st_filename, LOCATION(tp));
+        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;
         }
 
@@ -2615,12 +2625,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)) {
             return 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")) {
             return 0;
         }
         break;
@@ -2630,7 +2640,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")) {
             return 0;
         }
         break;
@@ -2640,7 +2650,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")) {
             return 0;
         }
         break;