From f75893a771d8e13e3ab334dff5548fb1effd6526 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Mattias=20Engdeg=C3=A5rd?= Date: Tue, 2 Apr 2024 18:58:09 +0200 Subject: [PATCH] Fix json-insert unibyte buffer bug (bug#70007) Previously, a unibyte target buffer could be put in an incorrect state if json-insert was used to insert non-ASCII characters. * src/json.c (Fjson_insert): Simplify. Don't attempt to decode the data being inserted: it is guaranteed to be correct UTF-8 and is correct for both unibyte and multibyte buffers. * test/src/json-tests.el (json-serialize/roundtrip) (json-serialize/roundtrip-scalars): Extend tests. (cherry picked from commit 617debf67392946b4b42fdf364c69da6f094a840) --- src/json.c | 50 ++++++++++-------------------------------- test/src/json-tests.el | 43 +++++++++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 47 deletions(-) diff --git a/src/json.c b/src/json.c index 0b4414956ee..45dcdde98ea 100644 --- a/src/json.c +++ b/src/json.c @@ -646,8 +646,6 @@ usage: (json-insert OBJECT &rest ARGS) */) json_out_t jo; json_serialize (&jo, args[0], nargs - 1, args + 1); - /* FIXME: All the work below just to insert a string into a buffer? */ - prepare_to_modify_buffer (PT, PT, NULL); move_gap_both (PT, PT_BYTE); if (GAP_SIZE < jo.size) @@ -657,48 +655,22 @@ usage: (json-insert OBJECT &rest ARGS) */) /* No need to keep allocation beyond this point. */ unbind_to (count, Qnil); - ptrdiff_t inserted = 0; + bool ub_buffer = NILP (BVAR (current_buffer, enable_multibyte_characters)); ptrdiff_t inserted_bytes = jo.size; + ptrdiff_t inserted = ub_buffer ? jo.size : jo.size - jo.chars_delta; + eassert (inserted > 0); - /* If required, decode the stuff we've read into the gap. */ - struct coding_system coding; - /* JSON strings are UTF-8 encoded strings. */ - setup_coding_system (Qutf_8_unix, &coding); - coding.dst_multibyte = !NILP (BVAR (current_buffer, - enable_multibyte_characters)); - if (CODING_MAY_REQUIRE_DECODING (&coding)) - { - /* Now we have all the new bytes at the beginning of the gap, - but `decode_coding_gap` needs them at the end of the gap, so - we need to move them. */ - memmove (GAP_END_ADDR - inserted_bytes, GPT_ADDR, inserted_bytes); - decode_coding_gap (&coding, inserted_bytes); - inserted = coding.produced_char; - } - else - { - /* Make the inserted text part of the buffer, as unibyte text. */ - eassert (NILP (BVAR (current_buffer, enable_multibyte_characters))); - insert_from_gap_1 (inserted_bytes, inserted_bytes, false); - - /* The target buffer is unibyte, so we don't need to decode. */ - invalidate_buffer_caches (current_buffer, - PT, PT + inserted_bytes); - adjust_after_insert (PT, PT_BYTE, - PT + inserted_bytes, - PT_BYTE + inserted_bytes, - inserted_bytes); - inserted = inserted_bytes; - } + insert_from_gap_1 (inserted, inserted_bytes, false); + invalidate_buffer_caches (current_buffer, PT, PT + inserted); + adjust_after_insert (PT, PT_BYTE, PT + inserted, PT_BYTE + inserted_bytes, + inserted); /* Call after-change hooks. */ signal_after_change (PT, 0, inserted); - if (inserted > 0) - { - update_compositions (PT, PT, CHECK_BORDER); - /* Move point to after the inserted text. */ - SET_PT_BOTH (PT + inserted, PT_BYTE + inserted_bytes); - } + + update_compositions (PT, PT, CHECK_BORDER); + /* Move point to after the inserted text. */ + SET_PT_BOTH (PT + inserted, PT_BYTE + inserted_bytes); return Qnil; } diff --git a/test/src/json-tests.el b/test/src/json-tests.el index 8b730ef8c90..ebac70fb1c7 100644 --- a/test/src/json-tests.el +++ b/test/src/json-tests.el @@ -27,28 +27,44 @@ (require 'map) (require 'subr-x) -(declare-function json-serialize "json.c" (object &rest args)) -(declare-function json-insert "json.c" (object &rest args)) -(declare-function json-parse-string "json.c" (string &rest args)) -(declare-function json-parse-buffer "json.c" (&rest args)) - (define-error 'json-tests--error "JSON test error") (ert-deftest json-serialize/roundtrip () ;; The noncharacter U+FFFF should be passed through, ;; cf. https://www.unicode.org/faq/private_use.html#noncharacters. - (let ((lisp [:null :false t 0 123 -456 3.75 "abc\uFFFFαβγ𝔸𝐁𝖢\"\\"]) - (json "[null,false,true,0,123,-456,3.75,\"abc\uFFFFαβγ𝔸𝐁𝖢\\\"\\\\\"]")) - (should (equal (json-serialize lisp) json)) + (let* ((lisp [:null :false t 0 123 -456 3.75 "abc\uFFFFαβγ𝔸𝐁𝖢\"\\"]) + (json + "[null,false,true,0,123,-456,3.75,\"abc\uFFFFαβγ𝔸𝐁𝖢\\\"\\\\\"]") + (json-bytes (encode-coding-string json 'utf-8))) + (should (equal (json-serialize lisp) json)) ; or `json-bytes'? (with-temp-buffer + ;; multibyte buffer (json-insert lisp) (should (equal (buffer-string) json)) + (should (equal (point) (1+ (length json)))) + (should (eobp))) + (with-temp-buffer + ;; unibyte buffer + (set-buffer-multibyte nil) + (json-insert lisp) + (should (equal (buffer-string) json-bytes)) + (should (equal (point) (1+ (length json-bytes)))) (should (eobp))) (should (equal (json-parse-string json) lisp)) (with-temp-buffer + ;; multibyte buffer (insert json) (goto-char 1) (should (equal (json-parse-buffer) lisp)) + (should (equal (point) (1+ (length json)))) + (should (eobp))) + (with-temp-buffer + ;; unibyte buffer + (set-buffer-multibyte nil) + (insert json-bytes) + (goto-char 1) + (should (equal (json-parse-buffer) lisp)) + (should (equal (point) (1+ (length json-bytes)))) (should (eobp))))) (ert-deftest json-serialize/roundtrip-scalars () @@ -71,11 +87,22 @@ (json-insert lisp) (should (equal (buffer-string) json)) (should (eobp))) + (with-temp-buffer + (set-buffer-multibyte nil) + (json-insert lisp) + (should (equal (buffer-string) (encode-coding-string json 'utf-8))) + (should (eobp))) (should (equal (json-parse-string json) lisp)) (with-temp-buffer (insert json) (goto-char 1) (should (equal (json-parse-buffer) lisp)) + (should (eobp))) + (with-temp-buffer + (set-buffer-multibyte nil) + (insert (encode-coding-string json 'utf-8)) + (goto-char 1) + (should (equal (json-parse-buffer) lisp)) (should (eobp))))))) (ert-deftest json-serialize/object () -- 2.39.5