Skip to content

Commit bb17e6f

Browse files
[3.12] gh-105340: include hidden fast-locals in locals() (GH-105715) (#106470)
gh-105340: include hidden fast-locals in locals() (GH-105715) * gh-105340: include hidden fast-locals in locals() (cherry picked from commit 104d7b7) Co-authored-by: Carl Meyer <[email protected]>
1 parent a49a29f commit bb17e6f

File tree

8 files changed

+158
-42
lines changed

8 files changed

+158
-42
lines changed

Include/internal/pycore_ceval.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ extern PyObject* _Py_MakeCoro(PyFunctionObject *func);
154154

155155
extern int _Py_HandlePending(PyThreadState *tstate);
156156

157+
extern PyObject * _PyEval_GetFrameLocals(void);
157158

158159

159160
#ifdef __cplusplus

Include/internal/pycore_frame.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,9 @@ _PyFrame_ClearExceptCode(_PyInterpreterFrame * frame);
226226
int
227227
_PyFrame_Traverse(_PyInterpreterFrame *frame, visitproc visit, void *arg);
228228

229+
PyObject *
230+
_PyFrame_GetLocals(_PyInterpreterFrame *frame, int include_hidden);
231+
229232
int
230233
_PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame);
231234

Lib/test/test_listcomps.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,28 @@ def b():
539539
self._check_in_scopes(code, {"x": True, "y": ["b"]}, scopes=["function"])
540540
self._check_in_scopes(code, raises=NameError, scopes=["class"])
541541

542+
def test_iter_var_available_in_locals(self):
543+
code = """
544+
l = [1, 2]
545+
y = 0
546+
items = [locals()["x"] for x in l]
547+
items2 = [vars()["x"] for x in l]
548+
items3 = [("x" in dir()) for x in l]
549+
items4 = [eval("x") for x in l]
550+
# x is available, and does not overwrite y
551+
[exec("y = x") for x in l]
552+
"""
553+
self._check_in_scopes(
554+
code,
555+
{
556+
"items": [1, 2],
557+
"items2": [1, 2],
558+
"items3": [True, True],
559+
"items4": [1, 2],
560+
"y": 0
561+
}
562+
)
563+
542564

543565
__test__ = {'doctests' : doctests}
544566

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Include the comprehension iteration variable in ``locals()`` inside a
2+
module- or class-scope comprehension.

Objects/frameobject.c

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,15 +1199,28 @@ frame_get_var(_PyInterpreterFrame *frame, PyCodeObject *co, int i,
11991199
return 1;
12001200
}
12011201

1202-
int
1203-
_PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame)
1202+
1203+
PyObject *
1204+
_PyFrame_GetLocals(_PyInterpreterFrame *frame, int include_hidden)
12041205
{
12051206
/* Merge fast locals into f->f_locals */
12061207
PyObject *locals = frame->f_locals;
12071208
if (locals == NULL) {
12081209
locals = frame->f_locals = PyDict_New();
12091210
if (locals == NULL) {
1210-
return -1;
1211+
return NULL;
1212+
}
1213+
}
1214+
PyObject *hidden = NULL;
1215+
1216+
/* If include_hidden, "hidden" fast locals (from inlined comprehensions in
1217+
module/class scopes) will be included in the returned dict, but not in
1218+
frame->f_locals; the returned dict will be a modified copy. Non-hidden
1219+
locals will still be updated in frame->f_locals. */
1220+
if (include_hidden) {
1221+
hidden = PyDict_New();
1222+
if (hidden == NULL) {
1223+
return NULL;
12111224
}
12121225
}
12131226

@@ -1223,6 +1236,11 @@ _PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame)
12231236
PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
12241237
_PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
12251238
if (kind & CO_FAST_HIDDEN) {
1239+
if (include_hidden && value != NULL) {
1240+
if (PyObject_SetItem(hidden, name, value) != 0) {
1241+
goto error;
1242+
}
1243+
}
12261244
continue;
12271245
}
12281246
if (value == NULL) {
@@ -1231,16 +1249,53 @@ _PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame)
12311249
PyErr_Clear();
12321250
}
12331251
else {
1234-
return -1;
1252+
goto error;
12351253
}
12361254
}
12371255
}
12381256
else {
12391257
if (PyObject_SetItem(locals, name, value) != 0) {
1240-
return -1;
1258+
goto error;
12411259
}
12421260
}
12431261
}
1262+
1263+
if (include_hidden && PyDict_Size(hidden)) {
1264+
PyObject *innerlocals = PyDict_New();
1265+
if (innerlocals == NULL) {
1266+
goto error;
1267+
}
1268+
if (PyDict_Merge(innerlocals, locals, 1) != 0) {
1269+
Py_DECREF(innerlocals);
1270+
goto error;
1271+
}
1272+
if (PyDict_Merge(innerlocals, hidden, 1) != 0) {
1273+
Py_DECREF(innerlocals);
1274+
goto error;
1275+
}
1276+
locals = innerlocals;
1277+
}
1278+
else {
1279+
Py_INCREF(locals);
1280+
}
1281+
Py_CLEAR(hidden);
1282+
1283+
return locals;
1284+
1285+
error:
1286+
Py_XDECREF(hidden);
1287+
return NULL;
1288+
}
1289+
1290+
1291+
int
1292+
_PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame)
1293+
{
1294+
PyObject *locals = _PyFrame_GetLocals(frame, 0);
1295+
if (locals == NULL) {
1296+
return -1;
1297+
}
1298+
Py_DECREF(locals);
12441299
return 0;
12451300
}
12461301

