From 8e8e327870b23bcf263e0210472e6ee87d19c424 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 25 Oct 2025 10:31:04 +0200 Subject: [PATCH 1/5] [ruby/json] Fix concurrent usage of JSON::Coder#dump Fix: https://github.com/rails/rails/commit/90616277e3d8fc46c9cf35d6a7470ff1ea0092f7#r168784389 Because the `depth` counter is inside `JSON::State` it can't be used concurrently, and in case of a circular reference the counter may be left at the max value. The depth counter should be moved outside `JSON_Generator_State` and into `struct generate_json_data`, but it's a larger refactor. In the meantime, `JSON::Coder` calls `State#generate_new` so I changed that method so that it first copy the state on the stack. https://github.com/ruby/json/commit/aefa671eca --- ext/json/generator/generator.c | 39 +++++++++++++++++++++++++++++----- test/json/json_coder_test.rb | 10 +++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index 6a38cc60a7964f..f3f27b29d58ecd 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -1124,7 +1124,7 @@ static inline long increase_depth(struct generate_json_data *data) JSON_Generator_State *state = data->state; long depth = ++state->depth; if (RB_UNLIKELY(depth > state->max_nesting && state->max_nesting)) { - rb_raise(eNestingError, "nesting of %ld is too deep", --state->depth); + rb_raise(eNestingError, "nesting of %ld is too deep. Did you try to serialize objects with circular references?", --state->depth); } return depth; } @@ -1491,10 +1491,39 @@ static VALUE cState_generate(int argc, VALUE *argv, VALUE self) rb_check_arity(argc, 1, 2); VALUE obj = argv[0]; VALUE io = argc > 1 ? argv[1] : Qnil; - VALUE result = cState_partial_generate(self, obj, generate_json, io); + return cState_partial_generate(self, obj, generate_json, io); +} + +static VALUE cState_generate_new(int argc, VALUE *argv, VALUE self) +{ + rb_check_arity(argc, 1, 2); + VALUE obj = argv[0]; + VALUE io = argc > 1 ? argv[1] : Qnil; + GET_STATE(self); - (void)state; - return result; + + JSON_Generator_State new_state; + MEMCPY(&new_state, state, JSON_Generator_State, 1); + + // FIXME: depth shouldn't be part of JSON_Generator_State, as that prevents it from being used concurrently. + new_state.depth = 0; + + char stack_buffer[FBUFFER_STACK_SIZE]; + FBuffer buffer = { + .io = RTEST(io) ? io : Qfalse, + }; + fbuffer_stack_init(&buffer, state->buffer_initial_length, stack_buffer, FBUFFER_STACK_SIZE); + + struct generate_json_data data = { + .buffer = &buffer, + .vstate = Qfalse, + .state = &new_state, + .obj = obj, + .func = generate_json + }; + rb_rescue(generate_json_try, (VALUE)&data, generate_json_rescue, (VALUE)&data); + + return fbuffer_finalize(&buffer); } static VALUE cState_initialize(int argc, VALUE *argv, VALUE self) @@ -2072,7 +2101,7 @@ void Init_generator(void) rb_define_method(cState, "buffer_initial_length", cState_buffer_initial_length, 0); rb_define_method(cState, "buffer_initial_length=", cState_buffer_initial_length_set, 1); rb_define_method(cState, "generate", cState_generate, -1); - rb_define_alias(cState, "generate_new", "generate"); // :nodoc: + rb_define_method(cState, "generate_new", cState_generate_new, -1); // :nodoc: rb_define_private_method(cState, "allow_duplicate_key?", cState_allow_duplicate_key_p, 0); diff --git a/test/json/json_coder_test.rb b/test/json/json_coder_test.rb index c7248353769969..fb9d7b30a59a11 100755 --- a/test/json/json_coder_test.rb +++ b/test/json/json_coder_test.rb @@ -66,4 +66,14 @@ def test_json_coder_dump_NaN_or_Infinity_loop end assert_include error.message, "NaN not allowed in JSON" end + + def test_nesting_recovery + coder = JSON::Coder.new + ary = [] + ary << ary + assert_raise JSON::NestingError do + coder.dump(ary) + end + assert_equal '{"a":1}', coder.dump({ a: 1 }) + end end From 226caf1a1f3542d9e39e1a72fb43fd4486540cc4 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 25 Oct 2025 10:59:15 +0200 Subject: [PATCH 2/5] [ruby/json] Release 2.15.2 https://github.com/ruby/json/commit/5e61cd7dce --- ext/json/lib/json/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/json/lib/json/version.rb b/ext/json/lib/json/version.rb index be5daf4c5b6902..dc01ba290b8cc2 100644 --- a/ext/json/lib/json/version.rb +++ b/ext/json/lib/json/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module JSON - VERSION = '2.15.1' + VERSION = '2.15.2' end From 10f0abeef1992f8708e3d9b2ecad54db8de7c2a3 Mon Sep 17 00:00:00 2001 From: git Date: Sat, 25 Oct 2025 09:01:37 +0000 Subject: [PATCH 3/5] Update default gems list at 226caf1a1f3542d9e39e1a72fb43fd [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 4939962a4049c9..f847fe6817821d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -188,7 +188,7 @@ The following default gems are updated. * io-console 0.8.1 * io-nonblock 0.3.2 * io-wait 0.3.2 -* json 2.15.1 +* json 2.15.2 * openssl 4.0.0.pre * optparse 0.7.0.dev.2 * pp 0.6.3 From 31e14ac7dadc99851fefbb5d5d4232ba9f568f1b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 25 Oct 2025 14:37:39 +0900 Subject: [PATCH 4/5] [DOC] Follow up GH-14470 `IS_TYPED_DATA` is no longer a flag in `type`, and the "embedded" flag has been shifted accordingly. ruby/ruby#14470 --- include/ruby/internal/core/rtypeddata.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/ruby/internal/core/rtypeddata.h b/include/ruby/internal/core/rtypeddata.h index 8e16c31d998080..bf0f60b9132029 100644 --- a/include/ruby/internal/core/rtypeddata.h +++ b/include/ruby/internal/core/rtypeddata.h @@ -360,8 +360,7 @@ struct RTypedData { /** * This is a `const rb_data_type_t *const` value, with the low bits set: * - * 1: Always set, to differentiate RTypedData from RData. - * 2: Set if object is embedded. + * 1: Set if object is embedded. * * This field stores various information about how Ruby should handle a * data. This roughly resembles a Ruby level class (apart from method From 377aa2a336cc700485c699ac49330f2a58b74906 Mon Sep 17 00:00:00 2001 From: tompng Date: Tue, 9 Sep 2025 21:21:22 +0900 Subject: [PATCH 5/5] Improve performance of UnicodeNormalize.canonical_ordering_one Use array_of_integer.sort! instead of buble-sort-like algorithm --- lib/unicode_normalize/normalize.rb | 22 ++++++++++++++-------- test/test_unicode_normalize.rb | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/lib/unicode_normalize/normalize.rb b/lib/unicode_normalize/normalize.rb index e67fad187a5fb1..7872f8a0bcffdd 100644 --- a/lib/unicode_normalize/normalize.rb +++ b/lib/unicode_normalize/normalize.rb @@ -82,16 +82,22 @@ def self.hangul_comp_one(string) ## Canonical Ordering def self.canonical_ordering_one(string) - sorting = string.each_char.collect { |c| [c, CLASS_TABLE[c]] } - (sorting.length-2).downto(0) do |i| # almost, but not exactly bubble sort - (0..i).each do |j| - later_class = sorting[j+1].last - if 0