]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.12] gh-119311: Fix name mangling with PEP 695 generic classes (#119464) (#119644)
authorJelle Zijlstra <jelle.zijlstra@gmail.com>
Tue, 4 Jun 2024 19:55:45 +0000 (12:55 -0700)
committerGitHub <noreply@github.com>
Tue, 4 Jun 2024 19:55:45 +0000 (19:55 +0000)
* [3.12] gh-119311: Fix name mangling with PEP 695 generic classes (#119464)

Fixes #119311. Fixes #119395.

(cherry picked from commit a9a74da4a0ca0645f049e67b6434a95e30592c32)

Doc/data/python3.12.abi
Include/internal/pycore_symtable.h
Lib/test/test_type_params.py
Misc/NEWS.d/next/Core and Builtins/2024-05-23-06-34-14.gh-issue-119395.z-Hsqb.rst [new file with mode: 0644]
Misc/NEWS.d/next/Core and Builtins/2024-05-23-06-34-45.gh-issue-119311.2DBwKR.rst [new file with mode: 0644]
Python/compile.c
Python/symtable.c

index 93dc9dbc71174a7501fd7271e403153d571dc775..95dfe64acdb8a9fbbb7f653d8de477c723bde250 100644 (file)
         <var-decl name='recursion_limit' type-id='type-id-8' visibility='default' filepath='./Include/internal/pycore_symtable.h' line='50' column='1'/>\r
       </data-member>\r
     </class-decl>\r
-    <class-decl name='_symtable_entry' size-in-bits='960' is-struct='yes' visibility='default' filepath='./Include/internal/pycore_symtable.h' line='53' column='1' id='type-id-1397'>\r
+    <class-decl name='_symtable_entry' size-in-bits='1024' is-struct='yes' visibility='default' filepath='./Include/internal/pycore_symtable.h' line='53' column='1' id='type-id-1397'>\r
       <data-member access='public' layout-offset-in-bits='0'>\r
         <var-decl name='ob_base' type-id='type-id-345' visibility='default' filepath='./Include/internal/pycore_symtable.h' line='54' column='1'/>\r
       </data-member>\r
       <data-member access='public' layout-offset-in-bits='896'>\r
         <var-decl name='ste_table' type-id='type-id-209' visibility='default' filepath='./Include/internal/pycore_symtable.h' line='89' column='1'/>\r
       </data-member>\r
+      <data-member access='public' layout-offset-in-bits='960'>\r
+        <var-decl name='ste_mangled_names' type-id='type-id-2' visibility='default' filepath='./Include/internal/pycore_symtable.h' line='90' column='1'/>\r
+      </data-member>\r
     </class-decl>\r
     <typedef-decl name='PySTEntryObject' type-id='type-id-1397' filepath='./Include/internal/pycore_symtable.h' line='90' column='1' id='type-id-1398'/>\r
     <typedef-decl name='basicblock' type-id='type-id-1386' filepath='Python/compile.c' line='90' column='1' id='type-id-1399'/>\r
index b2fef177204ec68906310b1b8739a415849d7f6d..3ff5c33efe50f04fbad5f55798b0de559ac1e9b4 100644 (file)
@@ -87,6 +87,7 @@ typedef struct _symtable_entry {
     int ste_opt_lineno;      /* lineno of last exec or import * */
     int ste_opt_col_offset;  /* offset of last exec or import * */
     struct symtable *ste_table;
+    PyObject *ste_mangled_names; /* set of names for which mangling should be applied */
 } PySTEntryObject;
 
 extern PyTypeObject PySTEntry_Type;
@@ -105,6 +106,7 @@ PyAPI_FUNC(PySTEntryObject *) PySymtable_Lookup(struct symtable *, void *);
 
 extern void _PySymtable_Free(struct symtable *);
 
+extern PyObject *_Py_MaybeMangle(PyObject *privateobj, PySTEntryObject *ste, PyObject *name);
 extern PyObject* _Py_Mangle(PyObject *p, PyObject *name);
 
 /* Flags for def-use information */
index ad5339ef4483ee349d1123aebefdf017c17ca15e..eae91690ce6eeb7b1f18bfafb9be69ee6bb5da52 100644 (file)
@@ -760,6 +760,100 @@ class TypeParamsManglingTest(unittest.TestCase):
 
         self.assertEqual(Foo.Alias.__value__, (T, V))
 
+    def test_no_leaky_mangling_in_module(self):
+        ns = run_code("""
+            __before = "before"
+            class X[T]: pass
+            __after = "after"
+        """)
+        self.assertEqual(ns["__before"], "before")
+        self.assertEqual(ns["__after"], "after")
+
+    def test_no_leaky_mangling_in_function(self):
+        ns = run_code("""
+            def f():
+                class X[T]: pass
+                _X_foo = 2
+                __foo = 1
+                assert locals()['__foo'] == 1
+                return __foo
+        """)
+        self.assertEqual(ns["f"](), 1)
+
+    def test_no_leaky_mangling_in_class(self):
+        ns = run_code("""
+            class Outer:
+                __before = "before"
+                class Inner[T]:
+                    __x = "inner"
+                __after = "after"
+        """)
+        Outer = ns["Outer"]
+        self.assertEqual(Outer._Outer__before, "before")
+        self.assertEqual(Outer.Inner._Inner__x, "inner")
+        self.assertEqual(Outer._Outer__after, "after")
+
+    def test_no_mangling_in_bases(self):
+        ns = run_code("""
+            class __Base:
+                def __init_subclass__(self, **kwargs):
+                    self.kwargs = kwargs
+
+            class Derived[T](__Base, __kwarg=1):
+                pass
+        """)
+        Derived = ns["Derived"]
+        self.assertEqual(Derived.__bases__, (ns["__Base"], Generic))
+        self.assertEqual(Derived.kwargs, {"__kwarg": 1})
+
+    def test_no_mangling_in_nested_scopes(self):
+        ns = run_code("""
+            from test.test_type_params import make_base
+
+            class __X:
+                pass
+
+            class Y[T: __X](
+                make_base(lambda: __X),
+                # doubly nested scope
+                make_base(lambda: (lambda: __X)),
+                # list comprehension
+                make_base([__X for _ in (1,)]),
+                # genexp
+                make_base(__X for _ in (1,)),
+            ):
+                pass
+        """)
+        Y = ns["Y"]
+        T, = Y.__type_params__
+        self.assertIs(T.__bound__, ns["__X"])
+        base0 = Y.__bases__[0]
+        self.assertIs(base0.__arg__(), ns["__X"])
+        base1 = Y.__bases__[1]
+        self.assertIs(base1.__arg__()(), ns["__X"])
+        base2 = Y.__bases__[2]
+        self.assertEqual(base2.__arg__, [ns["__X"]])
+        base3 = Y.__bases__[3]
+        self.assertEqual(list(base3.__arg__), [ns["__X"]])
+
+    def test_type_params_are_mangled(self):
+        ns = run_code("""
+            from test.test_type_params import make_base
+
+            class Foo[__T, __U: __T](make_base(__T), make_base(lambda: __T)):
+                param = __T
+        """)
+        Foo = ns["Foo"]
+        T, U = Foo.__type_params__
+        self.assertEqual(T.__name__, "__T")
+        self.assertEqual(U.__name__, "__U")
+        self.assertIs(U.__bound__, T)
+        self.assertIs(Foo.param, T)
+
+        base1, base2, *_ = Foo.__bases__
+        self.assertIs(base1.__arg__, T)
+        self.assertIs(base2.__arg__(), T)
+
 
 class TypeParamsComplexCallsTest(unittest.TestCase):
     def test_defaults(self):
diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-05-23-06-34-14.gh-issue-119395.z-Hsqb.rst b/Misc/NEWS.d/next/Core and Builtins/2024-05-23-06-34-14.gh-issue-119395.z-Hsqb.rst
new file mode 100644 (file)
index 0000000..24cd90a
--- /dev/null
@@ -0,0 +1,2 @@
+Fix bug where names appearing after a generic class are mangled as if they
+are in the generic class.
diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-05-23-06-34-45.gh-issue-119311.2DBwKR.rst b/Misc/NEWS.d/next/Core and Builtins/2024-05-23-06-34-45.gh-issue-119311.2DBwKR.rst
new file mode 100644 (file)
index 0000000..9e0db37
--- /dev/null
@@ -0,0 +1,2 @@
+Fix bug where names are unexpectedly mangled in the bases of generic
+classes.
index 6b7ac195ebe57d0d2822806be2efdb31029703c8..40335f6dc30af6c1a527124923de7c974bc3ef85 100644 (file)
@@ -1071,7 +1071,7 @@ static int
 compiler_addop_name(struct compiler_unit *u, location loc,
                     int opcode, PyObject *dict, PyObject *o)
 {
-    PyObject *mangled = _Py_Mangle(u->u_private, o);
+    PyObject *mangled = _Py_MaybeMangle(u->u_private, u->u_ste, o);
     if (!mangled) {
         return ERROR;
     }
@@ -1889,7 +1889,7 @@ compiler_visit_kwonlydefaults(struct compiler *c, location loc,
         arg_ty arg = asdl_seq_GET(kwonlyargs, i);
         expr_ty default_ = asdl_seq_GET(kw_defaults, i);
         if (default_) {
-            PyObject *mangled = _Py_Mangle(c->u->u_private, arg->arg);
+            PyObject *mangled = _Py_MaybeMangle(c->u->u_private, c->u->u_ste, arg->arg);
             if (!mangled) {
                 goto error;
             }
@@ -1946,7 +1946,7 @@ compiler_visit_argannotation(struct compiler *c, identifier id,
     if (!annotation) {
         return SUCCESS;
     }
-    PyObject *mangled = _Py_Mangle(c->u->u_private, id);
+    PyObject *mangled = _Py_MaybeMangle(c->u->u_private, c->u->u_ste, id);
     if (!mangled) {
         return ERROR;
     }
@@ -2554,7 +2554,6 @@ compiler_class(struct compiler *c, stmt_ty s)
     asdl_type_param_seq *type_params = s->v.ClassDef.type_params;
     int is_generic = asdl_seq_LEN(type_params) > 0;
     if (is_generic) {
-        Py_XSETREF(c->u->u_private, Py_NewRef(s->v.ClassDef.name));
         ADDOP(c, loc, PUSH_NULL);
         PyObject *type_params_name = PyUnicode_FromFormat("<generic parameters of %U>",
                                                          s->v.ClassDef.name);
@@ -2567,6 +2566,7 @@ compiler_class(struct compiler *c, stmt_ty s)
             return ERROR;
         }
         Py_DECREF(type_params_name);
+        Py_XSETREF(c->u->u_private, Py_NewRef(s->v.ClassDef.name));
         RETURN_IF_ERROR_IN_SCOPE(c, compiler_type_params(c, type_params));
         _Py_DECLARE_STR(type_params, ".type_params");
         RETURN_IF_ERROR_IN_SCOPE(c, compiler_nameop(c, loc, &_Py_STR(type_params), Store));
@@ -4123,7 +4123,7 @@ compiler_nameop(struct compiler *c, location loc,
         return ERROR;
     }
 
-    mangled = _Py_Mangle(c->u->u_private, name);
+    mangled = _Py_MaybeMangle(c->u->u_private, c->u->u_ste, name);
     if (!mangled) {
         return ERROR;
     }
@@ -6383,7 +6383,7 @@ compiler_annassign(struct compiler *c, stmt_ty s)
                 VISIT(c, expr, s->v.AnnAssign.annotation);
             }
             ADDOP_NAME(c, loc, LOAD_NAME, &_Py_ID(__annotations__), names);
-            mangled = _Py_Mangle(c->u->u_private, targ->v.Name.id);
+            mangled = _Py_MaybeMangle(c->u->u_private, c->u->u_ste, targ->v.Name.id);
             ADDOP_LOAD_CONST_NEW(c, loc, mangled);
             ADDOP(c, loc, STORE_SUBSCR);
         }
index 65ebdee0d70b1ed240927e516fcd95d1c3a0f67a..ba4284210bb4cc83957c8c002396607133fc54b4 100644 (file)
@@ -101,6 +101,7 @@ ste_new(struct symtable *st, identifier name, _Py_block_ty block,
     ste->ste_children = NULL;
 
     ste->ste_directives = NULL;
+    ste->ste_mangled_names = NULL;
 
     ste->ste_type = block;
     ste->ste_nested = 0;
@@ -164,6 +165,7 @@ ste_dealloc(PySTEntryObject *ste)
     Py_XDECREF(ste->ste_varnames);
     Py_XDECREF(ste->ste_children);
     Py_XDECREF(ste->ste_directives);
+    Py_XDECREF(ste->ste_mangled_names);
     PyObject_Free(ste);
 }
 
@@ -1231,6 +1233,11 @@ symtable_enter_block(struct symtable *st, identifier name, _Py_block_ty block,
     if (prev) {
         ste->ste_comp_iter_expr = prev->ste_comp_iter_expr;
     }
+    /* No need to inherit ste_mangled_names in classes, where all names
+     * are mangled. */
+    if (prev && prev->ste_mangled_names != NULL && block != ClassBlock) {
+        ste->ste_mangled_names = Py_NewRef(prev->ste_mangled_names);
+    }
     /* The entry is owned by the stack. Borrow it for st_cur. */
     Py_DECREF(ste);
     st->st_cur = ste;
@@ -1256,7 +1263,7 @@ symtable_enter_block(struct symtable *st, identifier name, _Py_block_ty block,
 static long
 symtable_lookup_entry(struct symtable *st, PySTEntryObject *ste, PyObject *name)
 {
-    PyObject *mangled = _Py_Mangle(st->st_private, name);
+    PyObject *mangled = _Py_MaybeMangle(st->st_private, ste, name);
     if (!mangled)
         return 0;
     long ret = _PyST_GetSymbol(ste, mangled);
@@ -1277,8 +1284,7 @@ symtable_add_def_helper(struct symtable *st, PyObject *name, int flag, struct _s
     PyObject *o;
     PyObject *dict;
     long val;
-    PyObject *mangled = _Py_Mangle(st->st_private, name);
-
+    PyObject *mangled = _Py_MaybeMangle(st->st_private, st->st_cur, name);
 
     if (!mangled)
         return 0;
@@ -1367,6 +1373,11 @@ static int
 symtable_add_def(struct symtable *st, PyObject *name, int flag,
                  int lineno, int col_offset, int end_lineno, int end_col_offset)
 {
+    if ((flag & DEF_TYPE_PARAM) && st->st_cur->ste_mangled_names != NULL) {
+        if(PySet_Add(st->st_cur->ste_mangled_names, name) < 0) {
+            return 0;
+        }
+    }
     return symtable_add_def_helper(st, name, flag, st->st_cur,
                         lineno, col_offset, end_lineno, end_col_offset);
 }
@@ -1401,7 +1412,6 @@ symtable_enter_type_param_block(struct symtable *st, identifier name,
                               lineno, col_offset, end_lineno, end_col_offset)) {
             return 0;
         }
-        st->st_private = name;
         // This is used for setting the generic base
         _Py_DECLARE_STR(generic_base, ".generic_base");
         if (!symtable_add_def(st, &_Py_STR(generic_base), DEF_LOCAL,
@@ -1490,7 +1500,7 @@ symtable_record_directive(struct symtable *st, identifier name, int lineno,
         if (!st->st_cur->ste_directives)
             return 0;
     }
-    mangled = _Py_Mangle(st->st_private, name);
+    mangled = _Py_MaybeMangle(st->st_private, st->st_cur, name);
     if (!mangled)
         return 0;
     data = Py_BuildValue("(Niiii)", mangled, lineno, col_offset, end_lineno, end_col_offset);
@@ -1566,6 +1576,7 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
             VISIT_QUIT(st, 0);
         if (s->v.ClassDef.decorator_list)
             VISIT_SEQ(st, expr, s->v.ClassDef.decorator_list);
+        tmp = st->st_private;
         if (asdl_seq_LEN(s->v.ClassDef.type_params) > 0) {
             if (!symtable_enter_type_param_block(st, s->v.ClassDef.name,
                                                 (void *)s->v.ClassDef.type_params,
@@ -1573,6 +1584,11 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
                                                 LOCATION(s))) {
                 VISIT_QUIT(st, 0);
             }
+            st->st_private = s->v.ClassDef.name;
+            st->st_cur->ste_mangled_names = PySet_New(NULL);
+            if (!st->st_cur->ste_mangled_names) {
+                VISIT_QUIT(st, 0);
+            }
             VISIT_SEQ(st, type_param, s->v.ClassDef.type_params);
         }
         VISIT_SEQ(st, expr, s->v.ClassDef.bases);
@@ -1581,7 +1597,6 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
                                   (void *)s, s->lineno, s->col_offset,
                                   s->end_lineno, s->end_col_offset))
             VISIT_QUIT(st, 0);
-        tmp = st->st_private;
         st->st_private = s->v.ClassDef.name;
         if (asdl_seq_LEN(s->v.ClassDef.type_params) > 0) {
             if (!symtable_add_def(st, &_Py_ID(__type_params__),
@@ -1595,13 +1610,13 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
             }
         }
         VISIT_SEQ(st, stmt, s->v.ClassDef.body);
-        st->st_private = tmp;
         if (!symtable_exit_block(st))
             VISIT_QUIT(st, 0);
         if (asdl_seq_LEN(s->v.ClassDef.type_params) > 0) {
             if (!symtable_exit_block(st))
                 VISIT_QUIT(st, 0);
         }
+        st->st_private = tmp;
         break;
     }
     case TypeAlias_kind: {
@@ -2663,6 +2678,26 @@ _Py_SymtableStringObjectFlags(const char *str, PyObject *filename,
     return st;
 }
 
+PyObject *
+_Py_MaybeMangle(PyObject *privateobj, PySTEntryObject *ste, PyObject *name)
+{
+    /* Special case for type parameter blocks around generic classes:
+     * we want to mangle type parameter names (so a type param with a private
+     * name can be used inside the class body), but we don't want to mangle
+     * any other names that appear within the type parameter scope.
+     */
+    if (ste->ste_mangled_names != NULL) {
+        int result = PySet_Contains(ste->ste_mangled_names, name);
+        if (result < 0) {
+            return NULL;
+        }
+        if (result == 0) {
+            return Py_NewRef(name);
+        }
+    }
+    return _Py_Mangle(privateobj, name);
+}
+
 PyObject *
 _Py_Mangle(PyObject *privateobj, PyObject *ident)
 {