Skip to content

Commit cd33947

Browse files
authored
Use tag types to generate attribute specific internals and avoid ODR violations (#493)
* Add failing test * Use tag types to generate attribute specific internals * NEWS bullet * Formatting
1 parent 4a133ee commit cd33947

9 files changed

Lines changed: 125 additions & 15 deletions

File tree

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# cpp11 (development version)
22

3+
* Fixed an issue where `cpp11::stop()` and `cpp11::warning()` calls with the same template instantiation could cause a crash on some systems (#491, #295).
4+
35
* `cpp_source()` now works with multiple `file`s (#492).
46

57
# cpp11 0.5.4

cpp11test/DESCRIPTION

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Package: cpp11test
22
Title: A test suite and benchmark code for 'cpp11'
33
Version: 0.0.0.9000
4-
Authors@R:
4+
Authors@R:
55
c(person(given = "Jim",
66
family = "Hester",
77
role = c("aut", "cre"),
@@ -21,3 +21,4 @@ Suggests:
2121
LazyData: true
2222
Roxygen: list(markdown = TRUE)
2323
RoxygenNote: 7.1.1
24+
Config/testthat/edition: 3

cpp11test/R/cpp11.R

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,14 @@ rcpp_push_and_truncate_ <- function(size_sxp) {
236236
.Call(`_cpp11test_rcpp_push_and_truncate_`, size_sxp)
237237
}
238238

239+
test_template_stop <- function() {
240+
invisible(.Call(`_cpp11test_test_template_stop`))
241+
}
242+
243+
test_template_warning <- function() {
244+
invisible(.Call(`_cpp11test_test_template_warning`))
245+
}
246+
239247
test_destruction_inner <- function() {
240248
invisible(.Call(`_cpp11test_test_destruction_inner`))
241249
}

cpp11test/src/cpp11.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,22 @@ extern "C" SEXP _cpp11test_rcpp_push_and_truncate_(SEXP size_sxp) {
444444
return cpp11::as_sexp(rcpp_push_and_truncate_(cpp11::as_cpp<cpp11::decay_t<SEXP>>(size_sxp)));
445445
END_CPP11
446446
}
447+
// template-1-stop.cpp
448+
void test_template_stop();
449+
extern "C" SEXP _cpp11test_test_template_stop() {
450+
BEGIN_CPP11
451+
test_template_stop();
452+
return R_NilValue;
453+
END_CPP11
454+
}
455+
// template-2-warn.cpp
456+
void test_template_warning();
457+
extern "C" SEXP _cpp11test_test_template_warning() {
458+
BEGIN_CPP11
459+
test_template_warning();
460+
return R_NilValue;
461+
END_CPP11
462+
}
447463
// test-protect-nested.cpp
448464
void test_destruction_inner();
449465
extern "C" SEXP _cpp11test_test_destruction_inner() {
@@ -534,6 +550,8 @@ static const R_CallMethodDef CallEntries[] = {
534550
{"_cpp11test_sum_int_foreach_", (DL_FUNC) &_cpp11test_sum_int_foreach_, 1},
535551
{"_cpp11test_test_destruction_inner", (DL_FUNC) &_cpp11test_test_destruction_inner, 0},
536552
{"_cpp11test_test_destruction_outer", (DL_FUNC) &_cpp11test_test_destruction_outer, 0},
553+
{"_cpp11test_test_template_stop", (DL_FUNC) &_cpp11test_test_template_stop, 0},
554+
{"_cpp11test_test_template_warning", (DL_FUNC) &_cpp11test_test_template_warning, 0},
537555
{"_cpp11test_upper_bound", (DL_FUNC) &_cpp11test_upper_bound, 2},
538556
{"run_testthat_tests", (DL_FUNC) &run_testthat_tests, 1},
539557
{NULL, NULL, 0}

cpp11test/src/template-1-stop.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#include "cpp11/protect.hpp"
2+
3+
[[cpp11::register]] void test_template_stop() { cpp11::stop("%s", "stop"); }

cpp11test/src/template-2-warn.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#include "cpp11/protect.hpp"
2+
3+
[[cpp11::register]] void test_template_warning() { cpp11::warning("%s", "warning"); }
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# equivalently templated `cpp11::stop()` and `cpp11::warning()` can coexist (#491)
2+
3+
Code
4+
test_template_stop()
5+
Condition
6+
Error:
7+
! stop
8+
9+
---
10+
11+
Code
12+
test_template_warning()
13+
Condition
14+
Warning:
15+
warning
16+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
test_that("equivalently templated `cpp11::stop()` and `cpp11::warning()` can coexist (#491)", {
2+
# It is important the the C++ files be named `template-1-stop` and
3+
# `template-2-warn` because the `cpp11::stop()` call needs to be linked in
4+
# before the `cpp11::warning()` call to reproduce the original issue,
5+
# otherwise the templates underlying `cpp11::warning()` will get instantiated
6+
# first and be reused in `cpp11::stop()` via ODR and that "works" fine.
7+
expect_snapshot(error = TRUE, {
8+
test_template_stop()
9+
})
10+
expect_snapshot({
11+
test_template_warning()
12+
})
13+
})

inst/include/cpp11/protect.hpp

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,52 @@ unwind_protect(Fun&& code) {
9595

9696
namespace 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+
98144
template <size_t...>
99145
struct index_sequence {
100146
using type = index_sequence;
@@ -113,29 +159,30 @@ struct make_index_sequence
113159
template <>
114160
struct 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>
117163
decltype(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>
123169
decltype(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>
131178
decltype(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>
136183
struct 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

Comments
 (0)