Skip to content

Commit 57bdb75

Browse files
gh-117694: Improve tests for PyEval_EvalCodeEx() (GH-117695)
1 parent a9107fe commit 57bdb75

File tree

2 files changed

+114
-91
lines changed

2 files changed

+114
-91
lines changed

Lib/test/test_capi/test_eval_code_ex.py

Lines changed: 81 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,125 @@
11
import unittest
2+
import builtins
3+
from collections import UserDict
24

35
from test.support import import_helper
6+
from test.support import swap_attr
47

58

69
# Skip this test if the _testcapi module isn't available.
710
_testcapi = import_helper.import_module('_testcapi')
811

12+
NULL = None
13+
914

1015
class PyEval_EvalCodeExTests(unittest.TestCase):
1116

1217
def test_simple(self):
1318
def f():
1419
return a
1520

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
21+
eval_code_ex = _testcapi.eval_code_ex
22+
code = f.__code__
23+
self.assertEqual(eval_code_ex(code, dict(a=1)), 1)
24+
25+
self.assertRaises(NameError, eval_code_ex, code, {})
26+
self.assertRaises(SystemError, eval_code_ex, code, UserDict(a=1))
27+
self.assertRaises(SystemError, eval_code_ex, code, [])
28+
self.assertRaises(SystemError, eval_code_ex, code, 1)
29+
# CRASHES eval_code_ex(code, NULL)
30+
# CRASHES eval_code_ex(1, {})
31+
# CRASHES eval_code_ex(NULL, {})
32+
33+
def test_custom_locals(self):
34+
# Monkey-patch __build_class__ to get a class code object.
35+
code = None
36+
def build_class(func, name, /, *bases, **kwds):
37+
nonlocal code
38+
code = func.__code__
39+
40+
with swap_attr(builtins, '__build_class__', build_class):
41+
class A:
42+
# Uses LOAD_NAME for a
43+
r[:] = [a]
44+
45+
eval_code_ex = _testcapi.eval_code_ex
46+
results = []
47+
g = dict(a=1, r=results)
48+
self.assertIsNone(eval_code_ex(code, g))
49+
self.assertEqual(results, [1])
50+
self.assertIsNone(eval_code_ex(code, g, dict(a=2)))
51+
self.assertEqual(results, [2])
52+
self.assertIsNone(eval_code_ex(code, g, UserDict(a=3)))
53+
self.assertEqual(results, [3])
54+
self.assertIsNone(eval_code_ex(code, g, {}))
55+
self.assertEqual(results, [1])
56+
self.assertIsNone(eval_code_ex(code, g, NULL))
57+
self.assertEqual(results, [1])
58+
59+
self.assertRaises(TypeError, eval_code_ex, code, g, [])
60+
self.assertRaises(TypeError, eval_code_ex, code, g, 1)
61+
self.assertRaises(NameError, eval_code_ex, code, dict(r=results), {})
62+
self.assertRaises(NameError, eval_code_ex, code, dict(r=results), NULL)
63+
self.assertRaises(TypeError, eval_code_ex, code, dict(r=results), [])
64+
self.assertRaises(TypeError, eval_code_ex, code, dict(r=results), 1)
2265

2366
def test_with_args(self):
2467
def f(a, b, c):
2568
return a
2669

27-
self.assertEqual(_testcapi.eval_code_ex(f.__code__, {}, {}, (1, 2, 3)), 1)
70+
eval_code_ex = _testcapi.eval_code_ex
71+
code = f.__code__
72+
self.assertEqual(eval_code_ex(code, {}, {}, (1, 2, 3)), 1)
73+
self.assertRaises(TypeError, eval_code_ex, code, {}, {}, (1, 2))
74+
self.assertRaises(TypeError, eval_code_ex, code, {}, {}, (1, 2, 3, 4))
2875

2976
def test_with_kwargs(self):
3077
def f(a, b, c):
3178
return a
3279

