diff --git a/ext/json/ext/parser/parser.c b/ext/json/ext/parser/parser.c index c216faf4..136aab6a 100644 --- a/ext/json/ext/parser/parser.c +++ b/ext/json/ext/parser/parser.c @@ -2300,16 +2300,19 @@ static VALUE cResumableParser_feed(VALUE self, VALUE str) const size_t size = parser->state.end - parser->state.start; const size_t consumed = size - remaining; - char *old_ptr = RSTRING_PTR(parser->buffer); if (RB_OBJ_FROZEN_RAW(parser->buffer)) { VALUE new_buffer = rb_obj_hide(rb_str_buf_new(remaining + RSTRING_LEN(str))); - MEMCPY(RSTRING_PTR(new_buffer), old_ptr + consumed, char, remaining); + + char *old_ptr = RSTRING_PTR(parser->buffer); + memcpy(RSTRING_PTR(new_buffer), old_ptr + consumed, remaining); rb_str_set_len(new_buffer, remaining); offset = 0; parser->buffer = new_buffer; } else if (consumed > (size / 2) && size >= 512) { - MEMMOVE(old_ptr, old_ptr + consumed, char, remaining); + rb_str_modify(parser->buffer); + char *old_ptr = RSTRING_PTR(parser->buffer); + memmove(old_ptr, old_ptr + consumed, remaining); rb_str_set_len(parser->buffer, remaining); offset = 0; } diff --git a/test/json/resumable_parser_test.rb b/test/json/resumable_parser_test.rb index 600cd33e..52f1356a 100644 --- a/test/json/resumable_parser_test.rb +++ b/test/json/resumable_parser_test.rb @@ -244,6 +244,21 @@ def test_spill_frames_stack assert_equal expected, @parser.value end + def test_buffer_shrink + doc1 = '{"a":"' + ("x" * 800) + '"} {' # >= 512 bytes + doc2 = '"b":1} ' + + parser = JSON::ResumableParser.new({}) + + parser << doc1 # internal buffer becomes a *shared* string here + parser.parse # consume doc1 -> >50% of a >=512B buffer is now consumed + parser.value + + parser << doc2 # buffer is shrinked + parser.parse + parser.value + end + private def assert_partial_value(expected, json)