Skip to content

Commit a8c5463

Browse files
committed
Use stack-local jump buffer to handle nested protected evaluations
1 parent dd3dcb2 commit a8c5463

File tree

2 files changed

+50
-13
lines changed

2 files changed

+50
-13
lines changed

inst/include/Rcpp/api/meat/Rcpp_eval.h

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,25 +32,22 @@ namespace internal {
3232

3333
#ifdef RCPP_USE_PROTECT_UNWIND
3434

35-
// Store the jump buffer as a static variable in function scope
36-
// because inline variables are a C++17 extension.
37-
inline std::jmp_buf* get_jmpbuf_ptr() {
38-
static std::jmp_buf jmpbuf;
39-
return &jmpbuf;
40-
}
41-
4235
struct EvalData {
4336
SEXP expr;
4437
SEXP env;
4538
EvalData(SEXP expr_, SEXP env_) : expr(expr_), env(env_) { }
4639
};
40+
struct EvalUnwindData {
41+
std::jmp_buf jmpbuf;
42+
};
4743

4844
// First jump back to the protected context with a C longjmp because
4945
// `Rcpp_protected_eval()` is called from C and we can't safely throw
5046
// exceptions across C frames.
51-
inline void Rcpp_maybe_throw(void* data, Rboolean jump) {
47+
inline void Rcpp_maybe_throw(void* unwind_data, Rboolean jump) {
5248
if (jump) {
53-
longjmp(*internal::get_jmpbuf_ptr(), 1);
49+
EvalUnwindData* data = static_cast<EvalUnwindData*>(unwind_data);
50+
longjmp(data->jmpbuf, 1);
5451
}
5552
}
5653

@@ -80,9 +77,10 @@ namespace internal {
8077

8178
inline SEXP Rcpp_fast_eval(SEXP expr, SEXP env) {
8279
internal::EvalData data(expr, env);
80+
internal::EvalUnwindData unwind_data;
8381
Shield<SEXP> token(::R_MakeUnwindCont());
8482

85-
if (setjmp(*internal::get_jmpbuf_ptr())) {
83+
if (setjmp(unwind_data.jmpbuf)) {
8684
// Keep the token protected while unwinding because R code might run
8785
// in C++ destructors. Can't use PROTECT() for this because
8886
// UNPROTECT() might be called in a destructor, for instance if a
@@ -93,7 +91,7 @@ namespace internal {
9391
}
9492

9593
return ::R_UnwindProtect(internal::Rcpp_protected_eval, &data,
96-
internal::Rcpp_maybe_throw, token,
94+
internal::Rcpp_maybe_throw, &unwind_data,
9795
token);
9896
}
9997

inst/unitTests/runit.stack.R

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ if (.runThisTest) {
2828
# leaks on longjumps
2929
hasUnwind <- getRversion() >= "3.5.0"
3030
checkUnwound <- if (hasUnwind) checkTrue else function(x) checkTrue(!x)
31+
checkErrorMessage <- function(x, msg) {
32+
if (!hasUnwind) {
33+
msg <- paste0("Evaluation error: ", msg, ".")
34+
}
35+
checkIdentical(x$message, msg)
36+
}
3137
EvalUnwind <- function(expr, indicator) {
3238
testFastEval(expr, parent.frame(), indicator)
3339
}
@@ -37,8 +43,7 @@ if (.runThisTest) {
3743
unwound <- FALSE
3844
out <- tryCatch(EvalUnwind(quote(stop("err")), unwound), error = identity)
3945
checkTrue(unwound)
40-
msg <- if (hasUnwind) "err" else "Evaluation error: err."
41-
checkIdentical(out$message, msg)
46+
checkErrorMessage(out, "err")
4247
}
4348

4449
test.stackUnwindsOnInterrupts <- function() {
@@ -103,4 +108,38 @@ if (.runThisTest) {
103108
checkIdentical(out, "abort")
104109
}
105110
}
111+
112+
# Longjump from the inner protected eval
113+
test.stackUnwindsOnNestedEvalsInner <- function() {
114+
unwound1 <- FALSE
115+
unwound2 <- FALSE
116+
innerUnwindExpr <- quote(EvalUnwind(quote(invokeRestart("here", "jump")), unwound2))
117+
out <- withRestarts(
118+
here = identity,
119+
EvalUnwind(innerUnwindExpr, unwound1)
120+
)
121+
122+
checkIdentical(out, "jump")
123+
checkUnwound(unwound1)
124+
checkUnwound(unwound2)
125+
}
126+
127+
# Longjump from the outer protected eval
128+
test.stackUnwindsOnNestedEvalsOuter <- function() {
129+
unwound1 <- FALSE
130+
unwound2 <- FALSE
131+
innerUnwindExpr <- quote({
132+
EvalUnwind(NULL, unwound2)
133+
invokeRestart("here", "jump")
134+
})
135+
out <- withRestarts(
136+
here = identity,
137+
EvalUnwind(innerUnwindExpr, unwound1)
138+
)
139+
140+
checkIdentical(out, "jump")
141+
checkUnwound(unwound1)
142+
checkTrue(unwound2) # Always unwound
143+
}
144+
106145
}

0 commit comments

Comments
 (0)