Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
_test_extension_cpp.cpp231 linesDownload Raw Back to tests
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 
codekingpro/portable-devtools · Team Ai