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:
parent
9d6694fd92
commit
a4e45d9a78
@ -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();
|
||||
|
||||
14
test/programs/condition-longmessage.flan
Normal file
14
test/programs/condition-longmessage.flan
Normal 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)
|
||||
@ -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
|
||||
|
||||
8
vendor/agent/flan_agent.c
vendored
8
vendor/agent/flan_agent.c
vendored
@ -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++)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user