Objects/object.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1704,13 +1704,15 @@ _dir_locals(void)
17041704
PyObject *names;
17051705
PyObject *locals;
17061706

1707-
locals = PyEval_GetLocals();
1707+
locals = _PyEval_GetFrameLocals();
17081708
if (locals == NULL)
17091709
return NULL;
17101710

17111711
names = PyMapping_Keys(locals);
1712-
if (!names)
1712+
Py_DECREF(locals);
1713+
if (!names) {
17131714
return NULL;
1715+
}
17141716
if (!PyList_Check(names)) {
17151717
PyErr_Format(PyExc_TypeError,
17161718
"dir(): expected keys() of locals to be a list, "
@@ -1722,7 +1724,6 @@ _dir_locals(void)
17221724
Py_DECREF(names);
17231725
return NULL;
17241726
}
1725-
/* the locals don't need to be DECREF'd */
17261727
return names;
17271728
}
17281729

Python/bltinmodule.c

Lines changed: 53 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,7 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals,
907907
PyObject *locals)
908908
/*[clinic end generated code: output=0a0824aa70093116 input=11ee718a8640e527]*/
909909
{
910-
PyObject *result, *source_copy;
910+
PyObject *result = NULL, *source_copy;
911911
const char *str;
912912

913913
if (locals != Py_None && !PyMapping_Check(locals)) {
@@ -923,54 +923,64 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals,
923923
if (globals == Py_None) {
924924
globals = PyEval_GetGlobals();
925925
if (locals == Py_None) {
926-
locals = PyEval_GetLocals();
926+
locals = _PyEval_GetFrameLocals();
927927
if (locals == NULL)
928928
return NULL;
929929
}
930+
else {
931+
Py_INCREF(locals);
932+
}
930933
}
931934
else if (locals == Py_None)
932-
locals = globals;
935+
locals = Py_NewRef(globals);
936+
else {
937+
Py_INCREF(locals);
938+
}
933939

934940
if (globals == NULL || locals == NULL) {
935941
PyErr_SetString(PyExc_TypeError,
936942
"eval must be given globals and locals "
937943
"when called without a frame");
938-
return NULL;
944+
goto error;
939945
}
940946

941947
int r = PyDict_Contains(globals, &_Py_ID(__builtins__));
942948
if (r == 0) {
943949
r = PyDict_SetItem(globals, &_Py_ID(__builtins__), PyEval_GetBuiltins());
944950
}
945951
if (r < 0) {
946-
return NULL;
952+
goto error;
947953
}
948954

949955
if (PyCode_Check(source)) {
950956
if (PySys_Audit("exec", "O", source) < 0) {
951-
return NULL;
957+
goto error;
952958
}
953959

954960
if (PyCode_GetNumFree((PyCodeObject *)source) > 0) {
955961
PyErr_SetString(PyExc_TypeError,
956962
"code object passed to eval() may not contain free variables");
957-
return NULL;
963+
goto error;
958964
}
959-
return PyEval_EvalCode(source, globals, locals);
965+
result = PyEval_EvalCode(source, globals, locals);
960966
}
967+
else {
968+
PyCompilerFlags cf = _PyCompilerFlags_INIT;
969+
cf.cf_flags = PyCF_SOURCE_IS_UTF8;
970+
str = _Py_SourceAsString(source, "eval", "string, bytes or code", &cf, &source_copy);
971+
if (str == NULL)
972+
goto error;
961973

962-
PyCompilerFlags cf = _PyCompilerFlags_INIT;
963-
cf.cf_flags = PyCF_SOURCE_IS_UTF8;
964-
str = _Py_SourceAsString(source, "eval", "string, bytes or code", &cf, &source_copy);
965-
if (str == NULL)
966-
return NULL;
974+
while (*str == ' ' || *str == '\t')
975+
str++;
967976

968-
while (*str == ' ' || *str == '\t')
969-
str++;
977+
(void)PyEval_MergeCompilerFlags(&cf);
978+
result = PyRun_StringFlags(str, Py_eval_input, globals, locals, &cf);
979+
Py_XDECREF(source_copy);
980+
}
970981

971-
(void)PyEval_MergeCompilerFlags(&cf);
972-
result = PyRun_StringFlags(str, Py_eval_input, globals, locals, &cf);
973-
Py_XDECREF(source_copy);
982+
error:
983+
Py_XDECREF(locals);
974984
return result;
975985
}
976986

@@ -1005,36 +1015,43 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
10051015
if (globals == Py_None) {
10061016
globals = PyEval_GetGlobals();
10071017
if (locals == Py_None) {
1008-
locals = PyEval_GetLocals();
1018+
locals = _PyEval_GetFrameLocals();
10091019
if (locals == NULL)
10101020
return NULL;
10111021
}
1022+
else {
1023+
Py_INCREF(locals);
1024+
}
10121025
if (!globals || !locals) {
10131026
PyErr_SetString(PyExc_SystemError,
10141027
"globals and locals cannot be NULL");
10151028
return NULL;
10161029
}
10171030
}
1018-
else if (locals == Py_None)
1019-
locals = globals;
1031+
else if (locals == Py_None) {
1032+
locals = Py_NewRef(globals);
1033+
}
1034+
else {
1035+
Py_INCREF(locals);
1036+
}
10201037

10211038
if (!PyDict_Check(globals)) {
10221039
PyErr_Format(PyExc_TypeError, "exec() globals must be a dict, not %.100s",
10231040
Py_TYPE(globals)->tp_name);
1024-
return NULL;
1041+
goto error;
10251042
}
10261043
if (!PyMapping_Check(locals)) {
10271044
PyErr_Format(PyExc_TypeError,
10281045
"locals must be a mapping or None, not %.100s",
10291046
Py_TYPE(locals)->tp_name);
1030-
return NULL;
1047+
goto error;
10311048
}
10321049
int r = PyDict_Contains(globals, &_Py_ID(__builtins__));
10331050
if (r == 0) {
10341051
r = PyDict_SetItem(globals, &_Py_ID(__builtins__), PyEval_GetBuiltins());
10351052
}
10361053
if (r < 0) {
1037-
return NULL;
1054+
goto error;
10381055
}
10391056

10401057
if (closure == Py_None) {
@@ -1047,7 +1064,7 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
10471064
if (closure) {
10481065
PyErr_SetString(PyExc_TypeError,
10491066
"cannot use a closure with this code object");
1050-
return NULL;
1067+
goto error;
10511068
}
10521069
} else {
10531070
int closure_is_ok =
@@ -1067,12 +1084,12 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
10671084
PyErr_Format(PyExc_TypeError,
10681085
"code object requires a closure of exactly length %zd",
10691086
num_free);
1070-
return NULL;
1087+
goto error;
10711088
}
10721089
}
10731090

