summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2023-07-05 23:31:37 (GMT)
committerGitHub <noreply@github.com>2023-07-05 23:31:37 (GMT)
commitbb17e6f5de2bca85746a06d504029f90e85e3892 (patch)
tree826075fb8af05894f14c348b1ac2c9f7041bb2dd
parenta49a29f22bcc39376e760823ec512df831d2e828 (diff)
downloadcpython-bb17e6f5de2bca85746a06d504029f90e85e3892.zip
cpython-bb17e6f5de2bca85746a06d504029f90e85e3892.tar.gz
cpython-bb17e6f5de2bca85746a06d504029f90e85e3892.tar.bz2
[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 104d7b760fed18055e4f04e5da3ca619e28bfc81) Co-authored-by: Carl Meyer <carl@oddbird.net>
-rw-r--r--Include/internal/pycore_ceval.h1
-rw-r--r--Include/internal/pycore_frame.h3
-rw-r--r--Lib/test/test_listcomps.py22
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2023-06-12-16-38-31.gh-issue-105340._jRHXe.rst2
-rw-r--r--Objects/frameobject.c65
-rw-r--r--Objects/object.c7
-rw-r--r--Python/bltinmodule.c87
-rw-r--r--Python/ceval.c13
8 files changed, 158 insertions, 42 deletions
diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h
index 9e9b523..fc0f72e 100644
--- a/Include/internal/pycore_ceval.h
+++ b/Include/internal/pycore_ceval.h
@@ -154,6 +154,7 @@ extern PyObject* _Py_MakeCoro(PyFunctionObject *func);
extern int _Py_HandlePending(PyThreadState *tstate);
+extern PyObject * _PyEval_GetFrameLocals(void);
#ifdef __cplusplus
diff --git a/Include/internal/pycore_frame.h b/Include/internal/pycore_frame.h
index a72e03f..158db2c 100644
--- a/Include/internal/pycore_frame.h
+++ b/Include/internal/pycore_frame.h
@@ -226,6 +226,9 @@ _PyFrame_ClearExceptCode(_PyInterpreterFrame * frame);
int
_PyFrame_Traverse(_PyInterpreterFrame *frame, visitproc visit, void *arg);
+PyObject *
+_PyFrame_GetLocals(_PyInterpreterFrame *frame, int include_hidden);
+
int
_PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame);
diff --git a/Lib/test/test_listcomps.py b/Lib/test/test_listcomps.py
index c2cf058..9f28ced 100644
--- a/Lib/test/test_listcomps.py
+++ b/Lib/test/test_listcomps.py
@@ -539,6 +539,28 @@ class ListComprehensionTest(unittest.TestCase):
self._check_in_scopes(code, {"x": True, "y": ["b"]}, scopes=["function"])
self._check_in_scopes(code, raises=NameError, scopes=["class"])
+ def test_iter_var_available_in_locals(self):
+ code = """
+ l = [1, 2]
+ y = 0
+ items = [locals()["x"] for x in l]
+ items2 = [vars()["x"] for x in l]
+ items3 = [("x" in dir()) for x in l]
+ items4 = [eval("x") for x in l]
+ # x is available, and does not overwrite y
+ [exec("y = x") for x in l]
+ """
+ self._check_in_scopes(
+ code,
+ {
+ "items": [1, 2],
+ "items2": [1, 2],
+ "items3": [True, True],
+ "items4": [1, 2],
+ "y": 0
+ }
+ )
+
__test__ = {'doctests' : doctests}
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-06-12-16-38-31.gh-issue-105340._jRHXe.rst b/Misc/NEWS.d/next/Core and Builtins/2023-06-12-16-38-31.gh-issue-105340._jRHXe.rst
new file mode 100644
index 0000000..f6d4fa8
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2023-06-12-16-38-31.gh-issue-105340._jRHXe.rst
@@ -0,0 +1,2 @@
+Include the comprehension iteration variable in ``locals()`` inside a
+module- or class-scope comprehension.
diff --git a/Objects/frameobject.c b/Objects/frameobject.c
index 2c90a6b..e62cfab 100644
--- a/Objects/frameobject.c
+++ b/Objects/frameobject.c
@@ -1199,15 +1199,28 @@ frame_get_var(_PyInterpreterFrame *frame, PyCodeObject *co, int i,
return 1;
}
-int
-_PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame)
+
+PyObject *
+_PyFrame_GetLocals(_PyInterpreterFrame *frame, int include_hidden)
{
/* Merge fast locals into f->f_locals */
PyObject *locals = frame->f_locals;
if (locals == NULL) {
locals = frame->f_locals = PyDict_New();
if (locals == NULL) {
- return -1;
+ return NULL;
+ }
+ }
+ PyObject *hidden = NULL;
+
+ /* If include_hidden, "hidden" fast locals (from inlined comprehensions in
+ module/class scopes) will be included in the returned dict, but not in
+ frame->f_locals; the returned dict will be a modified copy. Non-hidden
+ locals will still be updated in frame->f_locals. */
+ if (include_hidden) {
+ hidden = PyDict_New();
+ if (hidden == NULL) {
+ return NULL;
}
}
@@ -1223,6 +1236,11 @@ _PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame)
PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
_PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
if (kind & CO_FAST_HIDDEN) {
+ if (include_hidden && value != NULL) {
+ if (PyObject_SetItem(hidden, name, value) != 0) {
+ goto error;
+ }
+ }
continue;
}
if (value == NULL) {
@@ -1231,16 +1249,53 @@ _PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame)
PyErr_Clear();
}
else {
- return -1;
+ goto error;
}
}
}
else {
if (PyObject_SetItem(locals, name, value) != 0) {
- return -1;
+ goto error;
}
}
}
+
+ if (include_hidden && PyDict_Size(hidden)) {
+ PyObject *innerlocals = PyDict_New();
+ if (innerlocals == NULL) {
+ goto error;
+ }
+ if (PyDict_Merge(innerlocals, locals, 1) != 0) {
+ Py_DECREF(innerlocals);
+ goto error;
+ }
+ if (PyDict_Merge(innerlocals, hidden, 1) != 0) {
+ Py_DECREF(innerlocals);
+ goto error;
+ }
+ locals = innerlocals;
+ }
+ else {
+ Py_INCREF(locals);
+ }
+ Py_CLEAR(hidden);
+
+ return locals;
+
+ error:
+ Py_XDECREF(hidden);
+ return NULL;
+}
+
+
+int
+_PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame)
+{
+ PyObject *locals = _PyFrame_GetLocals(frame, 0);
+ if (locals == NULL) {
+ return -1;
+ }
+ Py_DECREF(locals);
return 0;
}
diff --git a/Objects/object.c b/Objects/object.c
index ece0c5e..b8bdf45 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -1704,13 +1704,15 @@ _dir_locals(void)
PyObject *names;
PyObject *locals;
- locals = PyEval_GetLocals();
+ locals = _PyEval_GetFrameLocals();
if (locals == NULL)
return NULL;
names = PyMapping_Keys(locals);
- if (!names)
+ Py_DECREF(locals);
+ if (!names) {
return NULL;
+ }
if (!PyList_Check(names)) {
PyErr_Format(PyExc_TypeError,
"dir(): expected keys() of locals to be a list, "
@@ -1722,7 +1724,6 @@ _dir_locals(void)
Py_DECREF(names);
return NULL;
}
- /* the locals don't need to be DECREF'd */
return names;
}
diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c
index 45ce3b7..7f366b4 100644
--- a/Python/bltinmodule.c
+++ b/Python/bltinmodule.c
@@ -907,7 +907,7 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals,
PyObject *locals)
/*[clinic end generated code: output=0a0824aa70093116 input=11ee718a8640e527]*/
{
- PyObject *result, *source_copy;
+ PyObject *result = NULL, *source_copy;
const char *str;
if (locals != Py_None && !PyMapping_Check(locals)) {
@@ -923,19 +923,25 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals,
if (globals == Py_None) {
globals = PyEval_GetGlobals();
if (locals == Py_None) {
- locals = PyEval_GetLocals();
+ locals = _PyEval_GetFrameLocals();
if (locals == NULL)
return NULL;
}
+ else {
+ Py_INCREF(locals);
+ }
}
else if (locals == Py_None)
- locals = globals;
+ locals = Py_NewRef(globals);
+ else {
+ Py_INCREF(locals);
+ }
if (globals == NULL || locals == NULL) {
PyErr_SetString(PyExc_TypeError,
"eval must be given globals and locals "
"when called without a frame");
- return NULL;
+ goto error;
}
int r = PyDict_Contains(globals, &_Py_ID(__builtins__));
@@ -943,34 +949,38 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals,
r = PyDict_SetItem(globals, &_Py_ID(__builtins__), PyEval_GetBuiltins());
}
if (r < 0) {
- return NULL;
+ goto error;
}
if (PyCode_Check(source)) {
if (PySys_Audit("exec", "O", source) < 0) {
- return NULL;
+ goto error;
}
if (PyCode_GetNumFree((PyCodeObject *)source) > 0) {
PyErr_SetString(PyExc_TypeError,
"code object passed to eval() may not contain free variables");
- return NULL;
+ goto error;
}
- return PyEval_EvalCode(source, globals, locals);
+ result = PyEval_EvalCode(source, globals, locals);
}
+ else {
+ PyCompilerFlags cf = _PyCompilerFlags_INIT;
+ cf.cf_flags = PyCF_SOURCE_IS_UTF8;
+ str = _Py_SourceAsString(source, "eval", "string, bytes or code", &cf, &source_copy);
+ if (str == NULL)
+ goto error;
- PyCompilerFlags cf = _PyCompilerFlags_INIT;
- cf.cf_flags = PyCF_SOURCE_IS_UTF8;
- str = _Py_SourceAsString(source, "eval", "string, bytes or code", &cf, &source_copy);
- if (str == NULL)
- return NULL;
+ while (*str == ' ' || *str == '\t')
+ str++;
- while (*str == ' ' || *str == '\t')
- str++;
+ (void)PyEval_MergeCompilerFlags(&cf);
+ result = PyRun_StringFlags(str, Py_eval_input, globals, locals, &cf);
+ Py_XDECREF(source_copy);
+ }
- (void)PyEval_MergeCompilerFlags(&cf);
- result = PyRun_StringFlags(str, Py_eval_input, globals, locals, &cf);
- Py_XDECREF(source_copy);
+ error:
+ Py_XDECREF(locals);
return result;
}
@@ -1005,36 +1015,43 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
if (globals == Py_None) {
globals = PyEval_GetGlobals();
if (locals == Py_None) {
- locals = PyEval_GetLocals();
+ locals = _PyEval_GetFrameLocals();
if (locals == NULL)
return NULL;
}
+ else {
+ Py_INCREF(locals);
+ }
if (!globals || !locals) {
PyErr_SetString(PyExc_SystemError,
"globals and locals cannot be NULL");
return NULL;
}
}
- else if (locals == Py_None)
- locals = globals;
+ else if (locals == Py_None) {
+ locals = Py_NewRef(globals);
+ }
+ else {
+ Py_INCREF(locals);
+ }
if (!PyDict_Check(globals)) {
PyErr_Format(PyExc_TypeError, "exec() globals must be a dict, not %.100s",
Py_TYPE(globals)->tp_name);
- return NULL;
+ goto error;
}
if (!PyMapping_Check(locals)) {
PyErr_Format(PyExc_TypeError,
"locals must be a mapping or None, not %.100s",
Py_TYPE(locals)->tp_name);
- return NULL;
+ goto error;
}
int r = PyDict_Contains(globals, &_Py_ID(__builtins__));
if (r == 0) {
r = PyDict_SetItem(globals, &_Py_ID(__builtins__), PyEval_GetBuiltins());
}
if (r < 0) {
- return NULL;
+ goto error;
}
if (closure == Py_None) {
@@ -1047,7 +1064,7 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
if (closure) {
PyErr_SetString(PyExc_TypeError,
"cannot use a closure with this code object");
- return NULL;
+ goto error;
}
} else {
int closure_is_ok =
@@ -1067,12 +1084,12 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
PyErr_Format(PyExc_TypeError,
"code object requires a closure of exactly length %zd",
num_free);
- return NULL;
+ goto error;
}
}
if (PySys_Audit("exec", "O", source) < 0) {
- return NULL;
+ goto error;
}
if (!closure) {
@@ -1099,7 +1116,7 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
"string, bytes or code", &cf,
&source_copy);
if (str == NULL)
- return NULL;
+ goto error;
if (PyEval_MergeCompilerFlags(&cf))
v = PyRun_StringFlags(str, Py_file_input, globals,
locals, &cf);
@@ -1108,9 +1125,14 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
Py_XDECREF(source_copy);
}
if (v == NULL)
- return NULL;
+ goto error;
+ Py_DECREF(locals);
Py_DECREF(v);
Py_RETURN_NONE;
+
+ error:
+ Py_XDECREF(locals);
+ return NULL;
}
@@ -1720,10 +1742,7 @@ static PyObject *
builtin_locals_impl(PyObject *module)
/*[clinic end generated code: output=b46c94015ce11448 input=7874018d478d5c4b]*/
{
- PyObject *d;
-
- d = PyEval_GetLocals();
- return Py_XNewRef(d);
+ return _PyEval_GetFrameLocals();
}
@@ -2443,7 +2462,7 @@ builtin_vars_impl(PyObject *module, PyObject *object)
PyObject *d;
if (object == NULL) {
- d = Py_XNewRef(PyEval_GetLocals());
+ d = _PyEval_GetFrameLocals();
}
else {
if (_PyObject_LookupAttr(object, &_Py_ID(__dict__), &d) == 0) {
diff --git a/Python/ceval.c b/Python/ceval.c
index 4762dfa..c883a90 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -2313,6 +2313,19 @@ PyEval_GetLocals(void)
}
PyObject *
+_PyEval_GetFrameLocals(void)
+{
+ PyThreadState *tstate = _PyThreadState_GET();
+ _PyInterpreterFrame *current_frame = _PyThreadState_GetFrame(tstate);
+ if (current_frame == NULL) {
+ _PyErr_SetString(tstate, PyExc_SystemError, "frame does not exist");
+ return NULL;
+ }
+
+ return _PyFrame_GetLocals(current_frame, 1);
+}
+
+PyObject *
PyEval_GetGlobals(void)
{
PyThreadState *tstate = _PyThreadState_GET();