Skip to content

Commit 63eaaf9

Browse files
gh-145701: Fix __classdict__ & __conditional_annotations__ in class-scope inlined comprehensions (GH-145702)
1 parent d6c1763 commit 63eaaf9

File tree

3 files changed

+38
-3
lines changed

3 files changed

+38
-3
lines changed

Lib/test/test_listcomps.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,18 @@ def test_references___class___defined(self):
180180
code, outputs={"res": [2]}, scopes=["module", "function"])
181181
self._check_in_scopes(code, raises=NameError, scopes=["class"])
182182

183+
def test_references___classdict__(self):
184+
code = """
185+
class i: [__classdict__ for x in y]
186+
"""
187+
self._check_in_scopes(code, raises=NameError)
188+
189+
def test_references___conditional_annotations__(self):
190+
code = """
191+
class i: [__conditional_annotations__ for x in y]
192+
"""
193+
self._check_in_scopes(code, raises=NameError)
194+
183195
def test_references___class___enclosing(self):
184196
code = """
185197
__class__ = 2
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :exc:`SystemError` when ``__classdict__`` or
2+
``__conditional_annotations__`` is in a class-scope inlined comprehension.
3+
Found by OSS Fuzz in :oss-fuzz:`491105000`.

Python/symtable.c

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,8 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp,
807807
PyObject *k, *v;
808808
Py_ssize_t pos = 0;
809809
int remove_dunder_class = 0;
810+
int remove_dunder_classdict = 0;
811+
int remove_dunder_cond_annotations = 0;
810812

811813
while (PyDict_Next(comp->ste_symbols, &pos, &k, &v)) {
812814
// skip comprehension parameter
@@ -829,15 +831,27 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp,
829831
if (existing == NULL && PyErr_Occurred()) {
830832
return 0;
831833
}
832-
// __class__ is never allowed to be free through a class scope (see
834+
// __class__, __classdict__ and __conditional_annotations__ are
835+
// never allowed to be free through a class scope (see
833836
// drop_class_free)
834837
if (scope == FREE && ste->ste_type == ClassBlock &&
835-
_PyUnicode_EqualToASCIIString(k, "__class__")) {
838+
(_PyUnicode_EqualToASCIIString(k, "__class__") ||
839+
_PyUnicode_EqualToASCIIString(k, "__classdict__") ||
840+
_PyUnicode_EqualToASCIIString(k, "__conditional_annotations__"))) {
836841
scope = GLOBAL_IMPLICIT;
837842
if (PySet_Discard(comp_free, k) < 0) {
838843
return 0;
839844
}
840-
remove_dunder_class = 1;
845+
846+
if (_PyUnicode_EqualToASCIIString(k, "__class__")) {
847+
remove_dunder_class = 1;
848+
}
849+
else if (_PyUnicode_EqualToASCIIString(k, "__conditional_annotations__")) {
850+
remove_dunder_cond_annotations = 1;
851+
}
852+
else {
853+
remove_dunder_classdict = 1;
854+
}
841855
}
842856
if (!existing) {
843857
// name does not exist in scope, copy from comprehension
@@ -877,6 +891,12 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp,
877891
if (remove_dunder_class && PyDict_DelItemString(comp->ste_symbols, "__class__") < 0) {
878892
return 0;
879893
}
894+
if (remove_dunder_classdict && PyDict_DelItemString(comp->ste_symbols, "__classdict__") < 0) {
895+
return 0;
896+
}
897+
if (remove_dunder_cond_annotations && PyDict_DelItemString(comp->ste_symbols, "__conditional_annotations__") < 0) {
898+
return 0;
899+
}
880900
return 1;
881901
}
882902

0 commit comments

Comments
 (0)