Skip to content

Commit

Permalink
Fix a memory leak in #to_json methods
Browse files Browse the repository at this point in the history
Fix: #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
```
  • Loading branch information
byroot committed Oct 30, 2024
1 parent 9d71186 commit 84c9aaa
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 27 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Changes

* Fix a memory leak when `#to_json` methods raise an exception.
* Gracefully handle formatting configs being set to `nil` instead of `""`.
* Workaround another issue caused by conflicting versions of both `json_pure` and `json` being loaded.

Expand Down
50 changes: 35 additions & 15 deletions ext/json/ext/generator/generator.c
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,9 @@ static char *fstrndup(const char *ptr, unsigned long len) {
*/
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);
}

/*
Expand All @@ -415,7 +417,9 @@ static VALUE mHash_to_json(int argc, VALUE *argv, VALUE self)
* produced JSON string output further.
*/
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
Expand All @@ -426,7 +430,9 @@ static VALUE mArray_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
Expand All @@ -437,7 +443,9 @@ static VALUE mInteger_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);
}

/*
Expand All @@ -447,7 +455,9 @@ static VALUE mFixnum_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

Expand All @@ -458,7 +468,9 @@ static VALUE mBignum_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);
}

/*
Expand All @@ -481,7 +493,9 @@ static VALUE mString_included_s(VALUE self, VALUE modul) {
*/
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);
}

/*
Expand Down Expand Up @@ -536,7 +550,8 @@ static VALUE mString_Extend_json_create(VALUE self, VALUE o)
*/
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);
}

/*
Expand All @@ -546,7 +561,8 @@ static VALUE mTrueClass_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);
}

/*
Expand All @@ -556,7 +572,8 @@ static VALUE mFalseClass_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);
}

/*
Expand All @@ -573,7 +590,7 @@ static VALUE mObject_to_json(int argc, VALUE *argv, VALUE self)
rb_scan_args(argc, argv, "01", &state);
Check_Type(string, T_STRING);
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)
Expand Down Expand Up @@ -826,6 +843,7 @@ static void generate_json_integer(FBuffer *buffer, VALUE Vstate, JSON_Generator_
generate_json_bignum(buffer, Vstate, state, obj);
}
#endif

static void generate_json_float(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj)
{
double value = RFLOAT_VALUE(obj);
Expand Down Expand Up @@ -911,13 +929,14 @@ struct generate_json_data {
VALUE vstate;
JSON_Generator_State *state;
VALUE obj;
void (*func)(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);
data->func(data->buffer, data->vstate, data->state, data->obj);

return Qnil;
}
Expand All @@ -932,7 +951,7 @@ static VALUE generate_json_rescue(VALUE d, VALUE exc)
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))
{
FBuffer *buffer = cState_prepare_buffer(self);
GET_STATE(self);
Expand All @@ -941,7 +960,8 @@ static VALUE cState_partial_generate(VALUE self, VALUE obj)
.buffer = buffer,
.vstate = self,
.state = state,
.obj = obj
.obj = obj,
.func = func
};
rb_rescue(generate_json_try, (VALUE)&data, generate_json_rescue, (VALUE)&data);

Expand All @@ -957,7 +977,7 @@ static VALUE cState_partial_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);
(void)state;
return result;
Expand Down
13 changes: 1 addition & 12 deletions ext/json/ext/generator/generator.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,6 @@ typedef struct JSON_Generator_StateStruct {
JSON_Generator_State *state; \
GET_STATE_TO(self, state)

#define GENERATE_JSON(type) \
FBuffer *buffer; \
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); \
buffer = cState_prepare_buffer(Vstate); \
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 mArray_to_json(int argc, VALUE *argv, VALUE self);
Expand Down Expand Up @@ -99,7 +88,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_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 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_from_state_s(VALUE self, VALUE opts);
static VALUE cState_indent(VALUE self);
Expand Down

0 comments on commit 84c9aaa

Please sign in to comment.