@@ -95,6 +95,52 @@ unwind_protect(Fun&& code) {
9595
9696namespace detail {
9797
98+ // Tag types to force templated `struct closure` and `apply()` infrastructure shared
99+ // across `struct function` and `struct noreturn_function` to generate different
100+ // attribute specific `struct closure` and `apply()` variants.
101+ //
102+ // Consider:
103+ //
104+ // ```
105+ // cpp11::stop("error: %s", message)
106+ // cpp11::warning("warning: %s", message)
107+ // ```
108+ //
109+ // These both end up constructing the exact same templated `struct closure` and `apply()`
110+ // functions. The `args` for the underlying `Rf_errorcall()` and `Rf_warningcall()` are:
111+ // - `R_NilValue`
112+ // - `const char* fmt`
113+ // - `const char* message`
114+ //
115+ // The only difference is that `cpp11::stop()` is marked as `[[noreturn]]` because the
116+ // underlying `Rf_errorcall()` is also marked as `[[noreturn]]` /
117+ // `__attribute__((noreturn))`.
118+ //
119+ // But this causes issues! Due to C++'s ODR (One Definition Rule), only 1 variant of
120+ // `apply()` and `struct closure` can be created per template combination. If the
121+ // `cpp11::stop()` variant is linked in first, then some compilers use the `[[noreturn]]`
122+ // hint on `cpp11::stop()` and `operator()` of `noreturn_function` to assert that the
123+ // `apply()` function also cannot return, and returning is deemed unreachable. So then
124+ // when `cpp11::warning()` tries to return from its call to `apply()`, a crash occurs. We
125+ // see this output under ASAN: `execution reached an unreachable program point`.
126+ //
127+ // We've seen this issue on macOS and Linux under clang (gcc does not seem to reproduce
128+ // this). To reproduce, you must have `cpp11::stop()` and `cpp11::warning()` calls in
129+ // different translation units / files and the file containing `cpp11::stop()` must be
130+ // linked first. Putting it first alphabetically seems to be enough, which is why we have
131+ // `template-1-stop.cpp` and `template-2-warn.cpp` in our tests, along with
132+ // `test-template.R` to test this exact issue. You also need to compile with `-O0`,
133+ // otherwise you'll just get a hang rather than a crash.
134+ //
135+ // Adding the tag into the template definition forces `safe[fn]()` and
136+ // `safe.noreturn[fn]()` calls to generate different `apply()` variants, avoiding this
137+ // issue.
138+ //
139+ // https://github.com/r-lib/cpp11/issues/491
140+ // https://github.com/r-lib/cpp11/issues/295
141+ struct return_tag {};
142+ struct no_return_tag {};
143+
98144template <size_t ...>
99145struct index_sequence {
100146 using type = index_sequence;
@@ -113,29 +159,30 @@ struct make_index_sequence
113159template <>
114160struct make_index_sequence <0 > : index_sequence<> {};
115161
116- template <typename F, typename ... Aref, size_t ... I>
162+ template <typename ReturnTag, typename F, typename ... Aref, size_t ... I>
117163decltype (std::declval<F&&>()(std::declval<Aref>()...)) apply(
118164 F&& f, std::tuple<Aref...>&& a, const index_sequence<I...>&) {
119165 return std::forward<F>(f)(std::get<I>(std::move (a))...);
120166}
121167
122- template <typename F, typename ... Aref>
168+ template <typename ReturnTag, typename F, typename ... Aref>
123169decltype (std::declval<F&&>()(std::declval<Aref>()...)) apply(F&& f,
124170 std::tuple<Aref...>&& a) {
125- return apply (std::forward<F>(f), std::move (a), make_index_sequence<sizeof ...(Aref)>{});
171+ return apply<ReturnTag>(std::forward<F>(f), std::move (a),
172+ make_index_sequence<sizeof ...(Aref)>{});
126173}
127174
128175// overload to silence a compiler warning that the (empty) tuple parameter is set but
129176// unused
130- template <typename F>
177+ template <typename ReturnTag, typename F>
131178decltype (std::declval<F&&>()()) apply(F&& f, std::tuple<>&&) {
132179 return std::forward<F>(f)();
133180}
134181
135- template <typename F, typename ... Aref>
182+ template <typename ReturnTag, typename F, typename ... Aref>
136183struct closure {
137184 decltype (std::declval<F*>()(std::declval<Aref>()...)) operator ()() && {
138- return apply (ptr_, std::move (arefs_));
185+ return apply<ReturnTag> (ptr_, std::move (arefs_));
139186 }
140187 F* ptr_;
141188 std::tuple<Aref...> arefs_;
@@ -149,17 +196,13 @@ struct protect {
149196 template <typename ... A>
150197 decltype (std::declval<F*>()(std::declval<A&&>()...)) operator ()(A&&... a) const {
151198 // workaround to support gcc4.8, which can't capture a parameter pack
152- return unwind_protect (
153- detail::closure<F, A&&...>{ ptr_, std::forward_as_tuple (std::forward<A>(a)...)});
199+ return unwind_protect (detail::closure<detail::return_tag, F, A&&...>{
200+ ptr_, std::forward_as_tuple (std::forward<A>(a)...)});
154201 }
155202
156203 F* ptr_;
157204 };
158205
159- // / May not be applied to a function bearing attributes, which interfere with linkage on
160- // / some compilers; use an appropriately attributed alternative. (For example, Rf_error
161- // / bears the [[noreturn]] attribute and must be protected with safe.noreturn rather
162- // / than safe.operator[]).
163206 template <typename F>
164207 constexpr function<F> operator [](F* raw) const {
165208 return {raw};
@@ -170,15 +213,18 @@ struct protect {
170213 template <typename ... A>
171214 void operator () [[noreturn]] (A&&... a) const {
172215 // workaround to support gcc4.8, which can't capture a parameter pack
173- unwind_protect (
174- detail::closure<F, A&&...>{ ptr_, std::forward_as_tuple (std::forward<A>(a)...)});
216+ unwind_protect (detail::closure<detail::no_return_tag, F, A&&...>{
217+ ptr_, std::forward_as_tuple (std::forward<A>(a)...)});
175218 // Compiler hint to allow [[noreturn]] attribute; this is never executed since
176219 // the above call will not return.
177220 throw std::runtime_error (" [[noreturn]]" );
178221 }
179222 F* ptr_;
180223 };
181224
225+ // To be used when wrapping functions tagged with `[[noreturn]]`, such as
226+ // `Rf_errorcall()`, to force generation of attribute specific `struct closure` and
227+ // `apply()` variants, see `struct return_tag` documentation for more details.
182228 template <typename F>
183229 constexpr noreturn_function<F> noreturn (F* raw) const {
184230 return {raw};
0 commit comments