forked from r-lib/cpp11
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathprotect.hpp
325 lines (269 loc) · 9.78 KB
/
protect.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
#pragma once
#include <csetjmp> // for longjmp, setjmp, jmp_buf
#include <exception> // for exception
#include <stdexcept> // for std::runtime_error
#include <string> // for string, basic_string
#include <tuple> // for tuple, make_tuple
// NB: cpp11/R.hpp must precede R_ext/Error.h to ensure R_NO_REMAP is defined
#include "cpp11/R.hpp" // for SEXP, SEXPREC, CDR, R_NilValue, CAR, R_Pres...
#include "R_ext/Boolean.h" // for Rboolean
#include "R_ext/Error.h" // for Rf_error, Rf_warning
#include "R_ext/Print.h" // for REprintf
#include "R_ext/Utils.h" // for R_CheckUserInterrupt
#include "Rversion.h" // for R_VERSION, R_Version
#if defined(R_VERSION) && R_VERSION >= R_Version(3, 5, 0)
#define HAS_UNWIND_PROTECT
#endif
#ifdef CPP11_USE_FMT
#define FMT_HEADER_ONLY
#include "fmt/core.h"
#endif
namespace cpp11 {
class unwind_exception : public std::exception {
public:
SEXP token;
unwind_exception(SEXP token_) : token(token_) {}
};
#ifdef HAS_UNWIND_PROTECT
/// Unwind Protection from C longjmp's, like those used in R error handling
///
/// @param code The code to which needs to be protected, as a nullary callable
template <typename Fun, typename = typename std::enable_if<std::is_same<
decltype(std::declval<Fun&&>()()), SEXP>::value>::type>
SEXP unwind_protect(Fun&& code) {
static SEXP token = [] {
SEXP res = R_MakeUnwindCont();
R_PreserveObject(res);
return res;
}();
std::jmp_buf jmpbuf;
if (setjmp(jmpbuf)) {
throw unwind_exception(token);
}
SEXP res = R_UnwindProtect(
[](void* data) -> SEXP {
auto callback = static_cast<decltype(&code)>(data);
return static_cast<Fun&&>(*callback)();
},
&code,
[](void* jmpbuf, Rboolean jump) {
if (jump == TRUE) {
// We need to first jump back into the C++ stacks because you can't safely
// throw exceptions from C stack frames.
longjmp(*static_cast<std::jmp_buf*>(jmpbuf), 1);
}
},
&jmpbuf, token);
// R_UnwindProtect adds the result to the CAR of the continuation token,
// which implicitly protects the result. However if there is no error and
// R_UwindProtect does a normal exit the memory shouldn't be protected, so we
// unset it here before returning the value ourselves.
SETCAR(token, R_NilValue);
return res;
}
template <typename Fun, typename = typename std::enable_if<std::is_same<
decltype(std::declval<Fun&&>()()), void>::value>::type>
void unwind_protect(Fun&& code) {
(void)unwind_protect([&] {
std::forward<Fun>(code)();
return R_NilValue;
});
}
template <typename Fun, typename R = decltype(std::declval<Fun&&>()())>
typename std::enable_if<!std::is_same<R, SEXP>::value && !std::is_same<R, void>::value,
R>::type
unwind_protect(Fun&& code) {
R out;
(void)unwind_protect([&] {
out = std::forward<Fun>(code)();
return R_NilValue;
});
return out;
}
#else
// Don't do anything if we don't have unwind protect. This will leak C++ resources,
// including those held by cpp11 objects, but the other alternatives are also not great.
template <typename Fun>
decltype(std::declval<Fun&&>()()) unwind_protect(Fun&& code) {
return std::forward<Fun>(code)();
}
#endif
namespace detail {
template <size_t...>
struct index_sequence {
using type = index_sequence;
};
template <typename, size_t>
struct appended_sequence;
template <std::size_t... I, std::size_t J>
struct appended_sequence<index_sequence<I...>, J> : index_sequence<I..., J> {};
template <size_t N>
struct make_index_sequence
: appended_sequence<typename make_index_sequence<N - 1>::type, N - 1> {};
template <>
struct make_index_sequence<0> : index_sequence<> {};
template <typename F, typename... Aref, size_t... I>
decltype(std::declval<F&&>()(std::declval<Aref>()...)) apply(
F&& f, std::tuple<Aref...>&& a, const index_sequence<I...>&) {
return std::forward<F>(f)(std::get<I>(std::move(a))...);
}
template <typename F, typename... Aref>
decltype(std::declval<F&&>()(std::declval<Aref>()...)) apply(F&& f,
std::tuple<Aref...>&& a) {
return apply(std::forward<F>(f), std::move(a), make_index_sequence<sizeof...(Aref)>{});
}
// overload to silence a compiler warning that the (empty) tuple parameter is set but
// unused
template <typename F>
decltype(std::declval<F&&>()()) apply(F&& f, std::tuple<>&&) {
return std::forward<F>(f)();
}
template <typename F, typename... Aref>
struct closure {
decltype(std::declval<F*>()(std::declval<Aref>()...)) operator()() && {
return apply(ptr_, std::move(arefs_));
}
F* ptr_;
std::tuple<Aref...> arefs_;
};
} // namespace detail
struct protect {
template <typename F>
struct function {
template <typename... A>
decltype(std::declval<F*>()(std::declval<A&&>()...)) operator()(A&&... a) const {
// workaround to support gcc4.8, which can't capture a parameter pack
return unwind_protect(
detail::closure<F, A&&...>{ptr_, std::forward_as_tuple(std::forward<A>(a)...)});
}
F* ptr_;
};
/// May not be applied to a function bearing attributes, which interfere with linkage on
/// some compilers; use an appropriately attributed alternative. (For example, Rf_error
/// bears the [[noreturn]] attribute and must be protected with safe.noreturn rather
/// than safe.operator[]).
template <typename F>
constexpr function<F> operator[](F* raw) const {
return {raw};
}
template <typename F>
struct noreturn_function {
template <typename... A>
void operator() [[noreturn]] (A&&... a) const {
// workaround to support gcc4.8, which can't capture a parameter pack
unwind_protect(
detail::closure<F, A&&...>{ptr_, std::forward_as_tuple(std::forward<A>(a)...)});
// Compiler hint to allow [[noreturn]] attribute; this is never executed since
// the above call will not return.
throw std::runtime_error("[[noreturn]]");
}
F* ptr_;
};
template <typename F>
constexpr noreturn_function<F> noreturn(F* raw) const {
return {raw};
}
};
constexpr struct protect safe = {};
inline void check_user_interrupt() { safe[R_CheckUserInterrupt](); }
#ifdef CPP11_USE_FMT
template <typename... Args>
void stop [[noreturn]] (const char* fmt_arg, Args&&... args) {
std::string msg = fmt::format(fmt_arg, std::forward<Args>(args)...);
safe.noreturn(Rf_errorcall)(R_NilValue, "%s", msg.c_str());
}
template <typename... Args>
void stop [[noreturn]] (const std::string& fmt_arg, Args&&... args) {
std::string msg = fmt::format(fmt_arg, std::forward<Args>(args)...);
safe.noreturn(Rf_errorcall)(R_NilValue, "%s", msg.c_str());
}
template <typename... Args>
void warning(const char* fmt_arg, Args&&... args) {
std::string msg = fmt::format(fmt_arg, std::forward<Args>(args)...);
safe[Rf_warningcall](R_NilValue, "%s", msg.c_str());
}
template <typename... Args>
void warning(const std::string& fmt_arg, Args&&... args) {
std::string msg = fmt::format(fmt_arg, std::forward<Args>(args)...);
safe[Rf_warningcall](R_NilValue, "%s", msg.c_str());
}
#else
template <typename... Args>
void stop [[noreturn]] (const char* fmt, Args... args) {
safe.noreturn(Rf_errorcall)(R_NilValue, fmt, args...);
}
template <typename... Args>
void stop [[noreturn]] (const std::string& fmt, Args... args) {
safe.noreturn(Rf_errorcall)(R_NilValue, fmt.c_str(), args...);
}
template <typename... Args>
void warning(const char* fmt, Args... args) {
safe[Rf_warningcall](R_NilValue, fmt, args...);
}
template <typename... Args>
void warning(const std::string& fmt, Args... args) {
safe[Rf_warningcall](R_NilValue, fmt.c_str(), args...);
}
#endif
/// A doubly-linked list of preserved objects, allowing O(1) insertion/release of
/// objects compared to O(N preserved) with R_PreserveObject.
static struct {
SEXP insert(SEXP obj) {
if (obj == R_NilValue) {
return R_NilValue;
}
PROTECT(obj);
static SEXP list_ = get_preserve_list();
// Get references to head, tail of the precious list.
SEXP head = list_;
SEXP tail = CDR(list_);
// Add a new cell that points to the current head + tail.
SEXP cell = PROTECT(Rf_cons(head, tail));
SET_TAG(cell, obj);
// Update the head + tail to point at the newly-created cell,
// effectively inserting that cell between the current head + tail.
SETCDR(head, cell);
SETCAR(tail, cell);
UNPROTECT(2);
return cell;
}
void print() {
static SEXP list_ = get_preserve_list();
for (SEXP head = list_; head != R_NilValue; head = CDR(head)) {
REprintf("%x CAR: %x CDR: %x TAG: %x\n", head, CAR(head), CDR(head), TAG(head));
}
REprintf("---\n");
}
void release(SEXP cell) {
if (cell == R_NilValue) {
return;
}
// Get a reference to the cells before and after the token.
SEXP lhs = CAR(cell);
SEXP rhs = CDR(cell);
// Remove the cell from the precious list -- effectively, we do this
// by updating the 'lhs' and 'rhs' references to point at each-other,
// effectively removing any references to the cell in the pairlist.
SETCDR(lhs, rhs);
SETCAR(rhs, lhs);
}
private:
// Each compilation unit purposefully gets its own preserve list.
// This avoids issues with sharing preserve list state across compilation units
// and across packages, which has historically caused many issues (#330).
static SEXP get_preserve_list() {
static SEXP out = init_preserve_list();
return out;
}
static SEXP init_preserve_list() {
// Initialize the list exactly once per compilation unit,
// and let R manage its memory
SEXP out = new_preserve_list();
R_PreserveObject(out);
return out;
}
static SEXP new_preserve_list() {
return Rf_cons(R_NilValue, Rf_cons(R_NilValue, R_NilValue));
}
} preserved;
} // namespace cpp11