8000 Stackless issue #166: Fix PyEval_EvalFrameEx() · akruis/cpython@d377c06 · GitHub
[go: up one dir, main page]

Skip to content

Commit d377c06

Browse files
author
Anselm Kruis
committed
Stackless issue python#166: Fix PyEval_EvalFrameEx()
The C-API functions PyEval_EvalFrameEx() and PyEval_EvalFrame() were broken, because Stackless does not use them and didn't test them. This commit adds test code and makes the functions compatible with C-Python. It is now save to call them from extension modules, i.e. modules created by Cython.
1 parent 13f5e18 commit d377c06

File tree

4 files changed

+224
-1
lines changed

4 files changed

+224
-1
lines changed

Python/ceval.c

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -695,7 +695,56 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
695695
* This method is not used by Stackless Python. It is provided for compatibility
696696
* with extension modules and Cython.
697697
*/
698-
return PyEval_EvalFrameEx_slp(f, throwflag, NULL);
698+
PyThreadState *tstate = PyThreadState_GET();
699+
PyObject * retval = NULL;
700+
701+
if (f == NULL)
702+
return NULL;
703+
/* make sure that the Stackless system has been initialized. */
704+
assert(tstate->st.main && tstate->st.current);
705+
/* sanity check. */
706+
assert(SLP_CURRENT_FRAME_IS_VALID(tstate));
707+
if (!throwflag) {
708+
/* If throwflag is true, retval must be NULL. Otherwise it must be non-NULL.
709+
*/
710+
Py_INCREF(Py_None);
711+
retval = Py_None;
712+
}
713+
if (PyFrame_Check(f) && f->f_execute == NULL) {
714+
/* A new frame returned from PyFrame_New() has f->f_execute == NULL.
715+
*/
716+
f->f_execute = PyEval_EvalFrameEx_slp;
717+
} else {
718+
/* The layout of PyFrameObject differs between Stackless and C-Python.
719+
* Stackless f->f_execute is C-Python f->f_code. Stackless f->f_code is at
720+
* the end, just before f_localsplus.
721+
*
722+
* In order to detect a C-Python frame, we must compare f->f_execute
723+
* with every valid frame function. Hard to implement completely.
724+
* Therefore I'll check only for relevant functions.
725+
* Amend the list as needed.
726+
*
727+
* If needed, we could try to fix an C-Python frame on the fly.
728+
*
729+
* (It is not possible to detect an C-Python frame by its size, because
730+
* we need the code object to compute the expected size and the location
731+
* of code objects varies between Stackless and C-Python frames).
732+
*/
733+
if (!PyCFrame_Check(f) &&
734+
f->f_execute != PyEval_EvalFrameEx_slp &&
735+
f->f_execute != slp_eval_frame_value &&
736+
f->f_execute != slp_eval_frame_noval &&
737+
f->f_execute != slp_eval_frame_iter &&
738+
f->f_execute != slp_eval_frame_setup_with &&
739+
f->f_execute != slp_eval_frame_with_cleanup) {
740+
PyErr_BadInternalCall();
741+
return NULL;
742+
}
743+
}
744+
retval = slp_frame_dispatch(f, f->f_back, throwflag, retval);
745+
assert(!STACKLESS_UNWINDING(retval));
746+
assert(SLP_CURRENT_FRAME_IS_VALID(tstate));
747+
return retval;
699748
}
700749

701750
PyObject *

Stackless/changelog.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ What's New in Stackless 2.7.XX?
1010

1111
*Release date: XXXX-XX-XX*
1212

13+
- https://github.com/stackless-dev/stackless/issues/166
14+
Fix C-API functions PyEval_EvalFrameEx() and PyEval_EvalFrame().
15+
They are now compatible with C-Python.
16+
1317

1418
What's New in Stackless 2.7.15?
1519
===============================

Stackless/module/stacklessmodule.c

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1130,6 +1130,74 @@ static int init_test_nostacklesscalltype(void)
11301130
return 0;
11311131
}
11321132

