Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
2026-08-03 Kevin Ushey <kevinushey@gmail.com>

* inst/include/Rcpp/api/meat/proxy.h: Protect fresh SEXPs returned
from proxy get() in conversion and assignment operators (#1491)
* inst/include/Rcpp/proxy/Binding.h: Idem
* inst/include/Rcpp/proxy/NamesProxy.h: Idem
* inst/include/Rcpp/proxy/SlotProxy.h: Idem
* inst/tinytest/cpp/misc.cpp: Add regression tests
* inst/tinytest/test_misc.R: Idem

2026-07-24 Dirk Eddelbuettel <edd@debian.org>

* DESCRIPTION (Version, Date): Roll micro version and date
Expand Down
44 changes: 30 additions & 14 deletions inst/include/Rcpp/api/meat/proxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ AttributeProxyPolicy<CLASS>::AttributeProxy::operator=(const T& rhs) {
template <typename CLASS>
template <typename T>
AttributeProxyPolicy<CLASS>::AttributeProxy::operator T() const {
return as<T>(get());
Shield<SEXP> x(get());
return as<T>(x);
}

template <typename CLASS>
Expand All @@ -48,7 +49,8 @@ AttributeProxyPolicy<CLASS>::AttributeProxy::operator SEXP() const {
template <typename CLASS>
template <typename T>
AttributeProxyPolicy<CLASS>::const_AttributeProxy::operator T() const {
return as<T>(get());
Shield<SEXP> x(get());
return as<T>(x);
}

template <typename CLASS>
Expand All @@ -68,13 +70,15 @@ NamesProxyPolicy<CLASS>::NamesProxy::operator=(const T& rhs) {
template <typename CLASS>
template <typename T>
NamesProxyPolicy<CLASS>::NamesProxy::operator T() const {
return as<T>( get() );
Shield<SEXP> x(get());
return as<T>(x);
}

template <typename CLASS>
template <typename T>
NamesProxyPolicy<CLASS>::const_NamesProxy::operator T() const {
return as<T>( get() );
Shield<SEXP> x(get());
return as<T>(x);
}

// SlotProxy
Expand All @@ -89,7 +93,8 @@ SlotProxyPolicy<CLASS>::SlotProxy::operator=(const T& rhs) {
template <typename CLASS>
template <typename T>
SlotProxyPolicy<CLASS>::SlotProxy::operator T() const {
return as<T>(get());
Shield<SEXP> x(get());
return as<T>(x);
}

// TagProxy
Expand All @@ -104,7 +109,8 @@ TagProxyPolicy<CLASS>::TagProxy::operator=(const T& rhs) {
template <typename CLASS>
template <typename T>
TagProxyPolicy<CLASS>::TagProxy::operator T() const {
return as<T>(get());
Shield<SEXP> x(get());
return as<T>(x);
}

template <typename CLASS>
Expand All @@ -115,7 +121,8 @@ TagProxyPolicy<CLASS>::TagProxy::operator SEXP() const {
template <typename CLASS>
template <typename T>
TagProxyPolicy<CLASS>::const_TagProxy::operator T() const {
return as<T>(get());
Shield<SEXP> x(get());
return as<T>(x);
}

template <typename CLASS>
Expand All @@ -135,13 +142,15 @@ BindingPolicy<CLASS>::Binding::operator=(const T& rhs) {
template <typename CLASS>
template <typename T>
BindingPolicy<CLASS>::Binding::operator T() const {
return as<T>(get());
Shield<SEXP> x(get());
return as<T>(x);
}

template <typename CLASS>
template <typename T>
BindingPolicy<CLASS>::const_Binding::operator T() const {
return as<T>(get());
Shield<SEXP> x(get());
return as<T>(x);
}

// DottedPairProxy
Expand All @@ -163,20 +172,25 @@ DottedPairProxyPolicy<CLASS>::DottedPairProxy::operator=(const traits::named_obj
template <typename CLASS>
template <typename T>
DottedPairProxyPolicy<CLASS>::DottedPairProxy::operator T() const {
return as<T>(get());
Shield<SEXP> x(get());
return as<T>(x);
}

template <typename CLASS>
template <typename T>
DottedPairProxyPolicy<CLASS>::const_DottedPairProxy::operator T() const {
return as<T>(get());
Shield<SEXP> x(get());
return as<T>(x);
}

// FieldProxy
template <typename CLASS>
typename FieldProxyPolicy<CLASS>::FieldProxy&
FieldProxyPolicy<CLASS>::FieldProxy::operator=(const FieldProxyPolicy<CLASS>::FieldProxy& rhs) {
if (this != &rhs) set(rhs.get());
if (this != &rhs) {
Shield<SEXP> x(rhs.get());
set(x);
}
return *this;
}

Expand All @@ -191,13 +205,15 @@ FieldProxyPolicy<CLASS>::FieldProxy::operator=(const T& rhs) {
template <typename CLASS>
template <typename T>
FieldProxyPolicy<CLASS>::FieldProxy::operator T() const {
return as<T>(get());
Shield<SEXP> x(get());
return as<T>(x);
}

template <typename CLASS>
template <typename T>
FieldProxyPolicy<CLASS>::const_FieldProxy::operator T() const {
return as<T>(get());
Shield<SEXP> x(get());
return as<T>(x);
}

}
Expand Down
8 changes: 6 additions & 2 deletions inst/include/Rcpp/proxy/Binding.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,12 @@ class BindingPolicy {
env.unlockBinding(name) ;
}
Binding& operator=(const Binding& rhs){
if( *this != rhs )
set( rhs.get() ) ;
// NB: '*this != rhs' previously used here would not compile
// when this operator was instantiated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Odd. Thanks for persisting here too.

if( env.get__() != rhs.env.get__() || name != rhs.name ) {
Shield<SEXP> x( rhs.get() ) ;
set(x) ;
}
return *this ;
}

Expand Down
5 changes: 4 additions & 1 deletion inst/include/Rcpp/proxy/NamesProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ class NamesProxyPolicy{

/* lvalue uses */
NamesProxy& operator=(const NamesProxy& rhs) {
if( this != &rhs) set( rhs.get() ) ;
if( this != &rhs) {
Shield<SEXP> x( rhs.get() ) ;
set(x) ;
}
return *this ;
}

Expand Down
6 changes: 4 additions & 2 deletions inst/include/Rcpp/proxy/SlotProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ class SlotProxyPolicy {
}

SlotProxy& operator=(const SlotProxy& rhs){
set( rhs.get() ) ;
Shield<SEXP> x( rhs.get() ) ;
set(x) ;
return *this ;
}

Expand Down Expand Up @@ -65,7 +66,8 @@ class SlotProxyPolicy {
}

template <typename T> operator T() const {
return as<T>( get() );
Shield<SEXP> x( get() );
return as<T>(x);
}
inline operator SEXP() const {
return get() ;
Expand Down
10 changes: 10 additions & 0 deletions inst/tinytest/cpp/misc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,16 @@ StretchyList named_stretchy_list() {
return out;
}

// [[Rcpp::export]]
void copy_field_gc(Reference a, Reference b) {
a.field("x") = b.field("y");
}

// [[Rcpp::export]]
void copy_binding_gc(Environment a, Environment b) {
a["x"] = b["y"];
}

// [[Rcpp::export]]
void test_stop_variadic() {
stop( "%s %d", "foo", 3 );
Expand Down
28 changes: 28 additions & 0 deletions inst/tinytest/test_misc.R
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,34 @@ expect_equal(stretchy_list(), pairlist( "foo", 1L, 3.2 ))
# test.named_StretchyList <- function(){
expect_equal(named_stretchy_list(), pairlist( a = "foo", b = 1L, c = 3.2 ))

# test.FieldProxy.gc <- function(){
## copying a field whose value is computed freshly on access must keep
## that value protected across the assignment (#1491)
FooGC <- setRefClass("FooGC", fields = list(
x = "ANY",
y = function(value) {
if (missing(value)) new.env(parent = emptyenv()) else stop("read-only")
}
))
a <- FooGC$new(x = NULL)
b <- FooGC$new(x = NULL)
gctorture(TRUE)
for (i in 1:20)
copy_field_gc(a, b)
gctorture(FALSE)
expect_true(is.environment(a$x))

# test.Binding.gc <- function(){
## same for environment-to-environment binding copies (#1491)
ea <- new.env()
eb <- new.env()
makeActiveBinding("y", function() new.env(parent = emptyenv()), eb)
gctorture(TRUE)
for (i in 1:20)
copy_binding_gc(ea, eb)
gctorture(FALSE)
expect_true(is.environment(ea$x))

# test.stop.variadic <- function(){
m <- tryCatch( test_stop_variadic(), error = function(e){
conditionMessage(e)
Expand Down
Loading