Skip to content

Commit bd9b323

Browse files
committed
deduplicate class closure names
1 parent df243ab commit bd9b323

2 files changed

Lines changed: 129 additions & 9 deletions

File tree

‎Lib/test/test_listcomps.py‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,35 @@ def method(self):
426426

427427
self.assertIs(C().method()[0].__thisclass__, C)
428428

429+
def test_class_closure_name_not_duplicated_in_f_locals(self):
430+
# Class-closure names are not slot-reused, so localsplus can hold
431+
# both a hidden comprehension local and the free. FrameLocalsProxy
432+
# must still present a unique key (first wins).
433+
class C:
434+
def method(self):
435+
__class__
436+
return [(
437+
dict(**sys._getframe().f_locals),
438+
len(sys._getframe().f_locals),
439+
list(sys._getframe().f_locals.keys()),
440+
list(sys._getframe().f_locals.values()),
441+
list(sys._getframe().f_locals.items()),
442+
) for __class__ in (int,)]
443+
444+
d, n, ks, vs, it = C().method()[0]
445+
self.assertEqual(d["__class__"], int)
446+
self.assertEqual(ks.count("__class__"), 1)
447+
self.assertEqual(n, len(ks))
448+
self.assertEqual(n, len(vs))
449+
self.assertEqual(n, len(it))
450+
self.assertEqual(d, dict(zip(ks, vs)))
451+
self.assertEqual(d, dict(it))
452+
# Duplicate slots are still present in the code object.
453+
code = C.method.__code__
454+
self.assertEqual(code.co_varnames.count("__class__")
455+
+ code.co_cellvars.count("__class__")
456+
+ code.co_freevars.count("__class__"), 2)
457+
429458
def test_reuse_class_body_locals_sees_iteration_var(self):
430459
# Class-body locals() must still expose the comprehension target
431460
# when that name reuses an enclosing free (no separate hidden slot).

‎Objects/frameobject.c‎

Lines changed: 100 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
1515
#include "pycore_opcode_metadata.h" // _PyOpcode_Caches
1616
#include "pycore_optimizer.h" // _Py_Executors_InvalidateDependency()
17+
#include "pycore_symtable.h" // _PyST_IsClassClosureName()
1718
#include "pycore_tuple.h" // _PyTuple_FromPair
1819
#include "pycore_unicodeobject.h" // _PyUnicode_Equal()
1920
#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
@@ -94,6 +95,28 @@ framelocalsproxy_hasval(_PyInterpreterFrame *frame, PyCodeObject *co, int i)
9495
return true;
9596
}
9697

98+
/* 1 = include, 0 = skip duplicate, -1 = error.
99+
* Class-closure names are not slot-reused, so localsplus can hold both a
100+
* comprehension local and a free; track only those names in seen. */
101+
static int
102+
framelocalsproxy_include_name(PyObject *seen, PyObject *name)
103+
{
104+
if (!_PyST_IsClassClosureName(name)) {
105+
return 1;
106+
}
107+
int found = PySet_Contains(seen, name);
108+
if (found < 0) {
109+
return -1;
110+
}
111+
if (found) {
112+
return 0;
113+
}
114+
if (PySet_Add(seen, name) < 0) {
115+
return -1;
116+
}
117+
return 1;
118+
}
119+
97120
static int
98121
framelocalsproxy_getkeyindex(PyFrameObject *frame, PyObject *key, bool read, PyObject **value_ptr)
99122
{
@@ -380,13 +403,24 @@ framelocalsproxy_keys(PyObject *self, PyObject *Py_UNUSED(ignored))
380403
if (names == NULL) {
381404
return NULL;
382405
}
406+
PyObject *seen = PySet_New(NULL);
407+
if (seen == NULL) {
408+
Py_DECREF(names);
409+
return NULL;
410+
}
383411

384412
for (int i = 0; i < co->co_nlocalsplus; i++) {
385413
if (framelocalsproxy_hasval(frame->f_frame, co, i)) {
386414
PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
415+
int include = framelocalsproxy_include_name(seen, name);
416+
if (include < 0) {
417+
goto error;
418+
}
419+
if (!include) {
420+
continue;
421+
}
387422
if (PyList_Append(names, name) < 0) {
388-
Py_DECREF(names);
389-
return NULL;
423+
goto error;
390424
}
391425
}
392426
}
@@ -401,15 +435,21 @@ framelocalsproxy_keys(PyObject *self, PyObject *Py_UNUSED(ignored))
401435

402436
while (PyDict_Next(frame->f_extra_locals, &i, &key, &value)) {
403437
if (PyList_Append(names, key) < 0) {
404-
Py_DECREF(names);
405-
return NULL;
438+
goto error;
406439
}
407440
}
408441
}
409442

