Skip to content

Use tag types to generate attribute specific internals and avoid ODR violations#493

Merged
DavisVaughan merged 4 commits intomainfrom
fix/noreturn-template-initialization
May 5, 2026
Merged

Use tag types to generate attribute specific internals and avoid ODR violations#493
DavisVaughan merged 4 commits intomainfrom
fix/noreturn-template-initialization

Conversation

@DavisVaughan
Copy link
Copy Markdown
Member

@DavisVaughan DavisVaughan commented May 5, 2026

Closes #491
Closes #422 (because all this did was change the warning() signature to something that no longer conflicted with stop(), so it didn't fix the underlying problem)
This was the actual solution for #295

My inline documentation fully describes the problem, so I will regurgitate it here

// Tag types to force templated `struct closure` and `apply()` infrastructure shared
// across `struct function` and `struct noreturn_function` to generate different
// attribute specific `struct closure` and `apply()` variants.
//
// Consider:
//
// ```
// cpp11::stop("error: %s", message)
// cpp11::warning("warning: %s", message)
// ```
//
// These both end up constructing the exact same templated `struct closure` and `apply()`
// functions. The `args` for the underlying `Rf_errorcall()` and `Rf_warningcall()` are:
// - `R_NilValue`
// - `const char* fmt`
// - `const char* message`
//
// The only difference is that `cpp11::stop()` is marked as `[[noreturn]]` because the
// underlying `Rf_errorcall()` is also marked as `[[noreturn]]` /
// `__attribute__((noreturn))`.
//
// But this causes issues! Due to C++'s ODR (One Definition Rule), only 1 variant of
// `apply()` and `struct closure` can be created per template combination. If the
// `cpp11::stop()` variant is linked in first, then some compilers use the `[[noreturn]]`
// hint on `cpp11::stop()` and `operator()` of `noreturn_function` to assert that the
// `apply()` function also cannot return, and returning is deemed unreachable. So then
// when `cpp11::warning()` tries to return from its call to `apply()`, a crash occurs. We
// see this output under ASAN: `execution reached an unreachable program point`.
//
// We've seen this issue on macOS and Linux under clang (gcc does not seem to reproduce
// this). To reproduce, you must have `cpp11::stop()` and `cpp11::warning()` calls in
// different translation units / files and the file containing `cpp11::stop()` must be
// linked first. Putting it first alphabetically seems to be enough, which is why we have
// `template-1-stop.cpp` and `template-2-warn.cpp` in our tests, along with
// `test-template.R` to test this exact issue. You also need to compile with `-O0`,
// otherwise you'll just get a hang rather than a crash.
//
// Adding the tag into the template definition forces `safe[fn]()` and
// `safe.noreturn[fn]()` calls to generate different `apply()` variants, avoiding this
// issue.
//
// https://github.com/r-lib/cpp11/issues/491
// https://github.com/r-lib/cpp11/issues/295
struct return_tag {};
struct no_return_tag {};

@DavisVaughan DavisVaughan merged commit cd33947 into main May 5, 2026
16 checks passed
@DavisVaughan DavisVaughan deleted the fix/noreturn-template-initialization branch May 5, 2026 18:26
@DavisVaughan
Copy link
Copy Markdown
Member Author

Some additional prior issues / prs related to this

#85
#87

My theory is that these just didn't go far enough in making sure the internals were also attribute specific

@DavisVaughan
Copy link
Copy Markdown
Member Author

@jimhester - @jennybc and I thought you'd enjoy the resolution to this gnarly issue that has caused cpp11::warning() to crash for unexplainable reasons over the years

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Behavior change in cpp11::warning(fmt, args...) between 0.5.3 and 0.5.4 (clang only)

1 participant