33-
self.assertEqual(_testcapi.eval_code_ex(f.__code__, {}, {}, (), dict(a=1, b=2, c=3)), 1)
80+
eval_code_ex = _testcapi.eval_code_ex
81+
code = f.__code__
82+
self.assertEqual(eval_code_ex(code, {}, {}, (), dict(a=1, b=2, c=3)), 1)
83+
self.assertRaises(TypeError, eval_code_ex, code, {}, {}, (), dict(a=1, b=2))
84+
self.assertRaises(TypeError, eval_code_ex, code, {}, {}, (), dict(a=1, b=2))
85+
self.assertRaises(TypeError, eval_code_ex, code, {}, {}, (), dict(a=1, b=2, c=3, d=4))
3486

3587
def test_with_default(self):
3688
def f(a):
3789
return a
3890

39-
self.assertEqual(_testcapi.eval_code_ex(f.__code__, {}, {}, (), {}, (1,)), 1)
91+
eval_code_ex = _testcapi.eval_code_ex
92+
code = f.__code__
93+
self.assertEqual(eval_code_ex(code, {}, {}, (), {}, (1,)), 1)
94+
self.assertRaises(TypeError, eval_code_ex, code, {}, {}, (), {}, ())
4095

4196
def test_with_kwarg_default(self):
4297
def f(*, a):
4398
return a
4499

45-
self.assertEqual(_testcapi.eval_code_ex(f.__code__, {}, {}, (), {}, (), dict(a=1)), 1)
100+
eval_code_ex = _testcapi.eval_code_ex
101+
code = f.__code__
102+
self.assertEqual(eval_code_ex(code, {}, {}, (), {}, (), dict(a=1)), 1)
103+
self.assertRaises(TypeError, eval_code_ex, code, {}, {}, (), {}, (), {})
104+
self.assertRaises(TypeError, eval_code_ex, code, {}, {}, (), {}, (), NULL)
105+
self.assertRaises(SystemError, eval_code_ex, code, {}, {}, (), {}, (), UserDict(a=1))
106+
self.assertRaises(SystemError, eval_code_ex, code, {}, {}, (), {}, (), [])
107+
self.assertRaises(SystemError, eval_code_ex, code, {}, {}, (), {}, (), 1)
46108

47109
def test_with_closure(self):
48110
a = 1
111+
b = 2
49112
def f():
113+
b
50114
return a
51115

52-
self.assertEqual(_testcapi.eval_code_ex(f.__code__, {}, {}, (), {}, (), {}, f.__closure__), 1)
116+
eval_code_ex = _testcapi.eval_code_ex
117+
code = f.__code__
118+
self.assertEqual(eval_code_ex(code, {}, {}, (), {}, (), {}, f.__closure__), 1)
119+
self.assertEqual(eval_code_ex(code, {}, {}, (), {}, (), {}, f.__closure__[::-1]), 2)
120+
121+
# CRASHES eval_code_ex(code, {}, {}, (), {}, (), {}, ()), 1)
122+
# CRASHES eval_code_ex(code, {}, {}, (), {}, (), {}, NULL), 1)
53123

54124

55125
if __name__ == "__main__":

Modules/_testcapimodule.c

Lines changed: 33 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -2645,107 +2645,60 @@ eval_eval_code_ex(PyObject *mod, PyObject *pos_args)
26452645

26462646
PyObject **c_kwargs = NULL;
26472647

2648-
if (!PyArg_UnpackTuple(pos_args,
2649-
"eval_code_ex",
2650-
2,
2651-
8,
2652-
&code,
2653-
&globals,
2654-
&locals,
2655-
&args,
2656-
&kwargs,
2657-
&defaults,
2658-
&kw_defaults,
2659-
&closure))
2648+
if (!PyArg_ParseTuple(pos_args,
2649+
"OO|OO!O!O!OO:eval_code_ex",
2650+
&code,
2651+
&globals,
2652+
&locals,
2653+
&PyTuple_Type, &args,
2654+
&PyDict_Type, &kwargs,
2655+
&PyTuple_Type, &defaults,
2656+
&kw_defaults,
2657+
&closure))
26602658
{
26612659
goto exit;
26622660
}
26632661

