Skip to content

Commit b1c6bf3

Browse files
Fix fcase() segfault (#6452) (#6451)
* More descriptive variable names for fcase counting variables * Fix fcase() segfault (#6452)
1 parent 0409294 commit b1c6bf3

File tree

3 files changed

+50
-31
lines changed

3 files changed

+50
-31
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ rowwiseDT(
5555

5656
5. Queries like `DT[, min(x):max(x)]` now work as expected, i.e. the same as `DT[, seq(min(x), max(x))]` or `with(DT, min(x):max(x))`, [#2069](https://github.com/Rdatatable/data.table/issues/2069). Shorthand like `DT[, a:b]` meaning "select from columns `a` through `b`" still works. Thanks to @franknarf1 for reporting and @jangorecki for the fix.
5757

58+
6. Fixed a segfault in `fcase()`, [#6448](https://github.com/Rdatatable/data.table/issues/6448). Thanks @ethanbsmith for reporting with reprex, @aitap for finding the root cause, and @MichaelChirico for the PR.
59+
5860
## NOTES
5961

6062
1. Tests run again when some Suggests packages are missing, [#6411](https://github.com/Rdatatable/data.table/issues/6411). Thanks @aadler for the note and @MichaelChirico for the fix.

inst/tests/tests.Rraw

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19182,3 +19182,14 @@ test(2285.09, merge(merge(y, x), z), data.table(a=3L, key="a"))
1918219182
test(2285.10, merge(merge(y, z), x), data.table(a=3L, key="a"))
1918319183
test(2285.11, merge(merge(z, x), y), data.table(a=3L, key="a"))
1918419184
test(2285.12, merge(merge(z, y), x), data.table(a=3L, key="a"))
19185+
19186+
# ensure proper PROTECT() within fcase, #6448
19187+
x <- 1:3
19188+
test(2286,
19189+
fcase(
19190+
x<2, structure(list(1), class = "foo"),
19191+
x<3, structure(list(2), class = "foo"),
19192+
# Force gc() and some allocations which have a good chance at landing in the region that was earlier left unprotected
19193+
{ gc(full = TRUE); replicate(10, FALSE); x<4 },
19194+
`attr<-`(list(3), "class", "foo")),
19195+
structure(list(1, 2, 3), class = "foo"))

src/fifelse.c

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -215,14 +215,16 @@ SEXP fcaseR(SEXP rho, SEXP args) {
215215
"Note that the default argument must be named explicitly, e.g., default=0"), narg - 2);
216216
}
217217
int nprotect=0, l;
218-
int64_t len0=0, len1=0, len2=0;
219-
SEXP ans=R_NilValue, value0=R_NilValue, tracker=R_NilValue, whens=R_NilValue, thens=R_NilValue;
218+
int64_t n_ans=0, n_this_arg=0, n_undecided=0;
219+
SEXP ans=R_NilValue, tracker=R_NilValue, whens=R_NilValue, thens=R_NilValue;
220+
SEXP ans_class, ans_levels;
220221
PROTECT_INDEX Iwhens, Ithens;
221222
PROTECT_WITH_INDEX(whens, &Iwhens); nprotect++;
222223
PROTECT_WITH_INDEX(thens, &Ithens); nprotect++;
223-
SEXPTYPE type0=NILSXP;
224+
SEXPTYPE ans_type=NILSXP;
224225
// naout means if the output is scalar logic na
225226
bool imask = true, naout = false, idefault = false;
227+
bool ans_is_factor;
226228
int *restrict p = NULL;
227229
const int n = narg/2;
228230
for (int i=0; i<n; ++i) {
@@ -238,35 +240,39 @@ SEXP fcaseR(SEXP rho, SEXP args) {
238240
const int *restrict pwhens = LOGICAL(whens);
239241
l = 0;
240242
if (i == 0) {
241-
len0 = xlength(whens);
242-
len2 = len0;
243-
type0 = TYPEOF(thens);
244-
value0 = thens;
245-
ans = PROTECT(allocVector(type0, len0)); nprotect++;
243+
n_ans = xlength(whens);
244+
n_undecided = n_ans;
245+
ans_type = TYPEOF(thens);
246+
ans_class = PROTECT(getAttrib(thens, R_ClassSymbol)); nprotect++;
247+
ans_is_factor = isFactor(thens);
248+
if (ans_is_factor) {
249+
ans_levels = PROTECT(getAttrib(thens, R_LevelsSymbol)); nprotect++;
250+
}
251+
ans = PROTECT(allocVector(ans_type, n_ans)); nprotect++;
246252
copyMostAttrib(thens, ans);
247-
tracker = PROTECT(allocVector(INTSXP, len0)); nprotect++;
253+
tracker = PROTECT(allocVector(INTSXP, n_ans)); nprotect++;
248254
p = INTEGER(tracker);
249255
} else {
250256
imask = false;
251257
naout = xlength(thens) == 1 && TYPEOF(thens) == LGLSXP && LOGICAL(thens)[0]==NA_LOGICAL;
252-
if (xlength(whens) != len0) {
258+
if (xlength(whens) != n_ans) {
253259
// no need to check `idefault` here because the con for default is always `TRUE`
254260
error(_("Argument #%d has length %lld which differs from that of argument #1 (%lld). "
255261
"Please make sure all logical conditions have the same length."),
256-
i*2+1, (long long)xlength(whens), (long long)len0);
262+
i*2+1, (long long)xlength(whens), (long long)n_ans);
257263
}
258-
if (!naout && TYPEOF(thens) != type0) {
264+
if (!naout && TYPEOF(thens) != ans_type) {
259265
if (idefault) {
260266
error(_("Resulting value is of type %s but 'default' is of type %s. "
261-
"Please make sure that both arguments have the same type."), type2char(type0), type2char(TYPEOF(thens)));
267+
"Please make sure that both arguments have the same type."), type2char(ans_type), type2char(TYPEOF(thens)));
262268
} else {
263269
error(_("Argument #%d is of type %s, however argument #2 is of type %s. "
264270
"Please make sure all output values have the same type."),
265-
i*2+2, type2char(TYPEOF(thens)), type2char(type0));
271+
i*2+2, type2char(TYPEOF(thens)), type2char(ans_type));
266272
}
267273
}
268274
if (!naout) {
269-
if (!R_compute_identical(PROTECT(getAttrib(value0, R_ClassSymbol)), PROTECT(getAttrib(thens, R_ClassSymbol)), 0)) {
275+
if (!R_compute_identical(ans_class, PROTECT(getAttrib(thens, R_ClassSymbol)), 0)) {
270276
if (idefault) {
271277
error(_("Resulting value has different class than 'default'. "
272278
"Please make sure that both arguments have the same class."));
@@ -275,35 +281,35 @@ SEXP fcaseR(SEXP rho, SEXP args) {
275281
"Please make sure all output values have the same class."), i*2+2);
276282
}
277283
}
278-
UNPROTECT(2); // class(value0), class(thens)
284+
UNPROTECT(1); // class(thens)
279285
}
280-
if (!naout && isFactor(value0)) {
281-
if (!R_compute_identical(PROTECT(getAttrib(value0, R_LevelsSymbol)), PROTECT(getAttrib(thens, R_LevelsSymbol)), 0)) {
286+
if (!naout && ans_is_factor) {
287+
if (!R_compute_identical(ans_levels, PROTECT(getAttrib(thens, R_LevelsSymbol)), 0)) {
282288
if (idefault) {
283289
error(_("Resulting value and 'default' are both type factor but their levels are different."));
284290
} else {
285291
error(_("Argument #2 and argument #%d are both factor but their levels are different."), i*2+2);
286292
}
287293
}
288-
UNPROTECT(2); // levels(value0), levels(thens)
294+
UNPROTECT(1); // levels(thens)
289295
}
290296
}
291-
len1 = xlength(thens);
292-
if (len1 != len0 && len1 != 1) {
297+
n_this_arg = xlength(thens);
298+
if (n_this_arg != n_ans && n_this_arg != 1) {
293299
if (idefault) {
294-
error(_("Length of 'default' must be 1 or %lld."), (long long)len0);
300+
error(_("Length of 'default' must be 1 or %lld."), (long long)n_ans);
295301
} else {
296-
error(_("Length of output value #%d (%lld) must either be 1 or match the length of the logical condition (%lld)."), i*2+2, (long long)len1, (long long)len0);
302+
error(_("Length of output value #%d (%lld) must either be 1 or match the length of the logical condition (%lld)."), i*2+2, (long long)n_this_arg, (long long)n_ans);
297303
}
298304
}
299-
int64_t thenMask = len1>1 ? INT64_MAX : 0;
305+
int64_t thenMask = n_this_arg>1 ? INT64_MAX : 0;
300306
switch(TYPEOF(ans)) {
301307
case LGLSXP: {
302308
const int *restrict pthens;
303309
if (!naout) pthens = LOGICAL(thens); // the content is not useful if out is NA_LOGICAL scalar
304310
int *restrict pans = LOGICAL(ans);
305311
const int pna = NA_LOGICAL;
306-
for (int64_t j=0; j<len2; ++j) {
312+
for (int64_t j=0; j<n_undecided; ++j) {
307313
const int64_t idx = imask ? j : p[j];
308314
if (pwhens[idx]==1) {
309315
pans[idx] = naout ? pna : pthens[idx & thenMask];
@@ -320,7 +326,7 @@ SEXP fcaseR(SEXP rho, SEXP args) {
320326
if (!naout) pthens = INTEGER(thens); // the content is not useful if out is NA_LOGICAL scalar
321327
int *restrict pans = INTEGER(ans);
322328
const int pna = NA_INTEGER;
323-
for (int64_t j=0; j<len2; ++j) {
329+
for (int64_t j=0; j<n_undecided; ++j) {
324330
const int64_t idx = imask ? j : p[j];
325331
if (pwhens[idx]==1) {
326332
pans[idx] = naout ? pna : pthens[idx & thenMask];
@@ -338,7 +344,7 @@ SEXP fcaseR(SEXP rho, SEXP args) {
338344
double *restrict pans = REAL(ans);
339345
const double na_double = INHERITS(ans, char_integer64) ? NA_INT64_D : NA_REAL;
340346
const double pna = na_double;
341-
for (int64_t j=0; j<len2; ++j) {
347+
for (int64_t j=0; j<n_undecided; ++j) {
342348
const int64_t idx = imask ? j : p[j];
343349
if (pwhens[idx]==1) {
344350
pans[idx] = naout ? pna : pthens[idx & thenMask];
@@ -355,7 +361,7 @@ SEXP fcaseR(SEXP rho, SEXP args) {
355361
if (!naout) pthens = COMPLEX(thens); // the content is not useful if out is NA_LOGICAL scalar
356362
Rcomplex *restrict pans = COMPLEX(ans);
357363
const Rcomplex pna = NA_CPLX;
358-
for (int64_t j=0; j<len2; ++j) {
364+
for (int64_t j=0; j<n_undecided; ++j) {
359365
const int64_t idx = imask ? j : p[j];
360366
if (pwhens[idx]==1) {
361367
pans[idx] = naout ? pna : pthens[idx & thenMask];
@@ -371,7 +377,7 @@ SEXP fcaseR(SEXP rho, SEXP args) {
371377
const SEXP *restrict pthens=NULL;
372378
if (!naout) pthens = STRING_PTR_RO(thens); // the content is not useful if out is NA_LOGICAL scalar
373379
const SEXP pna = NA_STRING;
374-
for (int64_t j=0; j<len2; ++j) {
380+
for (int64_t j=0; j<n_undecided; ++j) {
375381
const int64_t idx = imask ? j : p[j];
376382
if (pwhens[idx]==1) {
377383
SET_STRING_ELT(ans, idx, naout ? pna : pthens[idx & thenMask]);
@@ -388,7 +394,7 @@ SEXP fcaseR(SEXP rho, SEXP args) {
388394
// assign the NA values as it does for other atomic types
389395
const SEXP *restrict pthens=NULL;
390396
if (!naout) pthens = SEXPPTR_RO(thens); // the content is not useful if out is NA_LOGICAL scalar
391-
for (int64_t j=0; j<len2; ++j) {
397+
for (int64_t j=0; j<n_undecided; ++j) {
392398
const int64_t idx = imask ? j : p[j];
393399
if (pwhens[idx]==1) {
394400
if (!naout) SET_VECTOR_ELT(ans, idx, pthens[idx & thenMask]);
@@ -403,7 +409,7 @@ SEXP fcaseR(SEXP rho, SEXP args) {
403409
if (l==0) {
404410
break; // stop early as nothing left to do
405411
}
406-
len2 = l;
412+
n_undecided = l;
407413
}
408414
UNPROTECT(nprotect); // whens, thens, ans, tracker
409415
return ans;

0 commit comments

Comments
 (0)