gh-92203: Add closure support to exec(). (#92204)

Add a closure keyword-only parameter to exec(). It can only be specified when exec-ing a code object that uses free variables. When specified, it must be a tuple, with exactly the number of cell variables referenced by the code object. closure has a default value of None, and it must be None if the code object doesn't refer to any free variables.
This commit is contained in:
larryhastings 2022-05-06 10:09:35 -07:00 committed by GitHub
parent 973a5203c1
commit 5021064390
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 171 additions and 21 deletions

View file

@ -977,6 +977,8 @@ exec as builtin_exec
globals: object = None
locals: object = None
/
*
closure: object(c_default="NULL") = None
Execute the given source in the context of globals and locals.
@ -985,12 +987,14 @@ or a code object as returned by compile().
The globals must be a dictionary and locals can be any mapping,
defaulting to the current globals and locals.
If only globals is given, locals defaults to it.
The closure must be a tuple of cellvars, and can only be used
when source is a code object requiring exactly that many cellvars.
[clinic start generated code]*/
static PyObject *
builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
PyObject *locals)
/*[clinic end generated code: output=3c90efc6ab68ef5d input=01ca3e1c01692829]*/
PyObject *locals, PyObject *closure)
/*[clinic end generated code: output=7579eb4e7646743d input=f13a7e2b503d1d9a]*/
{
PyObject *v;
@ -1029,20 +1033,60 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
return NULL;
}
if (closure == Py_None) {
closure = NULL;
}
if (PyCode_Check(source)) {
Py_ssize_t num_free = PyCode_GetNumFree((PyCodeObject *)source);
if (num_free == 0) {
if (closure) {
PyErr_SetString(PyExc_TypeError,
"cannot use a closure with this code object");
return NULL;
}
} else {
int closure_is_ok =
closure
&& PyTuple_CheckExact(closure)
&& (PyTuple_GET_SIZE(closure) == num_free);
if (closure_is_ok) {
for (Py_ssize_t i = 0; i < num_free; i++) {
PyObject *cell = PyTuple_GET_ITEM(closure, i);
if (!PyCell_Check(cell)) {
closure_is_ok = 0;
break;
}
}
}
if (!closure_is_ok) {
PyErr_Format(PyExc_TypeError,
"code object requires a closure of exactly length %zd",
num_free);
return NULL;
}
}
if (PySys_Audit("exec", "O", source) < 0) {
return NULL;
}
if (PyCode_GetNumFree((PyCodeObject *)source) > 0) {
PyErr_SetString(PyExc_TypeError,
"code object passed to exec() may not "
"contain free variables");
return NULL;
if (!closure) {
v = PyEval_EvalCode(source, globals, locals);
} else {
v = PyEval_EvalCodeEx(source, globals, locals,
NULL, 0,
NULL, 0,
NULL, 0,
NULL,
closure);
}
v = PyEval_EvalCode(source, globals, locals);
}
else {
if (closure != NULL) {
PyErr_SetString(PyExc_TypeError,
"closure can only be used when source is a code object");
}
PyObject *source_copy;
const char *str;
PyCompilerFlags cf = _PyCompilerFlags_INIT;

View file

@ -408,7 +408,7 @@ exit:
}
PyDoc_STRVAR(builtin_exec__doc__,
"exec($module, source, globals=None, locals=None, /)\n"
"exec($module, source, globals=None, locals=None, /, *, closure=None)\n"
"--\n"
"\n"
"Execute the given source in the context of globals and locals.\n"
@ -417,37 +417,52 @@ PyDoc_STRVAR(builtin_exec__doc__,
"or a code object as returned by compile().\n"
"The globals must be a dictionary and locals can be any mapping,\n"
"defaulting to the current globals and locals.\n"
"If only globals is given, locals defaults to it.");
"If only globals is given, locals defaults to it.\n"
"The closure must be a tuple of cellvars, and can only be used\n"
"when source is a code object requiring exactly that many cellvars.");
#define BUILTIN_EXEC_METHODDEF \
{"exec", _PyCFunction_CAST(builtin_exec), METH_FASTCALL, builtin_exec__doc__},
{"exec", _PyCFunction_CAST(builtin_exec), METH_FASTCALL|METH_KEYWORDS, builtin_exec__doc__},
static PyObject *
builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
PyObject *locals);
PyObject *locals, PyObject *closure);
static PyObject *
builtin_exec(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
builtin_exec(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
static const char * const _keywords[] = {"", "", "", "closure", NULL};
static _PyArg_Parser _parser = {NULL, _keywords, "exec", 0};
PyObject *argsbuf[4];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
PyObject *source;
PyObject *globals = Py_None;
PyObject *locals = Py_None;
PyObject *closure = NULL;
if (!_PyArg_CheckPositional("exec", nargs, 1, 3)) {
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 3, 0, argsbuf);
if (!args) {
goto exit;
}
source = args[0];
if (nargs < 2) {
goto skip_optional;
goto skip_optional_posonly;
}
noptargs--;
globals = args[1];
if (nargs < 3) {
goto skip_optional;
goto skip_optional_posonly;
}
noptargs--;
locals = args[2];
skip_optional:
return_value = builtin_exec_impl(module, source, globals, locals);
skip_optional_posonly:
if (!noptargs) {
goto skip_optional_kwonly;
}
closure = args[3];
skip_optional_kwonly:
return_value = builtin_exec_impl(module, source, globals, locals, closure);
exit:
return return_value;
@ -1030,4 +1045,4 @@ builtin_issubclass(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
exit:
return return_value;
}
/*[clinic end generated code: output=6a2b78ef82bc5155 input=a9049054013a1b77]*/
/*[clinic end generated code: output=a2c5c53e8aead7c3 input=a9049054013a1b77]*/