2664-
if (!PyCode_Check(code)) {
2665-
PyErr_SetString(PyExc_TypeError,
2666-
"code must be a Python code object");
2667-
goto exit;
2668-
}
2669-
2670-
if (!PyDict_Check(globals)) {
2671-
PyErr_SetString(PyExc_TypeError, "globals must be a dict");
2672-
goto exit;
2673-
}
2674-
2675-
if (locals && !PyMapping_Check(locals)) {
2676-
PyErr_SetString(PyExc_TypeError, "locals must be a mapping");
2677-
goto exit;
2678-
}
2679-
if (locals == Py_None) {
2680-
locals = NULL;
2681-
}
2662+
NULLABLE(code);
2663+
NULLABLE(globals);
2664+
NULLABLE(locals);
2665+
NULLABLE(kw_defaults);
2666+
NULLABLE(closure);
26822667

26832668
PyObject **c_args = NULL;
26842669
Py_ssize_t c_args_len = 0;
2685-
2686-
if (args)
2687-
{
2688-
if (!PyTuple_Check(args)) {
2689-
PyErr_SetString(PyExc_TypeError, "args must be a tuple");
2690-
goto exit;
2691-
} else {
2692-
c_args = &PyTuple_GET_ITEM(args, 0);
2693-
c_args_len = PyTuple_Size(args);
2694-
}
2670+
if (args) {
2671+
c_args = &PyTuple_GET_ITEM(args, 0);
2672+
c_args_len = PyTuple_Size(args);
26952673
}
26962674

26972675
Py_ssize_t c_kwargs_len = 0;
2676+
if (kwargs) {
2677+
c_kwargs_len = PyDict_Size(kwargs);
2678+
if (c_kwargs_len > 0) {
2679+
c_kwargs = PyMem_NEW(PyObject*, 2 * c_kwargs_len);
2680+
if (!c_kwargs) {
2681+
PyErr_NoMemory();
2682+
goto exit;
2683+
}
26982684

2699-
if (kwargs)
2700-
{
2701-
if (!PyDict_Check(kwargs)) {
2702-
PyErr_SetString(PyExc_TypeError, "keywords must be a dict");
2703-
goto exit;
2704-
} else {
2705-
c_kwargs_len = PyDict_Size(kwargs);
2706-
if (c_kwargs_len > 0) {
2707-
c_kwargs = PyMem_NEW(PyObject*, 2 * c_kwargs_len);
2708-
if (!c_kwargs) {
2709-
PyErr_NoMemory();
2710-
goto exit;
2711-
}
2712-
2713-
Py_ssize_t i = 0;
2714-
Py_ssize_t pos = 0;
2715-
2716-
while (PyDict_Next(kwargs,
2717-
&pos,
2718-
&c_kwargs[i],
2719-
&c_kwargs[i + 1]))
2720-
{
2721-
i += 2;
2722-
}
2723-
c_kwargs_len = i / 2;
2724-
/* XXX This is broken if the caller deletes dict items! */
2685+
Py_ssize_t i = 0;
2686+
Py_ssize_t pos = 0;
2687+
while (PyDict_Next(kwargs, &pos, &c_kwargs[i], &c_kwargs[i + 1])) {
2688+
i += 2;
27252689
}
2690+
c_kwargs_len = i / 2;
2691+
/* XXX This is broken if the caller deletes dict items! */
27262692
}
27272693
}
27282694

2729-
27302695
PyObject **c_defaults = NULL;
27312696
Py_ssize_t c_defaults_len = 0;
2732-
2733-
if (defaults && PyTuple_Check(defaults)) {
2697+
if (defaults) {
27342698
c_defaults = &PyTuple_GET_ITEM(defaults, 0);
27352699
c_defaults_len = PyTuple_Size(defaults);
27362700
}
27372701

2738-
if (kw_defaults && !PyDict_Check(kw_defaults)) {
2739-
PyErr_SetString(PyExc_TypeError, "kw_defaults must be a dict");
2740-
goto exit;
2741-
}
2742-
2743-
if (closure && !PyTuple_Check(closure)) {
2744-
PyErr_SetString(PyExc_TypeError, "closure must be a tuple of cells");
2745-
goto exit;
2746-
}
2747-
2748-
27492702
result = PyEval_EvalCodeEx(
27502703
code,
27512704
globals,

0 commit comments

Comments
 (0)