[flori/json] Fix memory leak when exception is raised during JSON generation

If an exception is raised the FBuffer is leaked.

For example, the following script leaks memory:

    o = Object.new
    def o.to_json(a) = raise

    10.times do
      100_000.times do
        begin
          JSON(o)
        rescue
        end
      end

      puts `ps -o rss= -p #{$$}`
    end

Before:

    31824
    35696
    40240
    44304
    47424
    50944
    54000
    58384
    62416
    65296

After:

    24416
    24640
    24640
    24736
    24736
    24736
    24736
    24736
    24736
    24736

44df509dc2
This commit is contained in:
Peter Zhu 2024-03-21 12:17:15 -04:00 committed by Hiroshi SHIBATA
parent b2b665eba5
commit 6e34386794
No known key found for this signature in database
GPG key ID: F9CF13417264FAC2

View file

@ -892,7 +892,6 @@ static void generate_json_object(FBuffer *buffer, VALUE Vstate, JSON_Generator_S
struct hash_foreach_arg arg;
if (max_nesting != 0 && depth > max_nesting) {
fbuffer_free(buffer);
rb_raise(eNestingError, "nesting of %ld is too deep", --state->depth);
}
fbuffer_append_char(buffer, '{');
@ -927,7 +926,6 @@ static void generate_json_array(FBuffer *buffer, VALUE Vstate, JSON_Generator_St
long depth = ++state->depth;
int i, j;
if (max_nesting != 0 && depth > max_nesting) {
fbuffer_free(buffer);
rb_raise(eNestingError, "nesting of %ld is too deep", --state->depth);
}
fbuffer_append_char(buffer, '[');
@ -1020,10 +1018,8 @@ static void generate_json_float(FBuffer *buffer, VALUE Vstate, JSON_Generator_St
VALUE tmp = rb_funcall(obj, i_to_s, 0);
if (!allow_nan) {
if (isinf(value)) {
fbuffer_free(buffer);
rb_raise(eGeneratorError, "%"PRIsVALUE" not allowed in JSON", RB_OBJ_STRING(tmp));
} else if (isnan(value)) {
fbuffer_free(buffer);
rb_raise(eGeneratorError, "%"PRIsVALUE" not allowed in JSON", RB_OBJ_STRING(tmp));
}
}
@ -1096,11 +1092,45 @@ static FBuffer *cState_prepare_buffer(VALUE self)
return buffer;
}
struct generate_json_data {
FBuffer *buffer;
VALUE vstate;
JSON_Generator_State *state;
VALUE obj;
};
static VALUE generate_json_try(VALUE d)
{
struct generate_json_data *data = (struct generate_json_data *)d;
generate_json(data->buffer, data->vstate, data->state, data->obj);
return Qnil;
}
static VALUE generate_json_rescue(VALUE d, VALUE exc)
{
struct generate_json_data *data = (struct generate_json_data *)d;
fbuffer_free(data->buffer);
rb_exc_raise(exc);
return Qundef;
}
static VALUE cState_partial_generate(VALUE self, VALUE obj)
{
FBuffer *buffer = cState_prepare_buffer(self);
GET_STATE(self);
generate_json(buffer, self, state, obj);
struct generate_json_data data = {
.buffer = buffer,
.vstate = self,
.state = state,
.obj = obj
};
rb_rescue(generate_json_try, (VALUE)&data, generate_json_rescue, (VALUE)&data);
return fbuffer_to_s(buffer);
}