[3.14] gh-145701: Fix __classdict__ & __conditional_annotations__ in class-scope inlined comprehensions (GH-145702) (#145710)

(cherry picked from commit 63eaaf9599)

Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com>

* Add `:oss-fuzz:` supports

Backports part of 255e79fa95.

Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com>
This commit is contained in:
Miss Islington (bot) 2026-03-09 21:26:52 +01:00 committed by GitHub
parent ba1ea3a85a
commit 0db2beee6b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 39 additions and 3 deletions

View file

@ -554,6 +554,7 @@
# 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"),
}

View file

@ -180,6 +180,18 @@ def test_references___class___defined(self):
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

View file

@ -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`.

View file

@ -806,6 +806,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
@ -828,15 +830,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
@ -876,6 +890,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;
}