From 5ba81919f75d864d9f5f5d513ecc1151e74ef11a 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 | 7 +++++++ inst/include/Rcpp/api/meat/StretchyList.h | 2 +- inst/tinytest/cpp/misc.cpp | 7 +++++++ inst/tinytest/test_misc.R | 11 +++++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 15cefd8f8..6fbc4ba45 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +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 * DESCRIPTION (Version, Date): Roll micro version and date 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 87271e5d3..6a22c5b04 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 test_stop_variadic() { stop( "%s %d", "foo", 3 ); diff --git a/inst/tinytest/test_misc.R b/inst/tinytest/test_misc.R index 08df4b5cd..50f489308 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.stop.variadic <- function(){ m <- tryCatch( test_stop_variadic(), error = function(e){ conditionMessage(e)