Skip to content

Commit 503f11d

Browse files
authored
Merge pull request #877 from fastverse/claude/issue-876-20260827-2130
Fix: write barrier bypass in setv()/copyv() for STRSXP/VECSXP targets
2 parents ec1c67a + bdc3c87 commit 503f11d

2 files changed

Lines changed: 43 additions & 8 deletions

File tree

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# collapse 2.1.7
22

3+
* Fixed a bug in `setv()`/`copyv()` where assignments into character (`STRSXP`) or list (`VECSXP`) vectors bypassed R's generational write barrier, writing element pointers directly instead of using `SET_STRING_ELT()`/`SET_VECTOR_ELT()`. This could cause an old-generation target to hold an unrecorded reference to a younger value, which a subsequent young-generation garbage collection could free while still referenced, leading to memory corruption, cryptic `CHAR()`/`SET_STRING_ELT()` errors, or segfaults, most likely under heavy allocation in long-running processes. Thanks @SebKrantz for reporting and diagnosing (#876).
4+
35
* Fixed a bug in `fmatch()` (and thus `%in%`/`%!in%`/`%iin%`/`%!iin%` and joins) where a logical `NA` in `x` could spuriously match a non-`NA` value in `table` (e.g. `2L`) when `table` was not itself logical. Thanks @LJ-Jenkins for reporting (#870).
46

57
* Fixed a bug in `fslice()` (grouped, `n = 1`, `with.ties = FALSE`) that caused R to crash with a fatal error when a group had only missing values in `order.by`. Thanks @chihyunkim for reporting (#867).

src/programming.c

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,38 @@ SEXP setcopyv(SEXP x, SEXP val, SEXP rep, SEXP Rinvert, SEXP Rset, SEXP Rind1) {
341341
for(int i = 0; i != n; ++i) if(pv[i] == 0) px[i] = pr[i]; \
342342
}
343343

344+
// STRSXP/VECSXP need SET_STRING_ELT()/SET_VECTOR_ELT() instead of raw pointer
345+
// writes so that R's generational write barrier records the reference
346+
// (see issue about setv()/copyv() corrupting old-generation targets).
347+
#define setcopyvLOOP_SEXP(e, SETELT) \
348+
if(invert) { \
349+
for(int i = 0; i != n; ++i) if(px[i] != v) SETELT(target, i, e); \
350+
} else { \
351+
for(int i = 0; i != n; ++i) if(px[i] == v) SETELT(target, i, e); \
352+
}
353+
354+
#define setcopyvLOOPLVEC1_SEXP(SETELT) \
355+
if(tv == INTSXP) { \
356+
for(int i = 0; i < lv; ++i) SETELT(target, pv[i]-1, r); \
357+
} else if(invert == 0) { \
358+
for(int i = 0; i != n; ++i) if(pv[i] > 0) SETELT(target, i, r); \
359+
} else { \
360+
for(int i = 0; i != n; ++i) if(pv[i] == 0) SETELT(target, i, r); \
361+
}
362+
363+
#define setcopyvLOOPLVEC_SEXP(SETELT) \
364+
if(tv == INTSXP) { \
365+
if(lr == n) { \
366+
for(int i = 0; i < lv; ++i) SETELT(target, pv[i]-1, pr[pv[i]-1]); \
367+
} else { \
368+
for(int i = 0; i < lv; ++i) SETELT(target, pv[i]-1, pr[i]); \
369+
} \
370+
} else if(invert == 0) { \
371+
for(int i = 0; i != n; ++i) if(pv[i] > 0) SETELT(target, i, pr[i]); \
372+
} else { \
373+
for(int i = 0; i != n; ++i) if(pv[i] == 0) SETELT(target, i, pr[i]); \
374+
}
375+
344376
switch(tx) {
345377
case INTSXP:
346378
case LGLSXP:
@@ -413,44 +445,45 @@ SEXP setcopyv(SEXP x, SEXP val, SEXP rep, SEXP Rinvert, SEXP Rset, SEXP Rind1) {
413445
}
414446
case STRSXP:
415447
{
416-
SEXP *restrict px = set ? SEXPPTR(x) : SEXPPTR(ans);
448+
const SEXP target = set ? x : ans;
449+
const SEXP *restrict px = SEXPPTR_RO(target);
417450
if(lv == 1 && ind1 == 0) {
418451
const SEXP v = PROTECT(asChar(val));
419452
if(lr == 1) {
420453
const SEXP r = PROTECT(asChar(rep));
421-
setcopyvLOOP(r)
454+
setcopyvLOOP_SEXP(r, SET_STRING_ELT)
422455
UNPROTECT(1);
423456
} else {
424457
const SEXP *restrict pr = SEXPPTR_RO(rep);
425-
setcopyvLOOP(pr[i])
458+
setcopyvLOOP_SEXP(pr[i], SET_STRING_ELT)
426459
}
427460
UNPROTECT(1);
428461
} else {
429462
const int *restrict pv = INTEGER(val); // ALTREP(val) ? (const int *)ALTVEC_DATAPTR(val) :
430463
if(lr == 1) {
431464
const SEXP r = PROTECT(asChar(rep));
432-
setcopyvLOOPLVEC1
465+
setcopyvLOOPLVEC1_SEXP(SET_STRING_ELT)
433466
UNPROTECT(1);
434467
} else {
435468
const SEXP *restrict pr = SEXPPTR_RO(rep);
436-
setcopyvLOOPLVEC
469+
setcopyvLOOPLVEC_SEXP(SET_STRING_ELT)
437470
}
438471
}
439472
break;
440473
}
441474
case VECSXP:
442475
{
443476
if(set && ALTREP(x)) error("cannot modify ALTREP list by reference");
444-
SEXP *restrict px = set ? SEXPPTR(x) : SEXPPTR(ans);
477+
const SEXP target = set ? x : ans;
445478
if(lv == 1 && ind1 == 0) error("Cannot compare lists to a value");
446479
// if(tr != VECSXP) error("If X is a list and xlist = TRUE, R also needs to be a list");
447480
const int *restrict pv = INTEGER(val); // ALTREP(val) ? (const int *)ALTVEC_DATAPTR(val) :
448481
if(lr == 1) {
449482
const SEXP r = VECTOR_ELT(rep, 0);
450-
setcopyvLOOPLVEC1
483+
setcopyvLOOPLVEC1_SEXP(SET_VECTOR_ELT)
451484
} else {
452485
const SEXP *restrict pr = SEXPPTR_RO(rep);
453-
setcopyvLOOPLVEC
486+
setcopyvLOOPLVEC_SEXP(SET_VECTOR_ELT)
454487
}
455488
break;
456489
}

0 commit comments

Comments
 (0)