Skip to content

Compile without exceptions #1703

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ v2.3.0 (Not yet released)
* ``pybind11/stl.h`` does not convert strings to ``vector<string>`` anymore.
`#1258 <https://github.com/pybind/pybind11/issues/1258>`_.

* Add a ``PYBIND11_NOEXCEPTIONS`` identifier that allows for compilation
without exceptions enabled. Importing a python module with this identifier
that throws an exception will call ``std::terminate()``.

v2.2.4 (September 11, 2018)
-----------------------------------------------------

Expand Down
23 changes: 23 additions & 0 deletions include/pybind11/detail/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,16 @@ extern "C" {
return m.ptr();
}
\endrst */
#if defined(PYBIND11_NOEXCEPTIONS)
#define PYBIND11_PLUGIN(name) \
PYBIND11_DEPRECATED("PYBIND11_PLUGIN is deprecated, use PYBIND11_MODULE") \
static PyObject *pybind11_init(); \
PYBIND11_PLUGIN_IMPL(name) { \
PYBIND11_CHECK_PYTHON_VERSION \
return pybind11_init(); \
} \
PyObject *pybind11_init()
#else
#define PYBIND11_PLUGIN(name) \
PYBIND11_DEPRECATED("PYBIND11_PLUGIN is deprecated, use PYBIND11_MODULE") \
static PyObject *pybind11_init(); \
Expand All @@ -262,6 +272,7 @@ extern "C" {
} PYBIND11_CATCH_INIT_EXCEPTIONS \
} \
PyObject *pybind11_init()
#endif

/** \rst
This macro creates the entry point that will be invoked when the Python interpreter
Expand All @@ -280,6 +291,17 @@ extern "C" {
});
}
\endrst */
#if defined(PYBIND11_NOEXCEPTIONS)
#define PYBIND11_MODULE(name, variable) \
static void PYBIND11_CONCAT(pybind11_init_, name)(pybind11::module &); \
PYBIND11_PLUGIN_IMPL(name) { \
PYBIND11_CHECK_PYTHON_VERSION \
auto m = pybind11::module(PYBIND11_TOSTRING(name)); \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what happens now if constructing an extension module throws?

Imagine a line inside a user's PYBIND11_MODULE that says py::module foo = py::import("throwy_module"); and actually having a exception thrown from the throwy_module.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you've set the flag PYBIND11_NOEXCEPTIONS and you run without enable exceptions I think it will fail to compile.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was talking about raising an exception from pure python, which can't be detected at compile time. I gave it a test and here are the two similar cases that I've tried:

// foo.cpp
#include <pybind11/pybind11.h>
PYBIND11_MODULE(foo, m) {
	pybind11::module x = pybind11::module::import("boom");
}

If there is no boom.py in python's path, the following happens:

Python 3.7.3 (default, Mar 27 2019, 19:19:44)
[GCC 8.2.1 20181127] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> try:
...     import foo
... except Exception:
...
KeyboardInterrupt
>>> try:
...     import foo
... except Exception as e:
...     print(e)
...
terminate called after throwing an instance of 'pybind11::error_already_set'
  what():  ModuleNotFoundError: No module named 'boom'
zsh: abort      python

And if we add a boom.py that looks like this:

raise RuntimeError

the error becomes more verbose:

Python 3.7.3 (default, Mar 27 2019, 19:19:44)
[GCC 8.2.1 20181127] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import boom
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/bstaletic/Temp/pybind11/boom.py", line 1, in <module>
    raise RuntimeError
RuntimeError
>>> try:
...     import foo
... except Exception as e:
...     print(e)
...
terminate called after throwing an instance of 'pybind11::error_already_set'
  what():  RuntimeError:

At:
  /home/bstaletic/Temp/pybind11/boom.py(1): <module>
  <frozen importlib._bootstrap>(219): _call_with_frames_removed
  <frozen importlib._bootstrap_external>(728): exec_module
  <frozen importlib._bootstrap>(677): _load_unlocked
  <frozen importlib._bootstrap>(967): _find_and_load_unlocked
  <frozen importlib._bootstrap>(983): _find_and_load
  <frozen importlib._bootstrap>(219): _call_with_frames_removed
  <frozen importlib._bootstrap_external>(1043): create_module
  <frozen importlib._bootstrap>(583): module_from_spec
  <frozen importlib._bootstrap>(670): _load_unlocked
  <frozen importlib._bootstrap>(967): _find_and_load_unlocked
  <frozen importlib._bootstrap>(983): _find_and_load
  <stdin>(2): <module>

zsh: abort      python

Either way, it quickly calls std::terminate(), which is completely fine, but let's mention it in the changelog too.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah whoops, I totally misread that. Okay I'll update the changelog. Thanks @bstaletic

PYBIND11_CONCAT(pybind11_init_, name)(m); \
return m.ptr(); \
} \
void PYBIND11_CONCAT(pybind11_init_, name)(pybind11::module &variable)
#else
#define PYBIND11_MODULE(name, variable) \
static void PYBIND11_CONCAT(pybind11_init_, name)(pybind11::module &); \
PYBIND11_PLUGIN_IMPL(name) { \
Expand All @@ -291,6 +313,7 @@ extern "C" {
} PYBIND11_CATCH_INIT_EXCEPTIONS \
} \
void PYBIND11_CONCAT(pybind11_init_, name)(pybind11::module &variable)
#endif


NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
Expand Down
2 changes: 2 additions & 0 deletions include/pybind11/detail/internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ PYBIND11_NOINLINE inline internals &get_internals() {
builtins[id] = capsule(internals_pp);
internals_ptr->registered_exception_translators.push_front(
[](std::exception_ptr p) -> void {
#if !defined(PYBIND11_NOEXCEPTIONS)
try {
if (p) std::rethrow_exception(p);
} catch (error_already_set &e) { e.restore(); return;
Expand All @@ -230,6 +231,7 @@ PYBIND11_NOINLINE inline internals &get_internals() {
PyErr_SetString(PyExc_RuntimeError, "Caught an unknown exception!");
return;
}
#endif
}
);
internals_ptr->static_property_type = make_static_property_type();
Expand Down
4 changes: 4 additions & 0 deletions include/pybind11/pybind11.h
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,9 @@ class cpp_function : public function {
}
}
} catch (error_already_set &e) {
#if !defined(PYBIND11_NOEXCEPTIONS)
e.restore();
#endif
return nullptr;
} catch (...) {
/* When an exception is caught, give each registered exception
Expand Down Expand Up @@ -1784,6 +1786,7 @@ NAMESPACE_END(detail)
* This is intended for simple exception translations; for more complex translation, register the
* exception object and translator directly.
*/
#if !defined(PYBIND11_NOEXCEPTIONS)
template <typename CppException>
exception<CppException> &register_exception(handle scope,
const char *name,
Expand All @@ -1801,6 +1804,7 @@ exception<CppException> &register_exception(handle scope,
});
return ex;
}
#endif

NAMESPACE_BEGIN(detail)
PYBIND11_NOINLINE inline void print(tuple args, dict kwargs) {
Expand Down