diff --git a/src/jv_print.c b/src/jv_print.c index 94924203..568656dc 100644 --- a/src/jv_print.c +++ b/src/jv_print.c @@ -126,7 +126,18 @@ static void jvp_dump_string(jv str, int ascii_only, FILE* F, jv* S, int T) { int c = 0; char buf[32]; put_char('"', F, S, T); - while ((i = jvp_utf8_next((cstart = i), end, &c))) { + while (i < end) { + // Write each run of bytes that need no escaping with a single call. + for (cstart = i; cstart < end; cstart++) { + unsigned char b = *cstart; + if (b < 0x20 || b == '"' || b == '\\' || b == 0x7F || (ascii_only && b >= 0x80)) + break; + } + if (cstart > i) + put_buf(i, cstart - i, F, S, T); + if (cstart == end) + break; + i = jvp_utf8_next(cstart, end, &c); assert(c != -1); int unicode_escape = 0; if (0x20 <= c && c <= 0x7E) { diff --git a/tests/jq.test b/tests/jq.test index 9f2ab525..183950d4 100644 --- a/tests/jq.test +++ b/tests/jq.test @@ -100,6 +100,15 @@ null ["foo", 1, ["a", 1, "b", 2, {"foo":"bar"}]] ["foo",1,["a",1,"b",2,{"foo":"bar"}]] +# Put escapes on both sides of runs of bytes that need no escaping +map(tojson) +["abcdefg\"", "abcdefgh\\", "abcdefghi\u0001j", "abcdefghijklmno\u007f", "abcdefgh\té", "abcdefghijklmnop\n"] +["\"abcdefg\\\"\"","\"abcdefgh\\\\\"","\"abcdefghi\\u0001j\"","\"abcdefghijklmno\\u007f\"","\"abcdefgh\\té\"","\"abcdefghijklmnop\\n\""] + +[range(18) as $i | ("\"", "\\", "\u0001", "\u007f", "é", "😀") as $c | "abcdefghijklmnopq"[:$i] + $c + "abcdefghijklmnopq"[$i:] | (tojson | fromjson) == .] | all +null +true + # # Dictionary construction syntax # diff --git a/tests/utf8test b/tests/utf8test index 37d8043f..e9512210 100755 --- a/tests/utf8test +++ b/tests/utf8test @@ -21,4 +21,18 @@ for l in 1 2 3 4; do fi done +# --ascii-output escapes non-ASCII characters on both sides of runs of +# bytes that need no escaping +$VALGRIND $Q $JQ -cn '[range(18) as $i | ("é", "😀", "\u007f") as $c | + "abcdefghijklmnopq"[:$i] + $c + "abcdefghijklmnopq"[$i:]]' > $d/input +$VALGRIND $Q $JQ -ac . $d/input > $d/out +if LC_ALL=C grep -q '[^ -~]' $d/out; then + echo "--ascii-output printed a non-ASCII byte" + exit 1 +fi +if ! $VALGRIND $Q $JQ -e --slurpfile a $d/input '. == $a[0]' $d/out > /dev/null; then + echo "--ascii-output changed a string" + exit 1 +fi + exit 0