Skip to content

Commit 8bcc0f7

Browse files
authored
Remove non-API usage of R_NamespaceRegistry (#488)
* Don't rely on transitive include of `"fmt/core.h"` We got it through `cpp11/protect.hpp`, but we should not rely on that * Correctly use `fmt::runtime()` on runtime strings passed to `fmt::format()` In fmt, there is a `FMT_CONSTEVAL` macro that resolves to `consteval` on "new enough" C++ (otherwise it doesn't do anything). For R 4.6+, the default C++ used is finally "new enough" (`__cplusplus > 201703L`). This causes all `fmt::format()` calls to require a constant expression for `const char*` and `std::string&` input, which we are not currently doing via `fmt_arg`. We have a runtime provided string, which must now be wrapped in `fmt::runtime()`, which is what we should have been doing all along. * Add `r_ns_env()` and use in `get_namespace()` Throwing an informative (and tested!) error when we can't find the package namespace * Define `RCPP_NO_R_HEADERS_CHECK` before all `#include <Rcpp.h>` usage `#include <cpp11/R.hpp>` sets everything up the right way, and otherwise we get a warning from Rcpp which doesn't seem to end up being relevant for this use case * Use `r_env_has()` + `r_env_get()` To avoid triggering a NOTE about usage of `Rf_findVarInFrame3()` on R 4.5, where technically we had the tools to avoid that
1 parent adfb240 commit 8bcc0f7

13 files changed

Lines changed: 87 additions & 13 deletions

File tree

NEWS.md

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

3+
* Removed non-API usage of `R_NamespaceRegistry`.
4+
5+
* Fixed a bug with `CPP11_USE_FMT` where the input was not being correctly wrapped in `fmt::runtime()`.
6+
37
# cpp11 0.5.3
48

