jit: ljust/rjust/center truncate when width < content (#772)

Found by the JIT differential fuzzer (random nested softcode, JIT vs
interpreter).

fun_ljust/fun_rjust/fun_center call centerjustcombo(..., bTrunc=true), which
passes 1 to co_ljust/co_rjust/co_center → a too-wide string is truncated to
the width.  The rv64 blob wrappers co_ljust_wrap/co_rjust_wrap/co_center_wrap
passed 0 (no truncation), so the JIT/DBT path returned the full string where
the interpreter truncates:

  ljust(abcdef,3)   interp: abc   JIT (blob): abcdef
  rjust(abcdef,3)   interp: abc   JIT (blob): abcdef
  center(abcdef,3)  interp: abc   JIT (blob): abcdef

(The padding case width > content already agreed, which is why the earlier
flat audit missed this.)

Change the final bTrunc arg from 0 to 1 in all three wrappers and rebuild
the rv64 softlib blob.  This also matches the interpreter's empty-string
returns for width 0 and non-integer width: satoi→0 then truncate-to-0 → "".
There is no host-side fold for these (hir_lower has no LJUST/RJUST/CENTER
constant-fold), so the wrapper is the whole runtime path.

Tests: add TC006 to ljust_fn/rjust_fn/center_fn asserting truncation
(abcdef,3 → abc) plus width-0 and non-integer → "".  Because these
functions are not constant-folded, the JIT-compiled @if condition routes
them through the tier2 blob wrappers, so the tests are genuinely
fail-closed against this bug — verified by building the unfixed blob and
confirming all three TC006 cases fail, then pass after the fix.

Smoke (configured --enable-jit, runtime JIT confirmed active, tier2=872):
1086 / 0 failed / 0 crashes.

Closes #772.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stephen Dennis 2026-06-08 19:45:57 -06:00
parent 26d28db4ad
commit 4fc70fa540
6 changed files with 71 additions and 9 deletions

Binary file not shown.

Binary file not shown.

View file

@ -865,9 +865,12 @@ char *co_ljust_wrap(char *out, const char **fargs, int nfargs) {
fill = (const unsigned char *)fargs[2];
fill_len = rv64_slen(fargs[2]);
}
/* bTrunc=1: fun_ljust truncates when width < content (centerjustcombo
* passes bTrunc=true). satoi0 for width 0 / non-integer then truncates
* to "", matching the interpreter's empty returns. See #772. */
size_t n = co_ljust((unsigned char *)out,
(const unsigned char *)fargs[0], len,
(size_t)width, fill, fill_len, 0);
(size_t)width, fill, fill_len, 1);
out[n] = '\0';
return out;
}
@ -884,9 +887,10 @@ char *co_rjust_wrap(char *out, const char **fargs, int nfargs) {
fill = (const unsigned char *)fargs[2];
fill_len = rv64_slen(fargs[2]);
}
/* bTrunc=1: see co_ljust_wrap above (#772). */
size_t n = co_rjust((unsigned char *)out,
(const unsigned char *)fargs[0], len,
(size_t)width, fill, fill_len, 0);
(size_t)width, fill, fill_len, 1);
out[n] = '\0';
return out;
}
@ -903,9 +907,10 @@ char *co_center_wrap(char *out, const char **fargs, int nfargs) {
fill = (const unsigned char *)fargs[2];
fill_len = rv64_slen(fargs[2]);
}
/* bTrunc=1: see co_ljust_wrap above (#772). */
size_t n = co_center((unsigned char *)out,
(const unsigned char *)fargs[0], len,
(size_t)width, fill, fill_len, 0);
(size_t)width, fill, fill_len, 1);
out[n] = '\0';
return out;
}

View file

@ -94,11 +94,30 @@
strmatch(center(X,7,-+), -+-X-+-)
)=
{
@log smoke=TC005: Vary string and width with '-+' fill. Succeeded.;
@log smoke=TC005: Vary string and width with '-+' fill. Succeeded.
},
{
@log smoke=TC005: Vary string and width with '-+' fill. Failed (c2=[center(X,5,-+)] c3=[center(X,7,-+)]).
}
-
#
# Test Case #6 - Truncation when width < content (#772). fun_center passes
# bTrunc=true, so a too-wide string is truncated; width 0 and non-integer
# width collapse to empty. Previously untested. On the JIT/DBT blob
# backend this also guards co_center_wrap.
#
&tr.tc006 test_center_fn=
@if cand(
strmatch(center(abcdef,3), abc),
eq(strlen(center(abcdef,0)), 0),
eq(strlen(center(abcdef,xyz)), 0)
)=
{
@log smoke=TC006: center truncation. Succeeded.;
@trig me/tr.done
},
{
@log smoke=TC005: Vary string and width with '-+' fill. Failed (c2=[center(X,5,-+)] c3=[center(X,7,-+)]).;
@log smoke=TC006: center truncation. Failed (t=[center(abcdef,3)] w0=[center(abcdef,0)] wx=[center(abcdef,xyz)]).;
@trig me/tr.done
}
-

View file

@ -90,11 +90,30 @@
eq(strlen(ljust(123,11,-+)), 11)
)=
{
@log smoke=TC005: Vary string and width with '-+' fill. Succeeded.;
@log smoke=TC005: Vary string and width with '-+' fill. Succeeded.
},
{
@log smoke=TC005: Vary string and width with '-+' fill. Failed.
}
-
#
# Test Case #6 - Truncation when width < content (#772). fun_ljust passes
# bTrunc=true, so a too-wide string is truncated; width 0 and non-integer
# width collapse to empty. Previously untested. On the JIT/DBT blob
# backend this also guards co_ljust_wrap, which was the divergent path.
#
&tr.tc006 test_ljust_fn=
@if cand(
strmatch(ljust(abcdef,3), abc),
eq(strlen(ljust(abcdef,0)), 0),
eq(strlen(ljust(abcdef,xyz)), 0)
)=
{
@log smoke=TC006: ljust truncation. Succeeded.;
@trig me/tr.done
},
{
@log smoke=TC005: Vary string and width with '-+' fill. Failed.;
@log smoke=TC006: ljust truncation. Failed (t=[ljust(abcdef,3)] w0=[ljust(abcdef,0)] wx=[ljust(abcdef,xyz)]).;
@trig me/tr.done
}
-

View file

@ -90,11 +90,30 @@
eq(strlen(rjust(123,11,-+)), 11)
)=
{
@log smoke=TC005: Vary string and width with '-+' fill. Succeeded.;
@log smoke=TC005: Vary string and width with '-+' fill. Succeeded.
},
{
@log smoke=TC005: Vary string and width with '-+' fill. Failed.
}
-
#
# Test Case #6 - Truncation when width < content (#772). fun_rjust passes
# bTrunc=true, so a too-wide string is truncated (to the leading chars);
# width 0 and non-integer width collapse to empty. Previously untested.
# On the JIT/DBT blob backend this also guards co_rjust_wrap.
#
&tr.tc006 test_rjust_fn=
@if cand(
strmatch(rjust(abcdef,3), abc),
eq(strlen(rjust(abcdef,0)), 0),
eq(strlen(rjust(abcdef,xyz)), 0)
)=
{
@log smoke=TC006: rjust truncation. Succeeded.;
@trig me/tr.done
},
{
@log smoke=TC005: Vary string and width with '-+' fill. Failed.;
@log smoke=TC006: rjust truncation. Failed (t=[rjust(abcdef,3)] w0=[rjust(abcdef,0)] wx=[rjust(abcdef,xyz)]).;
@trig me/tr.done
}
-