443+
Py_DECREF(seen);
410444
return names;
445+
446+
error:
447+
Py_DECREF(seen);
448+
Py_DECREF(names);
449+
return NULL;
411450
}
412451

452+
413453
static void
414454
framelocalsproxy_dealloc(PyObject *self)
415455
{
@@ -589,14 +629,28 @@ framelocalsproxy_values(PyObject *self, PyObject *Py_UNUSED(ignored))
589629
if (values == NULL) {
590630
return NULL;
591631
}
632+
PyObject *seen = PySet_New(NULL);
633+
if (seen == NULL) {
634+
Py_DECREF(values);
635+
return NULL;
636+
}
592637

593638
for (int i = 0; i < co->co_nlocalsplus; i++) {
594639
PyObject *value = framelocalsproxy_getval(frame->f_frame, co, i);
595640
if (value) {
641+
PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
642+
int include = framelocalsproxy_include_name(seen, name);
643+
if (include < 0) {
644+
Py_DECREF(value);
645+
goto error;
646+
}
647+
if (!include) {
648+
Py_DECREF(value);
649+
continue;
650+
}
596651
if (PyList_Append(values, value) < 0) {
597652
Py_DECREF(value);
598-
Py_DECREF(values);
599-
return NULL;
653+
goto error;
600654
}
601655
Py_DECREF(value);
602656
}
@@ -609,15 +663,21 @@ framelocalsproxy_values(PyObject *self, PyObject *Py_UNUSED(ignored))
609663
PyObject *value = NULL;
610664
while (PyDict_Next(frame->f_extra_locals, &j, &key, &value)) {
611665
if (PyList_Append(values, value) < 0) {
612-
Py_DECREF(values);
613-
return NULL;
666+
goto error;
614667
}
615668
}
616669
}
617670

671+
Py_DECREF(seen);
618672
return values;
673+
674+
error:
675+
Py_DECREF(seen);
676+
Py_DECREF(values);
677+
return NULL;
619678
}
620679

680+
621681
static PyObject *
622682
framelocalsproxy_items(PyObject *self, PyObject *Py_UNUSED(ignored))
623683
{
@@ -627,12 +687,26 @@ framelocalsproxy_items(PyObject *self, PyObject *Py_UNUSED(ignored))
627687
if (items == NULL) {
628688
return NULL;
629689
}
690+
PyObject *seen = PySet_New(NULL);
691+
if (seen == NULL) {
692+
Py_DECREF(items);
693+
return NULL;
694+
}
630695

631696
for (int i = 0; i < co->co_nlocalsplus; i++) {
632697
PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
633698
PyObject *value = framelocalsproxy_getval(frame->f_frame, co, i);
634699

635700
if (value) {
701+
int include = framelocalsproxy_include_name(seen, name);
702+
if (include < 0) {
703+
Py_DECREF(value);
704+
goto error;
705+
}
706+
if (!include) {
707+
Py_DECREF(value);
708+
continue;
709+
}
636710
PyObject *pair = _PyTuple_FromPairSteal(Py_NewRef(name), value);
637711
if (pair == NULL) {
638712
goto error;
@@ -660,13 +734,16 @@ framelocalsproxy_items(PyObject *self, PyObject *Py_UNUSED(ignored))
660734
}
661735
}
662736

737+
Py_DECREF(seen);
663738
return items;
664739

665740
error:
741+
Py_DECREF(seen);
666742
Py_DECREF(items);
667743
return NULL;
668744
}
669745

746+
670747
static Py_ssize_t
671748
framelocalsproxy_length(PyObject *self)
672749
{
@@ -679,14 +756,28 @@ framelocalsproxy_length(PyObject *self)
679756
size += PyDict_Size(frame->f_extra_locals);
680757
}
681758

759+
PyObject *seen = PySet_New(NULL);
760+
if (seen == NULL) {
761+
return -1;
762+
}
682763
for (int i = 0; i < co->co_nlocalsplus; i++) {
683764
if (framelocalsproxy_hasval(frame->f_frame, co, i)) {
684-
size++;
765+
PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
766+
int include = framelocalsproxy_include_name(seen, name);
767+
if (include < 0) {
768+
Py_DECREF(seen);
769+
return -1;
770+
}
771+
if (include) {
772+
size++;
773+
}
685774
}
686775
}
776+
Py_DECREF(seen);
687777
return size;
688778
}
689779

780+
690781
static int
691782
framelocalsproxy_contains(PyObject *self, PyObject *key)
692783
{

0 commit comments

Comments
 (0)