59
* Removed non-API usage of `ATTRIB()` (#481).

R/register.R

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,13 @@ cpp_register <- function(path = ".", quiet = !is_interactive(), extension = c(".
8989

9090
extra_includes <- character()
9191
if (pkg_links_to_rcpp(path)) {
92-
extra_includes <- c(extra_includes, "#include <cpp11/R.hpp>", "#include <Rcpp.h>", "using namespace Rcpp;")
92+
extra_includes <- c(
93+
extra_includes,
94+
"#include <cpp11/R.hpp>",
95+
"#define RCPP_NO_R_HEADERS_CHECK",
96+
"#include <Rcpp.h>",
97+
"using namespace Rcpp;"
98+
)
9399
}
94100

95101
pkg_types <- c(

cpp11test/src/cpp11.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// clang-format off
33

44
#include <cpp11/R.hpp>
5+
#define RCPP_NO_R_HEADERS_CHECK
56
#include <Rcpp.h>
67
using namespace Rcpp;
78
#include "cpp11/declarations.hpp"

cpp11test/src/find-intervals.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "cpp11.hpp"
33
using namespace cpp11;
44

5+
#define RCPP_NO_R_HEADERS_CHECK
56
#include <Rcpp.h>
67
#include <algorithm>
78
using namespace Rcpp;

cpp11test/src/matrix.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ using namespace cpp11;
3232
return mat;
3333
}
3434

35+
#define RCPP_NO_R_HEADERS_CHECK
3536
#include <Rcpp.h>
3637
using namespace Rcpp;
3738

cpp11test/src/protect.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
#include <cpp11/sexp.hpp>
22
#include <vector>
33

4-
#include "Rcpp.h"
4+
#define RCPP_NO_R_HEADERS_CHECK
5+
#include <Rcpp.h>
56

67
[[cpp11::register]] void protect_one_(SEXP x, int n) {
78
for (R_xlen_t i = 0; i < n; ++i) {

cpp11test/src/release.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
#include <vector>
22
#include "cpp11/sexp.hpp"
33

4-
#include "Rcpp.h"
4+
#define RCPP_NO_R_HEADERS_CHECK
5+
#include <Rcpp.h>
56

67
[[cpp11::register]] void cpp11_release_(int n) {
78
std::vector<cpp11::sexp> x;

cpp11test/src/test-as.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77

88
#include <testthat.h>
99

10-
#include "Rcpp.h"
10+
#define RCPP_NO_R_HEADERS_CHECK
11+
#include <Rcpp.h>
1112

1213
context("as_cpp-C++") {
1314
test_that("as_cpp<integer>(INTSEXP)") {

inst/include/cpp11/R.hpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,29 @@ inline bool r_env_has(SEXP env, SEXP sym) {
113113
#endif
114114
}
115115

116+
/// Get a namespace from the namespace registry
117+
///
118+
/// Returns `R_NilValue` if the namespace is not in the registry, i.e. if the package has
119+
/// not been loaded yet.
120+
///
121+
/// Unlike `R_FindNamespace()`, does not attempt to load the package, does not error when
122+
/// the namespace can't be found, and does not go through R.
123+
///
124+
/// SAFETY: Keep as a pure C function. Call like an R API function, i.e. wrap in `safe[]`
125+
/// as required.
126+
inline SEXP r_ns_env(const char* name) {
127+
#if R_VERSION >= R_Version(4, 6, 0)
128+
return R_getRegisteredNamespace(name);
129+
#else
130+
SEXP sym = Rf_install(name);
131+
if (r_env_has(R_NamespaceRegistry, sym)) {
132+
return r_env_get(R_NamespaceRegistry, sym);
133+
} else {
134+
return R_NilValue;
135+
}
136+
#endif
137+
}
138+
116139
} // namespace detail
117140

118141
template <typename T>

inst/include/cpp11/function.hpp

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,14 @@
88
#include "cpp11/R.hpp" // for SEXP, SEXPREC, CDR, Rf_install, SETCAR
99
#include "cpp11/as.hpp" // for as_sexp
1010
#include "cpp11/named_arg.hpp" // for named_arg
11-
#include "cpp11/protect.hpp" // for protect, protect::function, safe
11+
#include "cpp11/protect.hpp" // for protect, protect::function, safe, stop
1212
#include "cpp11/sexp.hpp" // for sexp
1313

14+
#ifdef CPP11_USE_FMT
15+
#define FMT_HEADER_ONLY
16+
#include "fmt/core.h"
17+
#endif
18+
1419
namespace cpp11 {
1520

1621
class function {
@@ -66,8 +71,11 @@ class package {
6671
if (strcmp(name, "base") == 0) {
6772
return R_BaseEnv;
6873
}
69-
sexp name_sexp = safe[Rf_install](name);
70-
return safe[detail::r_env_get](R_NamespaceRegistry, name_sexp);
74+
SEXP env = safe[detail::r_ns_env](name);
75+
if (env == R_NilValue) {
76+
stop("Can't find namespace: '%s'.", name);
77+
}
78+
return env;
7179
}
7280

7381
// Either base env or in namespace registry, so no protection needed
@@ -106,7 +114,7 @@ inline void r_message(const char* x) {
106114

107115
inline void message(const char* fmt_arg) {
108116
#ifdef CPP11_USE_FMT
109-
std::string msg = fmt::format(fmt_arg);
117+
std::string msg = fmt::format(fmt::runtime(fmt_arg));
110118
safe[detail::r_message](msg.c_str());
111119
#else
112120
char buff[1024];
@@ -121,7 +129,7 @@ inline void message(const char* fmt_arg) {
121129
template <typename... Args>
122130
void message(const char* fmt_arg, Args... args) {
123131
#ifdef CPP11_USE_FMT
124-
std::string msg = fmt::format(fmt_arg, args...);
132+
std::string msg = fmt::format(fmt::runtime(fmt_arg), args...);
125133
safe[detail::r_message](msg.c_str());
126134
#else
127135
char buff[1024];

0 commit comments

Comments
 (0)