]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-101072: support default and kw default in PyEval_EvalCodeEx for 3.11+ (#101127)
authorMatthieu Dartiailh <m.dartiailh@gmail.com>
Tue, 7 Feb 2023 09:34:21 +0000 (10:34 +0100)
committerGitHub <noreply@github.com>
Tue, 7 Feb 2023 09:34:21 +0000 (10:34 +0100)
Co-authored-by: Ɓukasz Langa <lukasz@langa.pl>
Lib/test/test_capi/test_eval_code_ex.py [new file with mode: 0644]
Misc/NEWS.d/next/Core and Builtins/2023-02-06-20-13-36.gh-issue-92173.RQE0mk.rst [new file with mode: 0644]
Modules/_testcapimodule.c
Objects/funcobject.c
Python/ceval.c

diff --git a/Lib/test/test_capi/test_eval_code_ex.py b/Lib/test/test_capi/test_eval_code_ex.py
new file mode 100644 (file)
index 0000000..2d28e52
--- /dev/null
@@ -0,0 +1,56 @@
+import unittest
+
+from test.support import import_helper
+
+
+# Skip this test if the _testcapi module isn't available.
+_testcapi = import_helper.import_module('_testcapi')
+
+
+class PyEval_EvalCodeExTests(unittest.TestCase):
+
+    def test_simple(self):
+        def f():
+            return a
+
+        self.assertEqual(_testcapi.eval_code_ex(f.__code__, dict(a=1)), 1)
+
+    # Need to force the compiler to use LOAD_NAME
+    # def test_custom_locals(self):
+    #     def f():
+    #         return
+
+    def test_with_args(self):
+        def f(a, b, c):
+            return a
+
+        self.assertEqual(_testcapi.eval_code_ex(f.__code__, {}, {}, (1, 2, 3)), 1)
+
+    def test_with_kwargs(self):
+        def f(a, b, c):
+            return a
+
+        self.assertEqual(_testcapi.eval_code_ex(f.__code__, {}, {}, (), dict(a=1, b=2, c=3)), 1)
+
+    def test_with_default(self):
+        def f(a):
+            return a
+
+        self.assertEqual(_testcapi.eval_code_ex(f.__code__, {}, {}, (), {}, (1,)), 1)
+
+    def test_with_kwarg_default(self):
+        def f(*, a):
+            return a
+
+        self.assertEqual(_testcapi.eval_code_ex(f.__code__, {}, {}, (), {}, (), dict(a=1)), 1)
+
+    def test_with_closure(self):
+        a = 1
+        def f():
+            return a
+
+        self.assertEqual(_testcapi.eval_code_ex(f.__code__, {}, {}, (), {}, (), {}, f.__closure__), 1)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-02-06-20-13-36.gh-issue-92173.RQE0mk.rst b/Misc/NEWS.d/next/Core and Builtins/2023-02-06-20-13-36.gh-issue-92173.RQE0mk.rst
new file mode 100644 (file)
index 0000000..2d991f6
--- /dev/null
@@ -0,0 +1,8 @@
+macOS #.. section: IDLE #.. section: Tools/Demos #.. section: C API
+
+# Write your Misc/NEWS entry below.  It should be a simple ReST paragraph. #
+Don't start with "- Issue #<n>: " or "- gh-issue-<n>: " or that sort of
+stuff.
+###########################################################################
+
+Fix the ``defs`` and ``kwdefs`` arguments to :c:func:`PyEval_EvalCodeEx`.
index 5e47f4975a2d54d9c4a10528a5988f8f07b57150..5a6097ef0ac5a6888d306df16994249870aea303 100644 (file)
@@ -2237,7 +2237,7 @@ dict_get_version(PyObject *self, PyObject *args)
         return NULL;
 
     _Py_COMP_DIAG_PUSH
-    _Py_COMP_DIAG_IGNORE_DEPR_DECLS    
+    _Py_COMP_DIAG_IGNORE_DEPR_DECLS
     version = dict->ma_version_tag;
     _Py_COMP_DIAG_POP
 
@@ -3064,6 +3064,144 @@ eval_get_func_desc(PyObject *self, PyObject *func)
     return PyUnicode_FromString(PyEval_GetFuncDesc(func));
 }
 
