Skip to content

Unexpected error-handling since 1.0.10 #1268

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
hsloot opened this issue Jun 29, 2023 · 9 comments
Closed

Unexpected error-handling since 1.0.10 #1268

hsloot opened this issue Jun 29, 2023 · 9 comments

Comments

@hsloot
Copy link

hsloot commented Jun 29, 2023

Description

Errors produced by R functions (tested with stop) evaluated in C++ with Rcpp can no longer be caught as exceptions derived from std::expection as of 1.0.10. For me, the changed behavior does not seem intended. In addition, I have looked into the ChangeLog but have not found any entry that explains or justifies this behavior.

Reprex

Version 1.0.9

# RcppCore/[email protected]
library(Rcpp)

cppFunction(
  'void evalStopFunction(const Rcpp::Function& fn) {
    try {
      fn();
    } catch (const std::exception& e) {
      std::rethrow_exception(std::current_exception());
    } catch (...) {
      throw std::runtime_error("Unknown Error");
    }
  }'
)

fn <- function() stop("Test")
evalStopFunction(fn)
#> Error in evalStopFunction(fn): Evaluation error: Test.

Created on 2023-06-29 with reprex v2.0.2

Session info
sessioninfo::session_info()
#> ─ Session info ───────────────────────────────────────────────────────────────
#>  setting  value
#>  version  R version 4.3.1 (2023-06-16)
#>  os       macOS Ventura 13.4
#>  system   aarch64, darwin20
#>  ui       X11
#>  language (EN)
#>  collate  en_US.UTF-8
#>  ctype    en_US.UTF-8
#>  tz       Europe/Berlin
#>  date     2023-06-29
#>  pandoc   3.1.4 @ /opt/homebrew/bin/ (via rmarkdown)
#> 
#> ─ Packages ───────────────────────────────────────────────────────────────────
#>  package     * version date (UTC) lib source
#>  cli           3.6.1   2023-03-23 [1] CRAN (R 4.3.0)
#>  digest        0.6.31  2022-12-11 [1] CRAN (R 4.3.0)
#>  evaluate      0.21    2023-05-05 [1] CRAN (R 4.3.0)
#>  fastmap       1.1.1   2023-02-24 [1] CRAN (R 4.3.0)
#>  fs            1.6.2   2023-04-25 [1] CRAN (R 4.3.0)
#>  glue          1.6.2   2022-02-24 [1] CRAN (R 4.3.0)
#>  htmltools     0.5.5   2023-03-23 [1] CRAN (R 4.3.0)
#>  knitr         1.43    2023-05-25 [1] CRAN (R 4.3.0)
#>  lifecycle     1.0.3   2022-10-07 [1] CRAN (R 4.3.0)
#>  magrittr      2.0.3   2022-03-30 [1] CRAN (R 4.3.0)
#>  purrr         1.0.1   2023-01-10 [1] CRAN (R 4.3.0)
#>  R.cache       0.16.0  2022-07-21 [1] CRAN (R 4.3.0)
#>  R.methodsS3   1.8.2   2022-06-13 [1] CRAN (R 4.3.0)
#>  R.oo          1.25.0  2022-06-12 [1] CRAN (R 4.3.0)
#>  R.utils       2.12.2  2022-11-11 [1] CRAN (R 4.3.0)
#>  Rcpp        * 1.0.9   2023-06-28 [1] Github (RcppCore/Rcpp@0c87bb9)
#>  reprex        2.0.2   2022-08-17 [1] CRAN (R 4.3.0)
#>  rlang         1.1.1   2023-04-28 [1] CRAN (R 4.3.0)
#>  rmarkdown     2.22    2023-06-01 [1] CRAN (R 4.3.0)
#>  rstudioapi    0.14    2022-08-22 [1] CRAN (R 4.3.0)
#>  sessioninfo   1.2.2   2021-12-06 [1] CRAN (R 4.3.0)
#>  styler        1.10.1  2023-06-05 [1] CRAN (R 4.3.0)
#>  vctrs         0.6.3   2023-06-14 [1] CRAN (R 4.3.0)
#>  withr         2.5.0   2022-03-03 [1] CRAN (R 4.3.0)
#>  xfun          0.39    2023-04-20 [1] CRAN (R 4.3.0)
#>  yaml          2.3.7   2023-01-23 [1] CRAN (R 4.3.0)
#> 
#>  [1] /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/library
#> 
#> ──────────────────────────────────────────────────────────────────────────────

