Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
### Unreleased

* Add `JSON::ParserError#json_path` to locate parse errors in the document as a JSONPath-style string (e.g. `$.foo[0].bar`). For duplicate key errors it points at the duplicated key itself.
* Fix the parser to also reject lone trailing UTF-16 surrogates (`\uDCxx` with no leading partner), symmetric to the leading-surrogate case. The Java parser already rejected these; this closes the CRuby/JRuby parity gap.

### 2026-08-11 (3.0.0.rc1)

Expand Down
3 changes: 3 additions & 0 deletions ext/json/ext/parser/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,9 @@ NOINLINE(static) VALUE json_string_unescape(JSON_ParserState *state, JSON_Parser
raise_syntax_error_at("incomplete surrogate pair at %s", state, p);
break;
}
} else if ((ch & 0xFC00) == 0xDC00) {
raise_syntax_error_at("unpaired trailing surrogate at %s", state, p);
break;
}

int unescape_len = convert_UTF32_to_UTF8(buffer, ch);
Expand Down
6 changes: 3 additions & 3 deletions test/json/json_minefield_parser_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,9 @@ def define_test(name, &block)
i_string_overlong_sequence_2_bytes
i_string_not_in_unicode_range
i_string_lone_utf8_continuation_byte
i_string_lone_second_surrogate
i_string_iso_latin_1
i_string_invalid_utf-8
i_string_incomplete_surrogate_pair
i_string_UTF-8_invalid_sequence
i_object_key_lone_2nd_surrogate
)

COMMENT_TESTS = %w(
Expand Down Expand Up @@ -56,6 +53,9 @@ def define_test(name, &block)
i_string_utf16BE_no_BOM
i_string_utf16LE_no_BOM
i_structure_UTF-8_BOM_empty_object
i_string_lone_second_surrogate
i_string_incomplete_surrogate_pair
i_object_key_lone_2nd_surrogate
)

if RUBY_ENGINE == 'jruby'
Expand Down
9 changes: 9 additions & 0 deletions test/json/json_parser_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,15 @@ def test_invalid_surogates
assert_raise(JSON::ParserError) { parse('"\\uD800_________________"') }
assert_raise(JSON::ParserError) { parse('"\\uD800\\u0041"') }
assert_raise(JSON::ParserError) { parse('"\\uD800\\u004') }
# Lone trailing surrogate (issue #1069): parser previously returned an
# invalid-UTF-8 String instead of raising. Symmetric to the leading cases
# above.
assert_raise(JSON::ParserError) { parse('"\\uDC00"') }
assert_raise(JSON::ParserError) { parse('"\\uDC00_________________"') }
assert_raise(JSON::ParserError) { parse('"\\uDC00\\uD800"') }
# Valid pair still parses to the astral codepoint U+10000.
assert_predicate JSON.parse('"\\uD800\\uDC00"'), :valid_encoding?
assert_equal "\u{10000}", JSON.parse('"\\uD800\\uDC00"')
end

def test_parse_big_integers
Expand Down
Loading