Skip to content

Commit 47f284c

Browse files
authored
Remove usage of Rf_findVarInFrame3() (#386)
* Use `R_existsVarInFrame()` in `exists()` * Remove usage of `Rf_findVarInFrame3()` * Rename to `r_env_get()` * Extract out `r_env_has()` * Update FAQ vignette
1 parent 8c5bfd3 commit 47f284c

File tree

5 files changed

+90
-11
lines changed

5 files changed

+90
-11
lines changed

NEWS.md

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

3+
* The `environment` class no longer uses the non-API function
4+
`Rf_findVarInFrame3()` (#367).
5+
6+
* The `exists()` method now uses the new `R_existsVarInFrame()` function.
7+
8+
* The `SEXP` conversion operator now uses the new `R_getVar()` function. Note
9+
that this is stricter than `Rf_findVarInFrame3()` in 3 ways. The object
10+
must exist in the environment (i.e. `R_UnboundValue` is no longer returned),
11+
the object cannot be `R_MissingArg`, and if the object was a promise, that
12+
promise is now evaluated. We have backported this new strictness to older
13+
versions of R as well.
14+
315
* Fixed an issue with the `writable::matrix` copy constructor where the
416
underlying SEXP should have been copied but was not. It is now consistent with
517
the behavior of the equivalent `writable::r_vector` copy constructor.

cpp11test/src/test-environment.cpp

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,36 @@ context("environment-C++") {
2424

2525
x.remove("bar");
2626
expect_true(x.size() == 1);
27-
expect_true(x["bar"] == R_UnboundValue);
27+
// Object must exist in the environment when we convert to SEXP
28+
auto bar_proxy = x["bar"];
29+
expect_error_as(static_cast<SEXP>(bar_proxy), cpp11::unwind_exception);
2830

2931
x.remove("foo");
3032
expect_true(x.size() == 0);
31-
expect_true(x["foo"] == R_UnboundValue);
33+
// Object must exist in the environment when we convert to SEXP
34+
auto foo_proxy = x["foo"];
35+
expect_error_as(static_cast<SEXP>(foo_proxy), cpp11::unwind_exception);
3236

3337
expect_true(static_cast<SEXP>(x) == x_sxp);
3438

3539
UNPROTECT(1);
3640
}
41+
42+
test_that("environment doesn't leak `R_MissingArg`") {
43+
auto new_env = cpp11::package("base")["new.env"];
44+
SEXP x_sxp = PROTECT(new_env());
45+
46+
// Install `R_MissingArg` as a value in the environment
47+
Rf_defineVar(Rf_install("missing"), R_MissingArg, x_sxp);
48+
49+
cpp11::environment x(x_sxp);
50+
expect_true(x.size() == 1);
51+
52+
// Upon conversion to SEXP, we error on `R_MissingArg`.
53+
// Base R's `R_getVar()` tries hard not to leak this, so we do too.
54+
auto proxy = x["missing"];
55+
expect_error_as(static_cast<SEXP>(proxy), cpp11::unwind_exception);
56+
57+
UNPROTECT(1);
58+
}
3759
}

inst/include/cpp11/R.hpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#define R_NO_REMAP
1212
#define STRICT_R_HEADERS
13+
#include "R_ext/Boolean.h"
1314
#include "Rinternals.h"
1415
#include "Rversion.h"
1516

@@ -60,6 +61,53 @@ namespace detail {
6061
// which can throw warnings with `-Wsign-compare` on Windows.
6162
inline SEXPTYPE r_typeof(SEXP x) { return static_cast<SEXPTYPE>(TYPEOF(x)); }
6263

64+
/// Get an object from an environment
65+
///
66+
/// SAFETY: Keep as a pure C function. Call like an R API function, i.e. wrap in `safe[]`
67+
/// as required.
68+
inline SEXP r_env_get(SEXP env, SEXP sym) {
69+
#if defined(R_VERSION) && R_VERSION >= R_Version(4, 5, 0)
70+
const Rboolean inherits = FALSE;
71+
return R_getVar(sym, env, inherits);
72+
#else
73+
SEXP out = Rf_findVarInFrame3(env, sym, TRUE);
74+
75+
// Replicate the 3 checks from `R_getVar()` (along with exact error message):
76+
// - Object must be found in the `env`
77+
// - `R_MissingArg` can't leak from an `env` anymore
78+
// - Promises can't leak from an `env` anymore
79+
80+
if (out == R_MissingArg) {
81+
Rf_errorcall(R_NilValue, "argument \"%s\" is missing, with no default",
82+
CHAR(PRINTNAME(sym)));
83+
}
84+
85+
if (out == R_UnboundValue) {
86+
Rf_errorcall(R_NilValue, "object '%s' not found", CHAR(PRINTNAME(sym)));
87+
}
88+
89+
if (r_typeof(out) == PROMSXP) {
90+
PROTECT(out);
91+
out = Rf_eval(out, env);
92+
UNPROTECT(1);
93+
}
94+
95+
return out;
96+
#endif
97+
}
98+
99+
/// Check if an object exists in an environment
100+
///
101+
/// SAFETY: Keep as a pure C function. Call like an R API function, i.e. wrap in `safe[]`
102+
/// as required.
103+
inline bool r_env_has(SEXP env, SEXP sym) {
104+
#if R_VERSION >= R_Version(4, 2, 0)
105+
return R_existsVarInFrame(env, sym);
106+
#else
107+
return Rf_findVarInFrame3(env, sym, FALSE) != R_UnboundValue;
108+
#endif
109+
}
110+
63111
} // namespace detail
64112

65113
template <typename T>

inst/include/cpp11/environment.hpp

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#include <string> // for string, basic_string
44

55
#include "Rversion.h" // for R_VERSION, R_Version
6-
#include "cpp11/R.hpp" // for SEXP, SEXPREC, Rf_install, Rf_findVarIn...
6+
#include "cpp11/R.hpp" // for SEXP, SEXPREC, Rf_install, r_env_get...
77
#include "cpp11/as.hpp" // for as_sexp
88
#include "cpp11/protect.hpp" // for protect, protect::function, safe, unwin...
99
#include "cpp11/sexp.hpp" // for sexp
@@ -34,7 +34,7 @@ class environment {
3434
safe[Rf_defineVar](name_, as_sexp(value), parent_);
3535
return *this;
3636
}
37-
operator SEXP() const { return safe[Rf_findVarInFrame3](parent_, name_, TRUE); };
37+
operator SEXP() const { return safe[detail::r_env_get](parent_, name_); };
3838
operator sexp() const { return SEXP(); };
3939
};
4040