1133+
1134+
PyDoc_STRVAR(test_PyEval_EvalFrameEx__doc__,
1135+
"test_PyEval_EvalFrameEx(code, globals, args=()) -- a builtin testing function.\n\
1136+
This function tests the C-API function PyEval_EvalFrameEx(), which is not used\n\
1137+
by Stackless Python.\n\
1138+
The function creates a frame from code, globals and args and executes the frame.");
1139+
1140+
static PyObject* test_PyEval_EvalFrameEx(PyObject *self, PyObject *args, PyObject *kwds) {
1141+
static char *kwlist[] = {"code", "globals", "args", "alloca", NULL};
1142+
PyThreadState *tstate = PyThreadState_GET();
1143+
PyCodeObject *co;
1144+
PyObject *globals, *co_args = NULL;
1145+
Py_ssize_t alloca_size = 0;
1146+
PyFrameObject *f;
1147+
PyObject *result = NULL;
1148+
void *p;
1149+
Py_ssize_t na;
1150+
1151+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O!|O!n:test_PyEval_EvalFrameEx", kwlist,
1152+
&PyCode_Type, &co, &PyDict_Type, &globals, &PyTuple_Type, &co_args, &alloca_size))
1153+
return NULL;
1154+
p = alloca(alloca_size);
1155+
assert(globals != NULL);
1156+
assert(tstate != NULL);
1157+
na = PyTuple_Size(co->co_freevars);
1158+
if (na == -1)
1159+
return NULL;
1160+
if (na > 0) {
1161+
PyErr_Format(PyExc_ValueError, "code requires cell variables");
1162+
return NULL;
1163+
}
1164+
f = PyFrame_New(tstate, co, globals, NULL);
1165+
if (f == NULL) {
1166+
return NULL;
1167+
}
1168+
if (co_args) {
1169+
PyObject **fastlocals;
1170+
Py_ssize_t i;
1171+
na = PyTuple_Size(co_args);
1172+
if (na == -1)
1173+
goto exit;
1174+
if (na > co->co_argcount) {
1175+
PyErr_Format(PyExc_ValueError, "too many items in tuple 'args'");
1176+
goto exit;
1177+
}
1178+
fastlocals = f->f_localsplus;
1179+
for (i = 0; i < na; i++) {
1180+
PyObject *arg = PyTuple_GetItem(co_args, i);
1181+
if (arg == NULL) {
1182+
goto exit;
1183+
}
1184+
Py_INCREF(arg);
1185+
fastlocals[i] = arg;
1186+
}
1187+
if (alloca_size > 0 && na > 0) {
1188+
Py_SETREF(fastlocals[0], PyLong_FromVoidPtr(p));
1189+
}
1190+
}
1191+
result = PyEval_EvalFrameEx(f,0);
1192+
/* result = Py_None; Py_INCREF(Py_None); */
1193+
exit:
1194+
++tstate->recursion_depth;
1195+
Py_DECREF(f);
1196+
--tstate->recursion_depth;
1197+
return result;
1198+
}
1199+
1200+
11331201
/******************************************************
11341202
11351203
The Stackless External Interface
@@ -1601,6 +1669,8 @@ static PyMethodDef stackless_methods[] = {
16011669
test_outside__doc__},
16021670
{"test_cstate", (PCF)test_cstate, METH_O,
16031671
test_cstate__doc__},
1672+
{"test_PyEval_EvalFrameEx", (PCF)test_PyEval_EvalFrameEx, METH_VARARGS | METH_KEYWORDS,
1673+
test_PyEval_EvalFrameEx__doc__},
16041674
{"set_channel_callback", (PCF)set_channel_callback, METH_O,
16051675
set_channel_callback__doc__},
16061676
{"get_channel_callback", (PCF)get_channel_callback, METH_NOARGS,

Stackless/unittests/test_capi.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#
2+
# -*- coding: utf-8 -*-
3+
#
4+
# Copyright 2018 science + computing ag
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
18+
"""Test the (Stackless)-Python C-API
19+
20+
Tests not relevant for pure Python code.
21+
"""
22+
23+
from __future__ import print_function, absolute_import, division
24+
25+
import inspect
26+
import stackless
27+
import sys
28+
import unittest
29+
30+
from support import test_main # @UnusedImport
31+
from support import StacklessTestCase
32+
33+
34+
class Test_PyEval_EvalFrameEx(StacklessTestCase):
35+
@staticmethod
36+
def function(arg):
37+
return arg
38+
39+
def call_PyEval_EvalFrameEx(self, *args, **kw):
40+
f = self.function
41+
if inspect.ismethod(f):
42+
f = f.__func__
43+
return stackless.test_PyEval_EvalFrameEx(f.__code__, f.__globals__, args, **kw)
44+
45+
def test_free_vars(self):
46+
# stackless.test_PyEval_EvalFrameEx can't handle code, that contains free variables.
47+
x = None
48+
49+
def f():
50+
return x
51+
self.assertTupleEqual(f.__code__.co_freevars, ('x',))
52+
self.assertRaises(ValueError, stackless.test_PyEval_EvalFrameEx, f.__code__, f.__globals__)
53+
54+
def test_0_args(self):
55+
self.assertRaises(UnboundLocalError, self.call_PyEval_EvalFrameEx)
56+
57+
def test_1_args(self):
58+
self.assertEqual(self.call_PyEval_EvalFrameEx(47110815), 47110815)
59+
60+
def test_2_args(self):
61+
self.assertRaises(ValueError, self.call_PyEval_EvalFrameEx, 4711, None)
62+
63+
def test_cstack_spilling(self):
64+
# Force stack spilling. 16384 is the value of CSTACK_WATERMARK from slp_platformselect.h
65+
self.call_PyEval_EvalFrameEx(None, alloca=16384 * 8)
66+
67+
def test_stack_unwinding(self):
68+
# Calling the __init__ method of a new-style class involves stack unwinding
69+
class C(object):
70+
def __init__(self):
71+
self.initialized = True
72+
73+
def f(C):
74+
return C()
75+
76+
c = stackless.test_PyEval_EvalFrameEx(f.__code__, f.__globals__, (C,))
77+
self.assertIsInstance(c, C)
78+
self.assertTrue(c.initialized)
79+
80+
def test_tasklet_switch(self):
81+
# test a tasklet switch out of the running frame
82+
self.other_done = False
83+
84+
def other_tasklet():
85+
self.other_done = True
86+
87+
def f():
88+
stackless.run()
89+
return 666
90+
91+
stackless.tasklet(other_tasklet)()
92+
result = stackless.test_PyEval_EvalFrameEx(f.__code__, f.__globals__)
93+
self.assertTrue(self.other_done)
94+
self.assertEqual(result, 666)
95+
96+
97+
if __name__ == "__main__":
98+
if not sys.argv[1:]:
99+
sys.argv.append('-v')
100+
unittest.main()

0 commit comments

Comments
 (0)
0