diff --git a/runtime/flan_rt.c b/runtime/flan_rt.c index ada55fa..28de468 100644 --- a/runtime/flan_rt.c +++ b/runtime/flan_rt.c @@ -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; } diff --git a/test/programs/println.flan b/test/programs/println.flan index 394c074..2b252c7 100644 --- a/test/programs/println.flan +++ b/test/programs/println.flan @@ -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") diff --git a/test/test_acceptance.ml b/test/test_acceptance.ml index e5fb534..ee57812 100644 --- a/test/test_acceptance.ml +++ b/test/test_acceptance.ml @@ -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"