Skip to content

Commit 955ba28

Browse files
[3.11] gh-101072: support default and kw default in PyEval_EvalCodeEx for 3.11+ (GH-101127) (#101636)
Co-authored-by: Łukasz Langa <lukasz@langa.pl> Co-authored-by: Matthieu Dartiailh <m.dartiailh@gmail.com>
1 parent 358b02d commit 955ba28

File tree

5 files changed

+201
-6
lines changed

5 files changed

+201
-6
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import unittest
2+
3+
from test.support import import_helper
4+
5+
6+
# Skip this test if the _testcapi module isn't available.
7+
_testcapi = import_helper.import_module('_testcapi')
8+
9+
10+
class PyEval_EvalCodeExTests(unittest.TestCase):
11+
12+
def test_simple(self):
13+
def f():
14+
return a
15+
16+
self.assertEqual(_testcapi.eval_code_ex(f.__code__, dict(a=1)), 1)
17+
18+
# Need to force the compiler to use LOAD_NAME
19+
# def test_custom_locals(self):
20+
# def f():
21+
# return
22+
23+
def test_with_args(self):
24+
def f(a, b, c):
25+
return a
26+
27+
self.assertEqual(_testcapi.eval_code_ex(f.__code__, {}, {}, (1, 2, 3)), 1)
28+
29+
def test_with_kwargs(self):
30+
def f(a, b, c):
31+
return a
32+
33+
self.assertEqual(_testcapi.eval_code_ex(f.__code__, {}, {}, (), dict(a=1, b=2, c=3)), 1)
34+
35+
def test_with_default(self):
36+
def f(a):
37+
return a
38+
39+
self.assertEqual(_testcapi.eval_code_ex(f.__code__, {}, {}, (), {}, (1,)), 1)
40+
41+
def test_with_kwarg_default(self):
42+
def f(*, a):
43+
return a
44+
45+
self.assertEqual(_testcapi.eval_code_ex(f.__code__, {}, {}, (), {}, (), dict(a=1)), 1)
46+
47+
def test_with_closure(self):
48+
a = 1
49+
def f():
50+
return a
51+
52+
self.assertEqual(_testcapi.eval_code_ex(f.__code__, {}, {}, (), {}, (), {}, f.__closure__), 1)
53+
54+
55+
if __name__ == "__main__":
56+
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix the ``defs`` and ``kwdefs`` arguments to :c:func:`PyEval_EvalCodeEx`
2+
and a reference leak in that function.

Modules/_testcapimodule.c

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6058,6 +6058,144 @@ eval_get_func_desc(PyObject *self, PyObject *func)
60586058
return PyUnicode_FromString(PyEval_GetFuncDesc(func));
60596059
}
60606060

