Skip to content

Commit 4ee6759

Browse files
committed
altsxp: two fixes from the fifteenth review round
- The one-argument seq() over a length-1 opaque value asks the class for 1:x, and the class declines for an NA endpoint. Control then fell into the "one element, so count along it" branch and answered 1L, where the same call on an ordinary NA is an error. A numeric opaque vector now takes the length-1 endpoint route instead; a non-numeric one still falls through, since 1L is what base R answers for any other length-1 object. seq() never showed this because seq.default checks is.finite() in R first -- seq.int() goes straight to do_seq. - R_altsxp_format_common() allocated a scratch buffer per padded element inside one vmaxget/vmaxset pair, so a whole pass held O(n * w) bytes that were dead as soon as mkChar() had copied them out. One buffer, sized from the widest rendering measured in the pass that already measures the widest column: formatting 2e6 int64 values went from 84 MB of Vcells to 53 MB, with identical output.
1 parent 9bc6847 commit 4ee6759

3 files changed

Lines changed: 48 additions & 2 deletions

File tree

src/main/altrep.c

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,17 +1097,29 @@ attribute_hidden SEXP R_altsxp_format_common(SEXP fmt, Rboolean trim, int width)
10971097
const char *na = CHAR(R_print.na_string);
10981098
int na_w = R_print.na_width;
10991099
int w = 0;
1100+
/* the widest rendering in bytes, which is not the widest in columns once
1101+
a multibyte encoding is in play */
1102+
size_t maxb = strlen(na);
11001103

11011104
for (R_xlen_t i = 0; i < n; i++) {
11021105
SEXP e = STRING_ELT(fmt, i);
11031106
int wi = (e == NA_STRING) ? na_w : Rstrlen(e, 0);
11041107
if (wi > w) w = wi;
1108+
if (e != NA_STRING) {
1109+
size_t b = strlen(CHAR(e));
1110+
if (b > maxb) maxb = b;
1111+
}
11051112
}
11061113
if (trim) w = 0;
11071114
if (w < width) w = width;
11081115

11091116
SEXP ans = PROTECT(allocVector(STRSXP, n));
11101117
const void *vmax = vmaxget();
1118+
/* One buffer for the whole pass: mkChar() copies out of it immediately,
1119+
so the bytes are dead by the next iteration. Allocating per element
1120+
would hold O(n * w) of them live until the vmaxset() below. */
1121+
size_t bufsz = (size_t) w + maxb + 1;
1122+
char *buf = R_alloc(bufsz, 1);
11111123
for (R_xlen_t i = 0; i < n; i++) {
11121124
SEXP e = STRING_ELT(fmt, i);
11131125
const char *s = (e == NA_STRING) ? na : CHAR(e);
@@ -1116,8 +1128,7 @@ attribute_hidden SEXP R_altsxp_format_common(SEXP fmt, Rboolean trim, int width)
11161128
if (wi >= w)
11171129
SET_STRING_ELT(ans, i, (e == NA_STRING) ? mkChar(s) : e);
11181130
else {
1119-
char *buf = R_alloc((size_t) w + strlen(s) + 1, 1);
1120-
snprintf(buf, (size_t) w + strlen(s) + 1, "%*s%s", w - wi, "", s);
1131+
snprintf(buf, bufsz, "%*s%s", w - wi, "", s);
11211132
SET_STRING_ELT(ans, i, mkChar(buf));
11221133
}
11231134
}

src/main/seq.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,21 @@ attribute_hidden SEXP do_seq(SEXP call, SEXP op, SEXP args, SEXP rho)
998998
ans = altsxp_seq(call, one, from, NULL);
999999
UNPROTECT(1);
10001000
if(ans != NULL) goto done;
1001+
1002+
/* The class declined: an NA endpoint, or no exact arithmetic to
1003+
do it with. A numeric one is still an endpoint, so it takes
1004+
the length-1 route below rather than the "one element, so
1005+
count along it" branch, which would answer 1L for
1006+
seq.int(as.int64(NA)) where seq.int(NA_integer_) is an error.
1007+
A non-numeric opaque vector does fall through, since 1L is
1008+
what base R answers for any other length-1 object. */
1009+
if(isNumeric(from)) {
1010+
double rfrom = asReal(from);
1011+
if (!R_FINITE(rfrom))
1012+
errorcall(call, _("'%s' must be a finite number"), "from");
1013+
ans = seq_colon(1.0, rfrom, call);
1014+
goto done;
1015+
}
10011016
}
10021017
if(lf == 1 && (TYPEOF(from) == INTSXP || TYPEOF(from) == REALSXP)) {
10031018
double rfrom = asReal(from);

tests/altsxp.R

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2396,6 +2396,26 @@ local({
23962396
identical(sort(dec, decreasing = TRUE), dec))
23972397
})
23982398

2399+
## The one-argument seq() over an opaque value asks the class for 1:x, and
2400+
## the class declines for an NA endpoint. Control then fell into the "one
2401+
## element, so count along it" branch and answered 1L, where the same call on
2402+
## an ordinary NA is an error. seq() itself never showed it -- seq.default
2403+
## checks is.finite() in R first -- but seq.int() goes straight to the C.
2404+
local({
2405+
ref <- tryCatch(seq.int(NA_integer_), error = conditionMessage)
2406+
for (v in list(as.int64(NA), as.uint64(NA)))
2407+
stopifnot(identical(tryCatch(seq.int(v), error = conditionMessage), ref),
2408+
identical(tryCatch(seq(v), error = conditionMessage), ref))
2409+
2410+
## the cases that do work are unchanged, and still exact
2411+
stopifnot(identical(as.double(seq.int(as.int64(5))), c(1, 2, 3, 4, 5)),
2412+
identical(as.double(seq.int(as.int64(-3))), c(1, 0, -1, -2, -3)),
2413+
identical(typeof(seq.int(as.int64(5))), "int64"),
2414+
identical(as.double(seq.int(as.int64(0))), c(1, 0)),
2415+
## a length > 1 argument counts along it, as for any vector
2416+
identical(seq.int(as.int64(c(4, 9))), 1:2))
2417+
})
2418+
23992419
## --- generic ALTSXP region-contract regressions ----------------------
24002420

24012421
## Source-tree tests build a tiny pointer-less ALTSXP class whose Get/Set

0 commit comments

Comments
 (0)