diff options
author | Lamont Granquist <lamont@scriptkiddie.org> | 2014-11-22 18:41:20 -0800 |
---|---|---|
committer | Lamont Granquist <lamont@scriptkiddie.org> | 2014-11-22 18:41:20 -0800 |
commit | 96c9a8b43da09b4b06ea6133ecd60914952ed9e7 (patch) | |
tree | 37b68567ba48ea60babf4d317dd88d0c31ffd39a | |
parent | e3b2f01dc35a697d06afe169bf40c62e716d8412 (diff) | |
parent | 2e3e9bde48cd9f90a2b05881132b270f0bd18e06 (diff) | |
download | ffi-yajl-96c9a8b43da09b4b06ea6133ecd60914952ed9e7.tar.gz |
Merge pull request #33 from opscode/lcg/encoding-keys
Lcg/encoding keys
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | ext/ffi_yajl/ext/encoder/encoder.c | 128 | ||||
-rw-r--r-- | lib/ffi_yajl/ffi/encoder.rb | 87 | ||||
-rw-r--r-- | spec/ffi_yajl/encoder_spec.rb | 25 |
4 files changed, 187 insertions, 54 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index d0a27b9..eed081a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ ### Bugs fixed +* fixes using Arrays and Hashes (and true/false/nil) as Hash keys * fixes bare object parsing in issue #2 and #6 * fixes parsing nil object to not coredump the MRI ruby VM (issue #15) diff --git a/ext/ffi_yajl/ext/encoder/encoder.c b/ext/ffi_yajl/ext/encoder/encoder.c index 90b125b..3c22f8d 100644 --- a/ext/ffi_yajl/ext/encoder/encoder.c +++ b/ext/ffi_yajl/ext/encoder/encoder.c @@ -78,19 +78,30 @@ static VALUE rb_cHash_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { struct yajl_gen_t *yajl_gen; Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen); - extra = rb_hash_new(); /* FIXME: reduce garbage */ + if ( rb_hash_aref(state, rb_str_new2("processing_key")) == Qtrue ) { + ID sym_to_s = rb_intern("to_s"); + VALUE str = rb_funcall(self, sym_to_s, 0); + char *cptr = RSTRING_PTR(str); + int len = RSTRING_LEN(str); + + CHECK_STATUS( + yajl_gen_string(yajl_gen, (unsigned char *)cptr, len) + ); + } else { + extra = rb_hash_new(); /* FIXME: reduce garbage */ - rb_hash_aset(extra, rb_str_new2("yajl_gen"), rb_yajl_gen); + rb_hash_aset(extra, rb_str_new2("yajl_gen"), rb_yajl_gen); - rb_hash_aset(extra, rb_str_new2("state"), state); + rb_hash_aset(extra, rb_str_new2("state"), state); - CHECK_STATUS( - yajl_gen_map_open(yajl_gen) - ); - rb_hash_foreach(self, rb_cHash_ffi_yajl_callback, extra); - CHECK_STATUS( - yajl_gen_map_close(yajl_gen) - ); + CHECK_STATUS( + yajl_gen_map_open(yajl_gen) + ); + rb_hash_foreach(self, rb_cHash_ffi_yajl_callback, extra); + CHECK_STATUS( + yajl_gen_map_close(yajl_gen) + ); + } return Qnil; } @@ -103,16 +114,27 @@ static VALUE rb_cArray_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { struct yajl_gen_t *yajl_gen; Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen); - CHECK_STATUS( - yajl_gen_array_open(yajl_gen) - ); - for(i=0; i<RARRAY_LEN(self); i++) { - val = rb_ary_entry(self, i); - rb_funcall(val, sym_ffi_yajl, 2, rb_yajl_gen, state); + if ( rb_hash_aref(state, rb_str_new2("processing_key")) == Qtrue ) { + ID sym_to_s = rb_intern("to_s"); + VALUE str = rb_funcall(self, sym_to_s, 0); + char *cptr = RSTRING_PTR(str); + int len = RSTRING_LEN(str); + + CHECK_STATUS( + yajl_gen_string(yajl_gen, (unsigned char *)cptr, len) + ); + } else { + CHECK_STATUS( + yajl_gen_array_open(yajl_gen) + ); + for(i=0; i<RARRAY_LEN(self); i++) { + val = rb_ary_entry(self, i); + rb_funcall(val, sym_ffi_yajl, 2, rb_yajl_gen, state); + } + CHECK_STATUS( + yajl_gen_array_close(yajl_gen) + ); } - CHECK_STATUS( - yajl_gen_array_close(yajl_gen) - ); return Qnil; } @@ -121,9 +143,22 @@ static VALUE rb_cNilClass_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { yajl_gen_status status; struct yajl_gen_t *yajl_gen; Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen); - CHECK_STATUS( - yajl_gen_null(yajl_gen) - ); + + if ( rb_hash_aref(state, rb_str_new2("processing_key")) == Qtrue ) { + ID sym_to_s = rb_intern("to_s"); + VALUE str = rb_funcall(self, sym_to_s, 0); + char *cptr = RSTRING_PTR(str); + int len = RSTRING_LEN(str); + + CHECK_STATUS( + yajl_gen_string(yajl_gen, (unsigned char *)cptr, len) + ); + } else { + CHECK_STATUS( + yajl_gen_null(yajl_gen) + ); + } + return Qnil; } @@ -131,9 +166,22 @@ static VALUE rb_cTrueClass_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) yajl_gen_status status; struct yajl_gen_t *yajl_gen; Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen); - CHECK_STATUS( - yajl_gen_bool(yajl_gen, 1) - ); + + if ( rb_hash_aref(state, rb_str_new2("processing_key")) == Qtrue ) { + ID sym_to_s = rb_intern("to_s"); + VALUE str = rb_funcall(self, sym_to_s, 0); + char *cptr = RSTRING_PTR(str); + int len = RSTRING_LEN(str); + + CHECK_STATUS( + yajl_gen_string(yajl_gen, (unsigned char *)cptr, len) + ); + } else { + CHECK_STATUS( + yajl_gen_bool(yajl_gen, 1) + ); + } + return Qnil; } @@ -141,9 +189,22 @@ static VALUE rb_cFalseClass_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) yajl_gen_status status; struct yajl_gen_t *yajl_gen; Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen); - CHECK_STATUS( - yajl_gen_bool(yajl_gen, 0) - ); + + if ( rb_hash_aref(state, rb_str_new2("processing_key")) == Qtrue ) { + ID sym_to_s = rb_intern("to_s"); + VALUE str = rb_funcall(self, sym_to_s, 0); + char *cptr = RSTRING_PTR(str); + int len = RSTRING_LEN(str); + + CHECK_STATUS( + yajl_gen_string(yajl_gen, (unsigned char *)cptr, len) + ); + } else { + CHECK_STATUS( + yajl_gen_bool(yajl_gen, 0) + ); + } + return Qnil; } @@ -168,6 +229,7 @@ static VALUE rb_cFixnum_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { yajl_gen_number(yajl_gen, cptr, len) ); } + return Qnil; } @@ -179,6 +241,7 @@ static VALUE rb_cBignum_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { int len = RSTRING_LEN(str); struct yajl_gen_t *yajl_gen; Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen); + if (memcmp(cptr, "NaN", 3) == 0 || memcmp(cptr, "Infinity", 8) == 0 || memcmp(cptr, "-Infinity", 9) == 0) { rb_raise(cEncodeError, "'%s' is an invalid number", cptr); } @@ -191,6 +254,7 @@ static VALUE rb_cBignum_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { yajl_gen_number(yajl_gen, cptr, len) ); } + return Qnil; } @@ -202,6 +266,7 @@ static VALUE rb_cFloat_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { int len = RSTRING_LEN(str); struct yajl_gen_t *yajl_gen; Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen); + if (memcmp(cptr, "NaN", 3) == 0 || memcmp(cptr, "Infinity", 8) == 0 || memcmp(cptr, "-Infinity", 9) == 0) { rb_raise(cEncodeError, "'%s' is an invalid number", cptr); } @@ -214,6 +279,7 @@ static VALUE rb_cFloat_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { yajl_gen_number(yajl_gen, cptr, len) ); } + return Qnil; } @@ -221,9 +287,11 @@ static VALUE rb_cString_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { yajl_gen_status status; struct yajl_gen_t *yajl_gen; Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen); + CHECK_STATUS( yajl_gen_string(yajl_gen, (unsigned char *)RSTRING_PTR(self), RSTRING_LEN(self)) ); + return Qnil; } @@ -236,9 +304,11 @@ static VALUE object_to_s_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { int len = RSTRING_LEN(str); struct yajl_gen_t *yajl_gen; Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen); + CHECK_STATUS( yajl_gen_string(yajl_gen, (unsigned char *)cptr, len) ); + return Qnil; } @@ -258,9 +328,11 @@ static VALUE rb_cTime_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { int len = RSTRING_LEN(str); struct yajl_gen_t *yajl_gen; Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen); + CHECK_STATUS( yajl_gen_string(yajl_gen, (unsigned char *)cptr, len) ); + return Qnil; } diff --git a/lib/ffi_yajl/ffi/encoder.rb b/lib/ffi_yajl/ffi/encoder.rb index 8177f5f..add35f6 100644 --- a/lib/ffi_yajl/ffi/encoder.rb +++ b/lib/ffi_yajl/ffi/encoder.rb @@ -46,56 +46,91 @@ end class Hash def ffi_yajl(yajl_gen, state) - if ( status = FFI_Yajl.yajl_gen_map_open(yajl_gen) ) != 0 - FFI_Yajl::Encoder.raise_error_for_status(status) - end - self.each do |key, value| - # Perf Fix: mutate state hash rather than creating new copy - state[:processing_key] = true - key.ffi_yajl(yajl_gen, state) - state[:processing_key] = false - value.ffi_yajl(yajl_gen, state) - end - if ( status = FFI_Yajl.yajl_gen_map_close(yajl_gen) ) != 0 - FFI_Yajl::Encoder.raise_error_for_status(status) + if state[:processing_key] + str = self.to_s + if ( status = FFI_Yajl.yajl_gen_string(yajl_gen, str, str.bytesize) ) != 0 + FFI_Yajl::Encoder.raise_error_for_status(status) + end + else + if ( status = FFI_Yajl.yajl_gen_map_open(yajl_gen) ) != 0 + FFI_Yajl::Encoder.raise_error_for_status(status) + end + self.each do |key, value| + # Perf Fix: mutate state hash rather than creating new copy + state[:processing_key] = true + key.ffi_yajl(yajl_gen, state) + state[:processing_key] = false + value.ffi_yajl(yajl_gen, state) + end + if ( status = FFI_Yajl.yajl_gen_map_close(yajl_gen) ) != 0 + FFI_Yajl::Encoder.raise_error_for_status(status) + end end end end class Array def ffi_yajl(yajl_gen, state) - if ( status = FFI_Yajl.yajl_gen_array_open(yajl_gen) ) != 0 - FFI_Yajl::Encoder.raise_error_for_status(status) - end - self.each do |value| - value.ffi_yajl(yajl_gen, state) - end - if ( status = FFI_Yajl.yajl_gen_array_close(yajl_gen) ) != 0 - FFI_Yajl::Encoder.raise_error_for_status(status) + if state[:processing_key] + str = self.to_s + if ( status = FFI_Yajl.yajl_gen_string(yajl_gen, str, str.bytesize) ) != 0 + FFI_Yajl::Encoder.raise_error_for_status(status) + end + else + if ( status = FFI_Yajl.yajl_gen_array_open(yajl_gen) ) != 0 + FFI_Yajl::Encoder.raise_error_for_status(status) + end + self.each do |value| + value.ffi_yajl(yajl_gen, state) + end + if ( status = FFI_Yajl.yajl_gen_array_close(yajl_gen) ) != 0 + FFI_Yajl::Encoder.raise_error_for_status(status) + end end end end class NilClass def ffi_yajl(yajl_gen, state) - if ( status = FFI_Yajl.yajl_gen_null(yajl_gen) ) != 0 - FFI_Yajl::Encoder.raise_error_for_status(status) + if state[:processing_key] + str = self.to_s + if ( status = FFI_Yajl.yajl_gen_string(yajl_gen, str, str.bytesize) ) != 0 + FFI_Yajl::Encoder.raise_error_for_status(status) + end + else + if ( status = FFI_Yajl.yajl_gen_null(yajl_gen) ) != 0 + FFI_Yajl::Encoder.raise_error_for_status(status) + end end end end class TrueClass def ffi_yajl(yajl_gen, state) - if ( status = FFI_Yajl.yajl_gen_bool(yajl_gen, 1) ) != 0 - FFI_Yajl::Encoder.raise_error_for_status(status) + if state[:processing_key] + str = self.to_s + if ( status = FFI_Yajl.yajl_gen_string(yajl_gen, str, str.bytesize) ) != 0 + FFI_Yajl::Encoder.raise_error_for_status(status) + end + else + if ( status = FFI_Yajl.yajl_gen_bool(yajl_gen, 1) ) != 0 + FFI_Yajl::Encoder.raise_error_for_status(status) + end end end end class FalseClass def ffi_yajl(yajl_gen, state) - if ( status = FFI_Yajl.yajl_gen_bool(yajl_gen, 0) ) != 0 - FFI_Yajl::Encoder.raise_error_for_status(status) + if state[:processing_key] + str = self.to_s + if ( status = FFI_Yajl.yajl_gen_string(yajl_gen, str, str.bytesize) ) != 0 + FFI_Yajl::Encoder.raise_error_for_status(status) + end + else + if ( status = FFI_Yajl.yajl_gen_bool(yajl_gen, 0) ) != 0 + FFI_Yajl::Encoder.raise_error_for_status(status) + end end end end diff --git a/spec/ffi_yajl/encoder_spec.rb b/spec/ffi_yajl/encoder_spec.rb index 02bc020..809f5b5 100644 --- a/spec/ffi_yajl/encoder_spec.rb +++ b/spec/ffi_yajl/encoder_spec.rb @@ -7,6 +7,31 @@ describe "FFI_Yajl::Encoder" do let(:encoder) { FFI_Yajl::Encoder.new } + it "encodes hashes in keys as strings", :ruby_gte_193 => true do + ruby = { {'a' => 'b'} => 2 } + expect(encoder.encode(ruby)).to eq('{"{\"a\"=>\"b\"}":2}') + end + + it "encodes arrays in keys as strings", :ruby_gte_193 => true do + ruby = { [0,1] => 2 } + expect(encoder.encode(ruby)).to eq('{"[0, 1]":2}') + end + + it "encodes nil in keys as strings" do + ruby = { nil => 2 } + expect(encoder.encode(ruby)).to eq('{"":2}') + end + + it "encodes true in keys as strings" do + ruby = { true => 2 } + expect(encoder.encode(ruby)).to eq('{"true":2}') + end + + it "encodes false in keys as strings" do + ruby = { false => 2 } + expect(encoder.encode(ruby)).to eq('{"false":2}') + end + it "encodes fixnums in keys as strings" do ruby = { 1 => 2 } expect(encoder.encode(ruby)).to eq('{"1":2}') |