Version 1.0.10

# RcppCore/[email protected]
library(Rcpp)

cppFunction(
  'void evalStopFunction(const Rcpp::Function& fn) {
    try {
      fn();
    } catch (const std::exception& e) {
      std::rethrow_exception(std::current_exception());
    } catch (...) {
      throw std::runtime_error("Unknown Error");
    }
  }'
)

fn <- function() stop("Test")
evalStopFunction(fn)
#> Error in (function () : Test
#> Error in eval(expr, envir, enclos): Unknown Error

Created on 2023-06-29 with reprex v2.0.2

Session info
sessioninfo::session_info()
#> ─ Session info ───────────────────────────────────────────────────────────────
#>  setting  value
#>  version  R version 4.3.1 (2023-06-16)
#>  os       macOS Ventura 13.4
#>  system   aarch64, darwin20
#>  ui       X11
#>  language (EN)
#>  collate  en_US.UTF-8
#>  ctype    en_US.UTF-8
#>  tz       Europe/Berlin
#>  date     2023-06-29
#>  pandoc   3.1.4 @ /opt/homebrew/bin/ (via rmarkdown)
#> 
#> ─ Packages ───────────────────────────────────────────────────────────────────
#>  package     * version date (UTC) lib source
#>  cli           3.6.1   2023-03-23 [1] CRAN (R 4.3.0)
#>  digest        0.6.31  2022-12-11 [1] CRAN (R 4.3.0)
#>  evaluate      0.21    2023-05-05 [1] CRAN (R 4.3.0)
#>  fastmap       1.1.1   2023-02-24 [1] CRAN (R 4.3.0)
#>  fs            1.6.2   2023-04-25 [1] CRAN (R 4.3.0)
#>  glue          1.6.2   2022-02-24 [1] CRAN (R 4.3.0)
#>  htmltools     0.5.5   2023-03-23 [1] CRAN (R 4.3.0)
#>  knitr         1.43    2023-05-25 [1] CRAN (R 4.3.0)
#>  lifecycle     1.0.3   2022-10-07 [1] CRAN (R 4.3.0)
#>  magrittr      2.0.3   2022-03-30 [1] CRAN (R 4.3.0)
#>  purrr         1.0.1   2023-01-10 [1] CRAN (R 4.3.0)
#>  R.cache       0.16.0  2022-07-21 [1] CRAN (R 4.3.0)
#>  R.methodsS3   1.8.2   2022-06-13 [1] CRAN (R 4.3.0)
#>  R.oo          1.25.0  2022-06-12 [1] CRAN (R 4.3.0)
#>  R.utils       2.12.2  2022-11-11 [1] CRAN (R 4.3.0)
#>  Rcpp        * 1.0.10  2023-06-28 [1] Github (RcppCore/Rcpp@c5b1572)
#>  reprex        2.0.2   2022-08-17 [1] CRAN (R 4.3.0)
#>  rlang         1.1.1   2023-04-28 [1] CRAN (R 4.3.0)
#>  rmarkdown     2.22    2023-06-01 [1] CRAN (R 4.3.0)
#>  rstudioapi    0.14    2022-08-22 [1] CRAN (R 4.3.0)
#>  sessioninfo   1.2.2   2021-12-06 [1] CRAN (R 4.3.0)
#>  styler        1.10.1  2023-06-05 [1] CRAN (R 4.3.0)
#>  vctrs         0.6.3   2023-06-14 [1] CRAN (R 4.3.0)
#>  withr         2.5.0   2022-03-03 [1] CRAN (R 4.3.0)
#>  xfun          0.39    2023-04-20 [1] CRAN (R 4.3.0)
#>  yaml          2.3.7   2023-01-23 [1] CRAN (R 4.3.0)
#> 
#>  [1] /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/library
#> 
#> ──────────────────────────────────────────────────────────────────────────────
@eddelbuettel
Copy link
Member

That ... strikes me as odd. I work with Rcpp all day and I surely do see errors caught and returned to R. Mind you the default and standard approach is to let Rcpp::stop() throw an exception that will get caught, converted and returned.
And that still works:

> Rcpp::cppFunction(r"[void foo() { Rprintf("Hi\n"); Rcpp::stop("Bye"); Rprintf("Gone\n"); } ]")
> foo()
Hi
Error: Bye
> 

Also are you aware that Rcpp injects try catch blocks for you? The above is not what compiles, try adding verbose=TRUE, rebuild=TRUE to the end of your cppFunction() call. The BEGIN_RCPP and END_RCPP get expanded.

