Skip to content

Commit 995f17b

Browse files
lionel-eddelbuettel
authored andcommitted
Extract Rcpp::unwindProtect() from Rcpp::Rcpp_fast_eval() (#873)
* Extract Rcpp::protectUnwind() from fast eval * Deindent namespaces to follow Rcpp convention * Add std::function() overload for Rcpp::unwindProtect() * Add unwindProtect() unit tests for non-throwing cases * Mark unwindProtect() functions as inline
1 parent 97ccaeb commit 995f17b

File tree

5 files changed

+217
-56
lines changed

5 files changed

+217
-56
lines changed

ChangeLog

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
2+
2018-06-21 Lionel Henry <[email protected]>
3+
4+
* inst/include/Rcpp/api/meat/unwind.h: Extract unwind protection from
5+
Rcpp::Rcpp_fast_eval() into Rcpp::unwindProtect(). Use this function
6+
whenever you need to call a C function that might longjump, for instance
7+
a function from R's C API. Rcpp::unwindProtect() will protect your C++
8+
stack and throw a Rcpp::internal::LongJump exception to ensure all
9+
destructors are called. The R longjump is then resumed once it is safe
10+
to do so. This function powers Rcpp_fast_eval().
11+
12+
You can use Rcpp::unwindProtect() in two ways. First with a C-like
13+
callback interface that takes a `SEXP (*)(void* data)` function pointer
14+
and a `void*` data argument that is passed to the function. Second, if
15+
you have C++11 enabled, Rcpp::unwindProtect() implements an
16+
`std::function<SEXP(void)>` overload. You can pass any function object
17+
or lambda function with the right signature.
18+
* inst/include/Rcpp/api/meat/Rcpp_eval.h: Idem
19+
120
2018-06-15 Dirk Eddelbuettel <[email protected]>
221

322
* DESCRIPTION (Version, Date): Roll minor version

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

Lines changed: 29 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -23,83 +23,56 @@
2323

2424
#if (defined(RCPP_PROTECTED_EVAL) && defined(R_VERSION) && R_VERSION >= R_Version(3, 5, 0))
2525
#define RCPP_USE_PROTECT_UNWIND
26-
#include <csetjmp>
26+
#include <Rcpp/api/meat/unwind.h>
2727
#endif
2828

2929

30-
namespace Rcpp {
31-
namespace internal {
30+
namespace Rcpp { namespace internal {
3231

3332
#ifdef RCPP_USE_PROTECT_UNWIND
3433

35-
struct EvalData {
36-
SEXP expr;
37-
SEXP env;
38-
EvalData(SEXP expr_, SEXP env_) : expr(expr_), env(env_) { }
39-
};
40-
struct EvalUnwindData {
41-
std::jmp_buf jmpbuf;
42-
};
43-
44-
// First jump back to the protected context with a C longjmp because
45-
// `Rcpp_protected_eval()` is called from C and we can't safely throw
46-
// exceptions across C frames.
47-
inline void Rcpp_maybe_throw(void* unwind_data, Rboolean jump) {
48-
if (jump) {
49-
EvalUnwindData* data = static_cast<EvalUnwindData*>(unwind_data);
50-
longjmp(data->jmpbuf, 1);
51-
}
52-
}
34+
struct EvalData {
35+
SEXP expr;
36+
SEXP env;
37+
EvalData(SEXP expr_, SEXP env_) : expr(expr_), env(env_) { }
38+
};
5339

54-
inline SEXP Rcpp_protected_eval(void* eval_data) {
55-
EvalData* data = static_cast<EvalData*>(eval_data);
56-
return ::Rf_eval(data->expr, data->env);
57-
}
40+
inline SEXP Rcpp_protected_eval(void* eval_data) {
41+
EvalData* data = static_cast<EvalData*>(eval_data);
42+
return ::Rf_eval(data->expr, data->env);
43+
}
5844

59-
// This is used internally instead of Rf_eval() to make evaluation safer
60-
inline SEXP Rcpp_eval_impl(SEXP expr, SEXP env) {
61-
return Rcpp_fast_eval(expr, env);
62-
}
45+
// This is used internally instead of Rf_eval() to make evaluation safer
46+
inline SEXP Rcpp_eval_impl(SEXP expr, SEXP env) {
47+
return Rcpp_fast_eval(expr, env);
48+
}
6349

6450
#else // R < 3.5.0
6551

66-
// Fall back to Rf_eval() when the protect-unwind API is unavailable
67-
inline SEXP Rcpp_eval_impl(SEXP expr, SEXP env) {
68-
return ::Rf_eval(expr, env);
69-
}
52+
// Fall back to Rf_eval() when the protect-unwind API is unavailable
53+
inline SEXP Rcpp_eval_impl(SEXP expr, SEXP env) {
54+
return ::Rf_eval(expr, env);
55+
}
7056

7157
#endif
7258

73-
} // namespace internal
59+
}} // namespace Rcpp::internal
7460

7561

76-
#ifdef RCPP_USE_PROTECT_UNWIND
77-
78-
inline SEXP Rcpp_fast_eval(SEXP expr, SEXP env) {
79-
internal::EvalData data(expr, env);
80-
internal::EvalUnwindData unwind_data;
81-
Shield<SEXP> token(::R_MakeUnwindCont());
82-
83-
if (setjmp(unwind_data.jmpbuf)) {
84-
// Keep the token protected while unwinding because R code might run
85-
// in C++ destructors. Can't use PROTECT() for this because
86-
// UNPROTECT() might be called in a destructor, for instance if a
87-
// Shield<SEXP> is on the stack.
88-
::R_PreserveObject(token);
62+
namespace Rcpp {
8963

90-
throw internal::LongjumpException(token);
91-
}
64+
#ifdef RCPP_USE_PROTECT_UNWIND
9265

93-
return ::R_UnwindProtect(internal::Rcpp_protected_eval, &data,
94-
internal::Rcpp_maybe_throw, &unwind_data,
95-
token);
96-
}
66+
inline SEXP Rcpp_fast_eval(SEXP expr, SEXP env) {
67+
internal::EvalData data(expr, env);
68+
return unwindProtect(&internal::Rcpp_protected_eval, &data);
69+
}
9770

9871
#else
9972

100-
inline SEXP Rcpp_fast_eval(SEXP expr, SEXP env) {
101-
return Rcpp_eval(expr, env);
102-
}
73+
inline SEXP Rcpp_fast_eval(SEXP expr, SEXP env) {
74+
return Rcpp_eval(expr, env);
75+
}
10376

10477
#endif
10578

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

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// unwind.h: Rcpp R/C++ interface class library -- Unwind Protect
2+
//
3+
// Copyright (C) 2018 RStudio
4+
//
5+
// This file is part of Rcpp.
6+
//
7+
// Rcpp is free software: you can redistribute it and/or modify it
8+
// under the terms of the GNU General Public License as published by
9+
// the Free Software Foundation, either version 2 of the License, or
10+
// (at your option) any later version.
11+
//
12+
// Rcpp is distributed in the hope that it will be useful, but
13+
// WITHOUT ANY WARRANTY; without even the implied warranty of
14+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
// GNU General Public License for more details.
16+
//
17+
// You should have received a copy of the GNU General Public License
18+
// along with Rcpp. If not, see <http://www.gnu.org/licenses/>.
19+
20+
#ifndef RCPP_API_MEAT_UNWIND_H
21+
#define RCPP_API_MEAT_UNWIND_H
22+
23+
#include <csetjmp>
24+
25+
#ifdef RCPP_USING_CXX11
26+
#include <functional>
27+
#endif
28+
29+
30+
namespace Rcpp { namespace internal {
31+
32+
struct UnwindData {
33+
std::jmp_buf jmpbuf;
34+
};
35+
36+
// First jump back to the protected context with a C longjmp because
37+
// `Rcpp_protected_eval()` is called from C and we can't safely throw
38+
// exceptions across C frames.
39+
inline void maybeJump(void* unwind_data, Rboolean jump) {
40+
if (jump) {
41+
UnwindData* data = static_cast<UnwindData*>(unwind_data);
42+
longjmp(data->jmpbuf, 1);
43+
}
44+
}
45+
46+
#ifdef RCPP_USING_CXX11
47+
inline SEXP unwindProtectUnwrap(void* data) {
48+
std::function<SEXP(void)>* callback = (std::function<SEXP(void)>*) data;
49+
return (*callback)();
50+
}
51+
#endif
52+
53+
}} // namespace Rcpp::internal
54+
55+
56+
namespace Rcpp {
57+
58+
inline SEXP unwindProtect(SEXP (*callback)(void* data), void* data) {
59+
internal::UnwindData unwind_data;
60+
Shield<SEXP> token(::R_MakeUnwindCont());
61+
62+
if (setjmp(unwind_data.jmpbuf)) {
63+
// Keep the token protected while unwinding because R code might run
64+
// in C++ destructors. Can't use PROTECT() for this because
65+
// UNPROTECT() might be called in a destructor, for instance if a
66+
// Shield<SEXP> is on the stack.
67+
::R_PreserveObject(token);
68+
69+
throw internal::LongjumpException(token);
70+
}
71+
72+
return ::R_UnwindProtect(callback, data,
73+
internal::maybeJump, &unwind_data,
74+
token);
75+
}
76+
77+
#ifdef RCPP_USING_CXX11
78+
inline SEXP unwindProtect(std::function<SEXP(void)> callback) {
79+
return unwindProtect(&internal::unwindProtectUnwrap, &callback);
80+
}
81+
#endif
82+
83+
} // namespace Rcpp
84+
85+
#endif

inst/unitTests/cpp/stack.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,60 @@ SEXP testSendInterrupt() {
5151
Rf_onintr();
5252
return R_NilValue;
5353
}
54+
55+
SEXP maybeThrow(void* data) {
56+
bool* fail = (bool*) data;
57+
if (*fail)
58+
Rf_error("throw!");
59+
else
60+
return NumericVector::create(42);
61+
}
62+
63+
// [[Rcpp::export]]
64+
SEXP testUnwindProtect(LogicalVector indicator, bool fail) {
65+
unwindIndicator my_data(indicator);
66+
SEXP out = R_NilValue;
67+
68+
#if defined(R_VERSION) && R_VERSION >= R_Version(3, 5, 0)
69+
out = Rcpp::unwindProtect(&maybeThrow, &fail);
70+
#endif
71+
return out;
72+
}
73+
74+
75+
// [[Rcpp::plugins("cpp11")]]
76+
77+
// [[Rcpp::export]]
78+
SEXP testUnwindProtectLambda(LogicalVector indicator, bool fail) {
79+
unwindIndicator my_data(indicator);
80+
SEXP out = R_NilValue;
81+
82+
#if defined(R_VERSION) && R_VERSION >= R_Version(3, 5, 0)
83+
out = Rcpp::unwindProtect([&] () { return maybeThrow(&fail); });
84+
#endif
85+
86+
return out;
87+
}
88+
89+
struct FunctionObj {
90+
FunctionObj(int data_, bool fail_) : data(data_), fail(fail_) { }
91+
SEXP operator() () {
92+
NumericVector x = maybeThrow(&fail);
93+
x[0] = x[0] * data;
94+
return x;
95+
}
96+
int data;
97+
bool fail;
98+
};
99+
100+
// [[Rcpp::export]]
101+
SEXP testUnwindProtectFunctionObject(LogicalVector indicator, bool fail) {
102+
unwindIndicator my_data(indicator);
103+
SEXP out = R_NilValue;
104+
105+
#if defined(R_VERSION) && R_VERSION >= R_Version(3, 5, 0)
106+
out = Rcpp::unwindProtect(FunctionObj(10, fail));
107+
#endif
108+
109+
return out;
110+
}

inst/unitTests/runit.stack.R

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,31 @@ if (.runThisTest) {
142142
checkTrue(unwound2) # Always unwound
143143
}
144144

145+
test.unwindProtect <- function() {
146+
if (hasUnwind) {
147+
unwound <- FALSE
148+
checkException(testUnwindProtect(unwound, fail = TRUE))
149+
checkTrue(unwound)
150+
151+
unwound <- FALSE
152+
checkException(testUnwindProtectLambda(unwound, fail = TRUE))
153+
checkTrue(unwound)
154+
155+
unwound <- FALSE
156+
checkException(testUnwindProtectFunctionObject(unwound, fail = TRUE))
157+
checkTrue(unwound)
158+
159+
unwound <- FALSE
160+
checkEquals(testUnwindProtect(unwound, fail = FALSE), 42)
161+
checkTrue(unwound)
162+
163+
unwound <- FALSE
164+
checkEquals(testUnwindProtectLambda(unwound, fail = FALSE), 42)
165+
checkTrue(unwound)
166+
167+
unwound <- FALSE
168+
checkEquals(testUnwindProtectFunctionObject(unwound, fail = FALSE), 420)
169+
checkTrue(unwound)
170+
}
171+
}
145172
}

0 commit comments

Comments
 (0)