]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-145701: Fix `__classdict__` & `__conditional_annotations__` in class-scope inlined...
authorStan Ulbrych <89152624+StanFromIreland@users.noreply.github.com>
Mon, 9 Mar 2026 19:56:41 +0000 (19:56 +0000)
committerGitHub <noreply@github.com>
Mon, 9 Mar 2026 19:56:41 +0000 (12:56 -0700)
Lib/test/test_listcomps.py
Misc/NEWS.d/next/Core_and_Builtins/2026-03-09-18-52-03.gh-issue-145701.79KQyO.rst [new file with mode: 0644]
Python/symtable.c

index 70148dc30fc95792d5873aeb6fa2956eff705cfe..cee528722b85aa00e958fa8f7b34add6f7e9f04c 100644 (file)
@@ -180,6 +180,18 @@ class ListComprehensionTest(unittest.TestCase):
                 code, outputs={"res": [2]}, scopes=["module", "function"])
         self._check_in_scopes(code, raises=NameError, scopes=["class"])
 
+    def test_references___classdict__(self):
+        code = """
+            class i: [__classdict__ for x in y]
+        """
+        self._check_in_scopes(code, raises=NameError)
+
+    def test_references___conditional_annotations__(self):
+        code = """
+            class i: [__conditional_annotations__ for x in y]
+        """
+        self._check_in_scopes(code, raises=NameError)
+
     def test_references___class___enclosing(self):
         code = """
             __class__ = 2
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-09-18-52-03.gh-issue-145701.79KQyO.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-09-18-52-03.gh-issue-145701.79KQyO.rst
new file mode 100644 (file)
index 0000000..2379608
--- /dev/null
@@ -0,0 +1,3 @@
+Fix :exc:`SystemError` when ``__classdict__`` or
+``__conditional_annotations__`` is in a class-scope inlined comprehension.
+Found by OSS Fuzz in :oss-fuzz:`491105000`.
index beb6df88d097e393e4ad09f939cf4d1521727c5f..4b695e4b2588d8a9f17b7f1c59ec30775ac519ed 100644 (file)
@@ -807,6 +807,8 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp,
     PyObject *k, *v;
     Py_ssize_t pos = 0;
     int remove_dunder_class = 0;
+    int remove_dunder_classdict = 0;
+    int remove_dunder_cond_annotations = 0;
 
     while (PyDict_Next(comp->ste_symbols, &pos, &k, &v)) {
         // skip comprehension parameter
@@ -829,15 +831,27 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp,
         if (existing == NULL && PyErr_Occurred()) {
             return 0;
         }
-        // __class__ is never allowed to be free through a class scope (see
+        // __class__, __classdict__ and __conditional_annotations__ are
+        // never allowed to be free through a class scope (see
         // drop_class_free)
         if (scope == FREE && ste->ste_type == ClassBlock &&
-                _PyUnicode_EqualToASCIIString(k, "__class__")) {
+                (_PyUnicode_EqualToASCIIString(k, "__class__") ||
+                 _PyUnicode_EqualToASCIIString(k, "__classdict__") ||
+                 _PyUnicode_EqualToASCIIString(k, "__conditional_annotations__"))) {
             scope = GLOBAL_IMPLICIT;
             if (PySet_Discard(comp_free, k) < 0) {
                 return 0;
             }
-            remove_dunder_class = 1;
+
+            if (_PyUnicode_EqualToASCIIString(k, "__class__")) {
+                remove_dunder_class = 1;
+            }
+            else if (_PyUnicode_EqualToASCIIString(k, "__conditional_annotations__")) {
+                remove_dunder_cond_annotations = 1;
+            }
+            else {
+                remove_dunder_classdict = 1;
+            }
         }
         if (!existing) {
             // name does not exist in scope, copy from comprehension
@@ -877,6 +891,12 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp,
     if (remove_dunder_class && PyDict_DelItemString(comp->ste_symbols, "__class__") < 0) {
         return 0;
     }
+    if (remove_dunder_classdict && PyDict_DelItemString(comp->ste_symbols, "__classdict__") < 0) {
+        return 0;
+    }
+    if (remove_dunder_cond_annotations && PyDict_DelItemString(comp->ste_symbols, "__conditional_annotations__") < 0) {
+        return 0;
+    }
     return 1;
 }