Skip to content
This repository was archived by the owner on Feb 13, 2025. It is now read-only.

Commit a8a138f

Browse files
author
Anselm Kruis
committed
Stackless issue #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. (cherry picked from commit d377c06)
1 parent e93e490 commit a8a138f

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
@@ -803,7 +803,56 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
803803
* This method is not used by Stackless Python. It is provided for compatibility
804804
* with extension modules and Cython.
805805
*/
806-
return PyEval_EvalFrameEx_slp(f, throwflag, NULL);
806+
PyThreadState *tstate = PyThreadState_GET();
807+
PyObject * retval = NULL;
808+
809+
if (f == NULL)
810+
return NULL;
811+
/* make sure that the Stackless system has been initialized. */
812+
assert(tstate->st.main && tstate->st.current);
813+
/* sanity check. */
814+
assert(SLP_CURRENT_FRAME_IS_VALID(tstate));
815+
if (!throwflag) {
816+
/* If throwflag is true, retval must be NULL. Otherwise it must be non-NULL.
817+
*/
818+
Py_INCREF(Py_None);
819+
retval = Py_None;
820+
}
821+
if (PyFrame_Check(f) && f->f_execute == NULL) {
822+
/* A new frame returned from PyFrame_New() has f->f_execute == NULL.
823+
*/
824+
f->f_execute = PyEval_EvalFrameEx_slp;
825+
} else {
826+
/* The layout of PyFrameObject differs between Stackless and C-Python.
827+
* Stackless f->f_execute is C-Python f->f_code. Stackless f->f_code is at
828+
* the end, just before f_localsplus.
829+
*
830+
* In order to detect a C-Python frame, we must compare f->f_execute
831+
* with every valid frame function. Hard to implement completely.
832+
* Therefore I'll check only for relevant functions.
833+
* Amend the list as needed.
834+
*
835+
* If needed, we could try to fix an C-Python frame on the fly.
836+
*
837+
* (It is not possible to detect an C-Python frame by its size, because
838+
* we need the code object to compute the expected size and the location
839+
* of code objects varies between Stackless and C-Python frames).
840+
*/
841+
if (!PyCFrame_Check(f) &&
842+
f->f_execute != PyEval_EvalFrameEx_slp &&
843+
f->f_execute != slp_eval_frame_value &&
844+
f->f_execute != slp_eval_frame_noval &&
845+
f->f_execute != slp_eval_frame_iter &&
846+
f->f_execute != slp_eval_frame_setup_with &&
847+
f->f_execute != slp_eval_frame_with_cleanup) {
848+
PyErr_BadInternalCall();
849+
return NULL;
850+
}
851+
}
852+
retval = slp_frame_dispatch(f, f->f_back, throwflag, retval);
853+
assert(!STACKLESS_UNWINDING(retval));
854+
assert(SLP_CURRENT_FRAME_IS_VALID(tstate));
855+
return retval;
807856
}
808857

809858
PyObject *

Stackless/changelog.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ What's New in Stackless 3.X.X?
99

1010
*Release date: 20XX-XX-XX*
1111

12+
- https://github.com/stackless-dev/stackless/issues/166
13+
Fix C-API functions PyEval_EvalFrameEx() and PyEval_EvalFrame().
14+
They are now compatible with C-Python.
15+
1216
- https://github.com/stackless-dev/stackless/issues/167
1317
Replace 'printf(...)' calls by PySys_WriteStderr(...). They are used to emit
1418
an error message, if there is a pending error while entering Stackless

Stackless/module/stacklessmodule.c

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,6 +1126,74 @@ static PyObject *get_test_nostacklesscallobj(void)
11261126
return (PyObject*)PyObject_New(test_nostacklesscallObject, &test_nostacklesscallType);
11271127
}
11281128

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