From f9a2caafa2e8616a1cb4d3ae7160af08ce429ac2 Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Sun, 14 Jan 2018 09:18:37 +0100 Subject: [PATCH 1/3] Move stack unwinding tests to their own files --- inst/unitTests/cpp/misc.cpp | 30 ------------ inst/unitTests/cpp/stack.cpp | 53 +++++++++++++++++++++ inst/unitTests/runit.misc.R | 51 -------------------- inst/unitTests/runit.stack.R | 91 ++++++++++++++++++++++++++++++++++++ 4 files changed, 144 insertions(+), 81 deletions(-) create mode 100644 inst/unitTests/cpp/stack.cpp create mode 100644 inst/unitTests/runit.stack.R diff --git a/inst/unitTests/cpp/misc.cpp b/inst/unitTests/cpp/misc.cpp index f48570161..e804f3939 100644 --- a/inst/unitTests/cpp/misc.cpp +++ b/inst/unitTests/cpp/misc.cpp @@ -19,8 +19,6 @@ // You should have received a copy of the GNU General Public License // along with Rcpp. If not, see . -#define RCPP_PROTECTED_EVAL - #include using namespace Rcpp; using namespace std; @@ -226,31 +224,3 @@ String testNullableString(Rcpp::Nullable param = R_NilValue) { else return String(""); } - -// Class that indicates to R caller whether C++ stack was unwound -struct unwindIndicator { - unwindIndicator(LogicalVector indicator_) { - // Reset the indicator to FALSE - indicator = indicator_; - *LOGICAL(indicator) = 0; - } - - // Set indicator to TRUE when stack unwinds - ~unwindIndicator() { - *LOGICAL(indicator) = 1; - } - - LogicalVector indicator; -}; - -// [[Rcpp::export]] -SEXP testEvalUnwindImpl(RObject expr, Environment env, LogicalVector indicator) { - unwindIndicator my_data(indicator); - return Rcpp::Rcpp_fast_eval(expr, env); -} - -// [[Rcpp::export]] -SEXP testSendInterrupt() { - Rf_onintr(); - return R_NilValue; -} diff --git a/inst/unitTests/cpp/stack.cpp b/inst/unitTests/cpp/stack.cpp new file mode 100644 index 000000000..2e707c293 --- /dev/null +++ b/inst/unitTests/cpp/stack.cpp @@ -0,0 +1,53 @@ +// -*- mode: C++; c-indent-level: 4; c-basic-offset: 4; indent-tabs-mode: nil; -*- +// +// misc.cpp: Rcpp R/C++ interface class library -- misc unit tests +// +// Copyright (C) 2013 - 2015 Dirk Eddelbuettel and Romain Francois +// +// This file is part of Rcpp. +// +// Rcpp is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// Rcpp is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Rcpp. If not, see . + +#define RCPP_PROTECTED_EVAL + +#include +using namespace Rcpp; + +// Class that indicates to R caller whether C++ stack was unwound +struct unwindIndicator { + unwindIndicator(LogicalVector indicator_) { + // Reset the indicator to FALSE + indicator = indicator_; + *LOGICAL(indicator) = 0; + } + + // Set indicator to TRUE when stack unwinds + ~unwindIndicator() { + *LOGICAL(indicator) = 1; + } + + LogicalVector indicator; +}; + +// [[Rcpp::export]] +SEXP testFastEval(RObject expr, Environment env, LogicalVector indicator) { + unwindIndicator my_data(indicator); + return Rcpp::Rcpp_fast_eval(expr, env); +} + +// [[Rcpp::export]] +SEXP testSendInterrupt() { + Rf_onintr(); + return R_NilValue; +} diff --git a/inst/unitTests/runit.misc.R b/inst/unitTests/runit.misc.R index 9c4dd0d99..ccabe0ea9 100644 --- a/inst/unitTests/runit.misc.R +++ b/inst/unitTests/runit.misc.R @@ -214,55 +214,4 @@ if (.runThisTest) { checkTrue(nchar(Rcpp:::bib()) > 0, msg="bib file") } - test.stackUnwinds <- function() { - # On old versions of R, Rcpp_fast_eval() falls back to Rcpp_eval() and - # leaks on longjumps - hasUnwind <- getRversion() >= "3.5.0" - checkUnwound <- if (hasUnwind) checkTrue else function(x) checkTrue(!x) - testEvalUnwind <- function(expr, indicator) { - testEvalUnwindImpl(expr, parent.frame(), indicator) - } - - # On errors - Always unwound - unwound <- FALSE - out <- tryCatch(testEvalUnwind(quote(stop("err")), unwound), error = identity) - checkTrue(unwound) - msg <- if (hasUnwind) "err" else "Evaluation error: err." - checkIdentical(out$message, msg) - - # On interrupts - Always unwound - unwound <- FALSE - expr <- quote({ - repeat testSendInterrupt() - "returned" - }) - out <- tryCatch(testEvalUnwind(expr, unwound), interrupt = function(c) "onintr") - checkTrue(unwound) - checkIdentical(out, "onintr") - - # On caught conditions - unwound <- FALSE - expr <- quote(signalCondition(simpleCondition("cnd"))) - cnd <- tryCatch(testEvalUnwind(expr, unwound), condition = identity) - checkTrue(inherits(cnd, "simpleCondition")) - checkUnwound(unwound) - - # On restart jumps - unwound <- FALSE - expr <- quote(invokeRestart("rst")) - out <- withRestarts(testEvalUnwind(expr, unwound), rst = function(...) "restarted") - checkIdentical(out, "restarted") - checkUnwound(unwound) - - # On returns - unwound <- FALSE - expr <- quote(signalCondition(simpleCondition(NULL))) - out <- callCC(function(k) - withCallingHandlers(testEvalUnwind(expr, unwound), - simpleCondition = function(e) k("jumped") - ) - ) - checkIdentical(out, "jumped") - checkUnwound(unwound) - } } diff --git a/inst/unitTests/runit.stack.R b/inst/unitTests/runit.stack.R new file mode 100644 index 000000000..2829a5b62 --- /dev/null +++ b/inst/unitTests/runit.stack.R @@ -0,0 +1,91 @@ +#!/usr/bin/env r +# +# Copyright (C) 2010 - 2017 Dirk Eddelbuettel and Romain Francois +# +# This file is part of Rcpp. +# +# Rcpp is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# Rcpp is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Rcpp. If not, see . + +.runThisTest <- Sys.getenv("RunAllRcppTests") == "yes" + + +if (.runThisTest) { + + .setUp <- Rcpp:::unitTestSetup("stack.cpp") + + test.stackUnwinds <- function() { + # On old versions of R, Rcpp_fast_eval() falls back to Rcpp_eval() and + # leaks on longjumps + hasUnwind <- getRversion() >= "3.5.0" + checkUnwound <- if (hasUnwind) checkTrue else function(x) checkTrue(!x) + testEvalUnwind <- function(expr, indicator) { + testFastEval(expr, parent.frame(), indicator) + } + + # On errors - Always unwound + unwound <- FALSE + out <- tryCatch(testEvalUnwind(quote(stop("err")), unwound), error = identity) + checkTrue(unwound) + msg <- if (hasUnwind) "err" else "Evaluation error: err." + checkIdentical(out$message, msg) + + # On interrupts - Always unwound + unwound <- FALSE + expr <- quote({ + repeat testSendInterrupt() + "returned" + }) + out <- tryCatch(testEvalUnwind(expr, unwound), interrupt = function(c) "onintr") + checkTrue(unwound) + checkIdentical(out, "onintr") + + # On caught conditions + unwound <- FALSE + expr <- quote(signalCondition(simpleCondition("cnd"))) + cnd <- tryCatch(testEvalUnwind(expr, unwound), condition = identity) + checkTrue(inherits(cnd, "simpleCondition")) + checkUnwound(unwound) + + # On restart jumps + unwound <- FALSE + expr <- quote(invokeRestart("rst")) + out <- withRestarts(testEvalUnwind(expr, unwound), rst = function(...) "restarted") + checkIdentical(out, "restarted") + checkUnwound(unwound) + + # On returns + unwound <- FALSE + expr <- quote(signalCondition(simpleCondition(NULL))) + out <- callCC(function(k) { + withCallingHandlers(testEvalUnwind(expr, unwound), + simpleCondition = function(e) k("jumped") + ) + }) + checkIdentical(out, "jumped") + checkUnwound(unwound) + + # On returned condition + unwound <- FALSE + cnd <- simpleError("foo") + out <- tryCatch(testEvalUnwind(quote(cnd), unwound), + error = function(c) "abort" + ) + checkTrue(unwound) + if (hasUnwind) { + checkIdentical(out, cnd) + } else { + checkIdentical(out, "abort") + } + } +} From da902928fc919c33a821589e71d93d7cb436ce98 Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Sun, 14 Jan 2018 11:40:55 +0100 Subject: [PATCH 2/3] Disaggregate the stack unwinding tests --- inst/unitTests/runit.stack.R | 52 ++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/inst/unitTests/runit.stack.R b/inst/unitTests/runit.stack.R index 2829a5b62..f363d350e 100644 --- a/inst/unitTests/runit.stack.R +++ b/inst/unitTests/runit.stack.R @@ -24,64 +24,76 @@ if (.runThisTest) { .setUp <- Rcpp:::unitTestSetup("stack.cpp") - test.stackUnwinds <- function() { - # On old versions of R, Rcpp_fast_eval() falls back to Rcpp_eval() and - # leaks on longjumps - hasUnwind <- getRversion() >= "3.5.0" - checkUnwound <- if (hasUnwind) checkTrue else function(x) checkTrue(!x) - testEvalUnwind <- function(expr, indicator) { - testFastEval(expr, parent.frame(), indicator) - } + # On old versions of R, Rcpp_fast_eval() falls back to Rcpp_eval() and + # leaks on longjumps + hasUnwind <- getRversion() >= "3.5.0" + checkUnwound <- if (hasUnwind) checkTrue else function(x) checkTrue(!x) + EvalUnwind <- function(expr, indicator) { + testFastEval(expr, parent.frame(), indicator) + } - # On errors - Always unwound + # Stack is always unwound on errors and interrupts + test.stackUnwindsOnErrors <- function() { unwound <- FALSE - out <- tryCatch(testEvalUnwind(quote(stop("err")), unwound), error = identity) + out <- tryCatch(EvalUnwind(quote(stop("err")), unwound), error = identity) checkTrue(unwound) msg <- if (hasUnwind) "err" else "Evaluation error: err." checkIdentical(out$message, msg) + } - # On interrupts - Always unwound + test.stackUnwindsOnInterrupts <- function() { unwound <- FALSE expr <- quote({ repeat testSendInterrupt() "returned" }) - out <- tryCatch(testEvalUnwind(expr, unwound), interrupt = function(c) "onintr") + out <- tryCatch(EvalUnwind(expr, unwound), interrupt = function(c) "onintr") checkTrue(unwound) checkIdentical(out, "onintr") - # On caught conditions + } + + test.stackUnwindsOnCaughtConditions <- function() { unwound <- FALSE expr <- quote(signalCondition(simpleCondition("cnd"))) - cnd <- tryCatch(testEvalUnwind(expr, unwound), condition = identity) + cnd <- tryCatch(EvalUnwind(expr, unwound), condition = identity) checkTrue(inherits(cnd, "simpleCondition")) checkUnwound(unwound) - # On restart jumps + } + + test.stackUnwindsOnRestartJumps <- function() { unwound <- FALSE expr <- quote(invokeRestart("rst")) - out <- withRestarts(testEvalUnwind(expr, unwound), rst = function(...) "restarted") + out <- withRestarts(EvalUnwind(expr, unwound), rst = function(...) "restarted") checkIdentical(out, "restarted") checkUnwound(unwound) - # On returns + } + + test.stackUnwindsOnReturns <- function() { unwound <- FALSE expr <- quote(signalCondition(simpleCondition(NULL))) out <- callCC(function(k) { - withCallingHandlers(testEvalUnwind(expr, unwound), + withCallingHandlers(EvalUnwind(expr, unwound), simpleCondition = function(e) k("jumped") ) }) checkIdentical(out, "jumped") checkUnwound(unwound) - # On returned condition + } + + test.stackUnwindsOnReturnedConditions <- function() { unwound <- FALSE cnd <- simpleError("foo") - out <- tryCatch(testEvalUnwind(quote(cnd), unwound), + out <- tryCatch(EvalUnwind(quote(cnd), unwound), error = function(c) "abort" ) checkTrue(unwound) + + # The old mechanism cannot differentiate between a returned error and a + # thrown error if (hasUnwind) { checkIdentical(out, cnd) } else { From c9f70e57a84932c07c85ce03beb7d7d34e6df4bd Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Sun, 14 Jan 2018 21:14:14 +0100 Subject: [PATCH 3/3] Disable stack unwind unit tests --- inst/unitTests/runit.stack.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/unitTests/runit.stack.R b/inst/unitTests/runit.stack.R index f363d350e..18361a821 100644 --- a/inst/unitTests/runit.stack.R +++ b/inst/unitTests/runit.stack.R @@ -20,7 +20,7 @@ .runThisTest <- Sys.getenv("RunAllRcppTests") == "yes" -if (.runThisTest) { +if (FALSE && .runThisTest) { .setUp <- Rcpp:::unitTestSetup("stack.cpp")