[ruby/json] Ensure parser error snippets are valid UTF-8

Fix: https://github.com/ruby/json/issues/755

Error messages now include a snippet of the document
that doesn't parse to help locate the issue, however
the way it was done wasn't UTF-8 aware, and it could
result in exception messages with truncated characters.

It would be nice to go a bit farther and actually support
codepoints, but it's a lot of complexity to do it in C,
perhaps if we move that logic to Ruby given it's not a
performance sensitive codepath.

https://github.com/ruby/json/commit/e144793b72
This commit is contained in:
Jean Boussier 2025-02-25 17:12:26 +01:00 committed by Hiroshi SHIBATA
parent 75f07afd18
commit 0d62037fc0
No known key found for this signature in database
GPG Key ID: F9CF13417264FAC2
2 changed files with 28 additions and 3 deletions

View File

@ -454,15 +454,24 @@ RBIMPL_ATTR_NORETURN()
#endif
static void raise_parse_error(const char *format, const char *start)
{
char buffer[PARSE_ERROR_FRAGMENT_LEN + 1];
unsigned char buffer[PARSE_ERROR_FRAGMENT_LEN + 1];
size_t len = start ? strnlen(start, PARSE_ERROR_FRAGMENT_LEN) : 0;
const char *ptr = start;
if (len == PARSE_ERROR_FRAGMENT_LEN) {
MEMCPY(buffer, start, char, PARSE_ERROR_FRAGMENT_LEN);
buffer[PARSE_ERROR_FRAGMENT_LEN] = '\0';
ptr = buffer;
while (buffer[len - 1] >= 0x80 && buffer[len - 1] < 0xC0) { // Is continuation byte
len--;
}
if (buffer[len - 1] >= 0xC0) { // multibyte character start
len--;
}
buffer[len] = '\0';
ptr = (const char *)buffer;
}
rb_enc_raise(enc_utf8, rb_path2class("JSON::ParserError"), format, ptr);

View File

@ -645,6 +645,22 @@ class JSONParserTest < Test::Unit::TestCase
end
end
def test_parse_error_snippet
omit "C ext only test" unless RUBY_ENGINE == "ruby"
error = assert_raise(JSON::ParserError) { JSON.parse("あああああああああああああああああああああああ") }
assert_equal "unexpected character: 'ああああああああああ'", error.message
error = assert_raise(JSON::ParserError) { JSON.parse("aあああああああああああああああああああああああ") }
assert_equal "unexpected character: 'aああああああああああ'", error.message
error = assert_raise(JSON::ParserError) { JSON.parse("abあああああああああああああああああああああああ") }
assert_equal "unexpected character: 'abあああああああああ'", error.message
error = assert_raise(JSON::ParserError) { JSON.parse("abcあああああああああああああああああああああああ") }
assert_equal "unexpected character: 'abcあああああああああ'", error.message
end
def test_parse_leading_slash
# ref: https://github.com/ruby/ruby/pull/12598
assert_raise(JSON::ParserError) do