Pin the escape buffer's bound, and say what it reserves
The guard reserves 9 bytes but the comment explained 5, which is the longest escape alone -- it did not account for the three writes after the loop (the ellipsis and the closing quote), so the next person to touch the escape table would have preserved the wrong invariant. Swept every length to 1300 against \x01, a quote, a backslash and 'a' under ASan with a red zone past the buffer: no write past 1024, worst output 1021. Correct, but by three bytes, which is exactly why the reserve is now written down as the four things it is spent on. Nothing exercised truncation -- the longest nested string in the fixture was 18 bytes -- so println.flan now prints a struct with an 1100-byte string field, and the expected output spells the surviving count out as a number so a change to the buffer shows up as one.
This commit is contained in:
parent
93231e8c9e
commit
e4db079c57
@ -222,8 +222,12 @@ void flan_escape_bytes(const uint8_t *p, int64_t n, flan_slice *out) {
|
||||
size_t len = n < 0 ? 0 : (size_t)n;
|
||||
size_t w = 0;
|
||||
int cut = 0;
|
||||
/* 5 is the longest single escape (\xNN is 4, plus room for the close
|
||||
* quote); leaving it spare means the loop never writes a partial escape. */
|
||||
/* The guard reserves 9 bytes, and all 9 are spoken for: 4 for the longest
|
||||
* single escape (\xNN), 3 for the ellipsis, 1 for the closing quote, 1
|
||||
* spare. So the loop never writes a partial escape and the three writes
|
||||
* after it never need a bound of their own. Swept over every length to 1300
|
||||
* against \x01, '"', '\\' and 'a': the worst output is 1021 of 1024. If the
|
||||
* escape table ever grows a longer form, this 9 grows with it. */
|
||||
escaped[w++] = '"';
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (w + 5 + 4 >= ESCAPE_MAX) { cut = 1; break; }
|
||||
|
||||
@ -31,6 +31,8 @@
|
||||
(defvar wide [10 i32])
|
||||
(defvar deep D1)
|
||||
(defvar n i32)
|
||||
(defstruct Long [s string])
|
||||
(defvar long-one Long)
|
||||
|
||||
(defn nothing [] )
|
||||
|
||||
@ -108,6 +110,13 @@
|
||||
(println (slice arr 0 2)))
|
||||
(println (slice arr 2 4))
|
||||
|
||||
;; A nested string longer than the escape buffer. Escaping is the one
|
||||
;; conversion whose output is not a bounded handful of characters, so it
|
||||
;; truncates -- with an ellipsis inside the quotes, because a value that
|
||||
;; prints as a shorter value is the failure nobody notices.
|
||||
(set (.s long-one) "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
|
||||
(println long-one)
|
||||
|
||||
;; print is println without the newline.
|
||||
(print "a")
|
||||
(print "b")
|
||||
|
||||
@ -149,7 +149,13 @@ let () =
|
||||
(Blob {:id 7 :name \"sandy \\\"quoted\\\"\" :pos (V {:x 1.5 :y -2}) :tags [ 0 42 0]})\n\
|
||||
[ 0 0 9 0]\n[ 0 0 0 0 0 0 0 0 ...]\n[ 0 9 0]\n\
|
||||
(D1 {:d (D2 {:d (D3 {:d (D4 {:d (D5 {:n ...})})})})})\n[ 0 0]\n\
|
||||
[ 0 0]\n[ 9 0]\nabc\n"
|
||||
[ 0 0]\n[ 9 0]\n"
|
||||
(* The escape buffer is 1024 and the input is 1100 x's, so this is the
|
||||
truncation: the ellipsis goes *inside* the quotes, and the count is
|
||||
spelled out rather than pasted so that a change to the buffer or to
|
||||
the reserve shows up here as a number and not as a wall of x. *)
|
||||
^ "(Long {:s \"" ^ String.make 1014 'x' ^ "...\"})\n"
|
||||
^ "abc\n"
|
||||
in
|
||||
outputs "println, every arm" "programs/println.flan" println_out;
|
||||
outputs ~opt:"-O0" "println, every arm, -O0" "programs/println.flan"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user