From 6509a86da516fef76b936000db6e49971abe3392 Mon Sep 17 00:00:00 2001 From: Kevin Ushey Date: Sun, 2 Aug 2026 19:50:12 -0700 Subject: [PATCH] protect new cons cell in named StretchyList::push_back() The named variant of push_back() allocated the new cons cell before constructing the Symbol for its tag. If the tag name was not yet interned, Rf_install() could allocate and trigger a garbage collection that reclaimed the still-unreachable cell. Construct the Symbol first, matching push_front(). Fixes #1489. --- ChangeLog | 6 ++++++ inst/include/Rcpp/api/meat/StretchyList.h | 2 +- inst/tinytest/cpp/misc.cpp | 7 +++++++ inst/tinytest/test_misc.R | 11 +++++++++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 7321bc4bd..0bcf5f576 100644 --- a/ChangeLog +++ b/ChangeLog @@ -22,6 +22,12 @@ * inst/include/Rcpp/proxy/NamesProxy.h: Idem * inst/include/Rcpp/proxy/SlotProxy.h: Idem * inst/tinytest/cpp/misc.cpp: Add regression tests + +2026-08-02 Kevin Ushey + + * inst/include/Rcpp/api/meat/StretchyList.h: Protect the new cell + from collection during Rf_install() in named push_back() (#1489) + * inst/tinytest/cpp/misc.cpp: Add regression test * inst/tinytest/test_misc.R: Idem 2026-07-24 Dirk Eddelbuettel diff --git a/inst/include/Rcpp/api/meat/StretchyList.h b/inst/include/Rcpp/api/meat/StretchyList.h index e74016459..d3d51c435 100644 --- a/inst/include/Rcpp/api/meat/StretchyList.h +++ b/inst/include/Rcpp/api/meat/StretchyList.h @@ -35,8 +35,8 @@ namespace Rcpp{ template< typename T> StretchyList_Impl& StretchyList_Impl::push_back__impl( const T& obj, traits::true_type ){ Shield s( wrap(obj.object) ) ; - SEXP tmp = Rf_cons( s, R_NilValue ); Symbol tag = obj.name ; + SEXP tmp = Rf_cons( s, R_NilValue ); SET_TAG(tmp, tag) ; SEXP self = Storage::get__() ; SETCDR( CAR(self), tmp) ; diff --git a/inst/tinytest/cpp/misc.cpp b/inst/tinytest/cpp/misc.cpp index d0a4de6f9..eefdf8179 100644 --- a/inst/tinytest/cpp/misc.cpp +++ b/inst/tinytest/cpp/misc.cpp @@ -173,6 +173,13 @@ StretchyList named_stretchy_list() { return out; } +// [[Rcpp::export]] +StretchyList named_stretchy_list_dynamic(std::string name) { + StretchyList out; + out.push_back( Named(name, 42) ); + return out; +} + // [[Rcpp::export]] void copy_field_gc(Reference a, Reference b) { a.field("x") = b.field("y"); diff --git a/inst/tinytest/test_misc.R b/inst/tinytest/test_misc.R index 3e9730e7a..9f256b899 100644 --- a/inst/tinytest/test_misc.R +++ b/inst/tinytest/test_misc.R @@ -129,6 +129,17 @@ 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.named_StretchyList_gc <- function(){ +## push_back() must keep the new cell protected across the Rf_install() +## needed for a not-yet-interned tag name (#1489) +name <- paste(sample(c(letters, LETTERS), 32, TRUE), collapse = "") +gctorture(TRUE) +result <- named_stretchy_list_dynamic(name) +gctorture(FALSE) +expected <- pairlist(42L) +names(expected) <- name +expect_equal(result, expected) + # test.FieldProxy.gc <- function(){ ## copying a field whose value is computed freshly on access must keep ## that value protected across the assignment (#1491)