Skip to content

Commit ccca602

Browse files
etiennebarriebyroot
authored andcommitted
Fix handling of depth
1 parent 7b62fac commit ccca602

File tree

7 files changed

+124
-87
lines changed

7 files changed

+124
-87
lines changed

CHANGES.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
* Improve `JSON.load` and `JSON.unsafe_load` to allow passing options as second argument.
66
* Fix the parser to no longer ignore invalid escapes in strings.
77
Only `\"`, `\\`, `\b`, `\f`, `\n`, `\r`, `\t` and `\u` are valid JSON escapes.
8+
* Fixed `JSON::Coder` to use the depth it was initialized with.
89
* On TruffleRuby, fix the generator to not call `to_json` on the return value of `as_json` for `Float::NAN`.
10+
* Fixed handling of `state.depth`: when `to_json` changes `state.depth` but does not restore it, it is reset
11+
automatically to its initial value.
12+
In particular, when a `NestingError` is raised, `depth` is no longer equal to `max_nesting` after the call to
13+
generate, and is reset to its initial value. Similarly when `to_json` raises an exception.
914

1015
### 2025-11-07 (2.16.0)
1116

ext/json/ext/generator/generator.c

Lines changed: 7 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -968,14 +968,16 @@ static void vstate_spill(struct generate_json_data *data)
968968
RB_OBJ_WRITTEN(vstate, Qundef, state->as_json);
969969
}
970970

971-
static inline VALUE vstate_get(struct generate_json_data *data)
971+
static inline VALUE json_call_to_json(struct generate_json_data *data, VALUE obj)
972972
{
973973
if (RB_UNLIKELY(!data->vstate)) {
974974
vstate_spill(data);
975975
}
976976
GET_STATE(data->vstate);
977977
state->depth = data->depth;
978-
return data->vstate;
978+
VALUE tmp = rb_funcall(obj, i_to_json, 1, data->vstate);
979+
// no need to restore state->depth, vstate is just a temporary State
980+
return tmp;
979981
}
980982

981983
static VALUE
@@ -1293,9 +1295,7 @@ static void generate_json_fallback(FBuffer *buffer, struct generate_json_data *d
12931295
{
12941296
VALUE tmp;
12951297
if (rb_respond_to(obj, i_to_json)) {
1296-
tmp = rb_funcall(obj, i_to_json, 1, vstate_get(data));
1297-
GET_STATE(data->vstate);
1298-
data->depth = state->depth;
1298+
tmp = json_call_to_json(data, obj);
12991299
Check_Type(tmp, T_STRING);
13001300
fbuffer_append_str(buffer, tmp);
13011301
} else {
@@ -1477,16 +1477,6 @@ static VALUE generate_json_try(VALUE d)
14771477
return fbuffer_finalize(data->buffer);
14781478
}
14791479

1480-
// Preserves the deprecated behavior of State#depth being set.
1481-
static VALUE generate_json_ensure_deprecated(VALUE d)
1482-
{
1483-
struct generate_json_data *data = (struct generate_json_data *)d;
1484-
fbuffer_free(data->buffer);
1485-
data->state->depth = data->depth;
1486-
1487-
return Qundef;
1488-
}
1489-
14901480
static VALUE generate_json_ensure(VALUE d)
14911481
{
14921482
struct generate_json_data *data = (struct generate_json_data *)d;
@@ -1507,13 +1497,13 @@ static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func,
15071497

15081498
struct generate_json_data data = {
15091499
.buffer = &buffer,
1510-
.vstate = self,
1500+
.vstate = Qfalse, // don't use self as it may be frozen and its depth is mutated when calling to_json
15111501
.state = state,
15121502
.depth = state->depth,
15131503
.obj = obj,
15141504
.func = func
15151505
};
1516-
return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure_deprecated, (VALUE)&data);
1506+
return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data);
15171507
}
15181508

15191509
/* call-seq:
@@ -1532,31 +1522,6 @@ static VALUE cState_generate(int argc, VALUE *argv, VALUE self)
15321522
return cState_partial_generate(self, obj, generate_json, io);
15331523
}
15341524

1535-
static VALUE cState_generate_new(int argc, VALUE *argv, VALUE self)
1536-
{
1537-
rb_check_arity(argc, 1, 2);
1538-
VALUE obj = argv[0];
1539-
VALUE io = argc > 1 ? argv[1] : Qnil;
1540-
1541-
GET_STATE(self);
1542-
1543-
char stack_buffer[FBUFFER_STACK_SIZE];
1544-
FBuffer buffer = {
1545-
.io = RTEST(io) ? io : Qfalse,
1546-
};
1547-
fbuffer_stack_init(&buffer, state->buffer_initial_length, stack_buffer, FBUFFER_STACK_SIZE);
1548-
1549-
struct generate_json_data data = {
1550-
.buffer = &buffer,
1551-
.vstate = Qfalse,
1552-
.state = state,
1553-
.depth = state->depth,
1554-
.obj = obj,
1555-
.func = generate_json
1556-
};
1557-
return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data);
1558-
}
1559-
15601525
static VALUE cState_initialize(int argc, VALUE *argv, VALUE self)
15611526
{
15621527
rb_warn("The json gem extension was loaded with the stdlib ruby code. You should upgrade rubygems with `gem update --system`");
@@ -2145,7 +2110,6 @@ void Init_generator(void)
21452110
rb_define_method(cState, "buffer_initial_length", cState_buffer_initial_length, 0);
21462111
rb_define_method(cState, "buffer_initial_length=", cState_buffer_initial_length_set, 1);
21472112
rb_define_method(cState, "generate", cState_generate, -1);
2148-
rb_define_method(cState, "generate_new", cState_generate_new, -1); // :nodoc:
21492113

21502114
rb_define_private_method(cState, "allow_duplicate_key?", cState_allow_duplicate_key_p, 0);
21512115

java/src/json/ext/Generator.java

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,13 @@ static <T extends IRubyObject> RubyString generateJson(ThreadContext context, T
5454

5555
static <T extends IRubyObject> RubyString generateJson(ThreadContext context, T object, Handler<? super T> handler, IRubyObject arg0) {
5656
Session session = new Session(arg0);
57-
return handler.generateNew(context, session, object);
57+
GeneratorState state = session.getState(context);
58+
int depth = state.depth;
59+
try {
60+
return handler.generateNew(context, session, object);
61+
} finally {
62+
state.depth = depth;
63+
}
5864
}
5965

6066
/**
@@ -79,18 +85,25 @@ static <T extends IRubyObject> RubyString generateJson(ThreadContext context, T
7985
generateJson(ThreadContext context, T object,
8086
GeneratorState config, IRubyObject io) {
8187
Session session = new Session(config);
82-
Handler<? super T> handler = getHandlerFor(context.runtime, object);
88+
GeneratorState state = session.getState(context);
89+
int depth = state.depth;
8390

84-
if (io.isNil()) {
85-
return handler.generateNew(context, session, object);
86-
}
91+
try {
92+
Handler<? super T> handler = getHandlerFor(context.runtime, object);
93+
94+
if (io.isNil()) {
95+
return handler.generateNew(context, session, object);
96+
}
8797

88-
BufferedOutputStream buffer =
89-
new BufferedOutputStream(
90-
new PatchedIOOutputStream(io, UTF8Encoding.INSTANCE),
91-
IO_BUFFER_SIZE);
92-
handler.generateToBuffer(context, session, object, buffer);
93-
return io;
98+
BufferedOutputStream buffer =
99+
new BufferedOutputStream(
100+
new PatchedIOOutputStream(io, UTF8Encoding.INSTANCE),
101+
IO_BUFFER_SIZE);
102+
handler.generateToBuffer(context, session, object, buffer);
103+
return io;
104+
} finally {
105+
state.depth = depth;
106+
}
94107
}
95108

96109
/**
@@ -784,7 +797,13 @@ static RubyString generateGenericNew(ThreadContext context, Session session, IRu
784797
}
785798
throw Utils.buildGeneratorError(context, object, object + " not allowed in JSON").toThrowable();
786799
} else if (object.respondsTo("to_json")) {
787-
IRubyObject result = object.callMethod(context, "to_json", state);
800+
int depth = state.depth;
801+
IRubyObject result;
802+
try {
803+
result = object.callMethod(context, "to_json", state);
804+
} finally {
805+
state.depth = depth;
806+
}
788807
if (result instanceof RubyString) return (RubyString)result;
789808
throw context.runtime.newTypeError("to_json must return a String");
790809
} else {

java/src/json/ext/GeneratorState.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -256,18 +256,6 @@ public IRubyObject generate(ThreadContext context, IRubyObject obj) {
256256
return generate(context, obj, context.nil);
257257
}
258258

259-
@JRubyMethod
260-
public IRubyObject generate_new(ThreadContext context, IRubyObject obj, IRubyObject io) {
261-
GeneratorState newState = (GeneratorState)dup();
262-
return newState.generate(context, obj, io);
263-
}
264-
265-
@JRubyMethod
266-
public IRubyObject generate_new(ThreadContext context, IRubyObject obj) {
267-
GeneratorState newState = (GeneratorState)dup();
268-
return newState.generate(context, obj, context.nil);
269-
}
270-
271259
@JRubyMethod(name="[]")
272260
public IRubyObject op_aref(ThreadContext context, IRubyObject vName) {
273261
String name = vName.asJavaString();

lib/json/common.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1074,7 +1074,7 @@ def initialize(options = nil, &as_json)
10741074
#
10751075
# Serialize the given object into a \JSON document.
10761076
def dump(object, io = nil)
1077-
@state.generate_new(object, io)
1077+
@state.generate(object, io)
10781078
end
10791079
alias_method :generate, :dump
10801080

lib/json/truffle_ruby/generator.rb

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,9 @@ def to_h
330330
# created this method raises a
331331
# GeneratorError exception.
332332
def generate(obj, anIO = nil)
333+
return dup.generate(obj, anIO) if frozen?
334+
335+
depth = @depth
333336
if @indent.empty? and @space.empty? and @space_before.empty? and @object_nl.empty? and @array_nl.empty? and
334337
!@ascii_only and !@script_safe and @max_nesting == 0 and (!@strict || Symbol === obj)
335338
result = generate_json(obj, ''.dup)
@@ -346,10 +349,8 @@ def generate(obj, anIO = nil)
346349
else
347350
result
348351
end
349-
end
350-
351-
def generate_new(obj, anIO = nil) # :nodoc:
352-
dup.generate(obj, anIO)
352+
ensure
353+
@depth = depth unless frozen?
353354
end
354355

355356
# Handles @allow_nan, @buffer_initial_length, other ivars must be the default value (see above)
@@ -490,8 +491,11 @@ module Hash
490491
# _depth_ is used to find out nesting depth, to indent accordingly.
491492
def to_json(state = nil, *)
492493
state = State.from_state(state)
494+
depth = state.depth
493495
state.check_max_nesting
494496
json_transform(state)
497+
ensure
498+
state.depth = depth
495499
end
496500

497501
private
@@ -555,17 +559,19 @@ def json_transform(state)
555559
raise GeneratorError.new("#{value.class} returned by #{state.as_json} not allowed in JSON", value)
556560
end
557561
result << value.to_json(state)
562+
state.depth = depth
558563
else
559564
raise GeneratorError.new("#{value.class} not allowed in JSON", value)
560565
end
561566
elsif value.respond_to?(:to_json)
562567
result << value.to_json(state)
568+
state.depth = depth
563569
else
564570
result << %{"#{String(value)}"}
565571
end
566572
first = false
567573
}
568-
depth = state.depth -= 1
574+
depth -= 1
569575
unless first
570576
result << state.object_nl
571577
result << state.indent * depth if indent
@@ -582,8 +588,11 @@ module Array
582588
# produced JSON string output further.
583589
def to_json(state = nil, *)
584590
state = State.from_state(state)
591+
depth = state.depth
585592
state.check_max_nesting
586593
json_transform(state)
594+
ensure
595+
state.depth = depth
587596
end
588597

589598
private
@@ -621,12 +630,13 @@ def json_transform(state)
621630
end
622631
elsif value.respond_to?(:to_json)
623632
result << value.to_json(state)
633+
state.depth = depth
624634
else
625635
result << %{"#{String(value)}"}
626636
end
627637
first = false
628638
}
629-
state.depth = depth -= 1
639+
depth -= 1
630640
result << state.array_nl
631641
result << state.indent * depth if indent
632642
result << ']'

0 commit comments

Comments
 (0)