diff --git a/ext/json/ext/parser/parser.c b/ext/json/ext/parser/parser.c index a6154c83..a60f6a3c 100644 --- a/ext/json/ext/parser/parser.c +++ b/ext/json/ext/parser/parser.c @@ -2625,11 +2625,16 @@ static VALUE cResumableParser_partial_value_body(VALUE self) missing_object_value = 1; } - // Copy the value stack as we need to mutate it. + // Copy the value stack as we need to mutate it. The collapse loop folds each + // open container by popping its entries and pushing the single result, so a + // parent always reclaims its child's slot; head exceeds its live size by at + // most one, either for the missing-value placeholder pushed below or for the + // result of folding an empty innermost container. That one spare slot keeps + // rvalue_stack_push from growing (reallocating) this ALLOCV buffer. long capa = parser.value_stack.head; - parser.value_stack.capa = (capa + missing_object_value); - VALUE tmpbuf, *value_stack_buffer = ALLOCV_N(VALUE, tmpbuf, capa + missing_object_value); - MEMCPY(value_stack_buffer, parser.value_stack.ptr, VALUE, parser.value_stack.capa); + parser.value_stack.capa = capa + 1; + VALUE tmpbuf, *value_stack_buffer = ALLOCV_N(VALUE, tmpbuf, parser.value_stack.capa); + MEMCPY(value_stack_buffer, parser.value_stack.ptr, VALUE, capa); parser.value_stack.ptr = value_stack_buffer; JSON_ParserState *state = &parser.state; diff --git a/test/json/resumable_parser_test.rb b/test/json/resumable_parser_test.rb index 4cc6c085..734d6e22 100644 --- a/test/json/resumable_parser_test.rb +++ b/test/json/resumable_parser_test.rb @@ -247,6 +247,17 @@ def test_partial_value assert_partial_value([1, { "a" => 1, "b" => { "c" => nil } }], '[1, { "a": 1, "b": { "c"') end + def test_partial_value_collapses_nested_incomplete_containers + # partial_value rebuilds the open containers on a scratch value stack; folding + # an empty inner container pushes a value, so that stack must hold more than its + # live size or the push reallocates the scratch buffer. + assert_partial_value({ "abc" => {} }, '{"abc":{"d') + assert_partial_value({ "a" => { "b" => { "c" => {} } } }, '{"a":{"b":{"c":{"e') + assert_partial_value([1, { "a" => {} }], '[1,{"a":{"d') + assert_partial_value({ "a" => [1, { "b" => [2, { "c" => nil }] }] }, '{"a":[1,{"b":[2,{"c"') + assert_partial_value([1, [2, [3, { "x" => nil }]]], '[1,[2,[3,{"x":[') + end + def test_partial_value_issue_1005 data = <<~JSON [