Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upRcpp exception types are not correctly recognized when thrown across translation units #972
Comments
|
Hmpf. Patches welcome. Smells a litle like Tomas Kalibera telling us not to Any ideas as to how do make this more solid? |
|
I think the problem is that different 'versions' of the various Rcpp objects are getting compiled into the different shared objects generated by Here's what I see with some debug logging. Note that there's some complications from all the nested try-catch C++ contexts generated by attributes, as well as the R-level errors getting thrown around...
So the A similar story for the Rcpp interrupt exception:
... but I'm not sure why the interrupt exception gets properly caught this time. |
|
This seems relevant, from the GCC manual:
Tentative list of potential issues:
|
|
Now that I've done some more exploration, I think that my example might not be a good test of what I had described. This is because the functions are wrapped in R code that calls Rcpp code, and, as Kevin noted, the exceptions are caught by the Rcpp code that wraps the thrower functions, converted to R errors or interrupts, and then Rcpp converts those to C++ exceptions in the catcher function. So in short, the object that's thrown is not the same object that's caught. The example in https://github.com/jcheng5/exceptiontest is a better test, since the catcher function calls the thrower function's C++ code directly, without all the wrapping and unwrapping. Another observation: I think that in the vast majority of cases, a C++ function in one package won't directly call a C++ function (which throws a Rcpp exception) in another package. If C++ code in one package calls C++ code in another package, it's much more common for the callee to be wrapped in an R function, and so the caller will go through R, and the exception wrapping and unwrapping that I've described. So in practice, this issue may be a very rare one. |
|
R itself has a |
|
I'm fairly sure R dynamically links to the runtime by default, and that's true in all the cases we're testing here. (Although I guess that's not true on Windows?) I think the problem is that different versions of Rcpp objects are ending up in different translations units (and so the compiled shared libraries generated by |
|
Maybe it is an unreasonable assumption? The foreign function interface is in C and |
|
I just tried with a locally compiled R. Having R, I did not add
|
|
On macOS, I still see this behavior when using the CRAN binary of R, regardless of whether I compile with the system toolchain or the LLVM toolchain. I also see it with a locally-compiled version of R, even with R and all packages built from sources with the same toolchain. So (at least on macOS) it seems there could be something deeper going on. I still think the root cause of the issue is that different 'versions' of these exception objects are entering each shared library, and those versions aren't considered compatible for some reason. As an aside, I also see this behavior on Linux, when using the CRAN binary of R and the default gcc compiler (ie: in theory, all the same toolchain). But I haven't tried compiling R myself there yet. https://learning.oreilly.com/library/view/c-coding-standards/0321113586/ch63.html seems to suggest what I think we probably all agree on: throwing exceptions across modules isn't a good idea... |
|
Ah when I got a success it was with everything compiled with gcc. Maybe clang is the issue? Edit: And probably small differences in the gcc toolchain, as suggests your Linux experiment. In any case it seems this pattern should simply be avoided. |
|
From Kevin's link (C++ Coding Standards by Sutter and Alexandrescu, item 62):
If I understand it correctly, then it is not possible to be 100% sure that it is safe to use the C++ exception system for cases like this. Also from that chapter:
|
|
I encountered a similar error with Stan (which often loads a DLL with a C++ function compiled by
or you may need to specify additionally
or perhaps
I don't have a Mac and am not getting the unexpected behavior on Linux, so can someone else confirm? |
|
I think we all agree -- did you see what I put in the Rcpp FAQ vignette a few days about this? ## Can we use exceptions and stop() across shared libraries?
Within limits, yes. Code that is generated via Rcpp Attributes (see
\citet{CRAN:Rcpp:Attributes} and Section~\ref{using-attributes}) generally
handles this correctly and gracefully via the `try-catch` layer it adds
shielding the exception from propagating to another, separate dynamic
library.
However, this mechanism relies on dynamic linking with the (system library)
`libgcc` providing the C++ standard library (as well as on using the same C++
standard library across all compiled components). But this library is linked
statically on Windows putting a limitation on the use of `stop()` from within
Rcpp Modules \citep{CRAN:Rcpp:Modules}. Some more background on the linking
requirement is [in this SO
question](https://stackoverflow.com/questions/2424836/exceptions-are-not-caught-in-gcc-program). |
|
I had not seen that until now, but I agree. |
|
I think also ensuring that |
|
I'm not sure if this is an instance of the same core issue, but I'm seeing the same behavior in one of my packages, even though the exception is thrown and catched by the code in my shared library:
I have an R-side test that checks whether the thrown exception's message is the expected one, which fails some times because only the generic "c++ exception (unknown reason)" is thrown. What's more puzzling is that this is only failing on the following CRAN systems:
Debian clang devel works fine, and the OSX build on Travis doesn't show the issue either. I can of course adjust the test to ignore the message, but that still means the user will only get the generic one if a problem occurs. EDIT: turns out it's not only a problem with exception types, a segmentation fault actually occurs in the aforementioned CRAN systems ( |
|
It would be nicer if all this worked, but alas. You documented this well, but this is to me a restatement of what was already said aboce. Eg a quote (from a C++ authority)/
and
|
If there are Rcpp exceptions that are thrown from one translation unit and caught in another, they are sometimes not recognized with the correct type.
In this example, there are two functions that throw different kinds of exceptions,
Rcpp::exceptionandRcpp::internal::InterruptedException, and another function that catches exceptions and returns a string describing which type it caught.When the
Rcpp::exceptionis thrown, the catcher recognizes it as astd::exception, which is the parent class.When the
Rcpp::internal::InterruptedExceptionis thrown, it is recogized correctly. (This is actually a little surprising to me, since we have other cases where aRcpp::internal::InterruptedExceptionis not recognized, and this is the problem in practice for us.)In our use case, we want to know when an interrupt occurs during the execution of an
Rcpp::Functionand act accordingly, but since theRcpp::internal::InterruptedExceptionis not properly recognized, we aren't able to handle the interrupt correctly.Here is a package that demonstrates the issue with
Rcpp::internal::InterruptedExceptionnot being recognized: https://github.com/jcheng5/exceptiontest. And here is a related issue: r-lib/later#55cc: @kevinushey