-
Notifications
You must be signed in to change notification settings - Fork 53
Description
Assume you have cpp11 0.4.4 installed, with a "working" version of the cpp11_should_unwind_protect R global option ("working" in the sense that if you enter a nested unwind-protect call, then it notices that there is an outer unwind-protect present and decides not to unwind-protect its function too).
In this case, you can still have big problems with the following chain of events:
- cpp11 function
Bcalls an R level callback (settingshould_unwind_protect = FALSE) - R level callback itself calls cpp11 function
A - Say that
Asets up some complex C++ objects with destructors - Then that
Aends up callingcpp11::stop()or something that longjmps, butshould_unwind_protect = FALSEso it never set up asetjmp(). - The longjmp jumps all the way back to
B'ssetjmp(), completely bypassing any destructors needed by A
The big issue here is that A() and B() on their own can look very harmless, and like code that a package author would write without thinking twice about it.
I've come up with a reprex package to demonstrate this issue:
https://github.com/DavisVaughan/testcpp11unwind
A few options:
- Possibly the
BEGIN_CPP11entry macro should always resetshould_unwind_protect = TRUE(making sure it exists first). ThenA's call tounwind_protect()would still usesetjmp()andR_UnwindProtect(). - Consider removing the
cpp11_should_unwind_protectglobal option altogether
The cpp11_should_unwind_protect nest guard is used for two things:
- For performance, i.e. you can wrap a tight loop where each iteration calls
unwind_protect()in an outerunwind_protect()outside the loop so the protection is only set up once (mostly an issue with character vectors) - For safety, i.e. the following code doesn't work without the nest guard
[[cpp11::register]]
void test() {
cpp11::unwind_protect([&] {
cpp11::unwind_protect([&] {
Rf_error("oh no!");
});
});
}If test() is called from R:
- It goes through
.Calland our wrapper, which sets up theBEGIN_CPP11andEND_CPP11macros - It calls
unwind_protect()which callsR_UnwindProtect(), a C function! - Inside
R_UnwindProtect(), we callunwind_protect() - From there we
Rf_error() - The inner
unwind_protect()catches that C error and promotes it to a C++ exception which isthrown. - That is thrown across C stack frames, because we are inside the outer
R_UnwindProtect()and no try/catch was set up within that to catch the unwind exception. The only try/catch is that most outer one set up by the macros - That is UB and R crashes due to an uncaught exception