_test_extension_cpp.cpp 6.4 KB

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