+static PyObject *
+eval_eval_code_ex(PyObject *mod, PyObject *pos_args)
+{
+    PyObject *result = NULL;
+    PyObject *code;
+    PyObject *globals;
+    PyObject *locals = NULL;
+    PyObject *args = NULL;
+    PyObject *kwargs = NULL;
+    PyObject *defaults = NULL;
+    PyObject *kw_defaults = NULL;
+    PyObject *closure = NULL;
+
+    PyObject **c_kwargs = NULL;
+
+    if (!PyArg_UnpackTuple(pos_args,
+                           "eval_code_ex",
+                           2,
+                           8,
+                           &code,
+                           &globals,
+                           &locals,
+                           &args,
+                           &kwargs,
+                           &defaults,
+                           &kw_defaults,
+                           &closure))
+    {
+        goto exit;
+    }
+
+    if (!PyCode_Check(code)) {
+        PyErr_SetString(PyExc_TypeError,
+                        "code must be a Python code object");
+        goto exit;
+    }
+
+    if (!PyDict_Check(globals)) {
+        PyErr_SetString(PyExc_TypeError, "globals must be a dict");
+        goto exit;
+    }
+
+    if (locals && !PyMapping_Check(locals)) {
+        PyErr_SetString(PyExc_TypeError, "locals must be a mapping");
+        goto exit;
+    }
+    if (locals == Py_None) {
+        locals = NULL;
+    }
+
+    PyObject **c_args = NULL;
+    Py_ssize_t c_args_len = 0;
+
+    if (args)
+    {
+        if (!PyTuple_Check(args)) {
+            PyErr_SetString(PyExc_TypeError, "args must be a tuple");
+            goto exit;
+        } else {
+            c_args = &PyTuple_GET_ITEM(args, 0);
+            c_args_len = PyTuple_Size(args);
+        }
+    }
+
+    Py_ssize_t c_kwargs_len = 0;
+
+    if (kwargs)
+    {
+        if (!PyDict_Check(kwargs)) {
+            PyErr_SetString(PyExc_TypeError, "keywords must be a dict");
+            goto exit;
+        } else {
+            c_kwargs_len = PyDict_Size(kwargs);
+            if (c_kwargs_len > 0) {
+                c_kwargs = PyMem_NEW(PyObject*, 2 * c_kwargs_len);
+                if (!c_kwargs) {
+                    PyErr_NoMemory();
+                    goto exit;
+                }
+
+                Py_ssize_t i = 0;
+                Py_ssize_t pos = 0;
+
+                while (PyDict_Next(kwargs,
+                                   &pos,
+                                   &c_kwargs[i],
+                                   &c_kwargs[i + 1]))
+                {
+                    i += 2;
+                }
+                c_kwargs_len = i / 2;
+                /* XXX This is broken if the caller deletes dict items! */
+            }
+        }
+    }
+
+
+    PyObject **c_defaults = NULL;
+    Py_ssize_t c_defaults_len = 0;
+
+    if (defaults && PyTuple_Check(defaults)) {
+        c_defaults = &PyTuple_GET_ITEM(defaults, 0);
+        c_defaults_len = PyTuple_Size(defaults);
+    }
+
+    if (kw_defaults && !PyDict_Check(kw_defaults)) {
+        PyErr_SetString(PyExc_TypeError, "kw_defaults must be a dict");
+        goto exit;
+    }
+
+    if (closure && !PyTuple_Check(closure)) {
+        PyErr_SetString(PyExc_TypeError, "closure must be a tuple of cells");
+        goto exit;
+    }
+
+
+    result = PyEval_EvalCodeEx(
+        code,
+        globals,
+        locals,
+        c_args,
+        c_args_len,
+        c_kwargs,
+        c_kwargs_len,
+        c_defaults,
+        c_defaults_len,
+        kw_defaults,
+        closure
+    );
+
+exit:
+    if (c_kwargs) {
+        PyMem_DEL(c_kwargs);
+    }
+
+    return result;
+}
+
 static PyObject *
 get_feature_macros(PyObject *self, PyObject *Py_UNUSED(args))
 {
@@ -3385,6 +3523,7 @@ static PyMethodDef TestMethods[] = {
     {"set_exc_info",            test_set_exc_info,               METH_VARARGS},
     {"argparsing",              argparsing,                      METH_VARARGS},
     {"code_newempty",           code_newempty,                   METH_VARARGS},
+    {"eval_code_ex",            eval_eval_code_ex,               METH_VARARGS},
     {"make_exception_with_doc", _PyCFunction_CAST(make_exception_with_doc),
      METH_VARARGS | METH_KEYWORDS},
     {"make_memoryview_from_NULL_pointer", make_memoryview_from_NULL_pointer,
index baa360381a77242b3c7b1450cbc779e73f7b1fb0..91a6b3dd40a23247d72c5e1dfa477c2fcca41152 100644 (file)
@@ -87,8 +87,8 @@ _PyFunction_FromConstructor(PyFrameConstructor *constr)
     op->func_name = Py_NewRef(constr->fc_name);
     op->func_qualname = Py_NewRef(constr->fc_qualname);
     op->func_code = Py_NewRef(constr->fc_code);
-    op->func_defaults = NULL;
-    op->func_kwdefaults = NULL;
+    op->func_defaults = Py_XNewRef(constr->fc_defaults);
+    op->func_kwdefaults = Py_XNewRef(constr->fc_kwdefaults);
     op->func_closure = Py_XNewRef(constr->fc_closure);
     op->func_doc = Py_NewRef(Py_None);
     op->func_dict = NULL;
index 2e6fed580dede4be4f6acee1332faf0305e9ac1b..ecb5bf9655553edd8cff268c7a51d1e8609fa1c5 100644 (file)
@@ -1761,9 +1761,6 @@ PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
         }
         allargs = newargs;
     }
-    for (int i = 0; i < kwcount; i++) {
-        PyTuple_SET_ITEM(kwnames, i, Py_NewRef(kws[2*i]));
-    }
     PyFrameConstructor constr = {
         .fc_globals = globals,
         .fc_builtins = builtins,