diff --git a/ChangeLog b/ChangeLog index 364fc2244..88f09da0f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2026-10-22 Kevin Ushey + + * src/module.cpp (CppObject__finalize): Make finalize() a no-op for a + module object that was never constructed (created without arguments for a + class without a default constructor); it used to fail on the dummy + pointer, and the failure surfaced as an error message at garbage + collection + * inst/tinytest/test_module.R: Add test + 2026-10-22 Michael Sumner * R/00_classes.R: Define C++Field, C++OverloadedMethods and C++Constructor diff --git a/inst/tinytest/test_module.R b/inst/tinytest/test_module.R index db0edfc60..fe8323c17 100644 --- a/inst/tinytest/test_module.R +++ b/inst/tinytest/test_module.R @@ -123,3 +123,10 @@ gctorture(TRUE) res <- gadget$value() gctorture(FALSE) expect_identical( res, c(1, 2, 3) ) + +# test.Module.finalize.dummy <- function(){ +## an object created without arguments for a class lacking a default +## constructor holds the dummy pointer; finalizing it must be a no-op +## rather than an error (which used to surface at garbage collection) +r <- new( ModuleRandomizer ) +expect_identical( r$finalize(), NULL ) diff --git a/src/module.cpp b/src/module.cpp index d90d81ffb..ca02c5504 100644 --- a/src/module.cpp +++ b/src/module.cpp @@ -108,7 +108,14 @@ RCPP_FUN_4(SEXP, CppField__set, XP_Class cl, SEXP field_xp, SEXP obj, SEXP value cl->setProperty(field_xp, obj, value); return R_NilValue; } + +extern SEXP rcpp_dummy_pointer; + RCPP_FUN_2(SEXP, CppObject__finalize, XP_Class cl, SEXP obj) { + // an object created without arguments for a class lacking a default + // constructor holds the dummy pointer: there is nothing to finalize + if (obj == rcpp_dummy_pointer) + return R_NilValue; cl->run_finalizer(obj); return R_NilValue; }