From b954be51f81116f618804b118d8783108e627e78 Mon Sep 17 00:00:00 2001
From: Eric Wong <normalperson@yhbt.net>
Date: Tue, 9 Aug 2011 01:29:43 +0000
Subject: [PATCH] ext/json/generator/generator.c: prevent GC for temporary
 strings

We need to guard temporary strings from being collected while
we append to the JSON buffer.  The RSTRING_PAIR macro is
dangerous since it preserves no pointer to the original
string VALUE, allowing GC to reap the object while we
still use the (C) string pointer.

The included test case shows data corruption with large
Bignums without this fix.
---
 ext/json/generator/generator.c  |   16 +++++++++++++---
 ext/json/generator/generator.h  |    2 --
 test/json/test_json_generate.rb |   13 +++++++++++++
 3 files changed, 26 insertions(+), 5 deletions(-)

diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c
index 4e44178..a75118a 100644
--- a/ext/json/generator/generator.c
+++ b/ext/json/generator/generator.c
@@ -349,6 +349,16 @@ static void fbuffer_append(FBuffer *fb, const char *newstr, unsigned long len)
     }
 }
 
+static void fbuffer_append_str(FBuffer *fb, VALUE str)
+{
+    const char *newstr = RSTRING_PTR(str);
+    unsigned long len = RSTRING_LEN(str);
+
+    RB_GC_GUARD(str);
+
+    fbuffer_append(fb, newstr, len);
+}
+
 static void fbuffer_append_char(FBuffer *fb, char newchr)
 {
     fbuffer_inc_capa(fb, 1);
@@ -852,7 +862,7 @@ static void generate_json_fixnum(FBuffer *buffer, VALUE Vstate, JSON_Generator_S
 static void generate_json_bignum(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj)
 {
     VALUE tmp = rb_funcall(obj, i_to_s, 0);
-    fbuffer_append(buffer, RSTRING_PAIR(tmp));
+    fbuffer_append_str(buffer, tmp);
 }
 
 static void generate_json_float(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj)
@@ -869,7 +879,7 @@ static void generate_json_float(FBuffer *buffer, VALUE Vstate, JSON_Generator_St
             rb_raise(eGeneratorError, "%u: %s not allowed in JSON", __LINE__, StringValueCStr(tmp));
         }
     }
-    fbuffer_append(buffer, RSTRING_PAIR(tmp));
+    fbuffer_append_str(buffer, tmp);
 }
 
 static void generate_json(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj)
@@ -897,7 +907,7 @@ static void generate_json(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *s
     } else if (rb_respond_to(obj, i_to_json)) {
         tmp = rb_funcall(obj, i_to_json, 1, Vstate);
         Check_Type(tmp, T_STRING);
-        fbuffer_append(buffer, RSTRING_PAIR(tmp));
+        fbuffer_append_str(buffer, tmp);
     } else {
         tmp = rb_funcall(obj, i_to_s, 0);
         Check_Type(tmp, T_STRING);
diff --git a/ext/json/generator/generator.h b/ext/json/generator/generator.h
index ee496fe..2a83149 100644
--- a/ext/json/generator/generator.h
+++ b/ext/json/generator/generator.h
@@ -45,8 +45,6 @@
 #define RSTRING_LEN(string) RSTRING(string)->len
 #endif
 
-#define RSTRING_PAIR(string) RSTRING_PTR(string), RSTRING_LEN(string)
-
 /* fbuffer implementation */
 
 typedef struct FBufferStruct {
diff --git a/test/json/test_json_generate.rb b/test/json/test_json_generate.rb
index 9b0cff4..baf5188 100755
--- a/test/json/test_json_generate.rb
+++ b/test/json/test_json_generate.rb
@@ -177,4 +177,17 @@ EOT
     assert_raises(JSON::NestingError) { ary.to_json(s) }
     assert_equal 19, s.depth
   end
+
+  def test_gc
+    bignum_too_long_to_embed_as_string = 1234567890123456789012345
+    expect = bignum_too_long_to_embed_as_string.to_s
+    stress, GC.stress = GC.stress, true
+
+    10.times do |i|
+      tmp = bignum_too_long_to_embed_as_string.to_json
+      assert_equal expect, tmp
+    end
+    ensure
+      GC.stress = stress
+  end
 end
