A message longer than its buffer is cut at a character and ends in an ellipsis, and the break loop and the unhandled message use the full text

This commit is contained in:
Joseph Ferano 2026-09-25 13:41:58 +07:00
parent 9d6694fd92
commit a4e45d9a78
4 changed files with 101 additions and 22 deletions

View File

@ -106,19 +106,36 @@ typedef struct {
* was cut rather than passing for the whole of it. */
#define FLAN_MESSAGE_MAX 512
/* The most the break loop's sentence takes, and the unhandled message: the
* full text, not a message's cut copy. */
#define FLAN_SENTENCE_MAX 2048
/* How many of [p]'s [len] bytes fit in [max] without splitting a UTF-8
* character: a cut that lands on a continuation byte backs up to the start
* of that character, so a shortened text is still text. */
static int64_t utf8_fit(const uint8_t *p, int64_t len, int64_t max) {
int64_t m;
if (len <= max) return len;
m = max < 0 ? 0 : max;
while (m > 0 && (p[m] & 0xC0) == 0x80) m--;
return m;
}
static const char ellipsis[] = "\xe2\x80\xa6";
/* Where [render] prints: the buffer of the signal asking, set around the one
* synchronous call. A printer only formats, so nothing nests inside it. */
static char *msg_out;
static int64_t msg_cap;
static int64_t msg_len;
static int msg_cut;
static void msg_append(const uint8_t *p, int64_t n) {
static const char ell[] = "\xe2\x80\xa6";
if (msg_out == NULL || msg_cut || n <= 0) return;
if (msg_len + n > FLAN_MESSAGE_MAX - 4) {
int64_t room = FLAN_MESSAGE_MAX - 4 - msg_len;
if (msg_len + n > msg_cap - 4) {
int64_t room = utf8_fit(p, n, msg_cap - 4 - msg_len);
if (room > 0) { memcpy(msg_out + msg_len, p, (size_t)room); msg_len += room; }
memcpy(msg_out + msg_len, ell, 3);
memcpy(msg_out + msg_len, ellipsis, 3);
msg_len += 3;
msg_cut = 1;
return;
@ -132,7 +149,7 @@ void flan_msg_emit(const uint8_t *p, int64_t n) { msg_append(p, n); }
/* The view of [condition] for a parent's handler, its message in [buf],
* which the caller owns and which lives as long as the handler runs. */
static void rt_view(const flan_condesc *d, void *condition, char *buf,
flan_view *v) {
int64_t cap, flan_view *v) {
if (d->flags & FLAN_CONDESC_SELF) {
*v = *(const flan_view *)condition;
return;
@ -141,6 +158,7 @@ static void rt_view(const flan_condesc *d, void *condition, char *buf,
v->namelen = d->namelen;
v->message = (const uint8_t *)buf;
msg_out = buf;
msg_cap = cap;
msg_len = 0;
msg_cut = 0;
if (d->messagelen > 0)
@ -193,7 +211,10 @@ void flan_signal(const flan_condesc *d, void *condition, void *xfer) {
for (flan_handler *h = saved; h != NULL; h = h->prev) {
int how = flan_handles(h->type_id, d);
if (how) {
if (how == 2 && !viewed) { rt_view(d, condition, buf, &v); viewed = 1; }
if (how == 2 && !viewed) {
rt_view(d, condition, buf, FLAN_MESSAGE_MAX, &v);
viewed = 1;
}
handlers = h->prev;
h->fn(how == 1 ? condition : (void *)&v, xfer, h->env);
handlers = saved;
@ -884,14 +905,20 @@ _Noreturn void flan_trap(const uint8_t *name, int64_t namelen) {
* nothing to point at. */
const uint8_t *flan_break_site;
int64_t flan_break_site_len;
char flan_break_sentence[512];
char flan_break_sentence[FLAN_SENTENCE_MAX];
int64_t flan_break_sentence_len;
static void rt_sentencev(const char *fmt, va_list ap) {
int n = vsnprintf(flan_break_sentence, sizeof flan_break_sentence, fmt, ap);
if (n < 0) n = 0;
if (n >= (int)sizeof flan_break_sentence)
n = (int)sizeof flan_break_sentence - 1;
if (n >= (int)sizeof flan_break_sentence) {
/* Cut at a character and said to be cut. */
int64_t m = utf8_fit((const uint8_t *)flan_break_sentence,
(int64_t)sizeof flan_break_sentence - 1,
(int64_t)sizeof flan_break_sentence - 4);
memcpy(flan_break_sentence + m, ellipsis, 3);
n = (int)m + 3;
}
flan_break_sentence_len = n;
}
@ -905,8 +932,13 @@ static void rt_sentence(const char *fmt, ...) {
/* The sentence just formatted, NUL-terminated into a caller's buffer. */
static void rt_sentence_copy(char *out) {
int64_t n = flan_break_sentence_len;
if (n > FLAN_MESSAGE_MAX - 1) n = FLAN_MESSAGE_MAX - 1;
memcpy(out, flan_break_sentence, (size_t)n);
if (n > FLAN_MESSAGE_MAX - 1) {
n = utf8_fit((const uint8_t *)flan_break_sentence, n, FLAN_MESSAGE_MAX - 4);
memcpy(out, flan_break_sentence, (size_t)n);
memcpy(out + n, ellipsis, 3);
n += 3;
} else
memcpy(out, flan_break_sentence, (size_t)n);
out[n] = 0;
}
@ -1032,9 +1064,9 @@ void flan_error(const flan_condesc *d, void *condition, void *xfer) {
/* What the condition is, as a parent's handler would read it: the
* sentence, the printed condition, or an Error-shaped condition's own
* message. A condition with no parent and no sentence has none. */
char buf[FLAN_MESSAGE_MAX];
char buf[FLAN_SENTENCE_MAX];
flan_view v;
rt_view(d, condition, buf, &v);
rt_view(d, condition, buf, FLAN_SENTENCE_MAX, &v);
if (flan_break_hook != NULL) {
flan_break_site = d->loclen > 0 ? d->loc : NULL;
flan_break_site_len = d->loclen;
@ -1197,7 +1229,6 @@ static void rt_condesc(flan_condesc *d, uint32_t chain[2], const uint8_t *name,
* which case the caller returns and its caller's guard carries the transfer
* out. */
static int rt_error_break(const flan_condesc *d, void *condition, void *xfer) {
rt_sentence("%.*s", (int)d->messagelen, (const char *)d->message);
if (flan_break_hook != NULL) {
flan_break_site = d->loc;
flan_break_site_len = d->loclen;
@ -1229,6 +1260,11 @@ static int flan_bounds_signal(const uint8_t *loc, int64_t loclen, void *xfer,
loclen);
flan_signal(&d, &c, xfer);
if (*(void **)xfer != NULL) return 1;
/* Formatted again, in full: [said] may be cut, and a handler the walk ran
* may have written a sentence of its own over the shared one. */
if (kind == BOUNDS_AT) bounds_sentence(low, len);
else if (kind == BOUNDS_SLICE) slice_sentence(low, high, len);
else promise_sentence(high);
return rt_error_break(&d, &c, xfer);
}
@ -1399,6 +1435,7 @@ void flan_arith_error(const uint8_t *loc, int64_t loclen, int32_t op,
loclen);
flan_signal(&d, &c, xfer);
if (*(void **)xfer != NULL) return;
arith_sentence(op, lhs, rhs); /* in full; see flan_bounds_signal */
if (rt_error_break(&d, &c, xfer)) return;
rt_print_sentence(loc, loclen);
rt_die();
@ -1449,6 +1486,16 @@ static flan_slice flan_stale_copy(const char *s) {
return r;
}
static void stale_sentence(const char *callee, const char *want,
const char *now) {
rt_sentence("this call to %s was compiled for %s, and %s is defined as %s. "
"Evaluating the function this call is in again fixes its next "
"call. A function that is still running, such as main's loop, "
"is never called again: define %s with %s again, or run the "
"program again.",
callee, want, callee, now, callee, want);
}
void flan_stale_call(const char *site, const char *callee, const char *want,
void *const *cell, void *xfer) {
/* A registry cell nothing has published into yet has no text. */
@ -1461,17 +1508,13 @@ void flan_stale_call(const char *site, const char *callee, const char *want,
flan_condesc d;
uint32_t chain[2];
char said[FLAN_MESSAGE_MAX];
rt_sentence("this call to %s was compiled for %s, and %s is defined as %s. "
"Evaluating the function this call is in again fixes its next "
"call. A function that is still running, such as main's loop, "
"is never called again: define %s with %s again, or run the "
"program again.",
callee, want, callee, now, callee, want);
stale_sentence(callee, want, now);
rt_sentence_copy(said);
rt_condesc(&d, chain, flan_stale_name, FLAN_STALE_NAMELEN, said, where.ptr,
where.len);
flan_signal(&d, &c, xfer);
if (*(void **)xfer != NULL) return;
stale_sentence(callee, want, now); /* in full; see flan_bounds_signal */
if (rt_error_break(&d, &c, xfer)) return;
rt_print_sentence(where.ptr, where.len);
rt_die();

View File

@ -0,0 +1,14 @@
;;;; A message longer than the buffer that holds it is cut at a character,
;;;; never inside one, and says it was cut. The printed condition below starts
;;;; with an odd number of bytes before 400 two-byte characters, so a cut at a
;;;; fixed byte count lands inside one. The unhandled message at the end is
;;;; printed in full: it is not made from the handler's shortened copy.
(defstruct Wide :parent Error [code i32 why string])
(defstruct Long :parent Error)
(defn main [] i32
(handler-case (do (error (Wide {.code 123 .why "éééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé"})) 0)
[(Error [e] (println (.message e)) 0)])
(error (Long {.name "long" .message "éééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé"}))
0)

View File

@ -2929,6 +2929,24 @@ let () =
"programs/condition-messages.flan" messages_out;
outputs ~x86:true "a parent's message outlives the unwind, --x86"
"programs/condition-messages.flan" messages_out;
(* A message longer than its buffer is cut at a character boundary and
ends in an ellipsis; the unhandled message is the full text. *)
let e n = String.concat "" (List.init n (fun _ -> "\xc3\xa9")) in
let cut = "(Wide {.code 123 .why \"" ^ e 242 ^ "\xe2\x80\xa6\n" in
let full = "unhandled Long: " ^ e 400 ^ "\n" in
List.iter
(fun x86 ->
let exe = compile ~x86 "programs/condition-longmessage.flan" in
let code, text = run exe None in
if code <> 134 || not (contains text cut) || not (contains text full)
then begin
incr failures;
Printf.printf
"FAIL a long message is cut at a character%s\n got: %S (exit %d)\n"
(if x86 then ", --x86" else "") text code
end;
(try Sys.remove exe with Sys_error _ -> ()))
[ false; true ];
(* And the half that finishes that thought. bounds-condition.flan's last
line is `10 99 12 13` — an abandoned frame's leftovers — and a restart

View File

@ -570,7 +570,7 @@ typedef struct {
/* The runtime's sentence about the stop, copied and consumed with the
* site. Empty for a stop whose condition says what it is in its fields. */
int32_t sentencelen;
char sentence[512];
char sentence[2048]; /* FLAN_SENTENCE_MAX */
} snapshot;
/* One per nested break loop, because an inner break must not answer with the
@ -718,7 +718,11 @@ static int snap_push(int resumable, void *cond) {
s->sentencelen = 0;
if (flan_break_sentence_len > 0) {
int64_t k = flan_break_sentence_len;
if (k > (int64_t)sizeof s->sentence) k = (int64_t)sizeof s->sentence;
if (k > (int64_t)sizeof s->sentence) {
/* Never splitting a UTF-8 character. */
k = (int64_t)sizeof s->sentence;
while (k > 0 && ((uint8_t)flan_break_sentence[k] & 0xC0) == 0x80) k--;
}
memcpy(s->sentence, flan_break_sentence, (size_t)k);
/* One line on the wire: a newline in it would end the reply early. */
for (int64_t i = 0; i < k; i++)