]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-145701: Fix `__classdict__` & `__conditional_annotations__` in class-scope...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Mon, 9 Mar 2026 21:44:31 +0000 (22:44 +0100)
committerGitHub <noreply@github.com>
Mon, 9 Mar 2026 21:44:31 +0000 (14:44 -0700)
* gh-145701: Fix `__classdict__` & `__conditional_annotations__` in class-scope inlined comprehensions (GH-145702)
(cherry picked from commit 63eaaf95999c530cbd75b3addc3e660499d3adbe)

Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com>
* Add `:oss-fuzz:` macro support

Backports part of https://github.com/python/cpython/commit/255e79fa955ac5ffef9eb27087e8b1373e98e3bd.

---------

Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com>
Doc/conf.py
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 b15f31e7db184d91213a783b89799325b3b4c281..1e361f88d6f5d94cd8e77d041b4289870c8c3d83 100644 (file)
@@ -547,6 +547,7 @@ linkcheck_ignore = [
 # mapping unique short aliases to a base URL and a prefix.
 # https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html
 extlinks = {
+    "oss-fuzz": ("https://issues.oss-fuzz.com/issues/%s", "#%s"),
     "pypi": ("https://pypi.org/project/%s/", "%s"),
     "source": (SOURCE_URI, "%s"),
 }
index 9da6027c08b1553bf427a71aad3e0ad57eb59349..42bdefae21a91252f6228ebaee457f405d80f9ae 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 5322c5e9597c74ffd6ce88b0171950b57ba428bf..f60af2b6955d71c42f04edc78e9b0b26cef94feb 100644 (file)
@@ -763,6 +763,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
@@ -782,15 +784,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
@@ -823,6 +837,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;
 }