Lastly, note the first item in the release notes. This may be what rattles you, and as noted you can turn it off.

Rcpp/inst/NEWS.Rd

Lines 36 to 43 in 3456999

\item Unwind protection is enabled by default (Iñaki in \ghpr{1225}).
It can be disabled by defining \code{RCPP_NO_UNWIND_PROTECT} before
including \code{Rcpp.h}. \code{RCPP_USE_UNWIND_PROTECT} is not checked
anymore and has no effect. The associated plugin \code{unwindProtect}
is therefore deprecated and will be removed in a future release.
\item The 'finalize' method for Rcpp Modules is now eagerly materialized,
fixing an issue where errors can occur when Module finalizers are run
(Kevin in \ghpr{1231} closing \ghit{1230}).

Finally, if you use your standard C++ exception handling in functions not modified by Rcpp Attributes for you convenience it likely still works, We do not generally muck with exceptions because that would be ... crazy.

@hsloot
Copy link
Author

hsloot commented Jun 29, 2023

It seems that the behavior changed with d389a8a.

@eddelbuettel
Copy link
Member

eddelbuettel commented Jun 29, 2023

That is precisely the issue in NEWS that I pointed out to you, and it was preceeded by lengthy discussion. Unlikely to be reverted; I invite you to use the available toggle to turn it off if you do not want that behaviour. We try to give you a choice.

@eddelbuettel
Copy link
Member

Yup.

Code

#define RCPP_NO_UNWIND_PROTECT 1
#include <Rcpp/Rcpp>

// [[Rcpp::export]]
void evalStopFunction(const Rcpp::Function& fn) {
  try {
    fn();
  } catch (const std::exception& e) {
    std::rethrow_exception(std::current_exception());
  } catch (...) {
    throw std::runtime_error("Unknown Error");
  }
}


/*** R
packageVersion("Rcpp")
fn <- function() stop("Test")
evalStopFunction(fn)
*/

Output

> Rcpp::sourceCpp("/tmp/issue1268.cpp")

> packageVersion("Rcpp")
[1] ‘1.0.10.5> fn <- function() stop("Test")

> evalStopFunction(fn)
Error in evalStopFunction(fn) : Evaluation error: Test.
> 

All good?

@hsloot
Copy link
Author

hsloot commented Jun 29, 2023

Sorry, I sent my last comment before seeing your answer. Thanks for your example; I see that disabling unwind protect reverts to the original behavior. I will also have a look if that is an option for my actual code. However, I still find the changed behavior odd, since I would have expected that function evaluation errors throw a Rcpp::eval_error, deriving from std::exception, that I can catch. For me, it looks like the evaluation stop("Test") somehow bypasses the catch-block (see reprex below). If that is intended, please close the issue.

For a bit of background, the code I used for the reprex is a minimal working example of an actual use case, which is testing that exceptions generated by a C++ wrapper of R's C-API for numerical integration are thrown as intended. Rcpp is only used for convenience in testing the wrapper.

# RcppCore/[email protected]
library(Rcpp)

cppFunction(
  'void evalStopFunction(const Rcpp::Function& fn) {
    try {
      fn();
    } catch (...) {
      // ignore
    }
  }'
)

fn <- function() stop("Test")
evalStopFunction(fn)
#> Error in (function () : Test

Created on 2023-06-29 with reprex v2.0.2