10741091
if (PySys_Audit("exec", "O", source) < 0) {
1075-
return NULL;
1092+
goto error;
10761093
}
10771094

10781095
if (!closure) {
@@ -1099,7 +1116,7 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
10991116
"string, bytes or code", &cf,
11001117
&source_copy);
11011118
if (str == NULL)
1102-
return NULL;
1119+
goto error;
11031120
if (PyEval_MergeCompilerFlags(&cf))
11041121
v = PyRun_StringFlags(str, Py_file_input, globals,
11051122
locals, &cf);
@@ -1108,9 +1125,14 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
11081125
Py_XDECREF(source_copy);
11091126
}
11101127
if (v == NULL)
1111-
return NULL;
1128+
goto error;
1129+
Py_DECREF(locals);
11121130
Py_DECREF(v);
11131131
Py_RETURN_NONE;
1132+
1133+
error:
1134+
Py_XDECREF(locals);
1135+
return NULL;
11141136
}
11151137

11161138

@@ -1720,10 +1742,7 @@ static PyObject *
17201742
builtin_locals_impl(PyObject *module)
17211743
/*[clinic end generated code: output=b46c94015ce11448 input=7874018d478d5c4b]*/
17221744
{
1723-
PyObject *d;
1724-
1725-
d = PyEval_GetLocals();
1726-
return Py_XNewRef(d);
1745+
return _PyEval_GetFrameLocals();
17271746
}
17281747

17291748

@@ -2443,7 +2462,7 @@ builtin_vars_impl(PyObject *module, PyObject *object)
24432462
PyObject *d;
24442463

24452464
if (object == NULL) {
2446-
d = Py_XNewRef(PyEval_GetLocals());
2465+
d = _PyEval_GetFrameLocals();
24472466
}
24482467
else {
24492468
if (_PyObject_LookupAttr(object, &_Py_ID(__dict__), &d) == 0) {

0 commit comments

Comments
 (0)