Skip to content

Commit 5609f64

Browse files
committed
gh-105340: include hidden fast-locals in locals()
1 parent b9e7dc7 commit 5609f64

File tree

7 files changed

+140
-37
lines changed

7 files changed

+140
-37
lines changed

Include/cpython/ceval.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,5 @@ _PyEval_RequestCodeExtraIndex(freefunc f) {
3131

3232
PyAPI_FUNC(int) _PyEval_SliceIndex(PyObject *, Py_ssize_t *);
3333
PyAPI_FUNC(int) _PyEval_SliceIndexNotNone(PyObject *, Py_ssize_t *);
34+
35+
PyAPI_FUNC(PyObject *) PyEval_GetFrameLocals(void);

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: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,19 @@ 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 = [eval("x") for x in l]
549+
# x is available, and does not overwrite y
550+
[exec("y = x") for x in l]
551+
"""
552+
self._check_in_scopes(
553+
code, {"items": [1, 2], "items2": [1, 2], "items3": [1, 2], "y": 0})
554+
542555

543556
__test__ = {'doctests' : doctests}
544557

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

Python/bltinmodule.c

Lines changed: 47 additions & 32 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,61 @@ 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
}
930930
}
931931
else if (locals == Py_None)
932-
locals = globals;
932+
locals = Py_NewRef(globals);
933+
else {
934+
Py_INCREF(locals);
935+
}
933936

934937
if (globals == NULL || locals == NULL) {
935938
PyErr_SetString(PyExc_TypeError,
936939
"eval must be given globals and locals "
937940
"when called without a frame");
938-
return NULL;
941+
goto error;
939942
}
940943

941944
int r = PyDict_Contains(globals, &_Py_ID(__builtins__));
942945
if (r == 0) {
943946
r = PyDict_SetItem(globals, &_Py_ID(__builtins__), PyEval_GetBuiltins());
944947
}
945948
if (r < 0) {
946-
return NULL;
949+
goto error;
947950
}
948951

949952
if (PyCode_Check(source)) {
950953
if (PySys_Audit("exec", "O", source) < 0) {
951-
return NULL;
954+
goto error;
952955
}
953956

954957
if (PyCode_GetNumFree((PyCodeObject *)source) > 0) {
955958
PyErr_SetString(PyExc_TypeError,
956959
"code object passed to eval() may not contain free variables");
957-
return NULL;
960+
goto error;
958961
}
959-
return PyEval_EvalCode(source, globals, locals);
962+
result = PyEval_EvalCode(source, globals, locals);
960963
}
964+
else {
965+
PyCompilerFlags cf = _PyCompilerFlags_INIT;
966+
cf.cf_flags = PyCF_SOURCE_IS_UTF8;
967+
str = _Py_SourceAsString(source, "eval", "string, bytes or code", &cf, &source_copy);
968+
if (str == NULL)
969+
goto error;
961970

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;
971+
while (*str == ' ' || *str == '\t')
972+
str++;
967973

968-
while (*str == ' ' || *str == '\t')
969-
str++;
974+
(void)PyEval_MergeCompilerFlags(&cf);
975+
result = PyRun_StringFlags(str, Py_eval_input, globals, locals, &cf);
976+
Py_XDECREF(source_copy);
977+
}
970978

971-
(void)PyEval_MergeCompilerFlags(&cf);
972-
result = PyRun_StringFlags(str, Py_eval_input, globals, locals, &cf);
973-
Py_XDECREF(source_copy);
979+
error:
980+
Py_XDECREF(locals);
974981
return result;
975982
}
976983

@@ -1005,7 +1012,7 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
10051012
if (globals == Py_None) {
10061013
globals = PyEval_GetGlobals();
10071014
if (locals == Py_None) {
1008-
locals = PyEval_GetLocals();
1015+
locals = PyEval_GetFrameLocals();
10091016
if (locals == NULL)
10101017
return NULL;
10111018
}
@@ -1015,26 +1022,30 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
10151022
return NULL;
10161023
}
10171024
}
1018-
else if (locals == Py_None)
1019-
locals = globals;
1025+
else if (locals == Py_None) {
1026+
locals = Py_NewRef(globals);
1027+
}
1028+
else {
1029+
Py_INCREF(locals);
1030+
}
10201031

10211032
if (!PyDict_Check(globals)) {
10221033
PyErr_Format(PyExc_TypeError, "exec() globals must be a dict, not %.100s",
10231034
Py_TYPE(globals)->tp_name);
1024-
return NULL;
1035+
goto error;
10251036
}
10261037
if (!PyMapping_Check(locals)) {
10271038
PyErr_Format(PyExc_TypeError,
10281039
"locals must be a mapping or None, not %.100s",
10291040
Py_TYPE(locals)->tp_name);
1030-
return NULL;
1041+
goto error;
10311042
}
10321043
int r = PyDict_Contains(globals, &_Py_ID(__builtins__));
10331044
if (r == 0) {
10341045
r = PyDict_SetItem(globals, &_Py_ID(__builtins__), PyEval_GetBuiltins());
10351046
}
10361047
if (r < 0) {
1037-
return NULL;
1048+
goto error;
10381049
}
10391050

10401051
if (closure == Py_None) {
@@ -1047,7 +1058,7 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
10471058
if (closure) {
10481059
PyErr_SetString(PyExc_TypeError,
10491060
"cannot use a closure with this code object");
1050-
return NULL;
1061+
goto error;
10511062
}
10521063
} else {
10531064
int closure_is_ok =
@@ -1067,12 +1078,12 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
10671078
PyErr_Format(PyExc_TypeError,
10681079
"code object requires a closure of exactly length %zd",
10691080
num_free);
1070-
return NULL;
1081+
goto error;
10711082
}
10721083
}
10731084

10741085
if (PySys_Audit("exec", "O", source) < 0) {
1075-
return NULL;
1086+
goto error;
10761087
}
10771088

10781089
if (!closure) {
@@ -1099,7 +1110,7 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
10991110
"string, bytes or code", &cf,
11001111
&source_copy);
11011112
if (str == NULL)
1102-
return NULL;
1113+
goto error;
11031114
if (PyEval_MergeCompilerFlags(&cf))
11041115
v = PyRun_StringFlags(str, Py_file_input, globals,
11051116
locals, &cf);
@@ -1108,9 +1119,14 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
11081119
Py_XDECREF(source_copy);
11091120
}
11101121
if (v == NULL)
1111-
return NULL;
1122+
goto error;
1123+
Py_DECREF(locals);
11121124
Py_DECREF(v);
11131125
Py_RETURN_NONE;
1126+
1127+
error:
1128+
Py_XDECREF(locals);
1129+
return NULL;
11141130
}
11151131

11161132

@@ -1722,8 +1738,7 @@ builtin_locals_impl(PyObject *module)
17221738
{
17231739
PyObject *d;
17241740

1725-
d = PyEval_GetLocals();
1726-
return Py_XNewRef(d);
1741+
return PyEval_GetFrameLocals();
17271742
}
17281743

17291744

@@ -2441,7 +2456,7 @@ builtin_vars_impl(PyObject *module, PyObject *object)
24412456
PyObject *d;
24422457

24432458
if (object == NULL) {
2444-
d = Py_XNewRef(PyEval_GetLocals());
2459+
d = PyEval_GetFrameLocals();
24452460
}
24462461
else {
24472462
if (_PyObject_LookupAttr(object, &_Py_ID(__dict__), &d) == 0) {

Python/ceval.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2257,6 +2257,19 @@ PyEval_GetLocals(void)
22572257
return locals;
22582258
}
22592259

2260+
PyObject *
2261+
PyEval_GetFrameLocals(void)
2262+
{
2263+
PyThreadState *tstate = _PyThreadState_GET();
2264+
_PyInterpreterFrame *current_frame = _PyThreadState_GetFrame(tstate);
2265+
if (current_frame == NULL) {
2266+
_PyErr_SetString(tstate, PyExc_SystemError, "frame does not exist");
2267+
return NULL;
2268+
}
2269+
2270+
return _PyFrame_GetLocals(current_frame, 1);
2271+
}
2272+
22602273
PyObject *
22612274
PyEval_GetGlobals(void)
22622275
{

0 commit comments

Comments
 (0)