Skip to content

Commit ae62bdd

Browse files
gh-101072: support default and kw default in PyEval_EvalCodeEx for 3.11+ (#101127)
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
1 parent c4de6b1 commit ae62bdd

File tree

5 files changed

+206
-6
lines changed

5 files changed

+206
-6
lines changed
+56
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()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
macOS #.. section: IDLE #.. section: Tools/Demos #.. section: C API
2+
3+
# Write your Misc/NEWS entry below. It should be a simple ReST paragraph. #
4+
Don't start with "- Issue #<n>: " or "- gh-issue-<n>: " or that sort of
5+
stuff.
6+
###########################################################################
7+
8+
Fix the ``defs`` and ``kwdefs`` arguments to :c:func:`PyEval_EvalCodeEx`.

Modules/_testcapimodule.c

+140-1
Original file line numberDiff line numberDiff line change
@@ -2237,7 +2237,7 @@ dict_get_version(PyObject *self, PyObject *args)
22372237
return NULL;
22382238

22392239
_Py_COMP_DIAG_PUSH
2240-
_Py_COMP_DIAG_IGNORE_DEPR_DECLS
2240+
_Py_COMP_DIAG_IGNORE_DEPR_DECLS
22412241
version = dict->ma_version_tag;
22422242
_Py_COMP_DIAG_POP
22432243

@@ -3064,6 +3064,144 @@ eval_get_func_desc(PyObject *self, PyObject *func)
30643064
return PyUnicode_FromString(PyEval_GetFuncDesc(func));
30653065
}
30663066

