Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign up| #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 | |
| 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); | |
| } | |
| return 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); | |
| } | |
| 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](); } | |
| template <typename... Args> | |
| void stop [[noreturn]] (const char* fmt, Args... args) { | |
| safe.noreturn(Rf_error)(fmt, args...); | |
| } | |
| template <typename... Args> | |
| void stop [[noreturn]] (const std::string& fmt, Args... args) { | |
| safe.noreturn(Rf_error)(fmt.c_str(), args...); | |
| } | |
| template <typename... Args> | |
| void warning(const char* fmt, Args... args) { | |
| safe[Rf_warning](fmt, args...); | |
| } | |
| template <typename... Args> | |
| void warning(const std::string& fmt, Args... args) { | |
| safe[Rf_warning](fmt.c_str(), args...); | |
| } | |
| /// 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; | |
| } | |
| #ifdef CPP11_USE_PRESERVE_OBJECT | |
| PROTECT(obj); | |
| R_PreserveObject(obj); | |
| UNPROTECT(1); | |
| return obj; | |
| #endif | |
| PROTECT(obj); | |
| // Add a new cell that points to the previous end. | |
| SEXP cell = PROTECT(Rf_cons(list_, CDR(list_))); | |
| SET_TAG(cell, obj); | |
| SETCDR(list_, cell); | |
| if (CDR(cell) != R_NilValue) { | |
| SETCAR(CDR(cell), cell); | |
| } | |
| UNPROTECT(2); | |
| return cell; | |
| } | |
| void print() { | |
| 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"); | |
| } | |
| // This is currently unused, but client packages could use it to free leaked resources | |
| // in older R versions if needed | |
| void release_all() { | |
| #if !defined(CPP11_USE_PRESERVE_OBJECT) | |
| SEXP first = CDR(list_); | |
| if (first != R_NilValue) { | |
| SETCAR(first, R_NilValue); | |
| SETCDR(list_, R_NilValue); | |
| } | |
| #endif | |
| } | |
| void release(SEXP token) { | |
| if (token == R_NilValue) { | |
| return; | |
| } | |
| #ifdef CPP11_USE_PRESERVE_OBJECT | |
| R_ReleaseObject(token); | |
| return; | |
| #endif | |
| SEXP before = CAR(token); | |
| SEXP after = CDR(token); | |
| if (before == R_NilValue && after == R_NilValue) { | |
| Rf_error("should never happen"); | |
| } | |
| SETCDR(before, after); | |
| if (after != R_NilValue) { | |
| SETCAR(after, before); | |
| } | |
| } | |
| private: | |
| // We deliberately avoid using safe[] in the below code, as this code runs | |
| // when the shared library is loaded and will not be wrapped by | |
| // `CPP11_UNWIND`, so if an error occurs we will not catch the C++ exception | |
| // that safe emits. | |
| static void set_option(SEXP name, SEXP value) { | |
| SEXP opt = SYMVALUE(Rf_install(".Options")); | |
| SEXP t = opt; | |
| while (CDR(t) != R_NilValue) { | |
| t = CDR(t); | |
| } | |
| SETCDR(t, Rf_allocList(1)); | |
| opt = CDR(t); | |
| SET_TAG(opt, name); | |
| SETCAR(opt, value); | |
| } | |
| // The list_ singleton is stored in a XPtr within an R global option. | |
| // | |
| // It is not constructed as a static variable directly since many | |
| // translation units may be compiled, resulting in unrelated instances of each | |
| // static variable. | |
| // | |
| // We cannot store it in the cpp11 namespace, as cpp11 likely will not be loaded by | |
| // packages. | |
| // We cannot store it in R's global environment, as that is against CRAN | |
| // policies. | |
| // We instead store it as an XPtr in the global options, which avoids issues | |
| // both copying and serializing. | |
| static SEXP get_preserve_xptr() { | |
| static SEXP preserve_xptr = R_NilValue; | |
| if (preserve_xptr == R_NilValue) { | |
| SEXP preserve_xptr_sym = Rf_install("cpp11_preserve_xptr"); | |
| preserve_xptr = Rf_GetOption1(preserve_xptr_sym); | |
| if (preserve_xptr == R_NilValue) { | |
| SEXP preserve_list = Rf_cons(R_NilValue, R_NilValue); | |
| R_PreserveObject(preserve_list); | |
| preserve_xptr = R_MakeExternalPtr(preserve_list, R_NilValue, R_NilValue); | |
| set_option(preserve_xptr_sym, preserve_xptr); | |
| } | |
| } | |
| return preserve_xptr; | |
| } | |
| SEXP list_ = static_cast<SEXP>(R_ExternalPtrAddr(get_preserve_xptr())); | |
| } preserved; | |
| } // namespace cpp11 |