summaryrefslogtreecommitdiff
path: root/ext/json
diff options
context:
space:
mode:
authorJean Boussier <jean.boussier@gmail.com>2025-10-25 10:31:04 +0200
committergit <svn-admin@ruby-lang.org>2025-10-25 08:57:48 +0000
commit8e8e327870b23bcf263e0210472e6ee87d19c424 (patch)
tree941363da38f9b4c7c6a24058151992990bb02873 /ext/json
parentcb302881629f997f403e705425f69e5f6b0741ac (diff)
[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
Diffstat (limited to 'ext/json')
-rw-r--r--ext/json/generator/generator.c39
1 files changed, 34 insertions, 5 deletions
diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c
index 6a38cc60a7..f3f27b29d5 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);