3067+
static PyObject *
3068+
eval_eval_code_ex(PyObject *mod, PyObject *pos_args)
3069+
{
3070+
PyObject *result = NULL;
3071+
PyObject *code;
3072+
PyObject *globals;
3073+
PyObject *locals = NULL;
3074+
PyObject *args = NULL;
3075+
PyObject *kwargs = NULL;
3076+
PyObject *defaults = NULL;
3077+
PyObject *kw_defaults = NULL;
3078+
PyObject *closure = NULL;
3079+
3080+
PyObject **c_kwargs = NULL;
3081+
3082+
if (!PyArg_UnpackTuple(pos_args,
3083+
"eval_code_ex",
3084+
2,
3085+
8,
3086+
&code,
3087+
&globals,
3088+
&locals,
3089+
&args,
3090+
&kwargs,
3091+
&defaults,
3092+
&kw_defaults,
3093+
&closure))
3094+
{
3095+
goto exit;
3096+
}
3097+
3098+
if (!PyCode_Check(code)) {
3099+
PyErr_SetString(PyExc_TypeError,
3100+
"code must be a Python code object");
3101+
goto exit;
3102+
}
3103+
3104+
if (!PyDict_Check(globals)) {
3105+
PyErr_SetString(PyExc_TypeError, "globals must be a dict");
3106+
goto exit;
3107+
}
3108+
3109+
if (locals && !PyMapping_Check(locals)) {
3110+
PyErr_SetString(PyExc_TypeError, "locals must be a mapping");
3111+
goto exit;
3112+
}
3113+
if (locals == Py_None) {
3114+
locals = NULL;
3115+
}
3116+
3117+
PyObject **c_args = NULL;
3118+
Py_ssize_t c_args_len = 0;
3119+
3120+
if (args)
3121+
{
3122+
if (!PyTuple_Check(args)) {
3123+
PyErr_SetString(PyExc_TypeError, "args must be a tuple");
3124+
goto exit;
3125+
} else {
3126+
c_args = &PyTuple_GET_ITEM(args, 0);
3127+
c_args_len = PyTuple_Size(args);
3128+
}
3129+
}
3130+
3131+
Py_ssize_t c_kwargs_len = 0;
3132+
3133+
if (kwargs)
3134+
{
3135+
if (!PyDict_Check(kwargs)) {
3136+
PyErr_SetString(PyExc_TypeError, "keywords must be a dict");
3137+
goto exit;
3138+
} else {
3139+
c_kwargs_len = PyDict_Size(kwargs);
3140+
if (c_kwargs_len > 0) {
3141+
c_kwargs = PyMem_NEW(PyObject*, 2 * c_kwargs_len);
3142+
if (!c_kwargs) {
3143+
PyErr_NoMemory();
3144+
goto exit;
3145+
}
3146+
3147+
Py_ssize_t i = 0;
3148+
Py_ssize_t pos = 0;
3149+
3150+
while (PyDict_Next(kwargs,
3151+
&pos,
3152+
&c_kwargs[i],
3153+
&c_kwargs[i + 1]))
3154+
{
3155+
i += 2;
3156+
}
3157+
c_kwargs_len = i / 2;
3158+
/* XXX This is broken if the caller deletes dict items! */
3159+
}
3160+
}
3161+
}
3162+
3163+
3164+
PyObject **c_defaults = NULL;
3165+
Py_ssize_t c_defaults_len = 0;
3166+
3167+
if (defaults && PyTuple_Check(defaults)) {
3168+
c_defaults = &PyTuple_GET_ITEM(defaults, 0);
3169+
c_defaults_len = PyTuple_Size(defaults);
3170+
}
3171+
3172+
if (kw_defaults && !PyDict_Check(kw_defaults)) {
3173+
PyErr_SetString(PyExc_TypeError, "kw_defaults must be a dict");
3174+
goto exit;
3175+
}
3176+
3177+
if (closure && !PyTuple_Check(closure)) {
3178+
PyErr_SetString(PyExc_TypeError, "closure must be a tuple of cells");
3179+
goto exit;
3180+
}
3181+
3182+
3183+
result = PyEval_EvalCodeEx(
3184+
code,
3185+
globals,
3186+
locals,
3187+
c_args,
3188+
c_args_len,
3189+
c_kwargs,
3190+
c_kwargs_len,
3191+
c_defaults,
3192+
c_defaults_len,
3193+
kw_defaults,
3194+
closure
3195+
);
3196+
3197+
exit:
3198+
if (c_kwargs) {
3199+
PyMem_DEL(c_kwargs);
3200+
}
3201+
3202+
return result;
3203+
}
3204+
30673205
static PyObject *
30683206
get_feature_macros(PyObject *self, PyObject *Py_UNUSED(args))
30693207
{
@@ -3385,6 +3523,7 @@ static PyMethodDef TestMethods[] = {
33853523
{"set_exc_info", test_set_exc_info, METH_VARARGS},
33863524
{"argparsing", argparsing, METH_VARARGS},
33873525
{"code_newempty", code_newempty, METH_VARARGS},
3526+
{"eval_code_ex", eval_eval_code_ex, METH_VARARGS},
33883527
{"make_exception_with_doc", _PyCFunction_CAST(make_exception_with_doc),
33893528
METH_VARARGS | METH_KEYWORDS},
33903529
{"make_memoryview_from_NULL_pointer", make_memoryview_from_NULL_pointer,

Objects/funcobject.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ _PyFunction_FromConstructor(PyFrameConstructor *constr)
8787
op->func_name = Py_NewRef(constr->fc_name);
8888
op->func_qualname = Py_NewRef(constr->fc_qualname);
8989
op->func_code = Py_NewRef(constr->fc_code);
90-
op->func_defaults = NULL;
91-
op->func_kwdefaults = NULL;
90+
op->func_defaults = Py_XNewRef(constr->fc_defaults);
91+
op->func_kwdefaults = Py_XNewRef(constr->fc_kwdefaults);
9292
op->func_closure = Py_XNewRef(constr->fc_closure);
9393
op->func_doc = Py_NewRef(Py_None);
9494
op->func_dict = NULL;

Python/ceval.c

-3
Original file line numberDiff line numberDiff line change
@@ -1761,9 +1761,6 @@ PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
17611761
}
17621762
allargs = newargs;
17631763
}
1764-
for (int i = 0; i < kwcount; i++) {
1765-
PyTuple_SET_ITEM(kwnames, i, Py_NewRef(kws[2*i]));
1766-
}
17671764
PyFrameConstructor constr = {
17681765
.fc_globals = globals,
17691766
.fc_builtins = builtins,

0 commit comments

Comments
 (0)