[ruby/json] Fix a memory leak in #to_json methods

Fix: https://github.com/ruby/json/issues/460

The various `to_json` methods must rescue exceptions
to free the buffer.

```
require 'json'

data = 10_000.times.to_a << BasicObject.new
20.times do
  100.times do
    begin
      data.to_json
    rescue NoMethodError
    end
  end
  puts `ps -o rss= -p #{$$}`
end
```

```
 20128
 24992
 29920
 34672
 39600
 44336
 49136
 53936
 58816
 63616
 68416
 73232
 78032
 82896
 87696
 92528
 97408
102208
107008
111808
```

https://github.com/ruby/json/commit/d227d225ca
This commit is contained in:
Jean Boussier 2024-10-29 18:55:22 +01:00 committed by Hiroshi SHIBATA
parent 783dde2159
commit d329896fb5
2 changed files with 36 additions and 28 deletions

View File

@ -397,7 +397,9 @@ static char *fstrndup(const char *ptr, unsigned long len) {
*/ */
static VALUE mHash_to_json(int argc, VALUE *argv, VALUE self) static VALUE mHash_to_json(int argc, VALUE *argv, VALUE self)
{ {
GENERATE_JSON(object); rb_check_arity(argc, 0, 1);
VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil);
return cState_partial_generate(Vstate, self, generate_json_object);
} }
/* /*
@ -409,7 +411,9 @@ static VALUE mHash_to_json(int argc, VALUE *argv, VALUE self)
* produced JSON string output further. * produced JSON string output further.
*/ */
static VALUE mArray_to_json(int argc, VALUE *argv, VALUE self) { static VALUE mArray_to_json(int argc, VALUE *argv, VALUE self) {
GENERATE_JSON(array); rb_check_arity(argc, 0, 1);
VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil);
return cState_partial_generate(Vstate, self, generate_json_array);
} }
#ifdef RUBY_INTEGER_UNIFICATION #ifdef RUBY_INTEGER_UNIFICATION
@ -420,7 +424,9 @@ static VALUE mArray_to_json(int argc, VALUE *argv, VALUE self) {
*/ */
static VALUE mInteger_to_json(int argc, VALUE *argv, VALUE self) static VALUE mInteger_to_json(int argc, VALUE *argv, VALUE self)
{ {
GENERATE_JSON(integer); rb_check_arity(argc, 0, 1);
VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil);
return cState_partial_generate(Vstate, self, generate_json_integer);
} }
#else #else
@ -431,7 +437,9 @@ static VALUE mInteger_to_json(int argc, VALUE *argv, VALUE self)
*/ */
static VALUE mFixnum_to_json(int argc, VALUE *argv, VALUE self) static VALUE mFixnum_to_json(int argc, VALUE *argv, VALUE self)
{ {
GENERATE_JSON(fixnum); rb_check_arity(argc, 0, 1);
VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil);
return cState_partial_generate(Vstate, self, generate_json_fixnum);
} }
/* /*
@ -441,7 +449,9 @@ static VALUE mFixnum_to_json(int argc, VALUE *argv, VALUE self)
*/ */
static VALUE mBignum_to_json(int argc, VALUE *argv, VALUE self) static VALUE mBignum_to_json(int argc, VALUE *argv, VALUE self)
{ {
GENERATE_JSON(bignum); rb_check_arity(argc, 0, 1);
VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil);
return cState_partial_generate(Vstate, self, generate_json_bignum);
} }
#endif #endif
@ -452,7 +462,9 @@ static VALUE mBignum_to_json(int argc, VALUE *argv, VALUE self)
*/ */
static VALUE mFloat_to_json(int argc, VALUE *argv, VALUE self) static VALUE mFloat_to_json(int argc, VALUE *argv, VALUE self)
{ {
GENERATE_JSON(float); rb_check_arity(argc, 0, 1);
VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil);
return cState_partial_generate(Vstate, self, generate_json_float);
} }
/* /*
@ -475,7 +487,9 @@ static VALUE mString_included_s(VALUE self, VALUE modul) {
*/ */
static VALUE mString_to_json(int argc, VALUE *argv, VALUE self) static VALUE mString_to_json(int argc, VALUE *argv, VALUE self)
{ {
GENERATE_JSON(string); rb_check_arity(argc, 0, 1);
VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil);
return cState_partial_generate(Vstate, self, generate_json_string);
} }
/* /*
@ -530,7 +544,8 @@ static VALUE mString_Extend_json_create(VALUE self, VALUE o)
*/ */
static VALUE mTrueClass_to_json(int argc, VALUE *argv, VALUE self) static VALUE mTrueClass_to_json(int argc, VALUE *argv, VALUE self)
{ {
GENERATE_JSON(true); rb_check_arity(argc, 0, 1);
return rb_utf8_str_new("true", 4);
} }
/* /*
@ -540,7 +555,8 @@ static VALUE mTrueClass_to_json(int argc, VALUE *argv, VALUE self)
*/ */
static VALUE mFalseClass_to_json(int argc, VALUE *argv, VALUE self) static VALUE mFalseClass_to_json(int argc, VALUE *argv, VALUE self)
{ {
GENERATE_JSON(false); rb_check_arity(argc, 0, 1);
return rb_utf8_str_new("false", 5);
} }
/* /*
@ -550,7 +566,8 @@ static VALUE mFalseClass_to_json(int argc, VALUE *argv, VALUE self)
*/ */
static VALUE mNilClass_to_json(int argc, VALUE *argv, VALUE self) static VALUE mNilClass_to_json(int argc, VALUE *argv, VALUE self)
{ {
GENERATE_JSON(null); rb_check_arity(argc, 0, 1);
return rb_utf8_str_new("null", 4);
} }
/* /*
@ -567,7 +584,7 @@ static VALUE mObject_to_json(int argc, VALUE *argv, VALUE self)
rb_scan_args(argc, argv, "01", &state); rb_scan_args(argc, argv, "01", &state);
Check_Type(string, T_STRING); Check_Type(string, T_STRING);
state = cState_from_state_s(cState, state); state = cState_from_state_s(cState, state);
return cState_partial_generate(state, string); return cState_partial_generate(state, string, generate_json_string);
} }
static void State_free(void *ptr) static void State_free(void *ptr)
@ -834,6 +851,7 @@ static void generate_json_integer(FBuffer *buffer, VALUE Vstate, JSON_Generator_
generate_json_bignum(buffer, Vstate, state, obj); generate_json_bignum(buffer, Vstate, state, obj);
} }
#endif #endif
static void generate_json_float(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj) static void generate_json_float(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj)
{ {
double value = RFLOAT_VALUE(obj); double value = RFLOAT_VALUE(obj);
@ -910,13 +928,14 @@ struct generate_json_data {
VALUE vstate; VALUE vstate;
JSON_Generator_State *state; JSON_Generator_State *state;
VALUE obj; VALUE obj;
void (*func)(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj);
}; };
static VALUE generate_json_try(VALUE d) static VALUE generate_json_try(VALUE d)
{ {
struct generate_json_data *data = (struct generate_json_data *)d; struct generate_json_data *data = (struct generate_json_data *)d;
generate_json(data->buffer, data->vstate, data->state, data->obj); data->func(data->buffer, data->vstate, data->state, data->obj);
return Qnil; return Qnil;
} }
@ -931,7 +950,7 @@ static VALUE generate_json_rescue(VALUE d, VALUE exc)
return Qundef; return Qundef;
} }
static VALUE cState_partial_generate(VALUE self, VALUE obj) static VALUE cState_partial_generate(VALUE self, VALUE obj, void (*func)(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj))
{ {
GET_STATE(self); GET_STATE(self);
@ -942,7 +961,8 @@ static VALUE cState_partial_generate(VALUE self, VALUE obj)
.buffer = &buffer, .buffer = &buffer,
.vstate = self, .vstate = self,
.state = state, .state = state,
.obj = obj .obj = obj,
.func = func
}; };
rb_rescue(generate_json_try, (VALUE)&data, generate_json_rescue, (VALUE)&data); rb_rescue(generate_json_try, (VALUE)&data, generate_json_rescue, (VALUE)&data);
@ -958,7 +978,7 @@ static VALUE cState_partial_generate(VALUE self, VALUE obj)
*/ */
static VALUE cState_generate(VALUE self, VALUE obj) static VALUE cState_generate(VALUE self, VALUE obj)
{ {
VALUE result = cState_partial_generate(self, obj); VALUE result = cState_partial_generate(self, obj, generate_json);
GET_STATE(self); GET_STATE(self);
(void)state; (void)state;
return result; return result;

View File

@ -54,18 +54,6 @@ typedef struct JSON_Generator_StateStruct {
JSON_Generator_State *state; \ JSON_Generator_State *state; \
GET_STATE_TO(self, state) GET_STATE_TO(self, state)
#define GENERATE_JSON(type) \
VALUE Vstate; \
JSON_Generator_State *state; \
\
rb_scan_args(argc, argv, "01", &Vstate); \
Vstate = cState_from_state_s(cState, Vstate); \
TypedData_Get_Struct(Vstate, JSON_Generator_State, &JSON_Generator_State_type, state); \
FBuffer buffer = {0}; \
fbuffer_init(&buffer, state->buffer_initial_length); \
generate_json_##type(&buffer, Vstate, state, self); \
return fbuffer_to_s(&buffer)
static VALUE mHash_to_json(int argc, VALUE *argv, VALUE self); static VALUE mHash_to_json(int argc, VALUE *argv, VALUE self);
static VALUE mArray_to_json(int argc, VALUE *argv, VALUE self); static VALUE mArray_to_json(int argc, VALUE *argv, VALUE self);
#ifdef RUBY_INTEGER_UNIFICATION #ifdef RUBY_INTEGER_UNIFICATION
@ -99,7 +87,7 @@ static void generate_json_integer(FBuffer *buffer, VALUE Vstate, JSON_Generator_
static void generate_json_fixnum(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj); static void generate_json_fixnum(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj);
static void generate_json_bignum(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj); static void generate_json_bignum(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj);
static void generate_json_float(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj); static void generate_json_float(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj);
static VALUE cState_partial_generate(VALUE self, VALUE obj); static VALUE cState_partial_generate(VALUE self, VALUE obj, void (*func)(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj));
static VALUE cState_generate(VALUE self, VALUE obj); static VALUE cState_generate(VALUE self, VALUE obj);
static VALUE cState_from_state_s(VALUE self, VALUE opts); static VALUE cState_from_state_s(VALUE self, VALUE opts);
static VALUE cState_indent(VALUE self); static VALUE cState_indent(VALUE self);