From edebd83ea99651a47d4592f2cef12618da3a0736 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 3 Nov 2025 10:46:20 +0100 Subject: [ruby/json] parser.c: Skip checking for escape sequences in `rstring_cache_fetch` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The caller already know if the string contains escape sequences so this check is redundant. Also stop calling `rstring_cache_fetch` from `json_string_unescape` as we know it won't match anyways. ``` == Parsing twitter.json (567916 bytes) ruby 3.4.6 (2025-09-16 revision https://github.com/ruby/json/commit/dbd83256b1) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- after 122.000 i/100ms Calculating ------------------------------------- after 1.226k (± 0.3%) i/s (815.85 μs/i) - 6.222k in 5.076282s Comparison: before: 1206.2 i/s after: 1225.7 i/s - 1.02x faster ``` https://github.com/ruby/json/commit/b8cdf3282d Co-Authored-By: Scott Myron --- ext/json/parser/parser.c | 25 ------------------------- test/json/json_parser_test.rb | 12 ++++++++++++ 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index d9b578f245..68ff6d7ac3 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -122,12 +122,6 @@ static VALUE rstring_cache_fetch(rvalue_cache *cache, const char *str, const lon } } - if (RB_UNLIKELY(memchr(str, '\\', length))) { - // We assume the overwhelming majority of names don't need to be escaped. - // But if they do, we have to fallback to the slow path. - return Qfalse; - } - VALUE rstring = build_interned_string(str, length); if (cache->length < JSON_RVALUE_CACHE_CAPA) { @@ -174,12 +168,6 @@ static VALUE rsymbol_cache_fetch(rvalue_cache *cache, const char *str, const lon } } - if (RB_UNLIKELY(memchr(str, '\\', length))) { - // We assume the overwhelming majority of names don't need to be escaped. - // But if they do, we have to fallback to the slow path. - return Qfalse; - } - VALUE rsymbol = build_symbol(str, length); if (cache->length < JSON_RVALUE_CACHE_CAPA) { @@ -652,19 +640,6 @@ static VALUE json_string_unescape(JSON_ParserState *state, const char *string, c int unescape_len; char buf[4]; - if (is_name && state->in_array) { - VALUE cached_key; - if (RB_UNLIKELY(symbolize)) { - cached_key = rsymbol_cache_fetch(&state->name_cache, string, bufferSize); - } else { - cached_key = rstring_cache_fetch(&state->name_cache, string, bufferSize); - } - - if (RB_LIKELY(cached_key)) { - return cached_key; - } - } - VALUE result = rb_str_buf_new(bufferSize); rb_enc_associate_index(result, utf8_encindex); buffer = RSTRING_PTR(result); diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb index bab16a6fc2..6315c3e667 100644 --- a/test/json/json_parser_test.rb +++ b/test/json/json_parser_test.rb @@ -344,6 +344,18 @@ class JSONParserTest < Test::Unit::TestCase assert_equal orig, parse(json5) end + def test_parse_escaped_key + doc = { + "test\r1" => 1, + "entries" => [ + "test\t2" => 2, + "test\n3" => 3, + ] + } + + assert_equal doc, parse(JSON.generate(doc)) + end + def test_parse_duplicate_key expected = {"a" => 2} expected_sym = {a: 2} -- cgit v1.2.3