Thanks for the report and the careful analysis. This looks like a bug
in the wrapper implementation. I'll look more carefully and patch
R-devel/R-patched next week when I am back from useR. For caching it
should be sufficient for now to cache the writable pointer.
Best,
luke
On Mon, 6 Jul 2026, Ivan Krylov via R-devel wrote:
Hello R-devel,
Here's a simplification of the problem that currently plagues lme4:
x <- local({
# y[[1]] is large enough to have its own mmap() allocation
y <- list(rep(1, 1e7))
# y[[1]] is also large enough (>=64 elements) so structure() will
# create a wrapper instead of duplicating
structure(y[[1]], foo = 'bar')
# y[[1]] now has a reference count of >=2
})
# now x has the only reference to the original vector
writeLines('
#include <Rinternals.h>
static double run_arbitrary_R_code(SEXP x) {
const double * xd = REAL(x); // oops, forgot REAL_RO
// since the underlying vector is MAYBE_SHARED, the
// wrapper in x duplicated the underlying vector and
// returned a different data pointer
Rprintf("REAL(x) = %p\\n", xd);
double sum = 0;
for (R_xlen_t i = 0; i < XLENGTH(x); ++i) sum += xd[i];
R_gc();
// the original vector is now freed, memory unmapped
return sum;
}
SEXP foo(SEXP x) {
// cache a read-only pointer to the data of x
const double *xd = REAL_RO(x);
Rprintf("REAL_RO(x) = %p\\n", xd);
double ret1 = run_arbitrary_R_code(x);
// xd is now invalid
double ret2 = 0;
for (R_xlen_t i = 0; i < XLENGTH(x); ++i) ret2 += xd[i]; // <-- crash
return ScalarLogical(ret1 == ret2);
}
', 'foo.c')
stopifnot(tools::Rcmd('SHLIB foo.c') == 0)
dyn.load(paste0('foo', .Platform$dynlib.ext))
.Call('foo', x)
# REAL_RO(x) = 0x7f11527b4040
# REAL(x) = 0x7f114db68040
#
# *** caught segfault ***
# address 0x7f11527b4040, cause 'memory not mapped'
In the original lme4 code the data pointers are cached inside Eigen
objects courtesy of RcppEigen (shared with the R session, ensuring that
the R objects underlying the data pointers stay protected), and the role
of run_arbitrary_R_code() is played by a %*% operation (which uses
REAL() to read a vector).
Is there a nice way to solve this problem of a protected object's data
pointer suddenly becoming invalid? (Where is the best place to solve
it? lme4, Rcpp, R itself?) Would it be safe for lme4 to cache writable
data pointers (e.g. REAL() instead of DATAPTR_RO()), assuming that the
former will stay the same as long as the object exists? Or does it need
an extensive rewrite to always refresh cached data pointers?
--
Luke Tierney
Professor Emeritus
Department of Statistics and
Actuarial Science
University of Iowa
241 Schaeffer Hall email: [email protected]
Iowa City, IA 52242 WWW: http://stat.uiowa.edu
______________________________________________
[email protected] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel