summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/json/generator/generator.c50
-rw-r--r--ext/json/lib/json/common.rb2
-rwxr-xr-xtest/json/json_generator_test.rb77
3 files changed, 72 insertions, 57 deletions
diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c
index 9e6e617a59..d202e97ea1 100644
--- a/ext/json/generator/generator.c
+++ b/ext/json/generator/generator.c
@@ -968,14 +968,16 @@ static void vstate_spill(struct generate_json_data *data)
RB_OBJ_WRITTEN(vstate, Qundef, state->as_json);
}
-static inline VALUE vstate_get(struct generate_json_data *data)
+static inline VALUE json_call_to_json(struct generate_json_data *data, VALUE obj)
{
if (RB_UNLIKELY(!data->vstate)) {
vstate_spill(data);
}
GET_STATE(data->vstate);
state->depth = data->depth;
- return data->vstate;
+ VALUE tmp = rb_funcall(obj, i_to_json, 1, data->vstate);
+ // no need to restore state->depth, vstate is just a temporary State
+ return tmp;
}
static VALUE
@@ -1293,9 +1295,7 @@ static void generate_json_fallback(FBuffer *buffer, struct generate_json_data *d
{
VALUE tmp;
if (rb_respond_to(obj, i_to_json)) {
- tmp = rb_funcall(obj, i_to_json, 1, vstate_get(data));
- GET_STATE(data->vstate);
- data->depth = state->depth;
+ tmp = json_call_to_json(data, obj);
Check_Type(tmp, T_STRING);
fbuffer_append_str(buffer, tmp);
} else {
@@ -1477,16 +1477,6 @@ static VALUE generate_json_try(VALUE d)
return fbuffer_finalize(data->buffer);
}
-// Preserves the deprecated behavior of State#depth being set.
-static VALUE generate_json_ensure_deprecated(VALUE d)
-{
- struct generate_json_data *data = (struct generate_json_data *)d;
- fbuffer_free(data->buffer);
- data->state->depth = data->depth;
-
- return Qundef;
-}
-
static VALUE generate_json_ensure(VALUE d)
{
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,
struct generate_json_data data = {
.buffer = &buffer,
- .vstate = self,
+ .vstate = Qfalse, // don't use self as it may be frozen and its depth is mutated when calling to_json
.state = state,
.depth = state->depth,
.obj = obj,
.func = func
};
- return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure_deprecated, (VALUE)&data);
+ return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data);
}
/* call-seq:
@@ -1532,31 +1522,6 @@ static VALUE cState_generate(int argc, VALUE *argv, VALUE self)
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);
-
- 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 = state,
- .depth = state->depth,
- .obj = obj,
- .func = generate_json
- };
- return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data);
-}
-
static VALUE cState_initialize(int argc, VALUE *argv, VALUE self)
{
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)
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_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/ext/json/lib/json/common.rb b/ext/json/lib/json/common.rb
index fcdc0d9f0b..877b96814e 100644
--- a/ext/json/lib/json/common.rb
+++ b/ext/json/lib/json/common.rb
@@ -1074,7 +1074,7 @@ module JSON
#
# Serialize the given object into a \JSON document.
def dump(object, io = nil)
- @state.generate_new(object, io)
+ @state.generate(object, io)
end
alias_method :generate, :dump
diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb
index e623e05409..9f8b35de09 100755
--- a/test/json/json_generator_test.rb
+++ b/test/json/json_generator_test.rb
@@ -321,7 +321,8 @@ class JSONGeneratorTest < Test::Unit::TestCase
end
end
- def test_depth_bad_to_json
+ # An object that changes state.depth when it receives to_json(state)
+ def bad_to_json
obj = Object.new
def obj.to_json(state)
state.depth += 1
@@ -329,21 +330,44 @@ class JSONGeneratorTest < Test::Unit::TestCase
"#{state.indent * state.depth}\"foo\":#{state.space}1#{state.object_nl}"\
"#{state.indent * (state.depth - 1)}}"
end
- assert_equal <<~JSON.chomp, JSON.pretty_generate([obj] * 2)
+ obj
+ end
+
+ def test_depth_restored_bad_to_json
+ state = JSON::State.new
+ state.generate(bad_to_json)
+ assert_equal 0, state.depth
+ end
+
+ def test_depth_restored_bad_to_json_in_Array
+ assert_equal <<~JSON.chomp, JSON.pretty_generate([bad_to_json] * 2)
[
{
"foo": 1
},
{
- "foo": 1
- }
+ "foo": 1
+ }
]
JSON
- state = JSON::State.new(object_nl: "\n", array_nl: "\n", space: " ", indent: " ")
- state.generate(obj)
- assert_equal 1, state.depth # FIXME
- state.depth = 0
- state.generate([obj])
+ state = JSON::State.new
+ state.generate([bad_to_json])
+ assert_equal 0, state.depth
+ end
+
+ def test_depth_restored_bad_to_json_in_Hash
+ assert_equal <<~JSON.chomp, JSON.pretty_generate(a: bad_to_json, b: bad_to_json)
+ {
+ "a": {
+ "foo": 1
+ },
+ "b": {
+ "foo": 1
+ }
+ }
+ JSON
+ state = JSON::State.new
+ state.generate(a: bad_to_json)
assert_equal 0, state.depth
end
@@ -361,10 +385,36 @@ class JSONGeneratorTest < Test::Unit::TestCase
ary = []; ary << ary
assert_raise(JSON::NestingError) { generate(ary) }
assert_raise(JSON::NestingError) { JSON.pretty_generate(ary) }
- s = JSON.state.new
- assert_equal 0, s.depth
+ end
+
+ def test_depth_nesting_error_to_json
+ ary = []; ary << ary
+ s = JSON.state.new(depth: 1)
assert_raise(JSON::NestingError) { ary.to_json(s) }
- assert_equal 100, s.depth
+ assert_equal 1, s.depth
+ end
+
+ def test_depth_nesting_error_Hash_to_json
+ hash = {}; hash[:a] = hash
+ s = JSON.state.new(depth: 1)
+ assert_raise(JSON::NestingError) { hash.to_json(s) }
+ assert_equal 1, s.depth
+ end
+
+ def test_depth_nesting_error_generate
+ ary = []; ary << ary
+ s = JSON.state.new(depth: 1)
+ assert_raise(JSON::NestingError) { s.generate(ary) }
+ assert_equal 1, s.depth
+ end
+
+ def test_depth_exception_calling_to_json
+ def (obj = Object.new).to_json(*)
+ raise
+ end
+ s = JSON.state.new(depth: 1).freeze
+ assert_raise(RuntimeError) { s.generate([{ hash: obj }]) }
+ assert_equal 1, s.depth
end
def test_buffer_initial_length
@@ -1006,7 +1056,8 @@ class JSONGeneratorTest < Test::Unit::TestCase
state = JSON::State.new
ary = []
ary << ary
- assert_raise(JSON::NestingError) { state.generate_new(ary) }
+ assert_raise(JSON::NestingError) { state.generate(ary) }
+ assert_equal 0, state.depth
assert_equal '{"a":1}', state.generate({ a: 1 })
end
end