6061+
static PyObject *
6062+
eval_eval_code_ex(PyObject *mod, PyObject *pos_args)
6063+
{
6064+
PyObject *result = NULL;
6065+
PyObject *code;
6066+
PyObject *globals;
6067+
PyObject *locals = NULL;
6068+
PyObject *args = NULL;
6069+
PyObject *kwargs = NULL;
6070+
PyObject *defaults = NULL;
6071+
PyObject *kw_defaults = NULL;
6072+
PyObject *closure = NULL;
6073+
6074+
PyObject **c_kwargs = NULL;
6075+
6076+
if (!PyArg_UnpackTuple(pos_args,
6077+
"eval_code_ex",
6078+
2,
6079+
8,
6080+
&code,
6081+
&globals,
6082+
&locals,
6083+
&args,
6084+
&kwargs,
6085+
&defaults,
6086+
&kw_defaults,
6087+
&closure))
6088+
{
6089+
goto exit;
6090+
}
6091+
6092+
if (!PyCode_Check(code)) {
6093+
PyErr_SetString(PyExc_TypeError,
6094+
"code must be a Python code object");
6095+
goto exit;
6096+
}
6097+
6098+
if (!PyDict_Check(globals)) {
6099+
PyErr_SetString(PyExc_TypeError, "globals must be a dict");
6100+
goto exit;
6101+
}
6102+
6103+
if (locals && !PyMapping_Check(locals)) {
6104+
PyErr_SetString(PyExc_TypeError, "locals must be a mapping");
6105+
goto exit;
6106+
}
6107+
if (locals == Py_None) {
6108+
locals = NULL;
6109+
}
6110+
6111+
PyObject **c_args = NULL;
6112+
Py_ssize_t c_args_len = 0;
6113+
6114+
if (args)
6115+
{
6116+
if (!PyTuple_Check(args)) {
6117+
PyErr_SetString(PyExc_TypeError, "args must be a tuple");
6118+
goto exit;
6119+
} else {
6120+
c_args = &PyTuple_GET_ITEM(args, 0);
6121+
c_args_len = PyTuple_Size(args);
6122+
}
6123+
}
6124+
6125+
Py_ssize_t c_kwargs_len = 0;
6126+
6127+
if (kwargs)
6128+
{
6129+
if (!PyDict_Check(kwargs)) {
6130+
PyErr_SetString(PyExc_TypeError, "keywords must be a dict");
6131+
goto exit;
6132+
} else {
6133+
c_kwargs_len = PyDict_Size(kwargs);
6134+
if (c_kwargs_len > 0) {
6135+
c_kwargs = PyMem_NEW(PyObject*, 2 * c_kwargs_len);
6136+
if (!c_kwargs) {
6137+
PyErr_NoMemory();
6138+
goto exit;
6139+
}
6140+
6141+
Py_ssize_t i = 0;
6142+
Py_ssize_t pos = 0;
6143+
6144+
while (PyDict_Next(kwargs,
6145+
&pos,
6146+
&c_kwargs[i],
6147+
&c_kwargs[i + 1]))
6148+
{
6149+
i += 2;
6150+
}
6151+
c_kwargs_len = i / 2;
6152+
/* XXX This is broken if the caller deletes dict items! */
6153+
}
6154+
}
6155+
}
6156+
6157+
6158+
PyObject **c_defaults = NULL;
6159+
Py_ssize_t c_defaults_len = 0;
6160+
6161+
if (defaults && PyTuple_Check(defaults)) {
6162+
c_defaults = &PyTuple_GET_ITEM(defaults, 0);
6163+
c_defaults_len = PyTuple_Size(defaults);
6164+
}
6165+
6166+
if (kw_defaults && !PyDict_Check(kw_defaults)) {
6167+
PyErr_SetString(PyExc_TypeError, "kw_defaults must be a dict");
6168+
goto exit;
6169+
}
6170+
6171+
if (closure && !PyTuple_Check(closure)) {
6172+
PyErr_SetString(PyExc_TypeError, "closure must be a tuple of cells");
6173+
goto exit;
6174+
}
6175+
6176+
6177+
result = PyEval_EvalCodeEx(
6178+
code,
6179+
globals,
6180+
locals,
6181+
c_args,
6182+
c_args_len,
6183+
c_kwargs,
6184+
c_kwargs_len,
6185+
c_defaults,
6186+
c_defaults_len,
6187+
kw_defaults,
6188+
closure
6189+
);
6190+
6191+
exit:
6192+
if (c_kwargs) {
6193+
PyMem_DEL(c_kwargs);
6194+
}
6195+
6196+
return result;
6197+
}
6198+
60616199
static PyObject *
60626200
get_feature_macros(PyObject *self, PyObject *Py_UNUSED(args))
60636201
{
@@ -6402,6 +6540,7 @@ static PyMethodDef TestMethods[] = {
64026540
{"set_exc_info", test_set_exc_info, METH_VARARGS},
64036541
{"argparsing", argparsing, METH_VARARGS},
64046542
{"code_newempty", code_newempty, METH_VARARGS},
6543+
{"eval_code_ex", eval_eval_code_ex, METH_VARARGS},
64056544
{"make_exception_with_doc", _PyCFunction_CAST(make_exception_with_doc),
64066545
METH_VARARGS | METH_KEYWORDS},
64076546
{"make_memoryview_from_NULL_pointer", make_memoryview_from_NULL_pointer,

Objects/funcobject.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ _PyFunction_FromConstructor(PyFrameConstructor *constr)
2727
op->func_qualname = constr->fc_qualname;
2828
Py_INCREF(constr->fc_code);
2929
op->func_code = constr->fc_code;
30-
op->func_defaults = NULL;
31-
op->func_kwdefaults = NULL;
30+
Py_XINCREF(constr->fc_defaults);
31+
op->func_defaults = constr->fc_defaults;
32+
Py_XINCREF(constr->fc_kwdefaults);
33+
op->func_kwdefaults = constr->fc_kwdefaults;
3234
Py_XINCREF(constr->fc_closure);
3335
op->func_closure = constr->fc_closure;
3436
Py_INCREF(Py_None);

Python/ceval.c

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6489,10 +6489,6 @@ PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
64896489
}
64906490
allargs = newargs;
64916491
}
6492-
for (int i = 0; i < kwcount; i++) {
6493-
Py_INCREF(kws[2*i]);
6494-
PyTuple_SET_ITEM(kwnames, i, kws[2*i]);
6495-
}
64966492
PyFrameConstructor constr = {
64976493
.fc_globals = globals,
64986494
.fc_builtins = builtins,

0 commit comments

Comments
 (0)