Session info
sessioninfo::session_info()
#> ─ Session info ───────────────────────────────────────────────────────────────
#>  setting  value
#>  version  R version 4.3.1 (2023-06-16)
#>  os       macOS Ventura 13.4
#>  system   aarch64, darwin20
#>  ui       X11
#>  language (EN)
#>  collate  en_US.UTF-8
#>  ctype    en_US.UTF-8
#>  tz       Europe/Berlin
#>  date     2023-06-29
#>  pandoc   3.1.4 @ /opt/homebrew/bin/ (via rmarkdown)
#> 
#> ─ Packages ───────────────────────────────────────────────────────────────────
#>  package     * version date (UTC) lib source
#>  cli           3.6.1   2023-03-23 [1] CRAN (R 4.3.0)
#>  digest        0.6.31  2022-12-11 [1] CRAN (R 4.3.0)
#>  evaluate      0.21    2023-05-05 [1] CRAN (R 4.3.0)
#>  fastmap       1.1.1   2023-02-24 [1] CRAN (R 4.3.0)
#>  fs            1.6.2   2023-04-25 [1] CRAN (R 4.3.0)
#>  glue          1.6.2   2022-02-24 [1] CRAN (R 4.3.0)
#>  htmltools     0.5.5   2023-03-23 [1] CRAN (R 4.3.0)
#>  knitr         1.43    2023-05-25 [1] CRAN (R 4.3.0)
#>  lifecycle     1.0.3   2022-10-07 [1] CRAN (R 4.3.0)
#>  magrittr      2.0.3   2022-03-30 [1] CRAN (R 4.3.0)
#>  purrr         1.0.1   2023-01-10 [1] CRAN (R 4.3.0)
#>  R.cache       0.16.0  2022-07-21 [1] CRAN (R 4.3.0)
#>  R.methodsS3   1.8.2   2022-06-13 [1] CRAN (R 4.3.0)
#>  R.oo          1.25.0  2022-06-12 [1] CRAN (R 4.3.0)
#>  R.utils       2.12.2  2022-11-11 [1] CRAN (R 4.3.0)
#>  Rcpp        * 1.0.10  2023-01-22 [1] CRAN (R 4.3.0)
#>  reprex        2.0.2   2022-08-17 [1] CRAN (R 4.3.0)
#>  rlang         1.1.1   2023-04-28 [1] CRAN (R 4.3.0)
#>  rmarkdown     2.22    2023-06-01 [1] CRAN (R 4.3.0)
#>  rstudioapi    0.14    2022-08-22 [1] CRAN (R 4.3.0)
#>  sessioninfo   1.2.2   2021-12-06 [1] CRAN (R 4.3.0)
#>  styler        1.10.1  2023-06-05 [1] CRAN (R 4.3.0)
#>  vctrs         0.6.3   2023-06-14 [1] CRAN (R 4.3.0)
#>  withr         2.5.0   2022-03-03 [1] CRAN (R 4.3.0)
#>  xfun          0.39    2023-04-20 [1] CRAN (R 4.3.0)
#>  yaml          2.3.7   2023-01-23 [1] CRAN (R 4.3.0)
#> 
#>  [1] /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/library
#> 
#> ──────────────────────────────────────────────────────────────────────────────

@eddelbuettel
Copy link
Member

If Rcpp's convenience gets in your way, you can disable it (I showed you how) and/or do not use the wrapper creation (see my first comment) and/or do your own try/catch handling and/or write basic SEXP foo(SEXP, ....) and do.call() that,

We generally do what we do for a reason, and this change was fairly long in coming and fairly important for both performance and edge cases (catching exceptions across compilation units can get hairy).

@kevinushey
Copy link
Contributor

For reference, some of the related discussion:

#1227 (comment)

Indeed, Rcpp no longer catches R errors and attempts to re-throw those as C++ exceptions, so code of the OP's form won't work as it did in previous builds of Rcpp. The consensus was that this was worth the breakage, since the amount of infrastructure around "safe" evaluation meant executing R code from Rcpp was very expensive.

@Enchufa2
Copy link
Member

Enchufa2 commented Jun 29, 2023

And for completeness, note that you are still able to catch those errors in C++, but instead of std::exception in your reprex above, you need to catch Rcpp::LongjumpException. If you do this, please be sure to rethrow the exception, so that the unwind is completed. See #807 for a longer discussion.

library(Rcpp)

cppFunction(
  'void evalStopFunction(const Rcpp::Function& fn) {
    try {
      fn();
    } catch (const Rcpp::LongjumpException& e) {
      std::rethrow_exception(std::current_exception());
    } catch (...) {
      throw std::runtime_error("Unknown Error");
    }
  }'
)

fn <- function() stop("Test")
evalStopFunction(fn)
#> Error in (function () : Test

@eddelbuettel
Copy link
Member

Closing as documented and planned behavior exhibited here.

hsloot added a commit to hsloot/integratecpp that referenced this issue Jul 24, 2023
Rcpp does not guard R evaluations as before from Version 1.0.10 due to using UnwindProtect; see RcppCore/Rcpp#1268. Thus, catching R evaluation errors is not possible anymore. To remedy, `RCPP_NO_UNWIND_PROTECT` needs to be defined to get the old behaviour.
hsloot added a commit to hsloot/integratecpp that referenced this issue Jul 25, 2023
Otherwise, whether `RCPP_NO_UNWIND_PROTECT` is defined depends on factors such as the compiler's optimization level; see RcppCore/Rcpp#1268.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants