codekingpro/portable-devtools
114k
1/* This is a set of functions used to test C++ exceptions are not
2 * broken during greenlet switches
3 */
4
5#include "../greenlet.h"
6#include "../greenlet_compiler_compat.hpp"
7#include <exception>
8#include <stdexcept>
9
10struct exception_t {
11 int depth;
12 exception_t(int depth) : depth(depth) {}
13};
14
15/* Functions are called via pointers to prevent inlining */
16static void (*p_test_exception_throw_nonstd)(int depth);
17static void (*p_test_exception_throw_std)();
18static PyObject* (*p_test_exception_switch_recurse)(int depth, int left);
19
20static void
21test_exception_throw_nonstd(int depth)
22{
23 throw exception_t(depth);
24}
25
26static void
27test_exception_throw_std()
28{
29 throw std::runtime_error("Thrown from an extension.");
30}
31
32static PyObject*
33test_exception_switch_recurse(int depth, int left)
34{
35 if (left > 0) {
36 return p_test_exception_switch_recurse(depth, left - 1);
37 }
38
39 PyObject* result = NULL;
40 PyGreenlet* self = PyGreenlet_GetCurrent();
41 if (self == NULL)
42 return NULL;
43
44 try {
45 if (PyGreenlet_Switch(PyGreenlet_GET_PARENT(self), NULL, NULL) == NULL) {
46 Py_DECREF(self);
47 return NULL;
48 }
49 p_test_exception_throw_nonstd(depth);
50 PyErr_SetString(PyExc_RuntimeError,
51 "throwing C++ exception didn't work");
52 }
53 catch (const exception_t& e) {
54 if (e.depth != depth)
55 PyErr_SetString(PyExc_AssertionError, "depth mismatch");
56 else
57 result = PyLong_FromLong(depth);
58 }
59 catch (...) {
60 PyErr_SetString(PyExc_RuntimeError, "unexpected C++ exception");
61 }
62
63 Py_DECREF(self);
64 return result;
65}
66
67/* test_exception_switch(int depth)
68 * - recurses depth times
69 * - switches to parent inside try/catch block
70 * - throws an exception that (expected to be caught in the same function)
71 * - verifies depth matches (exceptions shouldn't be caught in other greenlets)
72 */
73static PyObject*
74test_exception_switch(PyObject* UNUSED(self), PyObject* args)
75{
76 int depth;
77 if (!PyArg_ParseTuple(args, "i", &depth))
78 return NULL;
79 return p_test_exception_switch_recurse(depth, depth);
80}
81
82
83static PyObject*
84py_test_exception_throw_nonstd(PyObject* UNUSED(self), PyObject* args)
85{
86 if (!PyArg_ParseTuple(args, ""))
87 return NULL;
88 p_test_exception_throw_nonstd(0);
89 PyErr_SetString(PyExc_AssertionError, "unreachable code running after throw");
90 return NULL;
91}
92
93static PyObject*
94py_test_exception_throw_std(PyObject* UNUSED(self), PyObject* args)
95{
96 if (!PyArg_ParseTuple(args, ""))
97 return NULL;
98 p_test_exception_throw_std();
99 PyErr_SetString(PyExc_AssertionError, "unreachable code running after throw");
100 return NULL;
101}
102
103static PyObject*
104py_test_call(PyObject* UNUSED(self), PyObject* arg)
105{
106 PyObject* noargs = PyTuple_New(0);
107 PyObject* ret = PyObject_Call(arg, noargs, nullptr);
108 Py_DECREF(noargs);
109 return ret;
110}
111
112
113
114/* test_exception_switch_and_do_in_g2(g2func)
115 * - creates new greenlet g2 to run g2func
116 * - switches to g2 inside try/catch block
117 * - verifies that no exception has been caught
118 *
119 * it is used together with test_exception_throw to verify that unhandled
120 * exceptions thrown in one greenlet do not propagate to other greenlet nor
121 * segfault the process.
122 */
123static PyObject*
124test_exception_switch_and_do_in_g2(PyObject* UNUSED(self), PyObject* args)
125{
126 PyObject* g2func = NULL;
127 PyObject* result = NULL;
128
129 if (!PyArg_ParseTuple(args, "O", &g2func))
130 return NULL;
131 PyGreenlet* g2 = PyGreenlet_New(g2func, NULL);
132 if (!g2) {
133 return NULL;
134 }
135
136 try {
137 result = PyGreenlet_Switch(g2, NULL, NULL);
138 if (!result) {
139 Py_DECREF(g2);
140 return NULL;
141 }
142 }
143 catch (const exception_t& e) {
144 /* if we are here the memory can be already corrupted and the program
145 * might crash before below py-level exception might become printed.
146 * -> print something to stderr to make it clear that we had entered
147 * this catch block.
148 * See comments in inner_bootstrap()
149 */
150#if defined(WIN32) || defined(_WIN32)
151 fprintf(stderr, "C++ exception unexpectedly caught in g1\n");
152 PyErr_SetString(PyExc_AssertionError, "C++ exception unexpectedly caught in g1");
153 Py_XDECREF(result);
154 return NULL;
155#else
156 throw;
157#endif
158 }
159
160 Py_XDECREF(result);
161 Py_RETURN_NONE;
162}
163
164static PyMethodDef test_methods[] = {
165 {"test_exception_switch",
166 (PyCFunction)&test_exception_switch,
167 METH_VARARGS,
168 "Switches to parent twice, to test exception handling and greenlet "
169 "switching."},
170 {"test_exception_switch_and_do_in_g2",
171 (PyCFunction)&test_exception_switch_and_do_in_g2,
172 METH_VARARGS,
173 "Creates new greenlet g2 to run g2func and switches to it inside try/catch "
174 "block. Used together with test_exception_throw to verify that unhandled "
175 "C++ exceptions thrown in a greenlet doe not corrupt memory."},
176 {"test_exception_throw_nonstd",
177 (PyCFunction)&py_test_exception_throw_nonstd,
178 METH_VARARGS,
179 "Throws non-standard C++ exception. Calling this function directly should abort the process."
180 },
181 {"test_exception_throw_std",
182 (PyCFunction)&py_test_exception_throw_std,
183 METH_VARARGS,
184 "Throws standard C++ exception. Calling this function directly should abort the process."
185 },
186 {"test_call",
187 (PyCFunction)&py_test_call,
188 METH_O,
189 "Call the given callable. Unlike calling it directly, this creates a "
190 "new C-level stack frame, which may be helpful in testing."
191 },
192 {NULL, NULL, 0, NULL}
193};
194
195
196static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT,
197 "greenlet.tests._test_extension_cpp",
198 NULL,
199 0,
200 test_methods,
201 NULL,
202 NULL,
203 NULL,
204 NULL};
205
206PyMODINIT_FUNC
207PyInit__test_extension_cpp(void)
208{
209 PyObject* module = NULL;
210
211 module = PyModule_Create(&moduledef);
212
213 if (module == NULL) {
214 return NULL;
215 }
216
217 PyGreenlet_Import();
218 if (_PyGreenlet_API == NULL) {
219 return NULL;
220 }
221
222 p_test_exception_throw_nonstd = test_exception_throw_nonstd;
223 p_test_exception_throw_std = test_exception_throw_std;
224 p_test_exception_switch_recurse = test_exception_switch_recurse;
225#ifdef Py_GIL_DISABLED
226 PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED);
227#endif
228
229 return module;
230}
231 