@@ -45,12 +45,8 @@ class environment {
4545
proxy operator[](const char* name) const { return operator[](safe[Rf_install](name)); }
4646
proxy operator[](const std::string& name) const { return operator[](name.c_str()); }
4747

48-
bool exists(SEXP name) const {
49-
SEXP res = safe[Rf_findVarInFrame3](env_, name, FALSE);
50-
return res != R_UnboundValue;
51-
}
48+
bool exists(SEXP name) const { return safe[detail::r_env_has](env_, name); }
5249
bool exists(const char* name) const { return exists(safe[Rf_install](name)); }
53-
5450
bool exists(const std::string& name) const { return exists(name.c_str()); }
5551

5652
void remove(SEXP name) {

vignettes/FAQ.Rmd

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,14 +153,15 @@ cpp11::environment create_environment() {
153153
#### 9. How do I assign and retrieve values in an environment? What happens if I try to get a value that doesn't exist?
154154

155155
Use `[]` to retrieve or assign values from an environment by name.
156-
If a value does not exist it will return `R_UnboundValue`.
156+
If a value does not exist, it will error.
157+
To check for existence ahead of time, use the `exists()` method.
157158

158159
```{cpp11}
159160
#include <cpp11.hpp>
160161
161162
[[cpp11::register]]
162163
bool foo_exists(cpp11::environment x) {
163-
return x["foo"] != R_UnboundValue;
164+
return x.exists("foo");
164165
}
165166
166167
[[cpp11::register]]

0 commit comments

Comments
 (0)