Skip to content

workaround mangle collision between decltype(Rf_error) and decltype(Rf_warning) #85

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ cpp11test/src/*.so
compile_flags.txt
inst/doc
vignettes/*_cache
compile_commands.json
5 changes: 5 additions & 0 deletions cpp11test/src/test-protect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ context("unwind_protect-C++") {
UNPROTECT(1);
}

test_that("stop throws an unwind_exception") {
expect_error_as(cpp11::stop("error"), cpp11::unwind_exception);
expect_error_as(cpp11::stop("error: %s", "message"), cpp11::unwind_exception);
}

test_that("safe wraps R functions and works if there is an R error") {
expect_error_as(cpp11::safe[Rf_allocVector](REALSXP, -1), cpp11::unwind_exception);
}
Expand Down
176 changes: 89 additions & 87 deletions inst/include/cpp11/protect.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,92 +103,69 @@ inline void release_protect(SEXP protect) {

#ifdef HAS_UNWIND_PROTECT

namespace internal {
struct unwind_data_t {
std::jmp_buf jmpbuf;
};

// We need to first jump back into the C++ stacks because you can't safely throw
// exceptions from C stack frames.
inline void maybe_jump(void* unwind_data, Rboolean jump) {
if (jump) {
unwind_data_t* data = static_cast<unwind_data_t*>(unwind_data);
longjmp(data->jmpbuf, 1);
}
}

} // namespace internal

inline SEXP init_unwind_continuation() {
SEXP res = R_MakeUnwindCont();
R_PreserveObject(res);
return res;
}

/// 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 = init_unwind_continuation();
internal::unwind_data_t unwind_data;
decltype(std::declval<Fun&&>()()), SEXP>::value>::type>
SEXP unwind_protect(Fun&& code) {
static SEXP token = [] {
SEXP res = R_MakeUnwindCont();
R_PreserveObject(res);
return res;
}();

if (setjmp(unwind_data.jmpbuf)) {
std::jmp_buf jmpbuf;
if (setjmp(jmpbuf)) {
throw unwind_exception(token);
}

return R_UnwindProtect(
[](void* data) -> SEXP {
Fun* callback = (Fun*)data;
return (*callback)();
auto callback = static_cast<decltype(&code)>(data);
return static_cast<Fun&&>(*callback)();
},
&code, internal::maybe_jump, &unwind_data, token);
}

template <typename Fun, typename = typename std::enable_if<std::is_same<
decltype(std::declval<Fun>()()), void>::value>::type>
void unwind_protect(Fun code) {
static SEXP token = init_unwind_continuation();
internal::unwind_data_t unwind_data;

if (setjmp(unwind_data.jmpbuf)) {
throw unwind_exception(token);
}

(void)R_UnwindProtect(
[](void* data) -> SEXP {
Fun* callback = (Fun*)data;
(*callback)();
return R_NilValue;
&code,
[](void* jmpbuf, Rboolean jump) {
if (jump) {
// 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);
}
},
&code, internal::maybe_jump, &unwind_data, token);
}
#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, typename = typename std::enable_if<std::is_same<
decltype(std::declval<Fun>()()), SEXP>::value>::type>
SEXP unwind_protect(Fun code) {
return code();
&jmpbuf, token);
}

template <typename Fun, typename = typename std::enable_if<std::is_same<
decltype(std::declval<Fun>()()), void>::value>::type>
void unwind_protect(Fun code) {
code();
decltype(std::declval<Fun&&>()()), void>::value>::type>
void unwind_protect(Fun&& code) {
(void)unwind_protect([&] {
std::forward<Fun>(code)();
return R_NilValue;
});
}
#endif

template <typename Fun, typename R = decltype(std::declval<Fun>()())>
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) {
unwind_protect(Fun&& code) {
R out;
unwind_protect([&] { out = code(); });
(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...>
Expand All @@ -209,64 +186,89 @@ struct make_index_sequence
template <>
struct make_index_sequence<0> : index_sequence<> {};

template <typename F, typename... A, size_t... I>
auto apply(F&& f, std::tuple<A...>&& a, const index_sequence<I...>&)
-> decltype(f(std::get<I>(std::move(a))...)) {
return f(std::get<I>(std::move(a))...);
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... A>
auto apply(F&& f, std::tuple<A...>&& a)
-> decltype(apply(f, std::move(a), make_index_sequence<sizeof...(A)>{})) {
return apply(f, std::move(a), make_index_sequence<sizeof...(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 tuple parameter is set but unused
// overload to silence a compiler warning that the (empty) tuple parameter is set but
// unused
template <typename F>
auto apply(F&& f, std::tuple<> &&) -> decltype(f()) {
return 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>
auto operator()(A&&... a) const
-> decltype(detail::apply(std::declval<F*>(),
std::forward_as_tuple(std::forward<A>(a)...))) {
decltype(std::declval<F*>()(std::declval<A&&>()...)) operator()(A&&... a) const {
// workaround to support gcc4.8, which can't capture a parameter pack
auto a_packed_refs = std::forward_as_tuple(std::forward<A>(a)...);
return unwind_protect(
[&] { return detail::apply(ptr_, std::move(a_packed_refs)); });
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](); }

template <typename... Args>
void stop [[noreturn]] (const char* fmt, Args... args) {
safe[Rf_error](fmt, args...);
// Compiler hint to allow [[noreturn]] attribute; this is never executed since Rf_error
// will longjmp
throw std::runtime_error("stop()");
safe.noreturn(Rf_error)(fmt, args...);
}

template <typename... Args>
void stop [[noreturn]] (const std::string& fmt, Args... args) {
safe[Rf_error](fmt.c_str(), args...);
// Compiler hint to allow [[noreturn]] attribute; this is never executed since Rf_error
// will longjmp
throw std::runtime_error("stop()");
safe.noreturn(Rf_error)(fmt.c_str(), args...);
}

template <typename... Args>
Expand Down