Skip to content

Commit ad7e397

Browse files
committed
Add unit tests for stack unwinding
1 parent 3f58dea commit ad7e397

File tree

2 files changed

+85
-0
lines changed

2 files changed

+85
-0
lines changed

inst/unitTests/cpp/misc.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,3 +224,37 @@ String testNullableString(Rcpp::Nullable<Rcpp::String> param = R_NilValue) {
224224
else
225225
return String("");
226226
}
227+
228+
// Class that indicates to R caller whether C++ stack was unwound
229+
struct unwindIndicator {
230+
unwindIndicator(LogicalVector indicator_) {
231+
// Reset the indicator to FALSE
232+
indicator = indicator_;
233+
*LOGICAL(indicator) = 0;
234+
}
235+
236+
// Set indicator to TRUE when stack unwinds
237+
~unwindIndicator() {
238+
*LOGICAL(indicator) = 1;
239+
}
240+
241+
LogicalVector indicator;
242+
};
243+
244+
// [[Rcpp::export]]
245+
SEXP testEvalUnwindImpl(RObject expr, Environment env, LogicalVector indicator) {
246+
unwindIndicator my_data(indicator);
247+
return Rcpp::Rcpp_fast_eval(expr, env);
248+
}
249+
250+
// [[Rcpp::export]]
251+
SEXP testLongjumpException() {
252+
throw Rcpp::internal::LongjumpException(R_NilValue);
253+
return R_NilValue;
254+
}
255+
256+
// [[Rcpp::export]]
257+
SEXP testSendInterrupt() {
258+
Rf_onintr();
259+
return R_NilValue;
260+
}

inst/unitTests/runit.misc.R

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,4 +214,55 @@ if (.runThisTest) {
214214
checkTrue(nchar(Rcpp:::bib()) > 0, msg="bib file")
215215
}
216216

217+
test.stackUnwinds <- function() {
218+
# On old versions of R, Rcpp_fast_eval() falls back to Rcpp_eval() and
219+
# leaks on longjumps
220+
hasUnwind <- getRversion() >= "3.5.0"
221+
checkUnwound <- if (hasUnwind) checkTrue else function(x) checkTrue(!x)
222+
testEvalUnwind <- function(expr, indicator) {
223+
testEvalUnwindImpl(expr, parent.frame(), indicator)
224+
}
225+
226+
# On errors - Always unwound
227+
unwound <- FALSE
228+
out <- tryCatch(testEvalUnwind(quote(stop("err")), unwound), error = identity)
229+
checkTrue(unwound)
230+
msg <- if (hasUnwind) "err" else "Evaluation error: err."
231+
checkIdentical(out$message, msg)
232+
233+
# On interrupts - Always unwound
234+
unwound <- FALSE
235+
expr <- quote({
236+
repeat testSendInterrupt()
237+
"returned"
238+
})
239+
out <- tryCatch(testEvalUnwind(expr, unwound), interrupt = function(c) "onintr")
240+
checkTrue(unwound)
241+
checkIdentical(out, "onintr")
242+
243+
# On caught conditions
244+
unwound <- FALSE
245+
expr <- quote(signalCondition(simpleCondition("cnd")))
246+
cnd <- tryCatch(testEvalUnwind(expr, unwound), condition = identity)
247+
checkTrue(inherits(cnd, "simpleCondition"))
248+
checkUnwound(unwound)
249+
250+
# On restart jumps
251+
unwound <- FALSE
252+
expr <- quote(invokeRestart("rst"))
253+
out <- withRestarts(testEvalUnwind(expr, unwound), rst = function(...) "restarted")
254+
checkIdentical(out, "restarted")
255+
checkUnwound(unwound)
256+
257+
# On returns
258+
unwound <- FALSE
259+
expr <- quote(signalCondition(simpleCondition(NULL)))
260+
out <- callCC(function(k)
261+
withCallingHandlers(testEvalUnwind(expr, unwound),
262+
simpleCondition = function(e) k("jumped")
263+
)
264+
)
265+
checkIdentical(out, "jumped")
266+
checkUnwound(unwound)
267+
}
217268
}

0 commit comments

Comments
 (0)