diff --git a/CHANGES.md b/CHANGES.md index f9efe041..3e68fb59 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -18,6 +18,7 @@ pre-existing support for comments, make it suitable to parse `jsonc` documents. * Many performance improvements to `JSON.parse` and `JSON.load`, up to `1.7x` faster on real world documents. * Some minor performance improvements to `JSON.dump` and `JSON.generate`. +* `JSON.pretty_generate` no longer include newline inside empty object and arrays. ### 2024-11-04 (2.7.6) diff --git a/Rakefile b/Rakefile index c5b518a1..09b69a2e 100644 --- a/Rakefile +++ b/Rakefile @@ -161,7 +161,7 @@ if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'jruby' file JRUBY_PARSER_JAR => :compile do cd 'java/src' do parser_classes = FileList[ - "json/ext/ByteListTranscoder*.class", + "json/ext/ByteList*.class", "json/ext/OptionsReader*.class", "json/ext/Parser*.class", "json/ext/RuntimeInfo*.class", @@ -179,7 +179,7 @@ if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'jruby' file JRUBY_GENERATOR_JAR => :compile do cd 'java/src' do generator_classes = FileList[ - "json/ext/ByteListTranscoder*.class", + "json/ext/ByteList*.class", "json/ext/OptionsReader*.class", "json/ext/Generator*.class", "json/ext/RuntimeInfo*.class", diff --git a/ext/json/ext/fbuffer/fbuffer.h b/ext/json/ext/fbuffer/fbuffer.h index 3e154a5f..0774c7e4 100644 --- a/ext/json/ext/fbuffer/fbuffer.h +++ b/ext/json/ext/fbuffer/fbuffer.h @@ -46,9 +46,11 @@ typedef struct FBufferStruct { unsigned long len; unsigned long capa; char *ptr; + VALUE io; } FBuffer; #define FBUFFER_STACK_SIZE 512 +#define FBUFFER_IO_BUFFER_SIZE (16384 - 1) #define FBUFFER_INITIAL_LENGTH_DEFAULT 1024 #define FBUFFER_PTR(fb) ((fb)->ptr) @@ -66,7 +68,7 @@ static void fbuffer_append_long(FBuffer *fb, long number); #endif static inline void fbuffer_append_char(FBuffer *fb, char newchr); #ifdef JSON_GENERATOR -static VALUE fbuffer_to_s(FBuffer *fb); +static VALUE fbuffer_finalize(FBuffer *fb); #endif static void fbuffer_stack_init(FBuffer *fb, unsigned long initial_length, char *stack_buffer, long stack_buffer_size) @@ -86,24 +88,19 @@ static void fbuffer_free(FBuffer *fb) } } -#ifndef JSON_GENERATOR static void fbuffer_clear(FBuffer *fb) { fb->len = 0; } -#endif -static void fbuffer_do_inc_capa(FBuffer *fb, unsigned long requested) +static void fbuffer_flush(FBuffer *fb) { - unsigned long required; - - if (RB_UNLIKELY(!fb->ptr)) { - fb->ptr = ALLOC_N(char, fb->initial_length); - fb->capa = fb->initial_length; - } - - for (required = fb->capa; requested > required - fb->len; required <<= 1); + rb_io_write(fb->io, rb_utf8_str_new(fb->ptr, fb->len)); + fbuffer_clear(fb); +} +static void fbuffer_realloc(FBuffer *fb, unsigned long required) +{ if (required > fb->capa) { if (fb->type == FBUFFER_STACK_ALLOCATED) { const char *old_buffer = fb->ptr; @@ -117,6 +114,32 @@ static void fbuffer_do_inc_capa(FBuffer *fb, unsigned long requested) } } +static void fbuffer_do_inc_capa(FBuffer *fb, unsigned long requested) +{ + if (RB_UNLIKELY(fb->io)) { + if (fb->capa < FBUFFER_IO_BUFFER_SIZE) { + fbuffer_realloc(fb, FBUFFER_IO_BUFFER_SIZE); + } else { + fbuffer_flush(fb); + } + + if (RB_LIKELY(requested < fb->capa)) { + return; + } + } + + unsigned long required; + + if (RB_UNLIKELY(!fb->ptr)) { + fb->ptr = ALLOC_N(char, fb->initial_length); + fb->capa = fb->initial_length; + } + + for (required = fb->capa; requested > required - fb->len; required <<= 1); + + fbuffer_realloc(fb, required); +} + static inline void fbuffer_inc_capa(FBuffer *fb, unsigned long requested) { if (RB_UNLIKELY(requested > fb->capa - fb->len)) { @@ -174,11 +197,18 @@ static void fbuffer_append_long(FBuffer *fb, long number) fbuffer_append(fb, buffer_end - len, len); } -static VALUE fbuffer_to_s(FBuffer *fb) +static VALUE fbuffer_finalize(FBuffer *fb) { - VALUE result = rb_utf8_str_new(FBUFFER_PTR(fb), FBUFFER_LEN(fb)); - fbuffer_free(fb); - return result; + if (fb->io) { + fbuffer_flush(fb); + fbuffer_free(fb); + rb_io_flush(fb->io); + return fb->io; + } else { + VALUE result = rb_utf8_str_new(FBUFFER_PTR(fb), FBUFFER_LEN(fb)); + fbuffer_free(fb); + return result; + } } #endif #endif diff --git a/ext/json/ext/generator/generator.c b/ext/json/ext/generator/generator.c index c4f356ac..d5c8bfd4 100644 --- a/ext/json/ext/generator/generator.c +++ b/ext/json/ext/generator/generator.c @@ -54,7 +54,7 @@ struct generate_json_data { }; static VALUE cState_from_state_s(VALUE self, VALUE opts); -static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func); +static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func, VALUE io); static void generate_json(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj); static void generate_json_object(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj); static void generate_json_array(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj); @@ -71,6 +71,31 @@ static void generate_json_float(FBuffer *buffer, struct generate_json_data *data static int usascii_encindex, utf8_encindex, binary_encindex; +#ifdef RBIMPL_ATTR_NORETURN +RBIMPL_ATTR_NORETURN() +#endif +static void raise_generator_error_str(VALUE invalid_object, VALUE str) +{ + VALUE exc = rb_exc_new_str(eGeneratorError, str); + rb_ivar_set(exc, rb_intern("@invalid_object"), invalid_object); + rb_exc_raise(exc); +} + +#ifdef RBIMPL_ATTR_NORETURN +RBIMPL_ATTR_NORETURN() +#endif +#ifdef RBIMPL_ATTR_FORMAT +RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 3) +#endif +static void raise_generator_error(VALUE invalid_object, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + VALUE str = rb_vsprintf(fmt, args); + va_end(args); + raise_generator_error_str(invalid_object, str); +} + /* Converts in_string to a JSON string (without the wrapping '"' * characters) in FBuffer out_buffer. * @@ -130,7 +155,7 @@ static void convert_UTF8_to_JSON(FBuffer *out_buffer, VALUE str, const char esca } case 3: { unsigned char b2 = ptr[pos + 1]; - if (RB_UNLIKELY(out_script_safe && b2 == 0x80)) { + if (RB_UNLIKELY(out_script_safe && ch == 0xE2 && b2 == 0x80)) { unsigned char b3 = ptr[pos + 2]; if (b3 == 0xA8) { FLUSH_POS(3); @@ -453,7 +478,7 @@ static VALUE mHash_to_json(int argc, VALUE *argv, VALUE self) { 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); + return cState_partial_generate(Vstate, self, generate_json_object, Qfalse); } /* @@ -467,7 +492,7 @@ static VALUE mHash_to_json(int argc, VALUE *argv, VALUE self) static VALUE mArray_to_json(int argc, VALUE *argv, VALUE self) { 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); + return cState_partial_generate(Vstate, self, generate_json_array, Qfalse); } #ifdef RUBY_INTEGER_UNIFICATION @@ -480,7 +505,7 @@ static VALUE mInteger_to_json(int argc, VALUE *argv, VALUE self) { 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); + return cState_partial_generate(Vstate, self, generate_json_integer, Qfalse); } #else @@ -493,7 +518,7 @@ static VALUE mFixnum_to_json(int argc, VALUE *argv, VALUE self) { 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); + return cState_partial_generate(Vstate, self, generate_json_fixnum, Qfalse); } /* @@ -505,7 +530,7 @@ static VALUE mBignum_to_json(int argc, VALUE *argv, VALUE self) { 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); + return cState_partial_generate(Vstate, self, generate_json_bignum, Qfalse); } #endif @@ -518,7 +543,7 @@ static VALUE mFloat_to_json(int argc, VALUE *argv, VALUE self) { 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); + return cState_partial_generate(Vstate, self, generate_json_float, Qfalse); } /* @@ -543,7 +568,7 @@ static VALUE mString_to_json(int argc, VALUE *argv, VALUE self) { 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); + return cState_partial_generate(Vstate, self, generate_json_string, Qfalse); } /* @@ -638,7 +663,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, generate_json_string); + return cState_partial_generate(state, string, generate_json_string, Qfalse); } static void State_mark(void *ptr) @@ -867,6 +892,17 @@ static inline int enc_utf8_compatible_p(int enc_idx) return 0; } +static VALUE encode_json_string_try(VALUE str) +{ + return rb_funcall(str, i_encode, 1, Encoding_UTF_8); +} + +static VALUE encode_json_string_rescue(VALUE str, VALUE exception) +{ + raise_generator_error_str(str, rb_funcall(exception, rb_intern("message"), 0)); + return Qundef; +} + static inline VALUE ensure_valid_encoding(VALUE str) { int encindex = RB_ENCODING_GET(str); @@ -886,7 +922,7 @@ static inline VALUE ensure_valid_encoding(VALUE str) } } - str = rb_funcall(str, i_encode, 1, Encoding_UTF_8); + str = rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str); } return str; } @@ -909,7 +945,7 @@ static void generate_json_string(FBuffer *buffer, struct generate_json_data *dat } break; default: - rb_raise(rb_path2class("JSON::GeneratorError"), "source sequence is illegal/malformed utf-8"); + raise_generator_error(obj, "source sequence is illegal/malformed utf-8"); break; } fbuffer_append_char(buffer, '"'); @@ -957,10 +993,8 @@ static void generate_json_float(FBuffer *buffer, struct generate_json_data *data char allow_nan = state->allow_nan; VALUE tmp = rb_funcall(obj, i_to_s, 0); if (!allow_nan) { - if (isinf(value)) { - rb_raise(eGeneratorError, "%"PRIsVALUE" not allowed in JSON", tmp); - } else if (isnan(value)) { - rb_raise(eGeneratorError, "%"PRIsVALUE" not allowed in JSON", tmp); + if (isinf(value) || isnan(value)) { + raise_generator_error(obj, "%"PRIsVALUE" not allowed in JSON", tmp); } } fbuffer_append_str(buffer, tmp); @@ -1008,7 +1042,7 @@ static void generate_json(FBuffer *buffer, struct generate_json_data *data, JSON default: general: if (state->strict) { - rb_raise(eGeneratorError, "%"PRIsVALUE" not allowed in JSON", CLASS_OF(obj)); + raise_generator_error(obj, "%"PRIsVALUE" not allowed in JSON", CLASS_OF(obj)); } else if (rb_respond_to(obj, i_to_json)) { tmp = rb_funcall(obj, i_to_json, 1, vstate_get(data)); Check_Type(tmp, T_STRING); @@ -1036,21 +1070,19 @@ static VALUE generate_json_rescue(VALUE d, VALUE exc) struct generate_json_data *data = (struct generate_json_data *)d; fbuffer_free(data->buffer); - if (RBASIC_CLASS(exc) == rb_path2class("Encoding::UndefinedConversionError")) { - exc = rb_exc_new_str(eGeneratorError, rb_funcall(exc, rb_intern("message"), 0)); - } - rb_exc_raise(exc); return Qundef; } -static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func) +static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func, VALUE io) { GET_STATE(self); char stack_buffer[FBUFFER_STACK_SIZE]; - FBuffer buffer = {0}; + FBuffer buffer = { + .io = RTEST(io) ? io : Qfalse, + }; fbuffer_stack_init(&buffer, state->buffer_initial_length, stack_buffer, FBUFFER_STACK_SIZE); struct generate_json_data data = { @@ -1062,19 +1094,12 @@ static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func) }; rb_rescue(generate_json_try, (VALUE)&data, generate_json_rescue, (VALUE)&data); - return fbuffer_to_s(&buffer); + return fbuffer_finalize(&buffer); } -/* - * call-seq: generate(obj) - * - * Generates a valid JSON document from object +obj+ and returns the - * result. If no valid JSON document can be created this method raises a - * GeneratorError exception. - */ -static VALUE cState_generate(VALUE self, VALUE obj) +static VALUE cState_generate(VALUE self, VALUE obj, VALUE io) { - VALUE result = cState_partial_generate(self, obj, generate_json); + VALUE result = cState_partial_generate(self, obj, generate_json, io); GET_STATE(self); (void)state; return result; @@ -1502,14 +1527,16 @@ static VALUE cState_configure(VALUE self, VALUE opts) return self; } -static VALUE cState_m_generate(VALUE klass, VALUE obj, VALUE opts) +static VALUE cState_m_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io) { JSON_Generator_State state = {0}; state_init(&state); configure_state(&state, opts); char stack_buffer[FBUFFER_STACK_SIZE]; - FBuffer buffer = {0}; + FBuffer buffer = { + .io = RTEST(io) ? io : Qfalse, + }; fbuffer_stack_init(&buffer, state.buffer_initial_length, stack_buffer, FBUFFER_STACK_SIZE); struct generate_json_data data = { @@ -1521,7 +1548,7 @@ static VALUE cState_m_generate(VALUE klass, VALUE obj, VALUE opts) }; rb_rescue(generate_json_try, (VALUE)&data, generate_json_rescue, (VALUE)&data); - return fbuffer_to_s(&buffer); + return fbuffer_finalize(&buffer); } /* @@ -1540,10 +1567,11 @@ void Init_generator(void) VALUE mExt = rb_define_module_under(mJSON, "Ext"); VALUE mGenerator = rb_define_module_under(mExt, "Generator"); + rb_global_variable(&eGeneratorError); eGeneratorError = rb_path2class("JSON::GeneratorError"); + + rb_global_variable(&eNestingError); eNestingError = rb_path2class("JSON::NestingError"); - rb_gc_register_mark_object(eGeneratorError); - rb_gc_register_mark_object(eNestingError); cState = rb_define_class_under(mGenerator, "State", rb_cObject); rb_define_alloc_func(cState, cState_s_allocate); @@ -1583,9 +1611,9 @@ void Init_generator(void) rb_define_method(cState, "depth=", cState_depth_set, 1); rb_define_method(cState, "buffer_initial_length", cState_buffer_initial_length, 0); rb_define_method(cState, "buffer_initial_length=", cState_buffer_initial_length_set, 1); - rb_define_method(cState, "generate", cState_generate, 1); + rb_define_private_method(cState, "_generate", cState_generate, 2); - rb_define_singleton_method(cState, "generate", cState_m_generate, 2); + rb_define_singleton_method(cState, "generate", cState_m_generate, 3); VALUE mGeneratorMethods = rb_define_module_under(mGenerator, "GeneratorMethods"); diff --git a/ext/json/ext/parser/extconf.rb b/ext/json/ext/parser/extconf.rb index 4c1ac52a..7bbcc33f 100644 --- a/ext/json/ext/parser/extconf.rb +++ b/ext/json/ext/parser/extconf.rb @@ -3,7 +3,6 @@ have_func("rb_enc_interned_str", "ruby.h") # RUBY_VERSION >= 3.0 have_func("rb_hash_new_capa", "ruby.h") # RUBY_VERSION >= 3.2 -have_func("rb_gc_mark_locations", "ruby.h") # Missing on TruffleRuby have_func("rb_hash_bulk_insert", "ruby.h") # Missing on TruffleRuby have_func("rb_category_warn", "ruby.h") # Missing on TruffleRuby diff --git a/ext/json/ext/parser/parser.c b/ext/json/ext/parser/parser.c index b6252556..dff021bd 100644 --- a/ext/json/ext/parser/parser.c +++ b/ext/json/ext/parser/parser.c @@ -28,19 +28,6 @@ static const char deprecated_create_additions_warning[] = "and will be removed in 3.0, use JSON.unsafe_load or explicitly " "pass `create_additions: true`"; -#ifndef HAVE_RB_GC_MARK_LOCATIONS -// For TruffleRuby -void rb_gc_mark_locations(const VALUE *start, const VALUE *end) -{ - VALUE *value = start; - - while (value < end) { - rb_gc_mark(*value); - value++; - } -} -#endif - #ifndef HAVE_RB_HASH_BULK_INSERT // For TruffleRuby void rb_hash_bulk_insert(long count, const VALUE *pairs, VALUE hash) @@ -266,7 +253,10 @@ static inline void rvalue_stack_pop(rvalue_stack *stack, long count) static void rvalue_stack_mark(void *ptr) { rvalue_stack *stack = (rvalue_stack *)ptr; - rb_gc_mark_locations(stack->ptr, stack->ptr + stack->head); + long index; + for (index = 0; index < stack->head; index++) { + rb_gc_mark(stack->ptr[index]); + } } static void rvalue_stack_free(void *ptr) @@ -449,11 +439,11 @@ static void raise_parse_error(const char *format, const char *start) -#line 475 "parser.rl" +#line 465 "parser.rl" -#line 457 "parser.c" +#line 447 "parser.c" enum {JSON_object_start = 1}; enum {JSON_object_first_final = 32}; enum {JSON_object_error = 0}; @@ -461,7 +451,7 @@ enum {JSON_object_error = 0}; enum {JSON_object_en_main = 1}; -#line 515 "parser.rl" +#line 505 "parser.rl" #define PUSH(result) rvalue_stack_push(json->stack, result, &json->stack_handle, &json->stack) @@ -477,14 +467,14 @@ static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *resu long stack_head = json->stack->head; -#line 481 "parser.c" +#line 471 "parser.c" { cs = JSON_object_start; } -#line 530 "parser.rl" +#line 520 "parser.rl" -#line 488 "parser.c" +#line 478 "parser.c" { short _widec; if ( p == pe ) @@ -513,7 +503,7 @@ case 2: goto st2; goto st0; tr2: -#line 494 "parser.rl" +#line 484 "parser.rl" { char *np; json->parsing_name = true; @@ -529,7 +519,7 @@ case 2: if ( ++p == pe ) goto _test_eof3; case 3: -#line 533 "parser.c" +#line 523 "parser.c" switch( (*p) ) { case 13: goto st3; case 32: goto st3; @@ -596,7 +586,7 @@ case 8: goto st8; goto st0; tr11: -#line 483 "parser.rl" +#line 473 "parser.rl" { char *np = JSON_parse_value(json, p, pe, result, current_nesting); if (np == NULL) { @@ -610,20 +600,20 @@ case 8: if ( ++p == pe ) goto _test_eof9; case 9: -#line 614 "parser.c" +#line 604 "parser.c" _widec = (*p); if ( (*p) < 13 ) { if ( (*p) > 9 ) { if ( 10 <= (*p) && (*p) <= 10 ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 492 "parser.rl" +#line 482 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else if ( (*p) >= 9 ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 492 "parser.rl" +#line 482 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else if ( (*p) > 13 ) { @@ -631,26 +621,26 @@ case 9: if ( 32 <= (*p) && (*p) <= 32 ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 492 "parser.rl" +#line 482 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else if ( (*p) > 44 ) { if ( 47 <= (*p) && (*p) <= 47 ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 492 "parser.rl" +#line 482 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else { _widec = (short)(128 + ((*p) - -128)); if ( -#line 492 "parser.rl" +#line 482 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else { _widec = (short)(128 + ((*p) - -128)); if ( -#line 492 "parser.rl" +#line 482 "parser.rl" json->allow_trailing_comma ) _widec += 256; } switch( _widec ) { @@ -671,14 +661,14 @@ case 9: goto st10; goto st0; tr4: -#line 505 "parser.rl" +#line 495 "parser.rl" { p--; {p++; cs = 32; goto _out;} } goto st32; st32: if ( ++p == pe ) goto _test_eof32; case 32: -#line 682 "parser.c" +#line 672 "parser.c" goto st0; st10: if ( ++p == pe ) @@ -780,13 +770,13 @@ case 20: if ( 47 <= (*p) && (*p) <= 47 ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 492 "parser.rl" +#line 482 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else if ( (*p) >= 42 ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 492 "parser.rl" +#line 482 "parser.rl" json->allow_trailing_comma ) _widec += 256; } switch( _widec ) { @@ -805,20 +795,20 @@ case 21: if ( (*p) <= 41 ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 492 "parser.rl" +#line 482 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else if ( (*p) > 42 ) { if ( 43 <= (*p) ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 492 "parser.rl" +#line 482 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else { _widec = (short)(128 + ((*p) - -128)); if ( -#line 492 "parser.rl" +#line 482 "parser.rl" json->allow_trailing_comma ) _widec += 256; } switch( _widec ) { @@ -841,13 +831,13 @@ case 22: if ( 42 <= (*p) && (*p) <= 42 ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 492 "parser.rl" +#line 482 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else { _widec = (short)(128 + ((*p) - -128)); if ( -#line 492 "parser.rl" +#line 482 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else if ( (*p) > 46 ) { @@ -855,19 +845,19 @@ case 22: if ( 48 <= (*p) ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 492 "parser.rl" +#line 482 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else if ( (*p) >= 47 ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 492 "parser.rl" +#line 482 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else { _widec = (short)(128 + ((*p) - -128)); if ( -#line 492 "parser.rl" +#line 482 "parser.rl" json->allow_trailing_comma ) _widec += 256; } switch( _widec ) { @@ -891,20 +881,20 @@ case 23: if ( (*p) <= 9 ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 492 "parser.rl" +#line 482 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else if ( (*p) > 10 ) { if ( 11 <= (*p) ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 492 "parser.rl" +#line 482 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else { _widec = (short)(128 + ((*p) - -128)); if ( -#line 492 "parser.rl" +#line 482 "parser.rl" json->allow_trailing_comma ) _widec += 256; } switch( _widec ) { @@ -1018,7 +1008,7 @@ case 31: _out: {} } -#line 531 "parser.rl" +#line 521 "parser.rl" if (cs >= JSON_object_first_final) { long count = json->stack->head - stack_head; @@ -1069,7 +1059,7 @@ case 31: } -#line 1073 "parser.c" +#line 1063 "parser.c" enum {JSON_value_start = 1}; enum {JSON_value_first_final = 29}; enum {JSON_value_error = 0}; @@ -1077,7 +1067,7 @@ enum {JSON_value_error = 0}; enum {JSON_value_en_main = 1}; -#line 664 "parser.rl" +#line 654 "parser.rl" static char *JSON_parse_value(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting) @@ -1085,14 +1075,14 @@ static char *JSON_parse_value(JSON_Parser *json, char *p, char *pe, VALUE *resul int cs = EVIL; -#line 1089 "parser.c" +#line 1079 "parser.c" { cs = JSON_value_start; } -#line 671 "parser.rl" +#line 661 "parser.rl" -#line 1096 "parser.c" +#line 1086 "parser.c" { if ( p == pe ) goto _test_eof; @@ -1126,7 +1116,7 @@ case 1: cs = 0; goto _out; tr2: -#line 609 "parser.rl" +#line 599 "parser.rl" { char *np = JSON_parse_string(json, p, pe, result); if (np == NULL) { @@ -1138,7 +1128,7 @@ cs = 0; } goto st29; tr3: -#line 619 "parser.rl" +#line 609 "parser.rl" { char *np; if(pe > p + 8 && !strncmp(MinusInfinity, p, 9)) { @@ -1158,7 +1148,7 @@ cs = 0; } goto st29; tr7: -#line 637 "parser.rl" +#line 627 "parser.rl" { char *np; json->in_array++; @@ -1168,7 +1158,7 @@ cs = 0; } goto st29; tr11: -#line 645 "parser.rl" +#line 635 "parser.rl" { char *np; np = JSON_parse_object(json, p, pe, result, current_nesting + 1); @@ -1176,7 +1166,7 @@ cs = 0; } goto st29; tr25: -#line 602 "parser.rl" +#line 592 "parser.rl" { if (json->allow_nan) { *result = CInfinity; @@ -1186,7 +1176,7 @@ cs = 0; } goto st29; tr27: -#line 595 "parser.rl" +#line 585 "parser.rl" { if (json->allow_nan) { *result = CNaN; @@ -1196,19 +1186,19 @@ cs = 0; } goto st29; tr31: -#line 589 "parser.rl" +#line 579 "parser.rl" { *result = Qfalse; } goto st29; tr34: -#line 586 "parser.rl" +#line 576 "parser.rl" { *result = Qnil; } goto st29; tr37: -#line 592 "parser.rl" +#line 582 "parser.rl" { *result = Qtrue; } @@ -1217,9 +1207,9 @@ cs = 0; if ( ++p == pe ) goto _test_eof29; case 29: -#line 651 "parser.rl" +#line 641 "parser.rl" { p--; {p++; cs = 29; goto _out;} } -#line 1223 "parser.c" +#line 1213 "parser.c" switch( (*p) ) { case 13: goto st29; case 32: goto st29; @@ -1460,7 +1450,7 @@ case 28: _out: {} } -#line 672 "parser.rl" +#line 662 "parser.rl" if (json->freeze) { OBJ_FREEZE(*result); @@ -1475,7 +1465,7 @@ case 28: } -#line 1479 "parser.c" +#line 1469 "parser.c" enum {JSON_integer_start = 1}; enum {JSON_integer_first_final = 3}; enum {JSON_integer_error = 0}; @@ -1483,7 +1473,7 @@ enum {JSON_integer_error = 0}; enum {JSON_integer_en_main = 1}; -#line 693 "parser.rl" +#line 683 "parser.rl" #define MAX_FAST_INTEGER_SIZE 18 @@ -1523,7 +1513,7 @@ static char *JSON_decode_integer(JSON_Parser *json, char *p, VALUE *result) } -#line 1527 "parser.c" +#line 1517 "parser.c" enum {JSON_float_start = 1}; enum {JSON_float_first_final = 6}; enum {JSON_float_error = 0}; @@ -1531,7 +1521,7 @@ enum {JSON_float_error = 0}; enum {JSON_float_en_main = 1}; -#line 745 "parser.rl" +#line 735 "parser.rl" static char *JSON_parse_number(JSON_Parser *json, char *p, char *pe, VALUE *result) @@ -1540,15 +1530,15 @@ static char *JSON_parse_number(JSON_Parser *json, char *p, char *pe, VALUE *resu bool is_float = false; -#line 1544 "parser.c" +#line 1534 "parser.c" { cs = JSON_float_start; } -#line 753 "parser.rl" +#line 743 "parser.rl" json->memo = p; -#line 1552 "parser.c" +#line 1542 "parser.c" { if ( p == pe ) goto _test_eof; @@ -1588,24 +1578,24 @@ case 6: goto st0; goto tr7; tr7: -#line 737 "parser.rl" +#line 727 "parser.rl" { p--; {p++; cs = 7; goto _out;} } goto st7; st7: if ( ++p == pe ) goto _test_eof7; case 7: -#line 1599 "parser.c" +#line 1589 "parser.c" goto st0; tr8: -#line 738 "parser.rl" +#line 728 "parser.rl" { is_float = true; } goto st3; st3: if ( ++p == pe ) goto _test_eof3; case 3: -#line 1609 "parser.c" +#line 1599 "parser.c" if ( 48 <= (*p) && (*p) <= 57 ) goto st8; goto st0; @@ -1624,14 +1614,14 @@ case 8: goto st0; goto tr7; tr9: -#line 738 "parser.rl" +#line 728 "parser.rl" { is_float = true; } goto st4; st4: if ( ++p == pe ) goto _test_eof4; case 4: -#line 1635 "parser.c" +#line 1625 "parser.c" switch( (*p) ) { case 43: goto st5; case 45: goto st5; @@ -1688,7 +1678,7 @@ case 10: _out: {} } -#line 755 "parser.rl" +#line 745 "parser.rl" if (cs >= JSON_float_first_final) { if (!is_float) { @@ -1744,7 +1734,7 @@ case 10: -#line 1748 "parser.c" +#line 1738 "parser.c" enum {JSON_array_start = 1}; enum {JSON_array_first_final = 22}; enum {JSON_array_error = 0}; @@ -1752,7 +1742,7 @@ enum {JSON_array_error = 0}; enum {JSON_array_en_main = 1}; -#line 835 "parser.rl" +#line 825 "parser.rl" static char *JSON_parse_array(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting) @@ -1765,14 +1755,14 @@ static char *JSON_parse_array(JSON_Parser *json, char *p, char *pe, VALUE *resul long stack_head = json->stack->head; -#line 1769 "parser.c" +#line 1759 "parser.c" { cs = JSON_array_start; } -#line 847 "parser.rl" +#line 837 "parser.rl" -#line 1776 "parser.c" +#line 1766 "parser.c" { short _widec; if ( p == pe ) @@ -1812,7 +1802,7 @@ case 2: goto st2; goto st0; tr2: -#line 815 "parser.rl" +#line 805 "parser.rl" { VALUE v = Qnil; char *np = JSON_parse_value(json, p, pe, &v, current_nesting); @@ -1827,12 +1817,12 @@ case 2: if ( ++p == pe ) goto _test_eof3; case 3: -#line 1831 "parser.c" +#line 1821 "parser.c" _widec = (*p); if ( 44 <= (*p) && (*p) <= 44 ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 825 "parser.rl" +#line 815 "parser.rl" json->allow_trailing_comma ) _widec += 256; } switch( _widec ) { @@ -1879,14 +1869,14 @@ case 7: goto st3; goto st7; tr4: -#line 827 "parser.rl" +#line 817 "parser.rl" { p--; {p++; cs = 22; goto _out;} } goto st22; st22: if ( ++p == pe ) goto _test_eof22; case 22: -#line 1890 "parser.c" +#line 1880 "parser.c" goto st0; st8: if ( ++p == pe ) @@ -1954,13 +1944,13 @@ case 13: if ( 10 <= (*p) && (*p) <= 10 ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 825 "parser.rl" +#line 815 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else if ( (*p) >= 9 ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 825 "parser.rl" +#line 815 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else if ( (*p) > 13 ) { @@ -1968,19 +1958,19 @@ case 13: if ( 47 <= (*p) && (*p) <= 47 ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 825 "parser.rl" +#line 815 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else if ( (*p) >= 32 ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 825 "parser.rl" +#line 815 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else { _widec = (short)(128 + ((*p) - -128)); if ( -#line 825 "parser.rl" +#line 815 "parser.rl" json->allow_trailing_comma ) _widec += 256; } switch( _widec ) { @@ -2019,13 +2009,13 @@ case 14: if ( 47 <= (*p) && (*p) <= 47 ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 825 "parser.rl" +#line 815 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else if ( (*p) >= 42 ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 825 "parser.rl" +#line 815 "parser.rl" json->allow_trailing_comma ) _widec += 256; } switch( _widec ) { @@ -2044,20 +2034,20 @@ case 15: if ( (*p) <= 41 ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 825 "parser.rl" +#line 815 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else if ( (*p) > 42 ) { if ( 43 <= (*p) ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 825 "parser.rl" +#line 815 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else { _widec = (short)(128 + ((*p) - -128)); if ( -#line 825 "parser.rl" +#line 815 "parser.rl" json->allow_trailing_comma ) _widec += 256; } switch( _widec ) { @@ -2080,13 +2070,13 @@ case 16: if ( 42 <= (*p) && (*p) <= 42 ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 825 "parser.rl" +#line 815 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else { _widec = (short)(128 + ((*p) - -128)); if ( -#line 825 "parser.rl" +#line 815 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else if ( (*p) > 46 ) { @@ -2094,19 +2084,19 @@ case 16: if ( 48 <= (*p) ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 825 "parser.rl" +#line 815 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else if ( (*p) >= 47 ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 825 "parser.rl" +#line 815 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else { _widec = (short)(128 + ((*p) - -128)); if ( -#line 825 "parser.rl" +#line 815 "parser.rl" json->allow_trailing_comma ) _widec += 256; } switch( _widec ) { @@ -2130,20 +2120,20 @@ case 17: if ( (*p) <= 9 ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 825 "parser.rl" +#line 815 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else if ( (*p) > 10 ) { if ( 11 <= (*p) ) { _widec = (short)(128 + ((*p) - -128)); if ( -#line 825 "parser.rl" +#line 815 "parser.rl" json->allow_trailing_comma ) _widec += 256; } } else { _widec = (short)(128 + ((*p) - -128)); if ( -#line 825 "parser.rl" +#line 815 "parser.rl" json->allow_trailing_comma ) _widec += 256; } switch( _widec ) { @@ -2215,7 +2205,7 @@ case 21: _out: {} } -#line 848 "parser.rl" +#line 838 "parser.rl" if(cs >= JSON_array_first_final) { long count = json->stack->head - stack_head; @@ -2409,7 +2399,7 @@ static VALUE json_string_unescape(JSON_Parser *json, char *string, char *stringE } -#line 2413 "parser.c" +#line 2403 "parser.c" enum {JSON_string_start = 1}; enum {JSON_string_first_final = 9}; enum {JSON_string_error = 0}; @@ -2417,7 +2407,7 @@ enum {JSON_string_error = 0}; enum {JSON_string_en_main = 1}; -#line 1071 "parser.rl" +#line 1061 "parser.rl" static int @@ -2438,15 +2428,15 @@ static char *JSON_parse_string(JSON_Parser *json, char *p, char *pe, VALUE *resu VALUE match_string; -#line 2442 "parser.c" +#line 2432 "parser.c" { cs = JSON_string_start; } -#line 1091 "parser.rl" +#line 1081 "parser.rl" json->memo = p; -#line 2450 "parser.c" +#line 2440 "parser.c" { if ( p == pe ) goto _test_eof; @@ -2471,14 +2461,14 @@ case 2: goto st0; goto st2; tr2: -#line 1053 "parser.rl" +#line 1043 "parser.rl" { *result = json_string_fastpath(json, json->memo + 1, p, json->parsing_name, json->parsing_name || json-> freeze, json->parsing_name && json->symbolize_names); {p = (( p + 1))-1;} p--; {p++; cs = 9; goto _out;} } -#line 1046 "parser.rl" +#line 1036 "parser.rl" { *result = json_string_unescape(json, json->memo + 1, p, json->parsing_name, json->parsing_name || json-> freeze, json->parsing_name && json->symbolize_names); {p = (( p + 1))-1;} @@ -2487,7 +2477,7 @@ case 2: } goto st9; tr6: -#line 1046 "parser.rl" +#line 1036 "parser.rl" { *result = json_string_unescape(json, json->memo + 1, p, json->parsing_name, json->parsing_name || json-> freeze, json->parsing_name && json->symbolize_names); {p = (( p + 1))-1;} @@ -2499,7 +2489,7 @@ case 2: if ( ++p == pe ) goto _test_eof9; case 9: -#line 2503 "parser.c" +#line 2493 "parser.c" goto st0; st3: if ( ++p == pe ) @@ -2587,7 +2577,7 @@ case 8: _out: {} } -#line 1093 "parser.rl" +#line 1083 "parser.rl" if (json->create_additions && RTEST(match_string = json->match_string)) { VALUE klass; @@ -2740,7 +2730,7 @@ static VALUE cParser_initialize(int argc, VALUE *argv, VALUE self) } -#line 2744 "parser.c" +#line 2734 "parser.c" enum {JSON_start = 1}; enum {JSON_first_final = 10}; enum {JSON_error = 0}; @@ -2748,7 +2738,7 @@ enum {JSON_error = 0}; enum {JSON_en_main = 1}; -#line 1259 "parser.rl" +#line 1249 "parser.rl" /* @@ -2777,16 +2767,16 @@ static VALUE cParser_parse(VALUE self) json->stack = &stack; -#line 2781 "parser.c" +#line 2771 "parser.c" { cs = JSON_start; } -#line 1287 "parser.rl" +#line 1277 "parser.rl" p = json->source; pe = p + json->len; -#line 2790 "parser.c" +#line 2780 "parser.c" { if ( p == pe ) goto _test_eof; @@ -2820,7 +2810,7 @@ case 1: cs = 0; goto _out; tr2: -#line 1251 "parser.rl" +#line 1241 "parser.rl" { char *np = JSON_parse_value(json, p, pe, &result, 0); if (np == NULL) { p--; {p++; cs = 10; goto _out;} } else {p = (( np))-1;} @@ -2830,7 +2820,7 @@ cs = 0; if ( ++p == pe ) goto _test_eof10; case 10: -#line 2834 "parser.c" +#line 2824 "parser.c" switch( (*p) ) { case 13: goto st10; case 32: goto st10; @@ -2919,7 +2909,7 @@ case 9: _out: {} } -#line 1290 "parser.rl" +#line 1280 "parser.rl" if (json->stack_handle) { rvalue_stack_eagerly_release(json->stack_handle); @@ -2955,16 +2945,16 @@ static VALUE cParser_m_parse(VALUE klass, VALUE source, VALUE opts) json->stack = &stack; -#line 2959 "parser.c" +#line 2949 "parser.c" { cs = JSON_start; } -#line 1325 "parser.rl" +#line 1315 "parser.rl" p = json->source; pe = p + json->len; -#line 2968 "parser.c" +#line 2958 "parser.c" { if ( p == pe ) goto _test_eof; @@ -2998,7 +2988,7 @@ case 1: cs = 0; goto _out; tr2: -#line 1251 "parser.rl" +#line 1241 "parser.rl" { char *np = JSON_parse_value(json, p, pe, &result, 0); if (np == NULL) { p--; {p++; cs = 10; goto _out;} } else {p = (( np))-1;} @@ -3008,7 +2998,7 @@ cs = 0; if ( ++p == pe ) goto _test_eof10; case 10: -#line 3012 "parser.c" +#line 3002 "parser.c" switch( (*p) ) { case 13: goto st10; case 32: goto st10; @@ -3097,7 +3087,7 @@ case 9: _out: {} } -#line 1328 "parser.rl" +#line 1318 "parser.rl" if (json->stack_handle) { rvalue_stack_eagerly_release(json->stack_handle); @@ -3122,8 +3112,10 @@ static void JSON_mark(void *ptr) rb_gc_mark(json->match_string); rb_gc_mark(json->stack_handle); - const VALUE *name_cache_entries = &json->name_cache.entries[0]; - rb_gc_mark_locations(name_cache_entries, name_cache_entries + json->name_cache.length); + long index; + for (index = 0; index < json->name_cache.length; index++) { + rb_gc_mark(json->name_cache.entries[index]); + } } static void JSON_free(void *ptr) diff --git a/ext/json/ext/parser/parser.rl b/ext/json/ext/parser/parser.rl index eab60b91..ac949345 100644 --- a/ext/json/ext/parser/parser.rl +++ b/ext/json/ext/parser/parser.rl @@ -26,19 +26,6 @@ static const char deprecated_create_additions_warning[] = "and will be removed in 3.0, use JSON.unsafe_load or explicitly " "pass `create_additions: true`"; -#ifndef HAVE_RB_GC_MARK_LOCATIONS -// For TruffleRuby -void rb_gc_mark_locations(const VALUE *start, const VALUE *end) -{ - VALUE *value = start; - - while (value < end) { - rb_gc_mark(*value); - value++; - } -} -#endif - #ifndef HAVE_RB_HASH_BULK_INSERT // For TruffleRuby void rb_hash_bulk_insert(long count, const VALUE *pairs, VALUE hash) @@ -264,7 +251,10 @@ static inline void rvalue_stack_pop(rvalue_stack *stack, long count) static void rvalue_stack_mark(void *ptr) { rvalue_stack *stack = (rvalue_stack *)ptr; - rb_gc_mark_locations(stack->ptr, stack->ptr + stack->head); + long index; + for (index = 0; index < stack->head; index++) { + rb_gc_mark(stack->ptr[index]); + } } static void rvalue_stack_free(void *ptr) @@ -1349,8 +1339,10 @@ static void JSON_mark(void *ptr) rb_gc_mark(json->match_string); rb_gc_mark(json->stack_handle); - const VALUE *name_cache_entries = &json->name_cache.entries[0]; - rb_gc_mark_locations(name_cache_entries, name_cache_entries + json->name_cache.length); + long index; + for (index = 0; index < json->name_cache.length; index++) { + rb_gc_mark(json->name_cache.entries[index]); + } } static void JSON_free(void *ptr) diff --git a/java/src/json/ext/ByteListDirectOutputStream.java b/java/src/json/ext/ByteListDirectOutputStream.java new file mode 100644 index 00000000..178cf11c --- /dev/null +++ b/java/src/json/ext/ByteListDirectOutputStream.java @@ -0,0 +1,16 @@ +package json.ext; + +import org.jcodings.Encoding; +import org.jruby.util.ByteList; + +import java.io.ByteArrayOutputStream; + +public class ByteListDirectOutputStream extends ByteArrayOutputStream { + ByteListDirectOutputStream(int size) { + super(size); + } + + public ByteList toByteListDirect(Encoding encoding) { + return new ByteList(buf, 0, count, encoding, false); + } +} diff --git a/java/src/json/ext/ByteListTranscoder.java b/java/src/json/ext/ByteListTranscoder.java index 6f6ab66c..78d8037c 100644 --- a/java/src/json/ext/ByteListTranscoder.java +++ b/java/src/json/ext/ByteListTranscoder.java @@ -9,13 +9,14 @@ import org.jruby.runtime.ThreadContext; import org.jruby.util.ByteList; +import java.io.IOException; +import java.io.OutputStream; + /** * A class specialized in transcoding a certain String format into another, * using UTF-8 ByteLists as both input and output. */ abstract class ByteListTranscoder { - protected final ThreadContext context; - protected ByteList src; protected int srcEnd; /** Position where the last read character started */ @@ -23,7 +24,6 @@ abstract class ByteListTranscoder { /** Position of the next character to read */ protected int pos; - private ByteList out; /** * When a character that can be copied straight into the output is found, * its index is stored on this variable, and copying is delayed until @@ -33,20 +33,15 @@ abstract class ByteListTranscoder { */ private int quoteStart = -1; - protected ByteListTranscoder(ThreadContext context) { - this.context = context; + protected void init(ByteList src) { + this.init(src, 0, src.length()); } - protected void init(ByteList src, ByteList out) { - this.init(src, 0, src.length(), out); - } - - protected void init(ByteList src, int start, int end, ByteList out) { + protected void init(ByteList src, int start, int end) { this.src = src; this.pos = start; this.charStart = start; this.srcEnd = end; - this.out = out; } /** @@ -67,52 +62,57 @@ private char next() { * Reads an UTF-8 character from the input and returns its code point, * while advancing the input position. * - *

Raises an {@link #invalidUtf8()} exception if an invalid byte + *

Raises an {@link #invalidUtf8(ThreadContext)} exception if an invalid byte * is found. */ - protected int readUtf8Char() { + protected int readUtf8Char(ThreadContext context) { charStart = pos; char head = next(); if (head <= 0x7f) { // 0b0xxxxxxx (ASCII) return head; } if (head <= 0xbf) { // 0b10xxxxxx - throw invalidUtf8(); // tail byte with no head + throw invalidUtf8(context); // tail byte with no head } if (head <= 0xdf) { // 0b110xxxxx - ensureMin(1); + ensureMin(context, 1); int cp = ((head & 0x1f) << 6) - | nextPart(); - if (cp < 0x0080) throw invalidUtf8(); + | nextPart(context); + if (cp < 0x0080) throw invalidUtf8(context); return cp; } if (head <= 0xef) { // 0b1110xxxx - ensureMin(2); + ensureMin(context, 2); int cp = ((head & 0x0f) << 12) - | (nextPart() << 6) - | nextPart(); - if (cp < 0x0800) throw invalidUtf8(); + | (nextPart(context) << 6) + | nextPart(context); + if (cp < 0x0800) throw invalidUtf8(context); return cp; } if (head <= 0xf7) { // 0b11110xxx - ensureMin(3); + ensureMin(context, 3); int cp = ((head & 0x07) << 18) - | (nextPart() << 12) - | (nextPart() << 6) - | nextPart(); - if (!Character.isValidCodePoint(cp)) throw invalidUtf8(); + | (nextPart(context) << 12) + | (nextPart(context) << 6) + | nextPart(context); + if (!Character.isValidCodePoint(cp)) throw invalidUtf8(context); return cp; } // 0b11111xxx? - throw invalidUtf8(); + throw invalidUtf8(context); + } + + protected int readASCIIChar() { + charStart = pos; + return next(); } /** * Throws a GeneratorError if the input list doesn't have at least this * many bytes left. */ - protected void ensureMin(int n) { - if (pos + n > srcEnd) throw incompleteUtf8(); + protected void ensureMin(ThreadContext context, int n) { + if (pos + n > srcEnd) throw incompleteUtf8(context); } /** @@ -121,10 +121,10 @@ protected void ensureMin(int n) { * *

Throws a GeneratorError if the byte is not a valid tail. */ - private int nextPart() { + private int nextPart(ThreadContext context) { char c = next(); // tail bytes must be 0b10xxxxxx - if ((c & 0xc0) != 0x80) throw invalidUtf8(); + if ((c & 0xc0) != 0x80) throw invalidUtf8(context); return c & 0x3f; } @@ -142,25 +142,21 @@ protected void quoteStart() { * recently read character, or {@link #charStart} to quote * until the character before it. */ - protected void quoteStop(int endPos) { + protected void quoteStop(int endPos) throws IOException { if (quoteStart != -1) { - out.append(src, quoteStart, endPos - quoteStart); + append(src.unsafeBytes(), src.begin() + quoteStart, endPos - quoteStart); quoteStart = -1; } } - protected void append(int b) { - out.append(b); - } + protected abstract void append(int b) throws IOException; - protected void append(byte[] origin, int start, int length) { - out.append(origin, start, length); - } + protected abstract void append(byte[] origin, int start, int length) throws IOException; - protected abstract RaiseException invalidUtf8(); + protected abstract RaiseException invalidUtf8(ThreadContext context); - protected RaiseException incompleteUtf8() { - return invalidUtf8(); + protected RaiseException incompleteUtf8(ThreadContext context) { + return invalidUtf8(context); } } diff --git a/java/src/json/ext/Generator.java b/java/src/json/ext/Generator.java index f76dcb38..4ab92805 100644 --- a/java/src/json/ext/Generator.java +++ b/java/src/json/ext/Generator.java @@ -5,21 +5,43 @@ */ package json.ext; +import org.jcodings.Encoding; +import org.jcodings.specific.ASCIIEncoding; +import org.jcodings.specific.USASCIIEncoding; +import org.jcodings.specific.UTF8Encoding; import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyBasicObject; import org.jruby.RubyBignum; import org.jruby.RubyBoolean; +import org.jruby.RubyClass; import org.jruby.RubyFixnum; import org.jruby.RubyFloat; import org.jruby.RubyHash; import org.jruby.RubyString; +import org.jruby.RubySymbol; +import org.jruby.RubyException; +import org.jruby.runtime.Helpers; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.util.ByteList; import org.jruby.exceptions.RaiseException; +import org.jruby.util.ConvertBytes; +import org.jruby.util.IOOutputStream; +import org.jruby.util.StringSupport; +import org.jruby.util.TypeConverter; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; + +import static java.nio.charset.StandardCharsets.*; public final class Generator { + + private static final int IO_BUFFER_SIZE = 8192; + private Generator() { throw new RuntimeException(); } @@ -27,33 +49,47 @@ private Generator() { /** * Encodes the given object as a JSON string, using the given handler. */ - static RubyString - generateJson(ThreadContext context, T object, - Handler handler, IRubyObject[] args) { - Session session = new Session(context, args.length > 0 ? args[0] : null); - return session.infect(handler.generateNew(session, object)); + static RubyString generateJson(ThreadContext context, T object, Handler handler) { + Session session = new Session(null); + return handler.generateNew(context, session, object); + } + + static RubyString generateJson(ThreadContext context, T object, Handler handler, IRubyObject arg0) { + Session session = new Session(arg0); + return handler.generateNew(context, session, object); } /** * Encodes the given object as a JSON string, detecting the appropriate handler * for the given object. */ - static RubyString - generateJson(ThreadContext context, T object, IRubyObject[] args) { + static RubyString generateJson(ThreadContext context, T object) { Handler handler = getHandlerFor(context.runtime, object); - return generateJson(context, object, handler, args); + return generateJson(context, object, handler); + } + + static RubyString generateJson(ThreadContext context, T object, IRubyObject arg0) { + Handler handler = getHandlerFor(context.runtime, object); + return generateJson(context, object, handler, arg0); } /** * Encodes the given object as a JSON string, using the appropriate * handler if one is found or calling #to_json if not. */ - public static RubyString + public static IRubyObject generateJson(ThreadContext context, T object, - GeneratorState config) { - Session session = new Session(context, config); + GeneratorState config, IRubyObject io) { + Session session = new Session(config); Handler handler = getHandlerFor(context.runtime, object); - return handler.generateNew(session, object); + + if (io.isNil()) { + return handler.generateNew(context, session, object); + } + + BufferedOutputStream buffer = new BufferedOutputStream(new IOOutputStream(io), IO_BUFFER_SIZE); + handler.generateToBuffer(context, session, object, buffer); + return io; } /** @@ -64,21 +100,21 @@ private Generator() { @SuppressWarnings("unchecked") private static Handler getHandlerFor(Ruby runtime, T object) { switch (((RubyBasicObject) object).getNativeClassIndex()) { - case NIL : return (Handler) NIL_HANDLER; - case TRUE : return (Handler) TRUE_HANDLER; - case FALSE : return (Handler) FALSE_HANDLER; - case FLOAT : return (Handler) FLOAT_HANDLER; - case FIXNUM : return (Handler) FIXNUM_HANDLER; - case BIGNUM : return (Handler) BIGNUM_HANDLER; + case NIL : return NIL_HANDLER; + case TRUE : return (Handler) TRUE_HANDLER; + case FALSE : return (Handler) FALSE_HANDLER; + case FLOAT : return (Handler) FLOAT_HANDLER; + case FIXNUM : return (Handler) FIXNUM_HANDLER; + case BIGNUM : return (Handler) BIGNUM_HANDLER; case STRING : - if (((RubyBasicObject) object).getMetaClass() != runtime.getString()) break; - return (Handler) STRING_HANDLER; + if (Helpers.metaclass(object) != runtime.getString()) break; + return (Handler) STRING_HANDLER; case ARRAY : - if (((RubyBasicObject) object).getMetaClass() != runtime.getArray()) break; - return (Handler) ARRAY_HANDLER; + if (Helpers.metaclass(object) != runtime.getArray()) break; + return (Handler) ARRAY_HANDLER; case HASH : - if (((RubyBasicObject) object).getMetaClass() != runtime.getHash()) break; - return (Handler) HASH_HANDLER; + if (Helpers.metaclass(object) != runtime.getHash()) break; + return (Handler) HASH_HANDLER; } return GENERIC_HANDLER; } @@ -94,67 +130,43 @@ private static Handler getHandlerFor(Ruby run * object; any handler directly called by container handlers (arrays and * hashes/objects) shares this object with its caller. * - *

Note that anything called indirectly (via {@link GENERIC_HANDLER}) + *

Note that anything called indirectly (via {@link #GENERIC_HANDLER}) * won't be part of the session. */ static class Session { - private final ThreadContext context; private GeneratorState state; private IRubyObject possibleState; private RuntimeInfo info; private StringEncoder stringEncoder; - private boolean tainted = false; - private boolean untrusted = false; - - Session(ThreadContext context, GeneratorState state) { - this.context = context; + Session(GeneratorState state) { this.state = state; } - Session(ThreadContext context, IRubyObject possibleState) { - this.context = context; + Session(IRubyObject possibleState) { this.possibleState = possibleState == null || possibleState.isNil() ? null : possibleState; } - public ThreadContext getContext() { - return context; - } - - public Ruby getRuntime() { - return context.getRuntime(); - } - - public GeneratorState getState() { + public GeneratorState getState(ThreadContext context) { if (state == null) { - state = GeneratorState.fromState(context, getInfo(), possibleState); + state = GeneratorState.fromState(context, getInfo(context), possibleState); } return state; } - public RuntimeInfo getInfo() { - if (info == null) info = RuntimeInfo.forRuntime(getRuntime()); + public RuntimeInfo getInfo(ThreadContext context) { + if (info == null) info = RuntimeInfo.forRuntime(context.runtime); return info; } - public StringEncoder getStringEncoder() { + public StringEncoder getStringEncoder(ThreadContext context) { if (stringEncoder == null) { - stringEncoder = new StringEncoder(context, getState().asciiOnly(), getState().scriptSafe()); + GeneratorState state = getState(context); + stringEncoder = new StringEncoder(state.asciiOnly(), state.scriptSafe()); } return stringEncoder; } - - public void infectBy(IRubyObject object) { - if (object.isTaint()) tainted = true; - if (object.isUntrusted()) untrusted = true; - } - - public T infect(T object) { - if (tainted) object.setTaint(true); - if (untrusted) object.setUntrusted(true); - return object; - } } @@ -166,22 +178,26 @@ private static abstract class Handler { * given object will take. Used for allocating enough buffer space * before invoking other methods. */ - int guessSize(Session session, T object) { + int guessSize(ThreadContext context, Session session, T object) { return 4; } - RubyString generateNew(Session session, T object) { - RubyString result; - ByteList buffer = new ByteList(guessSize(session, object)); - generate(session, object, buffer); - result = RubyString.newString(session.getRuntime(), buffer); - ThreadContext context = session.getContext(); - RuntimeInfo info = session.getInfo(); - result.force_encoding(context, info.utf8.get()); - return result; + RubyString generateNew(ThreadContext context, Session session, T object) { + ByteListDirectOutputStream buffer = new ByteListDirectOutputStream(guessSize(context, session, object)); + generateToBuffer(context, session, object, buffer); + return RubyString.newString(context.runtime, buffer.toByteListDirect(UTF8Encoding.INSTANCE)); } - abstract void generate(Session session, T object, ByteList buffer); + void generateToBuffer(ThreadContext context, Session session, T object, OutputStream buffer) { + try { + generate(context, session, object, buffer); + buffer.flush(); + } catch (IOException ioe) { + throw context.runtime.newIOErrorFromException(ioe); + } + } + + abstract void generate(ThreadContext context, Session session, T object, OutputStream buffer) throws IOException; } /** @@ -189,25 +205,25 @@ RubyString generateNew(Session session, T object) { */ private static class KeywordHandler extends Handler { - private final ByteList keyword; + private final byte[] keyword; private KeywordHandler(String keyword) { - this.keyword = new ByteList(ByteList.plain(keyword), false); + this.keyword = keyword.getBytes(UTF_8); } @Override - int guessSize(Session session, T object) { - return keyword.length(); + int guessSize(ThreadContext context, Session session, T object) { + return keyword.length; } @Override - RubyString generateNew(Session session, T object) { - return RubyString.newStringShared(session.getRuntime(), keyword); + RubyString generateNew(ThreadContext context, Session session, T object) { + return RubyString.newStringShared(context.runtime, keyword); } @Override - void generate(Session session, T object, ByteList buffer) { - buffer.append(keyword); + void generate(ThreadContext context, Session session, T object, OutputStream buffer) throws IOException { + buffer.write(keyword); } } @@ -217,44 +233,42 @@ void generate(Session session, T object, ByteList buffer) { static final Handler BIGNUM_HANDLER = new Handler() { @Override - void generate(Session session, RubyBignum object, ByteList buffer) { - // JRUBY-4751: RubyBignum.to_s() returns generic object - // representation (fixed in 1.5, but we maintain backwards - // compatibility; call to_s(IRubyObject[]) then - buffer.append(((RubyString)object.to_s(IRubyObject.NULL_ARRAY)).getByteList()); + void generate(ThreadContext context, Session session, RubyBignum object, OutputStream buffer) throws IOException { + BigInteger bigInt = object.getValue(); + buffer.write(bigInt.toString().getBytes(UTF_8)); } }; static final Handler FIXNUM_HANDLER = new Handler() { @Override - void generate(Session session, RubyFixnum object, ByteList buffer) { - buffer.append(object.to_s().getByteList()); + void generate(ThreadContext context, Session session, RubyFixnum object, OutputStream buffer) throws IOException { + buffer.write(ConvertBytes.longToCharBytes(object.getLongValue())); } }; static final Handler FLOAT_HANDLER = new Handler() { @Override - void generate(Session session, RubyFloat object, ByteList buffer) { - double value = RubyFloat.num2dbl(object); + void generate(ThreadContext context, Session session, RubyFloat object, OutputStream buffer) throws IOException { + double value = object.getValue(); if (Double.isInfinite(value) || Double.isNaN(value)) { - if (!session.getState().allowNaN()) { - throw Utils.newException(session.getContext(), - Utils.M_GENERATOR_ERROR, - object + " not allowed in JSON"); + if (!session.getState(context).allowNaN()) { + throw Utils.buildGeneratorError(context, object, object + " not allowed in JSON").toThrowable(); } } - buffer.append(((RubyString)object.to_s()).getByteList()); + + buffer.write(Double.toString(value).getBytes(UTF_8)); } }; - static final Handler ARRAY_HANDLER = - new Handler() { + private static final byte[] EMPTY_ARRAY_BYTES = "[]".getBytes(); + static final Handler> ARRAY_HANDLER = + new Handler>() { @Override - int guessSize(Session session, RubyArray object) { - GeneratorState state = session.getState(); + int guessSize(ThreadContext context, Session session, RubyArray object) { + GeneratorState state = session.getState(context); int depth = state.getDepth(); int perItem = 4 // prealloc @@ -264,18 +278,18 @@ int guessSize(Session session, RubyArray object) { } @Override - void generate(Session session, RubyArray object, ByteList buffer) { - ThreadContext context = session.getContext(); - Ruby runtime = context.getRuntime(); - GeneratorState state = session.getState(); - int depth = state.increaseDepth(); + void generate(ThreadContext context, Session session, RubyArray object, OutputStream buffer) throws IOException { + GeneratorState state = session.getState(context); + int depth = state.increaseDepth(context); if (object.isEmpty()) { - buffer.append("[]".getBytes()); + buffer.write(EMPTY_ARRAY_BYTES); state.decreaseDepth(); return; } + Ruby runtime = context.runtime; + ByteList indentUnit = state.getIndent(); byte[] shift = Utils.repeat(indentUnit, depth); @@ -285,39 +299,38 @@ void generate(Session session, RubyArray object, ByteList buffer) { System.arraycopy(arrayNl.unsafeBytes(), arrayNl.begin(), delim, 1, arrayNl.length()); - session.infectBy(object); - - buffer.append((byte)'['); - buffer.append(arrayNl); + buffer.write((byte)'['); + buffer.write(arrayNl.bytes()); boolean firstItem = true; + for (int i = 0, t = object.getLength(); i < t; i++) { IRubyObject element = object.eltInternal(i); - session.infectBy(element); if (firstItem) { firstItem = false; } else { - buffer.append(delim); + buffer.write(delim); } - buffer.append(shift); - Handler handler = (Handler) getHandlerFor(runtime, element); - handler.generate(session, element, buffer); + buffer.write(shift); + Handler handler = getHandlerFor(runtime, element); + handler.generate(context, session, element, buffer); } state.decreaseDepth(); - if (arrayNl.length() != 0) { - buffer.append(arrayNl); - buffer.append(shift, 0, state.getDepth() * indentUnit.length()); + if (!arrayNl.isEmpty()) { + buffer.write(arrayNl.bytes()); + buffer.write(shift, 0, state.getDepth() * indentUnit.length()); } - buffer.append((byte)']'); + buffer.write((byte)']'); } }; + private static final byte[] EMPTY_HASH_BYTES = "{}".getBytes(); static final Handler HASH_HANDLER = new Handler() { @Override - int guessSize(Session session, RubyHash object) { - GeneratorState state = session.getState(); + int guessSize(ThreadContext context, Session session, RubyHash object) { + GeneratorState state = session.getState(context); int perItem = 12 // key, colon, comma + (state.getDepth() + 1) * state.getIndent().length() @@ -327,71 +340,86 @@ int guessSize(Session session, RubyHash object) { } @Override - void generate(final Session session, RubyHash object, - final ByteList buffer) { - ThreadContext context = session.getContext(); - final Ruby runtime = context.getRuntime(); - final GeneratorState state = session.getState(); - final int depth = state.increaseDepth(); + void generate(ThreadContext context, final Session session, RubyHash object, final OutputStream buffer) throws IOException { + final GeneratorState state = session.getState(context); + final int depth = state.increaseDepth(context); if (object.isEmpty()) { - buffer.append("{}".getBytes()); + buffer.write(EMPTY_HASH_BYTES); state.decreaseDepth(); return; } final ByteList objectNl = state.getObjectNl(); + byte[] objectNLBytes = objectNl.unsafeBytes(); final byte[] indent = Utils.repeat(state.getIndent(), depth); final ByteList spaceBefore = state.getSpaceBefore(); final ByteList space = state.getSpace(); - buffer.append((byte)'{'); - buffer.append(objectNl); + buffer.write((byte)'{'); + buffer.write(objectNLBytes); final boolean[] firstPair = new boolean[]{true}; - object.visitAll(new RubyHash.Visitor() { + object.visitAll(context, new RubyHash.VisitorWithState() { @Override - public void visit(IRubyObject key, IRubyObject value) { - if (firstPair[0]) { - firstPair[0] = false; - } else { - buffer.append((byte)','); - buffer.append(objectNl); - } - if (objectNl.length() != 0) buffer.append(indent); - - IRubyObject keyStr = key.callMethod(context, "to_s"); - if (keyStr.getMetaClass() == runtime.getString()) { - STRING_HANDLER.generate(session, (RubyString)keyStr, buffer); - } else { - Utils.ensureString(keyStr); - Handler keyHandler = (Handler) getHandlerFor(runtime, keyStr); - keyHandler.generate(session, keyStr, buffer); + public void visit(ThreadContext context, RubyHash self, IRubyObject key, IRubyObject value, int index, boolean[] firstPair) { + try { + if (firstPair[0]) { + firstPair[0] = false; + } else { + buffer.write((byte) ','); + buffer.write(objectNLBytes); + } + if (!objectNl.isEmpty()) buffer.write(indent); + + Ruby runtime = context.runtime; + + IRubyObject keyStr; + RubyClass keyClass = key.getType(); + if (key instanceof RubyString) { + if (keyClass == runtime.getString()) { + keyStr = key; + } else { + keyStr = key.callMethod(context, "to_s"); + } + } else if (keyClass == runtime.getSymbol()) { + keyStr = key.asString(); + } else { + keyStr = TypeConverter.convertToType(key, runtime.getString(), "to_s"); + } + + if (keyStr.getMetaClass() == runtime.getString()) { + STRING_HANDLER.generate(context, session, (RubyString) keyStr, buffer); + } else { + Utils.ensureString(keyStr); + Handler keyHandler = getHandlerFor(runtime, keyStr); + keyHandler.generate(context, session, keyStr, buffer); + } + + buffer.write(spaceBefore.unsafeBytes()); + buffer.write((byte) ':'); + buffer.write(space.unsafeBytes()); + + Handler valueHandler = getHandlerFor(runtime, value); + valueHandler.generate(context, session, value, buffer); + } catch (Throwable t) { + Helpers.throwException(t); } - session.infectBy(key); - - buffer.append(spaceBefore); - buffer.append((byte)':'); - buffer.append(space); - - Handler valueHandler = (Handler) getHandlerFor(runtime, value); - valueHandler.generate(session, value, buffer); - session.infectBy(value); } - }); + }, firstPair); state.decreaseDepth(); - if (!firstPair[0] && objectNl.length() != 0) { - buffer.append(objectNl); + if (!firstPair[0] && !objectNl.isEmpty()) { + buffer.write(objectNLBytes); } - buffer.append(Utils.repeat(state.getIndent(), state.getDepth())); - buffer.append((byte)'}'); + buffer.write(Utils.repeat(state.getIndent(), state.getDepth())); + buffer.write((byte)'}'); } }; static final Handler STRING_HANDLER = new Handler() { @Override - int guessSize(Session session, RubyString object) { + int guessSize(ThreadContext context, Session session, RubyString object) { // for most applications, most strings will be just a set of // printable ASCII characters without any escaping, so let's // just allocate enough space for that + the quotes @@ -399,32 +427,59 @@ int guessSize(Session session, RubyString object) { } @Override - void generate(Session session, RubyString object, ByteList buffer) { - RuntimeInfo info = session.getInfo(); - RubyString src; - + void generate(ThreadContext context, Session session, RubyString object, OutputStream buffer) throws IOException { try { - if (object.encoding(session.getContext()) != info.utf8.get()) { - src = (RubyString)object.encode(session.getContext(), - info.utf8.get()); - } else { - src = object; - } - - session.getStringEncoder().encode(src.getByteList(), buffer); + object = ensureValidEncoding(context, object); } catch (RaiseException re) { - throw Utils.newException(session.getContext(), Utils.M_GENERATOR_ERROR, - re.getMessage()); + RubyException exc = Utils.buildGeneratorError(context, object, re.getMessage()); + exc.setCause(re.getException()); + throw exc.toThrowable(); + } + + StringEncoder stringEncoder = session.getStringEncoder(context); + ByteList byteList = object.getByteList(); + switch (object.scanForCodeRange()) { + case StringSupport.CR_7BIT: + stringEncoder.encodeASCII(context, byteList, buffer); + break; + case StringSupport.CR_VALID: + stringEncoder.encode(context, byteList, buffer); + break; + default: + throw Utils.buildGeneratorError(context, object, "source sequence is illegal/malformed utf-8").toThrowable(); } } }; + static RubyString ensureValidEncoding(ThreadContext context, RubyString str) { + Encoding encoding = str.getEncoding(); + RubyString utf8String; + if (!(encoding == USASCIIEncoding.INSTANCE || encoding == UTF8Encoding.INSTANCE)) { + if (encoding == ASCIIEncoding.INSTANCE) { + utf8String = str.strDup(context.runtime); + utf8String.setEncoding(UTF8Encoding.INSTANCE); + switch (utf8String.getCodeRange()) { + case StringSupport.CR_7BIT: + return utf8String; + case StringSupport.CR_VALID: + // For historical reason, we silently reinterpret binary strings as UTF-8 if it would work. + // TODO: Raise in 3.0.0 + context.runtime.getWarnings().warn("JSON.generate: UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0"); + return utf8String; + } + } + + str = (RubyString) str.encode(context, context.runtime.getEncodingService().convertEncodingToRubyEncoding(UTF8Encoding.INSTANCE)); + } + return str; + } + static final Handler TRUE_HANDLER = - new KeywordHandler("true"); + new KeywordHandler<>("true"); static final Handler FALSE_HANDLER = - new KeywordHandler("false"); + new KeywordHandler<>("false"); static final Handler NIL_HANDLER = - new KeywordHandler("null"); + new KeywordHandler<>("null"); /** * The default handler (Object#to_json): coerces the object @@ -433,15 +488,15 @@ void generate(Session session, RubyString object, ByteList buffer) { static final Handler OBJECT_HANDLER = new Handler() { @Override - RubyString generateNew(Session session, IRubyObject object) { + RubyString generateNew(ThreadContext context, Session session, IRubyObject object) { RubyString str = object.asString(); - return STRING_HANDLER.generateNew(session, str); + return STRING_HANDLER.generateNew(context, session, str); } @Override - void generate(Session session, IRubyObject object, ByteList buffer) { + void generate(ThreadContext context, Session session, IRubyObject object, OutputStream buffer) throws IOException { RubyString str = object.asString(); - STRING_HANDLER.generate(session, str, buffer); + STRING_HANDLER.generate(context, session, str, buffer); } }; @@ -452,25 +507,24 @@ void generate(Session session, IRubyObject object, ByteList buffer) { static final Handler GENERIC_HANDLER = new Handler() { @Override - RubyString generateNew(Session session, IRubyObject object) { - if (session.getState().strict()) { - throw Utils.newException(session.getContext(), - Utils.M_GENERATOR_ERROR, - object + " not allowed in JSON"); + RubyString generateNew(ThreadContext context, Session session, IRubyObject object) { + GeneratorState state = session.getState(context); + if (state.strict()) { + throw Utils.buildGeneratorError(context, object, object + " not allowed in JSON").toThrowable(); } else if (object.respondsTo("to_json")) { - IRubyObject result = object.callMethod(session.getContext(), "to_json", - new IRubyObject[] {session.getState()}); + IRubyObject result = object.callMethod(context, "to_json", state); if (result instanceof RubyString) return (RubyString)result; - throw session.getRuntime().newTypeError("to_json must return a String"); + throw context.runtime.newTypeError("to_json must return a String"); } else { - return OBJECT_HANDLER.generateNew(session, object); + return OBJECT_HANDLER.generateNew(context, session, object); } } @Override - void generate(Session session, IRubyObject object, ByteList buffer) { - RubyString result = generateNew(session, object); - buffer.append(result.getByteList()); + void generate(ThreadContext context, Session session, IRubyObject object, OutputStream buffer) throws IOException { + RubyString result = generateNew(context, session, object); + ByteList bytes = result.getByteList(); + buffer.write(bytes.unsafeBytes(), bytes.begin(), bytes.length()); } }; } diff --git a/java/src/json/ext/GeneratorMethods.java b/java/src/json/ext/GeneratorMethods.java index bde7a18d..eb31b9ef 100644 --- a/java/src/json/ext/GeneratorMethods.java +++ b/java/src/json/ext/GeneratorMethods.java @@ -12,7 +12,6 @@ import org.jruby.RubyFixnum; import org.jruby.RubyFloat; import org.jruby.RubyHash; -import org.jruby.RubyInteger; import org.jruby.RubyModule; import org.jruby.RubyNumeric; import org.jruby.RubyString; @@ -30,8 +29,8 @@ class GeneratorMethods { /** * Populates the given module with all modules and their methods - * @param info - * @param generatorMethodsModule The module to populate + * @param info The current RuntimeInfo + * @param module The module to populate * (normally JSON::Generator::GeneratorMethods) */ static void populate(RuntimeInfo info, RubyModule module) { @@ -45,65 +44,79 @@ static void populate(RuntimeInfo info, RubyModule module) { defineMethods(module, "String", RbString.class); defineMethods(module, "TrueClass", RbTrue.class); - info.stringExtendModule = new WeakReference(module.defineModuleUnder("String") - .defineModuleUnder("Extend")); - info.stringExtendModule.get().defineAnnotatedMethods(StringExtend.class); + RubyModule stringExtend = module.defineModuleUnder("String").defineModuleUnder("Extend"); + stringExtend.defineAnnotatedMethods(StringExtend.class); } /** * Convenience method for defining methods on a submodule. - * @param parentModule - * @param submoduleName - * @param klass + * @param parentModule the parent module + * @param submoduleName the submodule + * @param klass the class from which to define methods */ private static void defineMethods(RubyModule parentModule, - String submoduleName, Class klass) { + String submoduleName, Class klass) { RubyModule submodule = parentModule.defineModuleUnder(submoduleName); submodule.defineAnnotatedMethods(klass); } - public static class RbHash { - @JRubyMethod(rest=true) - public static IRubyObject to_json(ThreadContext context, - IRubyObject vSelf, IRubyObject[] args) { - return Generator.generateJson(context, (RubyHash)vSelf, - Generator.HASH_HANDLER, args); + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf) { + return Generator.generateJson(context, (RubyHash)vSelf, Generator.HASH_HANDLER); + } + + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf, IRubyObject arg0) { + return Generator.generateJson(context, (RubyHash)vSelf, Generator.HASH_HANDLER, arg0); } } public static class RbArray { - @JRubyMethod(rest=true) - public static IRubyObject to_json(ThreadContext context, - IRubyObject vSelf, IRubyObject[] args) { - return Generator.generateJson(context, (RubyArray)vSelf, - Generator.ARRAY_HANDLER, args); + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf) { + return Generator.generateJson(context, (RubyArray)vSelf, Generator.ARRAY_HANDLER); + } + + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf, IRubyObject arg0) { + return Generator.generateJson(context, (RubyArray)vSelf, Generator.ARRAY_HANDLER, arg0); } } public static class RbInteger { - @JRubyMethod(rest=true) - public static IRubyObject to_json(ThreadContext context, - IRubyObject vSelf, IRubyObject[] args) { - return Generator.generateJson(context, vSelf, args); + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf) { + return Generator.generateJson(context, vSelf); + } + + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf, IRubyObject arg0) { + return Generator.generateJson(context, vSelf, arg0); } } public static class RbFloat { - @JRubyMethod(rest=true) - public static IRubyObject to_json(ThreadContext context, - IRubyObject vSelf, IRubyObject[] args) { - return Generator.generateJson(context, (RubyFloat)vSelf, - Generator.FLOAT_HANDLER, args); + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf) { + return Generator.generateJson(context, (RubyFloat)vSelf, Generator.FLOAT_HANDLER); + } + + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf, IRubyObject arg0) { + return Generator.generateJson(context, (RubyFloat)vSelf, Generator.FLOAT_HANDLER, arg0); } } public static class RbString { - @JRubyMethod(rest=true) - public static IRubyObject to_json(ThreadContext context, - IRubyObject vSelf, IRubyObject[] args) { - return Generator.generateJson(context, (RubyString)vSelf, - Generator.STRING_HANDLER, args); + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf) { + return Generator.generateJson(context, (RubyString)vSelf, Generator.STRING_HANDLER); + } + + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf, IRubyObject arg0) { + return Generator.generateJson(context, (RubyString)vSelf, Generator.STRING_HANDLER, arg0); } /** @@ -112,12 +125,16 @@ public static IRubyObject to_json(ThreadContext context, *

This method creates a JSON text from the result of a call to * {@link #to_json_raw_object} of this String. */ - @JRubyMethod(rest=true) - public static IRubyObject to_json_raw(ThreadContext context, - IRubyObject vSelf, IRubyObject[] args) { + @JRubyMethod + public static IRubyObject to_json_raw(ThreadContext context, IRubyObject vSelf) { RubyHash obj = toJsonRawObject(context, Utils.ensureString(vSelf)); - return Generator.generateJson(context, obj, - Generator.HASH_HANDLER, args); + return Generator.generateJson(context, obj, Generator.HASH_HANDLER); + } + + @JRubyMethod + public static IRubyObject to_json_raw(ThreadContext context, IRubyObject vSelf, IRubyObject arg0) { + RubyHash obj = toJsonRawObject(context, Utils.ensureString(vSelf)); + return Generator.generateJson(context, obj, Generator.HASH_HANDLER, arg0); } /** @@ -128,15 +145,14 @@ public static IRubyObject to_json_raw(ThreadContext context, * method should be used if you want to convert raw strings to JSON * instead of UTF-8 strings, e.g. binary data. */ - @JRubyMethod(rest=true) - public static IRubyObject to_json_raw_object(ThreadContext context, - IRubyObject vSelf, IRubyObject[] args) { + @JRubyMethod + public static IRubyObject to_json_raw_object(ThreadContext context, IRubyObject vSelf) { return toJsonRawObject(context, Utils.ensureString(vSelf)); } private static RubyHash toJsonRawObject(ThreadContext context, RubyString self) { - Ruby runtime = context.getRuntime(); + Ruby runtime = context.runtime; RubyHash result = RubyHash.newHash(runtime); IRubyObject createId = RuntimeInfo.forRuntime(runtime) @@ -154,11 +170,10 @@ private static RubyHash toJsonRawObject(ThreadContext context, return result; } - @JRubyMethod(required=1, module=true) - public static IRubyObject included(ThreadContext context, - IRubyObject vSelf, IRubyObject module) { - RuntimeInfo info = RuntimeInfo.forRuntime(context.getRuntime()); - return module.callMethod(context, "extend", info.stringExtendModule.get()); + @JRubyMethod(module=true) + public static IRubyObject included(ThreadContext context, IRubyObject extendModule, IRubyObject module) { + RuntimeInfo info = RuntimeInfo.forRuntime(context.runtime); + return module.callMethod(context, "extend", ((RubyModule) extendModule).getConstant("Extend")); } } @@ -170,10 +185,10 @@ public static class StringExtend { * array for the key "raw"). The Ruby String can be created by this * module method. */ - @JRubyMethod(required=1) + @JRubyMethod public static IRubyObject json_create(ThreadContext context, IRubyObject vSelf, IRubyObject vHash) { - Ruby runtime = context.getRuntime(); + Ruby runtime = context.runtime; RubyHash o = vHash.convertToHash(); IRubyObject rawData = o.fastARef(runtime.newString("raw")); if (rawData == null) { @@ -195,37 +210,50 @@ public static IRubyObject json_create(ThreadContext context, } public static class RbTrue { - @JRubyMethod(rest=true) - public static IRubyObject to_json(ThreadContext context, - IRubyObject vSelf, IRubyObject[] args) { - return Generator.generateJson(context, (RubyBoolean)vSelf, - Generator.TRUE_HANDLER, args); + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf) { + return Generator.generateJson(context, (RubyBoolean)vSelf, Generator.TRUE_HANDLER); + } + + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf, IRubyObject arg0) { + return Generator.generateJson(context, (RubyBoolean)vSelf, Generator.TRUE_HANDLER, arg0); } } public static class RbFalse { - @JRubyMethod(rest=true) - public static IRubyObject to_json(ThreadContext context, - IRubyObject vSelf, IRubyObject[] args) { - return Generator.generateJson(context, (RubyBoolean)vSelf, - Generator.FALSE_HANDLER, args); + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf) { + return Generator.generateJson(context, (RubyBoolean)vSelf, Generator.FALSE_HANDLER); + } + + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf, IRubyObject arg0) { + return Generator.generateJson(context, (RubyBoolean)vSelf, Generator.FALSE_HANDLER, arg0); } } public static class RbNil { - @JRubyMethod(rest=true) - public static IRubyObject to_json(ThreadContext context, - IRubyObject vSelf, IRubyObject[] args) { - return Generator.generateJson(context, vSelf, - Generator.NIL_HANDLER, args); + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf) { + return Generator.generateJson(context, vSelf, Generator.NIL_HANDLER); + } + + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf, IRubyObject arg0) { + return Generator.generateJson(context, vSelf, Generator.NIL_HANDLER, arg0); } } public static class RbObject { - @JRubyMethod(rest=true) - public static IRubyObject to_json(ThreadContext context, - IRubyObject self, IRubyObject[] args) { - return RbString.to_json(context, self.asString(), args); + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject self) { + return RbString.to_json(context, self.asString()); + } + + @JRubyMethod + public static IRubyObject to_json(ThreadContext context, IRubyObject self, IRubyObject arg0) { + return RbString.to_json(context, self.asString(), arg0); } } } diff --git a/java/src/json/ext/GeneratorService.java b/java/src/json/ext/GeneratorService.java index e665ad14..7d3f86e5 100644 --- a/java/src/json/ext/GeneratorService.java +++ b/java/src/json/ext/GeneratorService.java @@ -23,7 +23,8 @@ public boolean basicLoad(Ruby runtime) throws IOException { runtime.getLoadService().require("json/common"); RuntimeInfo info = RuntimeInfo.initRuntime(runtime); - info.jsonModule = new WeakReference(runtime.defineModule("JSON")); + RubyModule jsonModule = runtime.defineModule("JSON"); + info.jsonModule = new WeakReference(jsonModule); RubyModule jsonExtModule = info.jsonModule.get().defineModuleUnder("Ext"); RubyModule generatorModule = jsonExtModule.defineModuleUnder("Generator"); @@ -37,6 +38,8 @@ public boolean basicLoad(Ruby runtime) throws IOException { generatorModule.defineModuleUnder("GeneratorMethods"); GeneratorMethods.populate(info, generatorMethods); + runtime.getLoadService().require("json/ext/generator/state"); + return true; } } diff --git a/java/src/json/ext/GeneratorState.java b/java/src/json/ext/GeneratorState.java index 1600b04a..fdd433c6 100644 --- a/java/src/json/ext/GeneratorState.java +++ b/java/src/json/ext/GeneratorState.java @@ -108,11 +108,7 @@ public class GeneratorState extends RubyObject { */ private int depth = 0; - static final ObjectAllocator ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klazz) { - return new GeneratorState(runtime, klazz); - } - }; + static final ObjectAllocator ALLOCATOR = GeneratorState::new; public GeneratorState(Ruby runtime, RubyClass metaClass) { super(runtime, metaClass); @@ -126,25 +122,23 @@ public GeneratorState(Ruby runtime, RubyClass metaClass) { * configured by opts, something else to create an * unconfigured instance. If opts is a State * object, it is just returned. - * @param clazzParam The receiver of the method call - * ({@link RubyClass} State) + * @param context The current thread context + * @param klass The receiver of the method call ({@link RubyClass} State) * @param opts The object to use as a base for the new State - * @param block The block passed to the method * @return A GeneratorState as determined above */ @JRubyMethod(meta=true) - public static IRubyObject from_state(ThreadContext context, - IRubyObject klass, IRubyObject opts) { + public static IRubyObject from_state(ThreadContext context, IRubyObject klass, IRubyObject opts) { return fromState(context, opts); } @JRubyMethod(meta=true) - public static IRubyObject generate(ThreadContext context, IRubyObject klass, IRubyObject obj, IRubyObject opts) { - return fromState(context, opts).generate(context, obj); + public static IRubyObject generate(ThreadContext context, IRubyObject klass, IRubyObject obj, IRubyObject opts, IRubyObject io) { + return fromState(context, opts)._generate(context, obj, io); } static GeneratorState fromState(ThreadContext context, IRubyObject opts) { - return fromState(context, RuntimeInfo.forRuntime(context.getRuntime()), opts); + return fromState(context, RuntimeInfo.forRuntime(context.runtime), opts); } static GeneratorState fromState(ThreadContext context, RuntimeInfo info, @@ -155,9 +149,8 @@ static GeneratorState fromState(ThreadContext context, RuntimeInfo info, if (klass.isInstance(opts)) return (GeneratorState)opts; // if the given parameter is a Hash, pass it to the instantiator - if (context.getRuntime().getHash().isInstance(opts)) { - return (GeneratorState)klass.newInstance(context, - new IRubyObject[] {opts}, Block.NULL_BLOCK); + if (context.runtime.getHash().isInstance(opts)) { + return (GeneratorState)klass.newInstance(context, opts, Block.NULL_BLOCK); } } @@ -167,9 +160,9 @@ static GeneratorState fromState(ThreadContext context, RuntimeInfo info, /** * State#initialize(opts = {}) - * + *

* Instantiates a new State object, configured by opts. - * + *

* opts can have the following keys: * *

@@ -194,15 +187,21 @@ static GeneratorState fromState(ThreadContext context, RuntimeInfo info, *
set to true if U+2028, U+2029 and forward slashes should be escaped * in the json output to make it safe to include in a JavaScript tag (default: false) */ - @JRubyMethod(optional=1, visibility=Visibility.PRIVATE) - public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { - configure(context, args.length > 0 ? args[0] : null); + @JRubyMethod(visibility=Visibility.PRIVATE) + public IRubyObject initialize(ThreadContext context) { + _configure(context, null); + return this; + } + + @JRubyMethod(visibility=Visibility.PRIVATE) + public IRubyObject initialize(ThreadContext context, IRubyObject arg0) { + _configure(context, arg0); return this; } @JRubyMethod public IRubyObject initialize_copy(ThreadContext context, IRubyObject vOrig) { - Ruby runtime = context.getRuntime(); + Ruby runtime = context.runtime; if (!(vOrig instanceof GeneratorState)) { throw runtime.newTypeError(vOrig, getType()); } @@ -228,30 +227,27 @@ public IRubyObject initialize_copy(ThreadContext context, IRubyObject vOrig) { * the result. If no valid JSON document can be created this method raises * a GeneratorError exception. */ - @JRubyMethod - public IRubyObject generate(ThreadContext context, IRubyObject obj) { - RubyString result = Generator.generateJson(context, obj, this); - RuntimeInfo info = RuntimeInfo.forRuntime(context.getRuntime()); - if (result.getEncoding() != UTF8Encoding.INSTANCE) { - if (result.isFrozen()) { - result = result.strDup(context.getRuntime()); - } - result.force_encoding(context, info.utf8.get()); + @JRubyMethod(visibility = Visibility.PRIVATE) + public IRubyObject _generate(ThreadContext context, IRubyObject obj, IRubyObject io) { + IRubyObject result = Generator.generateJson(context, obj, this, io); + RuntimeInfo info = RuntimeInfo.forRuntime(context.runtime); + if (!(result instanceof RubyString)) { + return result; } - return result; - } - private static boolean matchClosingBrace(ByteList bl, int pos, int len, - int brace) { - for (int endPos = len - 1; endPos > pos; endPos--) { - int b = bl.get(endPos); - if (Character.isWhitespace(b)) continue; - return b == brace; + RubyString resultString = result.convertToString(); + if (resultString.getEncoding() != UTF8Encoding.INSTANCE) { + if (resultString.isFrozen()) { + resultString = resultString.strDup(context.runtime); + } + resultString.setEncoding(UTF8Encoding.INSTANCE); + resultString.clearCodeRange(); } - return false; + + return resultString; } - @JRubyMethod(name="[]", required=1) + @JRubyMethod(name="[]") public IRubyObject op_aref(ThreadContext context, IRubyObject vName) { String name = vName.asJavaString(); if (getMetaClass().isMethodBound(name, true)) { @@ -262,16 +258,16 @@ public IRubyObject op_aref(ThreadContext context, IRubyObject vName) { } } - @JRubyMethod(name="[]=", required=2) + @JRubyMethod(name="[]=") public IRubyObject op_aset(ThreadContext context, IRubyObject vName, IRubyObject value) { String name = vName.asJavaString(); String nameWriter = name + "="; if (getMetaClass().isMethodBound(nameWriter, true)) { - return send(context, context.getRuntime().newString(nameWriter), value, Block.NULL_BLOCK); + return send(context, context.runtime.newString(nameWriter), value, Block.NULL_BLOCK); } else { getInstanceVariables().setInstanceVariable("@" + name, value); } - return context.getRuntime().getNil(); + return context.nil; } public ByteList getIndent() { @@ -280,7 +276,7 @@ public ByteList getIndent() { @JRubyMethod(name="indent") public RubyString indent_get(ThreadContext context) { - return context.getRuntime().newString(indent); + return context.runtime.newString(indent); } @JRubyMethod(name="indent=") @@ -295,7 +291,7 @@ public ByteList getSpace() { @JRubyMethod(name="space") public RubyString space_get(ThreadContext context) { - return context.getRuntime().newString(space); + return context.runtime.newString(space); } @JRubyMethod(name="space=") @@ -310,7 +306,7 @@ public ByteList getSpaceBefore() { @JRubyMethod(name="space_before") public RubyString space_before_get(ThreadContext context) { - return context.getRuntime().newString(spaceBefore); + return context.runtime.newString(spaceBefore); } @JRubyMethod(name="space_before=") @@ -326,7 +322,7 @@ public ByteList getObjectNl() { @JRubyMethod(name="object_nl") public RubyString object_nl_get(ThreadContext context) { - return context.getRuntime().newString(objectNl); + return context.runtime.newString(objectNl); } @JRubyMethod(name="object_nl=") @@ -342,7 +338,7 @@ public ByteList getArrayNl() { @JRubyMethod(name="array_nl") public RubyString array_nl_get(ThreadContext context) { - return context.getRuntime().newString(arrayNl); + return context.runtime.newString(arrayNl); } @JRubyMethod(name="array_nl=") @@ -354,19 +350,12 @@ public IRubyObject array_nl_set(ThreadContext context, @JRubyMethod(name="check_circular?") public RubyBoolean check_circular_p(ThreadContext context) { - return context.getRuntime().newBoolean(maxNesting != 0); - } - - /** - * Returns the maximum level of nesting configured for this state. - */ - public int getMaxNesting() { - return maxNesting; + return RubyBoolean.newBoolean(context, maxNesting != 0); } @JRubyMethod(name="max_nesting") public RubyInteger max_nesting_get(ThreadContext context) { - return context.getRuntime().newFixnum(maxNesting); + return context.runtime.newFixnum(maxNesting); } @JRubyMethod(name="max_nesting=") @@ -384,7 +373,7 @@ public boolean scriptSafe() { @JRubyMethod(name="script_safe", alias="escape_slash") public RubyBoolean script_safe_get(ThreadContext context) { - return context.getRuntime().newBoolean(scriptSafe); + return RubyBoolean.newBoolean(context, scriptSafe); } @JRubyMethod(name="script_safe=", alias="escape_slash=") @@ -395,7 +384,7 @@ public IRubyObject script_safe_set(IRubyObject script_safe) { @JRubyMethod(name="script_safe?", alias="escape_slash?") public RubyBoolean script_safe_p(ThreadContext context) { - return context.getRuntime().newBoolean(scriptSafe); + return RubyBoolean.newBoolean(context, scriptSafe); } /** @@ -405,9 +394,9 @@ public boolean strict() { return strict; } - @JRubyMethod(name="strict") + @JRubyMethod(name={"strict","strict?"}) public RubyBoolean strict_get(ThreadContext context) { - return context.getRuntime().newBoolean(strict); + return RubyBoolean.newBoolean(context, strict); } @JRubyMethod(name="strict=") @@ -422,7 +411,7 @@ public boolean allowNaN() { @JRubyMethod(name="allow_nan?") public RubyBoolean allow_nan_p(ThreadContext context) { - return context.getRuntime().newBoolean(allowNaN); + return RubyBoolean.newBoolean(context, allowNaN); } public boolean asciiOnly() { @@ -431,12 +420,12 @@ public boolean asciiOnly() { @JRubyMethod(name="ascii_only?") public RubyBoolean ascii_only_p(ThreadContext context) { - return context.getRuntime().newBoolean(asciiOnly); + return RubyBoolean.newBoolean(context, asciiOnly); } @JRubyMethod(name="buffer_initial_length") public RubyInteger buffer_initial_length_get(ThreadContext context) { - return context.getRuntime().newFixnum(bufferInitialLength); + return context.runtime.newFixnum(bufferInitialLength); } @JRubyMethod(name="buffer_initial_length=") @@ -452,7 +441,7 @@ public int getDepth() { @JRubyMethod(name="depth") public RubyInteger depth_get(ThreadContext context) { - return context.getRuntime().newFixnum(depth); + return context.runtime.newFixnum(depth); } @JRubyMethod(name="depth=") @@ -463,9 +452,8 @@ public IRubyObject depth_set(IRubyObject vDepth) { private ByteList prepareByteList(ThreadContext context, IRubyObject value) { RubyString str = value.convertToString(); - RuntimeInfo info = RuntimeInfo.forRuntime(context.getRuntime()); - if (str.encoding(context) != info.utf8.get()) { - str = (RubyString)str.encode(context, info.utf8.get()); + if (str.getEncoding() != UTF8Encoding.INSTANCE) { + str = (RubyString)str.encode(context, context.runtime.getEncodingService().convertEncodingToRubyEncoding(UTF8Encoding.INSTANCE)); } return str.getByteList().dup(); } @@ -478,8 +466,8 @@ private ByteList prepareByteList(ThreadContext context, IRubyObject value) { * @param vOpts The options hash * @return The receiver */ - @JRubyMethod(alias = "merge") - public IRubyObject configure(ThreadContext context, IRubyObject vOpts) { + @JRubyMethod(visibility=Visibility.PRIVATE) + public IRubyObject _configure(ThreadContext context, IRubyObject vOpts) { OptionsReader opts = new OptionsReader(context, vOpts); ByteList indent = opts.getString("indent"); @@ -521,7 +509,7 @@ public IRubyObject configure(ThreadContext context, IRubyObject vOpts) { */ @JRubyMethod(alias = "to_hash") public RubyHash to_h(ThreadContext context) { - Ruby runtime = context.getRuntime(); + Ruby runtime = context.runtime; RubyHash result = RubyHash.newHash(runtime); result.op_aset(context, runtime.newSymbol("indent"), indent_get(context)); @@ -542,26 +530,24 @@ public RubyHash to_h(ThreadContext context) { return result; } - public int increaseDepth() { + public int increaseDepth(ThreadContext context) { depth++; - checkMaxNesting(); + checkMaxNesting(context); return depth; } - public int decreaseDepth() { - return --depth; + public void decreaseDepth() { + --depth; } /** * Checks if the current depth is allowed as per this state's options. - * @param context - * @param depth The current depth + * @param context The current context */ - private void checkMaxNesting() { + private void checkMaxNesting(ThreadContext context) { if (maxNesting != 0 && depth > maxNesting) { depth--; - throw Utils.newException(getRuntime().getCurrentContext(), - Utils.M_NESTING_ERROR, "nesting of " + depth + " is too deep"); + throw Utils.newException(context, Utils.M_NESTING_ERROR, "nesting of " + depth + " is too deep"); } } } diff --git a/java/src/json/ext/OptionsReader.java b/java/src/json/ext/OptionsReader.java index 70426d42..ff976c38 100644 --- a/java/src/json/ext/OptionsReader.java +++ b/java/src/json/ext/OptionsReader.java @@ -5,6 +5,7 @@ */ package json.ext; +import org.jcodings.specific.UTF8Encoding; import org.jruby.Ruby; import org.jruby.RubyClass; import org.jruby.RubyHash; @@ -22,7 +23,7 @@ final class OptionsReader { OptionsReader(ThreadContext context, IRubyObject vOpts) { this.context = context; - this.runtime = context.getRuntime(); + this.runtime = context.runtime; if (vOpts == null || vOpts.isNil()) { opts = null; } else if (vOpts.respondsTo("to_hash")) { @@ -41,7 +42,7 @@ private RuntimeInfo getRuntimeInfo() { } /** - * Efficiently looks up items with a {@link RubySymbol Symbol} key + * Efficiently looks up items with a {@link org.jruby.RubySymbol Symbol} key * @param key The Symbol name to look up for * @return The item in the {@link RubyHash Hash}, or null * if not found @@ -69,7 +70,7 @@ int getInt(String key, int defaultValue) { * @param key The Symbol name to look up for * @return null if the key is not in the Hash or if * its value evaluates to false - * @throws RaiseException TypeError if the value does not + * @throws org.jruby.exceptions.RaiseException TypeError if the value does not * evaluate to false and can't be * converted to string */ @@ -83,9 +84,8 @@ RubyString getString(String key, RubyString defaultValue) { if (value == null || !value.isTrue()) return defaultValue; RubyString str = value.convertToString(); - RuntimeInfo info = getRuntimeInfo(); - if (str.encoding(context) != info.utf8.get()) { - str = (RubyString)str.encode(context, info.utf8.get()); + if (str.getEncoding() != UTF8Encoding.INSTANCE) { + str = (RubyString)str.encode(context, context.runtime.getEncodingService().convertEncodingToRubyEncoding(UTF8Encoding.INSTANCE)); } return str; } diff --git a/java/src/json/ext/Parser.java b/java/src/json/ext/Parser.java index 74037d37..47e66795 100644 --- a/java/src/json/ext/Parser.java +++ b/java/src/json/ext/Parser.java @@ -7,10 +7,12 @@ */ package json.ext; +import org.jcodings.Encoding; +import org.jcodings.specific.ASCIIEncoding; +import org.jcodings.specific.UTF8Encoding; import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyClass; -import org.jruby.RubyEncoding; import org.jruby.RubyFloat; import org.jruby.RubyHash; import org.jruby.RubyInteger; @@ -20,13 +22,13 @@ import org.jruby.exceptions.JumpException; import org.jruby.exceptions.RaiseException; import org.jruby.runtime.Block; +import org.jruby.runtime.Helpers; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.util.ByteList; import org.jruby.util.ConvertBytes; -import org.jruby.util.ConvertDouble; import java.util.function.BiFunction; @@ -41,7 +43,7 @@ * This is performed for you when you include "json/ext". * *

This class does not perform the actual parsing, just acts as an interface - * to Ruby code. When the {@link #parse()} method is invoked, a + * to Ruby code. When the {@link #parse(ThreadContext)} method is invoked, a * Parser.ParserSession object is instantiated, which handles the process. * * @author mernen @@ -71,11 +73,7 @@ public class Parser extends RubyObject { private static final String CONST_INFINITY = "Infinity"; private static final String CONST_MINUS_INFINITY = "MinusInfinity"; - static final ObjectAllocator ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klazz) { - return new Parser(runtime, klazz); - } - }; + static final ObjectAllocator ALLOCATOR = Parser::new; /** * Multiple-value return for internal parser methods. @@ -113,42 +111,42 @@ public Parser(Ruby runtime, RubyClass metaClass) { * source. * It will be configured by the opts Hash. * opts can have the following keys: - * + *

*

*
:max_nesting *
The maximum depth of nesting allowed in the parsed data * structures. Disable depth checking with :max_nesting => false|nil|0, * it defaults to 100. - * + *

*

:allow_nan *
If set to true, allow NaN, * Infinity and -Infinity in defiance of RFC 4627 * to be parsed by the Parser. This option defaults to false. - * + *

*

:allow_trailing_comma *
If set to true, allow arrays and objects with a trailing * comma in defiance of RFC 4627 to be parsed by the Parser. * This option defaults to false. - * + *

*

:symbolize_names *
If set to true, returns symbols for the names (keys) in * a JSON object. Otherwise strings are returned, which is also the default. - * + *

*

:create_additions *
If set to false, the Parser doesn't create additions * even if a matching class and create_id was found. This option * defaults to true. - * + *

*

:object_class *
Defaults to Hash. If another type is provided, it will be used * instead of Hash to represent JSON objects. The type must respond to * new without arguments, and return an object that respond to []=. - * + *

*

:array_class *
Defaults to Array. If another type is provided, it will be used * instead of Hash to represent JSON arrays. The type must respond to * new without arguments, and return an object that respond to <<. - * + *

*

:decimal_class *
Specifies which class to use instead of the default (Float) when * parsing decimal numbers. This class must accept a single string argument @@ -156,31 +154,44 @@ public Parser(Ruby runtime, RubyClass metaClass) { *
*/ - @JRubyMethod(name = "new", required = 1, optional = 1, meta = true) - public static IRubyObject newInstance(IRubyObject clazz, IRubyObject[] args, Block block) { + @JRubyMethod(name = "new", meta = true) + public static IRubyObject newInstance(IRubyObject clazz, IRubyObject arg0, Block block) { Parser parser = (Parser)((RubyClass)clazz).allocate(); - parser.callInit(args, block); + parser.callInit(arg0, block); + + return parser; + } + + @JRubyMethod(name = "new", meta = true) + public static IRubyObject newInstance(IRubyObject clazz, IRubyObject arg0, IRubyObject arg1, Block block) { + Parser parser = (Parser)((RubyClass)clazz).allocate(); + + parser.callInit(arg0, arg1, block); return parser; } @JRubyMethod(meta=true) public static IRubyObject parse(ThreadContext context, IRubyObject clazz, IRubyObject source, IRubyObject opts) { - IRubyObject[] args = new IRubyObject[] {source, opts}; Parser parser = (Parser)((RubyClass)clazz).allocate(); - parser.callInit(args, null); + parser.callInit(source, opts, null); return parser.parse(context); } - @JRubyMethod(required = 1, optional = 1, visibility = Visibility.PRIVATE) - public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { - Ruby runtime = context.getRuntime(); + @JRubyMethod(visibility = Visibility.PRIVATE) + public IRubyObject initialize(ThreadContext context, IRubyObject arg0) { + return initialize(context, arg0, null); + } + + @JRubyMethod(visibility = Visibility.PRIVATE) + public IRubyObject initialize(ThreadContext context, IRubyObject arg0, IRubyObject arg1) { + Ruby runtime = context.runtime; if (this.vSource != null) { throw runtime.newTypeError("already initialized instance"); - } + } - OptionsReader opts = new OptionsReader(context, args.length > 1 ? args[1] : null); + OptionsReader opts = new OptionsReader(context, arg1); this.maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING); this.allowNaN = opts.getBool("allow_nan", false); this.allowTrailingComma = opts.getBool("allow_trailing_comma", false); @@ -215,12 +226,9 @@ public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { } if(symbolizeNames && createAdditions) { - throw runtime.newArgumentError( - "options :symbolize_names and :create_additions cannot be " + - " used in conjunction" - ); + throw runtime.newArgumentError("options :symbolize_names and :create_additions cannot be used in conjunction"); } - this.vSource = args[0].convertToString(); + this.vSource = arg0.convertToString(); this.vSource = convertEncoding(context, vSource); return this; @@ -232,44 +240,17 @@ public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { * Returns the source string if no conversion is needed. */ private RubyString convertEncoding(ThreadContext context, RubyString source) { - RubyEncoding encoding = (RubyEncoding)source.encoding(context); - if (encoding == info.ascii8bit.get()) { + Encoding encoding = source.getEncoding(); + if (encoding == ASCIIEncoding.INSTANCE) { source = (RubyString) source.dup(); - source.force_encoding(context, info.utf8.get()); - } else { - source = (RubyString) source.encode(context, info.utf8.get()); + source.setEncoding(UTF8Encoding.INSTANCE); + source.clearCodeRange(); + } else if (encoding != UTF8Encoding.INSTANCE) { + source = (RubyString) source.encode(context, context.runtime.getEncodingService().convertEncodingToRubyEncoding(UTF8Encoding.INSTANCE)); } return source; } - /** - * Checks the first four bytes of the given ByteList to infer its encoding, - * using the principle demonstrated on section 3 of RFC 4627 (JSON). - */ - private static String sniffByteList(ByteList bl) { - if (bl.length() < 4) return null; - if (bl.get(0) == 0 && bl.get(2) == 0) { - return bl.get(1) == 0 ? "utf-32be" : "utf-16be"; - } - if (bl.get(1) == 0 && bl.get(3) == 0) { - return bl.get(2) == 0 ? "utf-32le" : "utf-16le"; - } - return null; - } - - /** - * Assumes the given (binary) RubyString to be in the given encoding, then - * converts it to UTF-8. - */ - private RubyString reinterpretEncoding(ThreadContext context, - RubyString str, String sniffedEncoding) { - RubyEncoding actualEncoding = info.getEncoding(context, sniffedEncoding); - RubyEncoding targetEncoding = info.utf8.get(); - RubyString dup = (RubyString)str.dup(); - dup.force_encoding(context, actualEncoding); - return (RubyString)dup.encode_bang(context, targetEncoding); - } - /** * Parser#parse() * @@ -278,7 +259,7 @@ private RubyString reinterpretEncoding(ThreadContext context, */ @JRubyMethod public IRubyObject parse(ThreadContext context) { - return new ParserSession(this, context, info).parse(); + return new ParserSession(this, context, info).parse(context); } /** @@ -288,15 +269,15 @@ public IRubyObject parse(ThreadContext context) { * used to construct this Parser. */ @JRubyMethod(name = "source") - public IRubyObject source_get() { - return checkAndGetSource().dup(); + public IRubyObject source_get(ThreadContext context) { + return checkAndGetSource(context).dup(); } - public RubyString checkAndGetSource() { + public RubyString checkAndGetSource(ThreadContext context) { if (vSource != null) { return vSource; } else { - throw getRuntime().newTypeError("uninitialized instance"); + throw context.runtime.newTypeError("uninitialized instance"); } } @@ -335,47 +316,35 @@ private IRubyObject createCustomDecimal(final ThreadContext context, final ByteL @SuppressWarnings("fallthrough") private static class ParserSession { private final Parser parser; - private final ThreadContext context; private final RuntimeInfo info; private final ByteList byteList; private final ByteList view; private final byte[] data; private final StringDecoder decoder; private int currentNesting = 0; - private final DoubleConverter dc; - - // initialization value for all state variables. - // no idea about the origins of this value, ask Flori ;) - private static final int EVIL = 0x666; private ParserSession(Parser parser, ThreadContext context, RuntimeInfo info) { this.parser = parser; - this.context = context; this.info = info; - this.byteList = parser.checkAndGetSource().getByteList(); + this.byteList = parser.checkAndGetSource(context).getByteList(); this.data = byteList.unsafeBytes(); this.view = new ByteList(data, false); - this.decoder = new StringDecoder(context); - this.dc = new DoubleConverter(); + this.decoder = new StringDecoder(); } - private RaiseException unexpectedToken(int absStart, int absEnd) { - RubyString msg = getRuntime().newString("unexpected token at '") + private RaiseException unexpectedToken(ThreadContext context, int absStart, int absEnd) { + RubyString msg = context.runtime.newString("unexpected token at '") .cat(data, absStart, Math.min(absEnd - absStart, 32)) .cat((byte)'\''); - return newException(Utils.M_PARSER_ERROR, msg); - } - - private Ruby getRuntime() { - return context.getRuntime(); + return newException(context, Utils.M_PARSER_ERROR, msg); } -// line 397 "Parser.rl" +// line 366 "Parser.rl" -// line 379 "Parser.java" +// line 348 "Parser.java" private static byte[] init__JSON_value_actions_0() { return new byte [] { @@ -489,22 +458,22 @@ private static byte[] init__JSON_value_from_state_actions_0() static final int JSON_value_en_main = 1; -// line 503 "Parser.rl" +// line 472 "Parser.rl" - void parseValue(ParserResult res, int p, int pe) { - int cs = EVIL; + void parseValue(ThreadContext context, ParserResult res, int p, int pe) { + int cs; IRubyObject result = null; -// line 501 "Parser.java" +// line 470 "Parser.java" { cs = JSON_value_start; } -// line 510 "Parser.rl" +// line 479 "Parser.rl" -// line 508 "Parser.java" +// line 477 "Parser.java" { int _klen; int _trans = 0; @@ -530,13 +499,13 @@ void parseValue(ParserResult res, int p, int pe) { while ( _nacts-- > 0 ) { switch ( _JSON_value_actions[_acts++] ) { case 9: -// line 488 "Parser.rl" +// line 457 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 540 "Parser.java" +// line 509 "Parser.java" } } @@ -599,45 +568,45 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) switch ( _JSON_value_actions[_acts++] ) { case 0: -// line 405 "Parser.rl" +// line 374 "Parser.rl" { - result = getRuntime().getNil(); + result = context.nil; } break; case 1: -// line 408 "Parser.rl" +// line 377 "Parser.rl" { - result = getRuntime().getFalse(); + result = context.fals; } break; case 2: -// line 411 "Parser.rl" +// line 380 "Parser.rl" { - result = getRuntime().getTrue(); + result = context.tru; } break; case 3: -// line 414 "Parser.rl" +// line 383 "Parser.rl" { if (parser.allowNaN) { result = getConstant(CONST_NAN); } else { - throw unexpectedToken(p - 2, pe); + throw unexpectedToken(context, p - 2, pe); } } break; case 4: -// line 421 "Parser.rl" +// line 390 "Parser.rl" { if (parser.allowNaN) { result = getConstant(CONST_INFINITY); } else { - throw unexpectedToken(p - 7, pe); + throw unexpectedToken(context, p - 7, pe); } } break; case 5: -// line 428 "Parser.rl" +// line 397 "Parser.rl" { if (pe > p + 8 && absSubSequence(p, p + 9).equals(JSON_MINUS_INFINITY)) { @@ -648,15 +617,15 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } else { - throw unexpectedToken(p, pe); + throw unexpectedToken(context, p, pe); } } - parseFloat(res, p, pe); + parseFloat(context, res, p, pe); if (res.result != null) { result = res.result; {p = (( res.p))-1;} } - parseInteger(res, p, pe); + parseInteger(context, res, p, pe); if (res.result != null) { result = res.result; {p = (( res.p))-1;} @@ -666,9 +635,9 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 6: -// line 454 "Parser.rl" +// line 423 "Parser.rl" { - parseString(res, p, pe); + parseString(context, res, p, pe); if (res.result == null) { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} @@ -679,10 +648,10 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 7: -// line 464 "Parser.rl" +// line 433 "Parser.rl" { currentNesting++; - parseArray(res, p, pe); + parseArray(context, res, p, pe); currentNesting--; if (res.result == null) { p--; @@ -694,10 +663,10 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 8: -// line 476 "Parser.rl" +// line 445 "Parser.rl" { currentNesting++; - parseObject(res, p, pe); + parseObject(context, res, p, pe); currentNesting--; if (res.result == null) { p--; @@ -708,7 +677,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } } break; -// line 712 "Parser.java" +// line 681 "Parser.java" } } } @@ -728,7 +697,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) break; } } -// line 511 "Parser.rl" +// line 480 "Parser.rl" if (cs >= JSON_value_first_final && result != null) { if (parser.freeze) { @@ -741,7 +710,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } -// line 745 "Parser.java" +// line 714 "Parser.java" private static byte[] init__JSON_integer_actions_0() { return new byte [] { @@ -840,33 +809,32 @@ private static byte[] init__JSON_integer_trans_actions_0() static final int JSON_integer_en_main = 1; -// line 533 "Parser.rl" +// line 502 "Parser.rl" - void parseInteger(ParserResult res, int p, int pe) { + void parseInteger(ThreadContext context, ParserResult res, int p, int pe) { int new_p = parseIntegerInternal(p, pe); if (new_p == -1) { res.update(null, p); return; } - RubyInteger number = createInteger(p, new_p); + RubyInteger number = createInteger(context, p, new_p); res.update(number, new_p + 1); - return; } int parseIntegerInternal(int p, int pe) { - int cs = EVIL; + int cs; -// line 862 "Parser.java" +// line 830 "Parser.java" { cs = JSON_integer_start; } -// line 550 "Parser.rl" +// line 518 "Parser.rl" int memo = p; -// line 870 "Parser.java" +// line 838 "Parser.java" { int _klen; int _trans = 0; @@ -947,13 +915,13 @@ else if ( data[p] > _JSON_integer_trans_keys[_mid+1] ) switch ( _JSON_integer_actions[_acts++] ) { case 0: -// line 527 "Parser.rl" +// line 496 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 957 "Parser.java" +// line 925 "Parser.java" } } } @@ -973,7 +941,7 @@ else if ( data[p] > _JSON_integer_trans_keys[_mid+1] ) break; } } -// line 552 "Parser.rl" +// line 520 "Parser.rl" if (cs < JSON_integer_first_final) { return -1; @@ -982,8 +950,8 @@ else if ( data[p] > _JSON_integer_trans_keys[_mid+1] ) return p; } - RubyInteger createInteger(int p, int new_p) { - Ruby runtime = getRuntime(); + RubyInteger createInteger(ThreadContext context, int p, int new_p) { + Ruby runtime = context.runtime; ByteList num = absSubSequence(p, new_p); return bytesToInum(runtime, num); } @@ -993,7 +961,7 @@ RubyInteger bytesToInum(Ruby runtime, ByteList num) { } -// line 997 "Parser.java" +// line 965 "Parser.java" private static byte[] init__JSON_float_actions_0() { return new byte [] { @@ -1095,10 +1063,10 @@ private static byte[] init__JSON_float_trans_actions_0() static final int JSON_float_en_main = 1; -// line 585 "Parser.rl" +// line 553 "Parser.rl" - void parseFloat(ParserResult res, int p, int pe) { + void parseFloat(ThreadContext context, ParserResult res, int p, int pe) { int new_p = parseFloatInternal(p, pe); if (new_p == -1) { res.update(null, p); @@ -1111,18 +1079,18 @@ void parseFloat(ParserResult res, int p, int pe) { } int parseFloatInternal(int p, int pe) { - int cs = EVIL; + int cs; -// line 1118 "Parser.java" +// line 1086 "Parser.java" { cs = JSON_float_start; } -// line 603 "Parser.rl" +// line 571 "Parser.rl" int memo = p; -// line 1126 "Parser.java" +// line 1094 "Parser.java" { int _klen; int _trans = 0; @@ -1203,13 +1171,13 @@ else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) switch ( _JSON_float_actions[_acts++] ) { case 0: -// line 576 "Parser.rl" +// line 544 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1213 "Parser.java" +// line 1181 "Parser.java" } } } @@ -1229,7 +1197,7 @@ else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) break; } } -// line 605 "Parser.rl" +// line 573 "Parser.rl" if (cs < JSON_float_first_final) { return -1; @@ -1239,7 +1207,7 @@ else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) } -// line 1243 "Parser.java" +// line 1211 "Parser.java" private static byte[] init__JSON_string_actions_0() { return new byte [] { @@ -1341,23 +1309,23 @@ private static byte[] init__JSON_string_trans_actions_0() static final int JSON_string_en_main = 1; -// line 644 "Parser.rl" +// line 612 "Parser.rl" - void parseString(ParserResult res, int p, int pe) { - int cs = EVIL; + void parseString(ThreadContext context, ParserResult res, int p, int pe) { + int cs; IRubyObject result = null; -// line 1353 "Parser.java" +// line 1321 "Parser.java" { cs = JSON_string_start; } -// line 651 "Parser.rl" +// line 619 "Parser.rl" int memo = p; -// line 1361 "Parser.java" +// line 1329 "Parser.java" { int _klen; int _trans = 0; @@ -1438,12 +1406,12 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) switch ( _JSON_string_actions[_acts++] ) { case 0: -// line 619 "Parser.rl" +// line 587 "Parser.rl" { int offset = byteList.begin(); - ByteList decoded = decoder.decode(byteList, memo + 1 - offset, + ByteList decoded = decoder.decode(context, byteList, memo + 1 - offset, p - offset); - result = getRuntime().newString(decoded); + result = context.runtime.newString(decoded); if (result == null) { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} @@ -1453,13 +1421,13 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) } break; case 1: -// line 632 "Parser.rl" +// line 600 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1463 "Parser.java" +// line 1431 "Parser.java" } } } @@ -1479,29 +1447,21 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) break; } } -// line 653 "Parser.rl" +// line 621 "Parser.rl" if (parser.createAdditions) { RubyHash matchString = parser.match_string; if (matchString != null) { final IRubyObject[] memoArray = { result, null }; try { - matchString.visitAll(new RubyHash.Visitor() { - @Override - public void visit(IRubyObject pattern, IRubyObject klass) { - if (pattern.callMethod(context, "===", memoArray[0]).isTrue()) { - memoArray[1] = klass; - throw JumpException.SPECIAL_JUMP; - } - } - }); + matchString.visitAll(context, MATCH_VISITOR, memoArray); } catch (JumpException e) { } if (memoArray[1] != null) { RubyClass klass = (RubyClass) memoArray[1]; if (klass.respondsTo("json_creatable?") && klass.callMethod(context, "json_creatable?").isTrue()) { if (parser.deprecatedCreateAdditions) { - klass.getRuntime().getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); + context.runtime.getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); } result = klass.callMethod(context, "json_create", result); } @@ -1512,10 +1472,11 @@ public void visit(IRubyObject pattern, IRubyObject klass) { if (cs >= JSON_string_first_final && result != null) { if (result instanceof RubyString) { RubyString string = (RubyString)result; - string.force_encoding(context, info.utf8.get()); + string.setEncoding(UTF8Encoding.INSTANCE); + string.clearCodeRange(); if (parser.freeze) { string.setFrozen(true); - string = getRuntime().freezeAndDedupString(string); + string = context.runtime.freezeAndDedupString(string); } res.update(string, p + 1); } else { @@ -1527,7 +1488,7 @@ public void visit(IRubyObject pattern, IRubyObject klass) { } -// line 1531 "Parser.java" +// line 1492 "Parser.java" private static byte[] init__JSON_array_actions_0() { return new byte [] { @@ -1694,34 +1655,34 @@ private static byte[] init__JSON_array_trans_actions_0() static final int JSON_array_en_main = 1; -// line 738 "Parser.rl" +// line 699 "Parser.rl" - void parseArray(ParserResult res, int p, int pe) { - int cs = EVIL; + void parseArray(ThreadContext context, ParserResult res, int p, int pe) { + int cs; if (parser.maxNesting > 0 && currentNesting > parser.maxNesting) { - throw newException(Utils.M_NESTING_ERROR, + throw newException(context, Utils.M_NESTING_ERROR, "nesting of " + currentNesting + " is too deep"); } IRubyObject result; - if (parser.arrayClass == getRuntime().getArray()) { - result = RubyArray.newArray(getRuntime()); + if (parser.arrayClass == context.runtime.getArray()) { + result = RubyArray.newArray(context.runtime); } else { result = parser.arrayClass.newInstance(context, IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); } -// line 1718 "Parser.java" +// line 1679 "Parser.java" { cs = JSON_array_start; } -// line 757 "Parser.rl" +// line 718 "Parser.rl" -// line 1725 "Parser.java" +// line 1686 "Parser.java" { int _klen; int _trans = 0; @@ -1764,7 +1725,7 @@ else if ( _widec > _JSON_array_cond_keys[_mid+1] ) case 0: { _widec = 65536 + (data[p] - 0); if ( -// line 705 "Parser.rl" +// line 666 "Parser.rl" parser.allowTrailingComma ) _widec += 65536; break; } @@ -1834,14 +1795,14 @@ else if ( _widec > _JSON_array_trans_keys[_mid+1] ) switch ( _JSON_array_actions[_acts++] ) { case 0: -// line 707 "Parser.rl" +// line 668 "Parser.rl" { - parseValue(res, p, pe); + parseValue(context, res, p, pe); if (res.result == null) { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } else { - if (parser.arrayClass == getRuntime().getArray()) { + if (parser.arrayClass == context.runtime.getArray()) { ((RubyArray)result).append(res.result); } else { result.callMethod(context, "<<", res.result); @@ -1851,13 +1812,13 @@ else if ( _widec > _JSON_array_trans_keys[_mid+1] ) } break; case 1: -// line 722 "Parser.rl" +// line 683 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1861 "Parser.java" +// line 1822 "Parser.java" } } } @@ -1877,17 +1838,17 @@ else if ( _widec > _JSON_array_trans_keys[_mid+1] ) break; } } -// line 758 "Parser.rl" +// line 719 "Parser.rl" if (cs >= JSON_array_first_final) { res.update(result, p + 1); } else { - throw unexpectedToken(p, pe); + throw unexpectedToken(context, p, pe); } } -// line 1891 "Parser.java" +// line 1852 "Parser.java" private static byte[] init__JSON_object_actions_0() { return new byte [] { @@ -2064,24 +2025,24 @@ private static byte[] init__JSON_object_trans_actions_0() static final int JSON_object_en_main = 1; -// line 819 "Parser.rl" +// line 780 "Parser.rl" - void parseObject(ParserResult res, int p, int pe) { - int cs = EVIL; + void parseObject(ThreadContext context, ParserResult res, int p, int pe) { + int cs; IRubyObject lastName = null; boolean objectDefault = true; if (parser.maxNesting > 0 && currentNesting > parser.maxNesting) { - throw newException(Utils.M_NESTING_ERROR, + throw newException(context, Utils.M_NESTING_ERROR, "nesting of " + currentNesting + " is too deep"); } // this is guaranteed to be a RubyHash due to the earlier // allocator test at OptionsReader#getClass IRubyObject result; - if (parser.objectClass == getRuntime().getHash()) { - result = RubyHash.newHash(getRuntime()); + if (parser.objectClass == context.runtime.getHash()) { + result = RubyHash.newHash(context.runtime); } else { objectDefault = false; result = parser.objectClass.newInstance(context, @@ -2089,14 +2050,14 @@ void parseObject(ParserResult res, int p, int pe) { } -// line 2093 "Parser.java" +// line 2054 "Parser.java" { cs = JSON_object_start; } -// line 843 "Parser.rl" +// line 804 "Parser.rl" -// line 2100 "Parser.java" +// line 2061 "Parser.java" { int _klen; int _trans = 0; @@ -2139,7 +2100,7 @@ else if ( _widec > _JSON_object_cond_keys[_mid+1] ) case 0: { _widec = 65536 + (data[p] - 0); if ( -// line 772 "Parser.rl" +// line 733 "Parser.rl" parser.allowTrailingComma ) _widec += 65536; break; } @@ -2209,26 +2170,26 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) switch ( _JSON_object_actions[_acts++] ) { case 0: -// line 774 "Parser.rl" +// line 735 "Parser.rl" { - parseValue(res, p, pe); + parseValue(context, res, p, pe); if (res.result == null) { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } else { - if (parser.objectClass == getRuntime().getHash()) { + if (parser.objectClass == context.runtime.getHash()) { ((RubyHash)result).op_aset(context, lastName, res.result); } else { - result.callMethod(context, "[]=", new IRubyObject[] { lastName, res.result }); + Helpers.invoke(context, result, "[]=", lastName, res.result); } {p = (( res.p))-1;} } } break; case 1: -// line 789 "Parser.rl" +// line 750 "Parser.rl" { - parseString(res, p, pe); + parseString(context, res, p, pe); if (res.result == null) { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} @@ -2244,13 +2205,13 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) } break; case 2: -// line 805 "Parser.rl" +// line 766 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 2254 "Parser.java" +// line 2215 "Parser.java" } } } @@ -2270,7 +2231,7 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) break; } } -// line 844 "Parser.rl" +// line 805 "Parser.rl" if (cs < JSON_object_first_final) { res.update(null, p + 1); @@ -2295,7 +2256,7 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) if (klass.respondsTo("json_creatable?") && klass.callMethod(context, "json_creatable?").isTrue()) { if (parser.deprecatedCreateAdditions) { - klass.getRuntime().getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); + context.runtime.getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); } returnedResult = klass.callMethod(context, "json_create", result); @@ -2306,7 +2267,7 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) } -// line 2310 "Parser.java" +// line 2271 "Parser.java" private static byte[] init__JSON_actions_0() { return new byte [] { @@ -2409,26 +2370,26 @@ private static byte[] init__JSON_trans_actions_0() static final int JSON_en_main = 1; -// line 898 "Parser.rl" +// line 859 "Parser.rl" - public IRubyObject parseImplemetation() { - int cs = EVIL; + public IRubyObject parseImplementation(ThreadContext context) { + int cs; int p, pe; IRubyObject result = null; ParserResult res = new ParserResult(); -// line 2423 "Parser.java" +// line 2384 "Parser.java" { cs = JSON_start; } -// line 907 "Parser.rl" +// line 868 "Parser.rl" p = byteList.begin(); pe = p + byteList.length(); -// line 2432 "Parser.java" +// line 2393 "Parser.java" { int _klen; int _trans = 0; @@ -2509,9 +2470,9 @@ else if ( data[p] > _JSON_trans_keys[_mid+1] ) switch ( _JSON_actions[_acts++] ) { case 0: -// line 884 "Parser.rl" +// line 845 "Parser.rl" { - parseValue(res, p, pe); + parseValue(context, res, p, pe); if (res.result == null) { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} @@ -2521,7 +2482,7 @@ else if ( data[p] > _JSON_trans_keys[_mid+1] ) } } break; -// line 2525 "Parser.java" +// line 2486 "Parser.java" } } } @@ -2541,23 +2502,23 @@ else if ( data[p] > _JSON_trans_keys[_mid+1] ) break; } } -// line 910 "Parser.rl" +// line 871 "Parser.rl" if (cs >= JSON_first_final && p == pe) { return result; } else { - throw unexpectedToken(p, pe); + throw unexpectedToken(context, p, pe); } } - public IRubyObject parse() { - return parseImplemetation(); + public IRubyObject parse(ThreadContext context) { + return parseImplementation(context); } /** * Updates the "view" bytelist with the new offsets and returns it. - * @param start - * @param end + * @param absStart + * @param absEnd */ private ByteList absSubSequence(int absStart, int absEnd) { view.setBegin(absStart); @@ -2573,18 +2534,22 @@ private IRubyObject getConstant(String name) { return parser.info.jsonModule.get().getConstant(name); } - private RaiseException newException(String className, String message) { + private RaiseException newException(ThreadContext context, String className, String message) { return Utils.newException(context, className, message); } - private RaiseException newException(String className, RubyString message) { + private RaiseException newException(ThreadContext context, String className, RubyString message) { return Utils.newException(context, className, message); } - private RaiseException newException(String className, - String messageBegin, ByteList messageEnd) { - return newException(className, - getRuntime().newString(messageBegin).cat(messageEnd)); - } + RubyHash.VisitorWithState MATCH_VISITOR = new RubyHash.VisitorWithState() { + @Override + public void visit(ThreadContext context, RubyHash self, IRubyObject pattern, IRubyObject klass, int index, IRubyObject[] state) { + if (pattern.callMethod(context, "===", state[0]).isTrue()) { + state[1] = klass; + throw JumpException.SPECIAL_JUMP; + } + } + }; } } diff --git a/java/src/json/ext/Parser.rl b/java/src/json/ext/Parser.rl index 9d2b96d6..bf42b445 100644 --- a/java/src/json/ext/Parser.rl +++ b/java/src/json/ext/Parser.rl @@ -5,10 +5,12 @@ */ package json.ext; +import org.jcodings.Encoding; +import org.jcodings.specific.ASCIIEncoding; +import org.jcodings.specific.UTF8Encoding; import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyClass; -import org.jruby.RubyEncoding; import org.jruby.RubyFloat; import org.jruby.RubyHash; import org.jruby.RubyInteger; @@ -18,13 +20,13 @@ import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.JumpException; import org.jruby.exceptions.RaiseException; import org.jruby.runtime.Block; +import org.jruby.runtime.Helpers; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.util.ByteList; import org.jruby.util.ConvertBytes; -import org.jruby.util.ConvertDouble; import java.util.function.BiFunction; @@ -39,7 +41,7 @@ import static org.jruby.util.ConvertDouble.DoubleConverter; * This is performed for you when you include "json/ext". * *

This class does not perform the actual parsing, just acts as an interface - * to Ruby code. When the {@link #parse()} method is invoked, a + * to Ruby code. When the {@link #parse(ThreadContext)} method is invoked, a * Parser.ParserSession object is instantiated, which handles the process. * * @author mernen @@ -69,11 +71,7 @@ public class Parser extends RubyObject { private static final String CONST_INFINITY = "Infinity"; private static final String CONST_MINUS_INFINITY = "MinusInfinity"; - static final ObjectAllocator ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klazz) { - return new Parser(runtime, klazz); - } - }; + static final ObjectAllocator ALLOCATOR = Parser::new; /** * Multiple-value return for internal parser methods. @@ -111,42 +109,42 @@ public class Parser extends RubyObject { * source. * It will be configured by the opts Hash. * opts can have the following keys: - * + *

*

*
:max_nesting *
The maximum depth of nesting allowed in the parsed data * structures. Disable depth checking with :max_nesting => false|nil|0, * it defaults to 100. - * + *

*

:allow_nan *
If set to true, allow NaN, * Infinity and -Infinity in defiance of RFC 4627 * to be parsed by the Parser. This option defaults to false. - * + *

*

:allow_trailing_comma *
If set to true, allow arrays and objects with a trailing * comma in defiance of RFC 4627 to be parsed by the Parser. * This option defaults to false. - * + *

*

:symbolize_names *
If set to true, returns symbols for the names (keys) in * a JSON object. Otherwise strings are returned, which is also the default. - * + *

*

:create_additions *
If set to false, the Parser doesn't create additions * even if a matching class and create_id was found. This option * defaults to true. - * + *

*

:object_class *
Defaults to Hash. If another type is provided, it will be used * instead of Hash to represent JSON objects. The type must respond to * new without arguments, and return an object that respond to []=. - * + *

*

:array_class *
Defaults to Array. If another type is provided, it will be used * instead of Hash to represent JSON arrays. The type must respond to * new without arguments, and return an object that respond to <<. - * + *

*

:decimal_class *
Specifies which class to use instead of the default (Float) when * parsing decimal numbers. This class must accept a single string argument @@ -154,31 +152,44 @@ public class Parser extends RubyObject { *
*/ - @JRubyMethod(name = "new", required = 1, optional = 1, meta = true) - public static IRubyObject newInstance(IRubyObject clazz, IRubyObject[] args, Block block) { + @JRubyMethod(name = "new", meta = true) + public static IRubyObject newInstance(IRubyObject clazz, IRubyObject arg0, Block block) { Parser parser = (Parser)((RubyClass)clazz).allocate(); - parser.callInit(args, block); + parser.callInit(arg0, block); + + return parser; + } + + @JRubyMethod(name = "new", meta = true) + public static IRubyObject newInstance(IRubyObject clazz, IRubyObject arg0, IRubyObject arg1, Block block) { + Parser parser = (Parser)((RubyClass)clazz).allocate(); + + parser.callInit(arg0, arg1, block); return parser; } @JRubyMethod(meta=true) public static IRubyObject parse(ThreadContext context, IRubyObject clazz, IRubyObject source, IRubyObject opts) { - IRubyObject[] args = new IRubyObject[] {source, opts}; Parser parser = (Parser)((RubyClass)clazz).allocate(); - parser.callInit(args, null); + parser.callInit(source, opts, null); return parser.parse(context); } - @JRubyMethod(required = 1, optional = 1, visibility = Visibility.PRIVATE) - public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { - Ruby runtime = context.getRuntime(); + @JRubyMethod(visibility = Visibility.PRIVATE) + public IRubyObject initialize(ThreadContext context, IRubyObject arg0) { + return initialize(context, arg0, null); + } + + @JRubyMethod(visibility = Visibility.PRIVATE) + public IRubyObject initialize(ThreadContext context, IRubyObject arg0, IRubyObject arg1) { + Ruby runtime = context.runtime; if (this.vSource != null) { throw runtime.newTypeError("already initialized instance"); - } + } - OptionsReader opts = new OptionsReader(context, args.length > 1 ? args[1] : null); + OptionsReader opts = new OptionsReader(context, arg1); this.maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING); this.allowNaN = opts.getBool("allow_nan", false); this.allowTrailingComma = opts.getBool("allow_trailing_comma", false); @@ -213,12 +224,9 @@ public class Parser extends RubyObject { } if(symbolizeNames && createAdditions) { - throw runtime.newArgumentError( - "options :symbolize_names and :create_additions cannot be " + - " used in conjunction" - ); + throw runtime.newArgumentError("options :symbolize_names and :create_additions cannot be used in conjunction"); } - this.vSource = args[0].convertToString(); + this.vSource = arg0.convertToString(); this.vSource = convertEncoding(context, vSource); return this; @@ -230,44 +238,17 @@ public class Parser extends RubyObject { * Returns the source string if no conversion is needed. */ private RubyString convertEncoding(ThreadContext context, RubyString source) { - RubyEncoding encoding = (RubyEncoding)source.encoding(context); - if (encoding == info.ascii8bit.get()) { + Encoding encoding = source.getEncoding(); + if (encoding == ASCIIEncoding.INSTANCE) { source = (RubyString) source.dup(); - source.force_encoding(context, info.utf8.get()); - } else { - source = (RubyString) source.encode(context, info.utf8.get()); + source.setEncoding(UTF8Encoding.INSTANCE); + source.clearCodeRange(); + } else if (encoding != UTF8Encoding.INSTANCE) { + source = (RubyString) source.encode(context, context.runtime.getEncodingService().convertEncodingToRubyEncoding(UTF8Encoding.INSTANCE)); } return source; } - /** - * Checks the first four bytes of the given ByteList to infer its encoding, - * using the principle demonstrated on section 3 of RFC 4627 (JSON). - */ - private static String sniffByteList(ByteList bl) { - if (bl.length() < 4) return null; - if (bl.get(0) == 0 && bl.get(2) == 0) { - return bl.get(1) == 0 ? "utf-32be" : "utf-16be"; - } - if (bl.get(1) == 0 && bl.get(3) == 0) { - return bl.get(2) == 0 ? "utf-32le" : "utf-16le"; - } - return null; - } - - /** - * Assumes the given (binary) RubyString to be in the given encoding, then - * converts it to UTF-8. - */ - private RubyString reinterpretEncoding(ThreadContext context, - RubyString str, String sniffedEncoding) { - RubyEncoding actualEncoding = info.getEncoding(context, sniffedEncoding); - RubyEncoding targetEncoding = info.utf8.get(); - RubyString dup = (RubyString)str.dup(); - dup.force_encoding(context, actualEncoding); - return (RubyString)dup.encode_bang(context, targetEncoding); - } - /** * Parser#parse() * @@ -276,7 +257,7 @@ public class Parser extends RubyObject { */ @JRubyMethod public IRubyObject parse(ThreadContext context) { - return new ParserSession(this, context, info).parse(); + return new ParserSession(this, context, info).parse(context); } /** @@ -286,15 +267,15 @@ public class Parser extends RubyObject { * used to construct this Parser. */ @JRubyMethod(name = "source") - public IRubyObject source_get() { - return checkAndGetSource().dup(); + public IRubyObject source_get(ThreadContext context) { + return checkAndGetSource(context).dup(); } - public RubyString checkAndGetSource() { + public RubyString checkAndGetSource(ThreadContext context) { if (vSource != null) { return vSource; } else { - throw getRuntime().newTypeError("uninitialized instance"); + throw context.runtime.newTypeError("uninitialized instance"); } } @@ -333,39 +314,27 @@ public class Parser extends RubyObject { @SuppressWarnings("fallthrough") private static class ParserSession { private final Parser parser; - private final ThreadContext context; private final RuntimeInfo info; private final ByteList byteList; private final ByteList view; private final byte[] data; private final StringDecoder decoder; private int currentNesting = 0; - private final DoubleConverter dc; - - // initialization value for all state variables. - // no idea about the origins of this value, ask Flori ;) - private static final int EVIL = 0x666; private ParserSession(Parser parser, ThreadContext context, RuntimeInfo info) { this.parser = parser; - this.context = context; this.info = info; - this.byteList = parser.checkAndGetSource().getByteList(); + this.byteList = parser.checkAndGetSource(context).getByteList(); this.data = byteList.unsafeBytes(); this.view = new ByteList(data, false); - this.decoder = new StringDecoder(context); - this.dc = new DoubleConverter(); + this.decoder = new StringDecoder(); } - private RaiseException unexpectedToken(int absStart, int absEnd) { - RubyString msg = getRuntime().newString("unexpected token at '") + private RaiseException unexpectedToken(ThreadContext context, int absStart, int absEnd) { + RubyString msg = context.runtime.newString("unexpected token at '") .cat(data, absStart, Math.min(absEnd - absStart, 32)) .cat((byte)'\''); - return newException(Utils.M_PARSER_ERROR, msg); - } - - private Ruby getRuntime() { - return context.getRuntime(); + return newException(context, Utils.M_PARSER_ERROR, msg); } %%{ @@ -403,26 +372,26 @@ public class Parser extends RubyObject { write data; action parse_null { - result = getRuntime().getNil(); + result = context.nil; } action parse_false { - result = getRuntime().getFalse(); + result = context.fals; } action parse_true { - result = getRuntime().getTrue(); + result = context.tru; } action parse_nan { if (parser.allowNaN) { result = getConstant(CONST_NAN); } else { - throw unexpectedToken(p - 2, pe); + throw unexpectedToken(context, p - 2, pe); } } action parse_infinity { if (parser.allowNaN) { result = getConstant(CONST_INFINITY); } else { - throw unexpectedToken(p - 7, pe); + throw unexpectedToken(context, p - 7, pe); } } action parse_number { @@ -435,15 +404,15 @@ public class Parser extends RubyObject { fhold; fbreak; } else { - throw unexpectedToken(p, pe); + throw unexpectedToken(context, p, pe); } } - parseFloat(res, fpc, pe); + parseFloat(context, res, fpc, pe); if (res.result != null) { result = res.result; fexec res.p; } - parseInteger(res, fpc, pe); + parseInteger(context, res, fpc, pe); if (res.result != null) { result = res.result; fexec res.p; @@ -452,7 +421,7 @@ public class Parser extends RubyObject { fbreak; } action parse_string { - parseString(res, fpc, pe); + parseString(context, res, fpc, pe); if (res.result == null) { fhold; fbreak; @@ -463,7 +432,7 @@ public class Parser extends RubyObject { } action parse_array { currentNesting++; - parseArray(res, fpc, pe); + parseArray(context, res, fpc, pe); currentNesting--; if (res.result == null) { fhold; @@ -475,7 +444,7 @@ public class Parser extends RubyObject { } action parse_object { currentNesting++; - parseObject(res, fpc, pe); + parseObject(context, res, fpc, pe); currentNesting--; if (res.result == null) { fhold; @@ -502,8 +471,8 @@ public class Parser extends RubyObject { ) %*exit; }%% - void parseValue(ParserResult res, int p, int pe) { - int cs = EVIL; + void parseValue(ThreadContext context, ParserResult res, int p, int pe) { + int cs; IRubyObject result = null; %% write init; @@ -532,19 +501,18 @@ public class Parser extends RubyObject { main := '-'? ( '0' | [1-9][0-9]* ) ( ^[0-9]? @exit ); }%% - void parseInteger(ParserResult res, int p, int pe) { + void parseInteger(ThreadContext context, ParserResult res, int p, int pe) { int new_p = parseIntegerInternal(p, pe); if (new_p == -1) { res.update(null, p); return; } - RubyInteger number = createInteger(p, new_p); + RubyInteger number = createInteger(context, p, new_p); res.update(number, new_p + 1); - return; } int parseIntegerInternal(int p, int pe) { - int cs = EVIL; + int cs; %% write init; int memo = p; @@ -557,8 +525,8 @@ public class Parser extends RubyObject { return p; } - RubyInteger createInteger(int p, int new_p) { - Ruby runtime = getRuntime(); + RubyInteger createInteger(ThreadContext context, int p, int new_p) { + Ruby runtime = context.runtime; ByteList num = absSubSequence(p, new_p); return bytesToInum(runtime, num); } @@ -584,7 +552,7 @@ public class Parser extends RubyObject { ( ^[0-9Ee.\-]? @exit ); }%% - void parseFloat(ParserResult res, int p, int pe) { + void parseFloat(ThreadContext context, ParserResult res, int p, int pe) { int new_p = parseFloatInternal(p, pe); if (new_p == -1) { res.update(null, p); @@ -597,7 +565,7 @@ public class Parser extends RubyObject { } int parseFloatInternal(int p, int pe) { - int cs = EVIL; + int cs; %% write init; int memo = p; @@ -618,9 +586,9 @@ public class Parser extends RubyObject { action parse_string { int offset = byteList.begin(); - ByteList decoded = decoder.decode(byteList, memo + 1 - offset, + ByteList decoded = decoder.decode(context, byteList, memo + 1 - offset, p - offset); - result = getRuntime().newString(decoded); + result = context.runtime.newString(decoded); if (result == null) { fhold; fbreak; @@ -643,8 +611,8 @@ public class Parser extends RubyObject { ) '"' @exit; }%% - void parseString(ParserResult res, int p, int pe) { - int cs = EVIL; + void parseString(ThreadContext context, ParserResult res, int p, int pe) { + int cs; IRubyObject result = null; %% write init; @@ -656,22 +624,14 @@ public class Parser extends RubyObject { if (matchString != null) { final IRubyObject[] memoArray = { result, null }; try { - matchString.visitAll(new RubyHash.Visitor() { - @Override - public void visit(IRubyObject pattern, IRubyObject klass) { - if (pattern.callMethod(context, "===", memoArray[0]).isTrue()) { - memoArray[1] = klass; - throw JumpException.SPECIAL_JUMP; - } - } - }); + matchString.visitAll(context, MATCH_VISITOR, memoArray); } catch (JumpException e) { } if (memoArray[1] != null) { RubyClass klass = (RubyClass) memoArray[1]; if (klass.respondsTo("json_creatable?") && klass.callMethod(context, "json_creatable?").isTrue()) { if (parser.deprecatedCreateAdditions) { - klass.getRuntime().getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); + context.runtime.getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); } result = klass.callMethod(context, "json_create", result); } @@ -682,10 +642,11 @@ public class Parser extends RubyObject { if (cs >= JSON_string_first_final && result != null) { if (result instanceof RubyString) { RubyString string = (RubyString)result; - string.force_encoding(context, info.utf8.get()); + string.setEncoding(UTF8Encoding.INSTANCE); + string.clearCodeRange(); if (parser.freeze) { string.setFrozen(true); - string = getRuntime().freezeAndDedupString(string); + string = context.runtime.freezeAndDedupString(string); } res.update(string, p + 1); } else { @@ -705,12 +666,12 @@ public class Parser extends RubyObject { action allow_trailing_comma { parser.allowTrailingComma } action parse_value { - parseValue(res, fpc, pe); + parseValue(context, res, fpc, pe); if (res.result == null) { fhold; fbreak; } else { - if (parser.arrayClass == getRuntime().getArray()) { + if (parser.arrayClass == context.runtime.getArray()) { ((RubyArray)result).append(res.result); } else { result.callMethod(context, "<<", res.result); @@ -737,17 +698,17 @@ public class Parser extends RubyObject { end_array @exit; }%% - void parseArray(ParserResult res, int p, int pe) { - int cs = EVIL; + void parseArray(ThreadContext context, ParserResult res, int p, int pe) { + int cs; if (parser.maxNesting > 0 && currentNesting > parser.maxNesting) { - throw newException(Utils.M_NESTING_ERROR, + throw newException(context, Utils.M_NESTING_ERROR, "nesting of " + currentNesting + " is too deep"); } IRubyObject result; - if (parser.arrayClass == getRuntime().getArray()) { - result = RubyArray.newArray(getRuntime()); + if (parser.arrayClass == context.runtime.getArray()) { + result = RubyArray.newArray(context.runtime); } else { result = parser.arrayClass.newInstance(context, IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); @@ -759,7 +720,7 @@ public class Parser extends RubyObject { if (cs >= JSON_array_first_final) { res.update(result, p + 1); } else { - throw unexpectedToken(p, pe); + throw unexpectedToken(context, p, pe); } } @@ -772,22 +733,22 @@ public class Parser extends RubyObject { action allow_trailing_comma { parser.allowTrailingComma } action parse_value { - parseValue(res, fpc, pe); + parseValue(context, res, fpc, pe); if (res.result == null) { fhold; fbreak; } else { - if (parser.objectClass == getRuntime().getHash()) { + if (parser.objectClass == context.runtime.getHash()) { ((RubyHash)result).op_aset(context, lastName, res.result); } else { - result.callMethod(context, "[]=", new IRubyObject[] { lastName, res.result }); + Helpers.invoke(context, result, "[]=", lastName, res.result); } fexec res.p; } } action parse_name { - parseString(res, fpc, pe); + parseString(context, res, fpc, pe); if (res.result == null) { fhold; fbreak; @@ -818,21 +779,21 @@ public class Parser extends RubyObject { ) @exit; }%% - void parseObject(ParserResult res, int p, int pe) { - int cs = EVIL; + void parseObject(ThreadContext context, ParserResult res, int p, int pe) { + int cs; IRubyObject lastName = null; boolean objectDefault = true; if (parser.maxNesting > 0 && currentNesting > parser.maxNesting) { - throw newException(Utils.M_NESTING_ERROR, + throw newException(context, Utils.M_NESTING_ERROR, "nesting of " + currentNesting + " is too deep"); } // this is guaranteed to be a RubyHash due to the earlier // allocator test at OptionsReader#getClass IRubyObject result; - if (parser.objectClass == getRuntime().getHash()) { - result = RubyHash.newHash(getRuntime()); + if (parser.objectClass == context.runtime.getHash()) { + result = RubyHash.newHash(context.runtime); } else { objectDefault = false; result = parser.objectClass.newInstance(context, @@ -865,7 +826,7 @@ public class Parser extends RubyObject { if (klass.respondsTo("json_creatable?") && klass.callMethod(context, "json_creatable?").isTrue()) { if (parser.deprecatedCreateAdditions) { - klass.getRuntime().getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); + context.runtime.getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); } returnedResult = klass.callMethod(context, "json_create", result); @@ -882,7 +843,7 @@ public class Parser extends RubyObject { write data; action parse_value { - parseValue(res, fpc, pe); + parseValue(context, res, fpc, pe); if (res.result == null) { fhold; fbreak; @@ -897,8 +858,8 @@ public class Parser extends RubyObject { ignore*; }%% - public IRubyObject parseImplemetation() { - int cs = EVIL; + public IRubyObject parseImplementation(ThreadContext context) { + int cs; int p, pe; IRubyObject result = null; ParserResult res = new ParserResult(); @@ -911,18 +872,18 @@ public class Parser extends RubyObject { if (cs >= JSON_first_final && p == pe) { return result; } else { - throw unexpectedToken(p, pe); + throw unexpectedToken(context, p, pe); } } - public IRubyObject parse() { - return parseImplemetation(); + public IRubyObject parse(ThreadContext context) { + return parseImplementation(context); } /** * Updates the "view" bytelist with the new offsets and returns it. - * @param start - * @param end + * @param absStart + * @param absEnd */ private ByteList absSubSequence(int absStart, int absEnd) { view.setBegin(absStart); @@ -938,18 +899,22 @@ public class Parser extends RubyObject { return parser.info.jsonModule.get().getConstant(name); } - private RaiseException newException(String className, String message) { + private RaiseException newException(ThreadContext context, String className, String message) { return Utils.newException(context, className, message); } - private RaiseException newException(String className, RubyString message) { + private RaiseException newException(ThreadContext context, String className, RubyString message) { return Utils.newException(context, className, message); } - private RaiseException newException(String className, - String messageBegin, ByteList messageEnd) { - return newException(className, - getRuntime().newString(messageBegin).cat(messageEnd)); - } + RubyHash.VisitorWithState MATCH_VISITOR = new RubyHash.VisitorWithState() { + @Override + public void visit(ThreadContext context, RubyHash self, IRubyObject pattern, IRubyObject klass, int index, IRubyObject[] state) { + if (pattern.callMethod(context, "===", state[0]).isTrue()) { + state[1] = klass; + throw JumpException.SPECIAL_JUMP; + } + } + }; } } diff --git a/java/src/json/ext/RuntimeInfo.java b/java/src/json/ext/RuntimeInfo.java index 2323bd94..f712dc25 100644 --- a/java/src/json/ext/RuntimeInfo.java +++ b/java/src/json/ext/RuntimeInfo.java @@ -6,12 +6,10 @@ package json.ext; import java.lang.ref.WeakReference; -import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; import org.jruby.Ruby; import org.jruby.RubyClass; -import org.jruby.RubyEncoding; import org.jruby.RubyModule; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; @@ -20,7 +18,7 @@ final class RuntimeInfo { // since the vast majority of cases runs just one runtime, // we optimize for that - private static WeakReference runtime1 = new WeakReference(null); + private static WeakReference runtime1 = new WeakReference<>(null); private static RuntimeInfo info1; // store remaining runtimes here (does not include runtime1) private static Map runtimes; @@ -30,32 +28,12 @@ final class RuntimeInfo { // the Ruby runtime object, which would cause memory leaks in the runtimes map above. /** JSON */ WeakReference jsonModule; - /** JSON::Ext::Generator::GeneratorMethods::String::Extend */ - WeakReference stringExtendModule; /** JSON::Ext::Generator::State */ WeakReference generatorStateClass; /** JSON::SAFE_STATE_PROTOTYPE */ WeakReference safeStatePrototype; - final WeakReference utf8; - final WeakReference ascii8bit; - // other encodings - private final Map> encodings; - - private RuntimeInfo(Ruby runtime) { - RubyClass encodingClass = runtime.getEncoding(); - if (encodingClass == null) { // 1.8 mode - utf8 = ascii8bit = null; - encodings = null; - } else { - ThreadContext context = runtime.getCurrentContext(); - - utf8 = new WeakReference((RubyEncoding)RubyEncoding.find(context, - encodingClass, runtime.newString("utf-8"))); - ascii8bit = new WeakReference((RubyEncoding)RubyEncoding.find(context, - encodingClass, runtime.newString("ascii-8bit"))); - encodings = new HashMap>(); - } + private RuntimeInfo() { } static RuntimeInfo initRuntime(Ruby runtime) { @@ -63,16 +41,16 @@ static RuntimeInfo initRuntime(Ruby runtime) { if (runtime1.get() == runtime) { return info1; } else if (runtime1.get() == null) { - runtime1 = new WeakReference(runtime); - info1 = new RuntimeInfo(runtime); + runtime1 = new WeakReference<>(runtime); + info1 = new RuntimeInfo(); return info1; } else { if (runtimes == null) { - runtimes = new WeakHashMap(1); + runtimes = new WeakHashMap<>(1); } RuntimeInfo cache = runtimes.get(runtime); if (cache == null) { - cache = new RuntimeInfo(runtime); + cache = new RuntimeInfo(); runtimes.put(runtime, cache); } return cache; @@ -90,26 +68,13 @@ public static RuntimeInfo forRuntime(Ruby runtime) { } } - public RubyEncoding getEncoding(ThreadContext context, String name) { - synchronized (encodings) { - WeakReference encoding = encodings.get(name); - if (encoding == null) { - Ruby runtime = context.getRuntime(); - encoding = new WeakReference((RubyEncoding)RubyEncoding.find(context, - runtime.getEncoding(), runtime.newString(name))); - encodings.put(name, encoding); - } - return encoding.get(); - } - } - public GeneratorState getSafeStatePrototype(ThreadContext context) { if (safeStatePrototype == null) { IRubyObject value = jsonModule.get().getConstant("SAFE_STATE_PROTOTYPE"); if (!(value instanceof GeneratorState)) { - throw context.getRuntime().newTypeError(value, generatorStateClass.get()); + throw context.runtime.newTypeError(value, generatorStateClass.get()); } - safeStatePrototype = new WeakReference((GeneratorState)value); + safeStatePrototype = new WeakReference<>((GeneratorState) value); } return safeStatePrototype.get(); } diff --git a/java/src/json/ext/StringDecoder.java b/java/src/json/ext/StringDecoder.java index 76cf1837..bf616c1e 100644 --- a/java/src/json/ext/StringDecoder.java +++ b/java/src/json/ext/StringDecoder.java @@ -9,6 +9,8 @@ import org.jruby.runtime.ThreadContext; import org.jruby.util.ByteList; +import java.io.IOException; + /** * A decoder that reads a JSON-encoded string from the given sources and * returns its decoded form on a new ByteList. Escaped Unicode characters @@ -21,36 +23,37 @@ final class StringDecoder extends ByteListTranscoder { */ private int surrogatePairStart = -1; - // Array used for writing multi-byte characters into the buffer at once - private final byte[] aux = new byte[4]; + private ByteList out; - StringDecoder(ThreadContext context) { - super(context); - } + // Array used for writing multibyte characters into the buffer at once + private final byte[] aux = new byte[4]; - ByteList decode(ByteList src, int start, int end) { - ByteList out = new ByteList(end - start); - out.setEncoding(src.getEncoding()); - init(src, start, end, out); - while (hasNext()) { - handleChar(readUtf8Char()); + ByteList decode(ThreadContext context, ByteList src, int start, int end) { + try { + init(src, start, end); + this.out = new ByteList(end - start); + while (hasNext()) { + handleChar(context, readUtf8Char(context)); + } + quoteStop(pos); + return out; + } catch (IOException e) { + throw context.runtime.newIOErrorFromException(e); } - quoteStop(pos); - return out; } - private void handleChar(int c) { + private void handleChar(ThreadContext context, int c) throws IOException { if (c == '\\') { quoteStop(charStart); - handleEscapeSequence(); + handleEscapeSequence(context); } else { quoteStart(); } } - private void handleEscapeSequence() { - ensureMin(1); - switch (readUtf8Char()) { + private void handleEscapeSequence(ThreadContext context) throws IOException { + ensureMin(context, 1); + switch (readUtf8Char(context)) { case 'b': append('\b'); break; @@ -67,13 +70,13 @@ private void handleEscapeSequence() { append('\t'); break; case 'u': - ensureMin(4); - int cp = readHex(); + ensureMin(context, 4); + int cp = readHex(context); if (Character.isHighSurrogate((char)cp)) { - handleLowSurrogate((char)cp); + handleLowSurrogate(context, (char)cp); } else if (Character.isLowSurrogate((char)cp)) { // low surrogate with no high surrogate - throw invalidUtf8(); + throw invalidUtf8(context); } else { writeUtf8Char(cp); } @@ -83,15 +86,23 @@ private void handleEscapeSequence() { } } - private void handleLowSurrogate(char highSurrogate) { + protected void append(int b) throws IOException { + out.append(b); + } + + protected void append(byte[] origin, int start, int length) throws IOException { + out.append(origin, start, length); + } + + private void handleLowSurrogate(ThreadContext context, char highSurrogate) throws IOException { surrogatePairStart = charStart; - ensureMin(1); - int lowSurrogate = readUtf8Char(); + ensureMin(context, 1); + int lowSurrogate = readUtf8Char(context); if (lowSurrogate == '\\') { - ensureMin(5); - if (readUtf8Char() != 'u') throw invalidUtf8(); - lowSurrogate = readHex(); + ensureMin(context, 5); + if (readUtf8Char(context) != 'u') throw invalidUtf8(context); + lowSurrogate = readHex(context); } if (Character.isLowSurrogate((char)lowSurrogate)) { @@ -99,11 +110,11 @@ private void handleLowSurrogate(char highSurrogate) { (char)lowSurrogate)); surrogatePairStart = -1; } else { - throw invalidUtf8(); + throw invalidUtf8(context); } } - private void writeUtf8Char(int codePoint) { + private void writeUtf8Char(int codePoint) throws IOException { if (codePoint < 0x80) { append(codePoint); } else if (codePoint < 0x800) { @@ -131,12 +142,12 @@ private byte tailByte(int value) { /** * Reads a 4-digit unsigned hexadecimal number from the source. */ - private int readHex() { + private int readHex(ThreadContext context) { int numberStart = pos; int result = 0; int length = 4; for (int i = 0; i < length; i++) { - int digit = readUtf8Char(); + int digit = readUtf8Char(context); int digitValue; if (digit >= '0' && digit <= '9') { digitValue = digit - '0'; @@ -154,13 +165,13 @@ private int readHex() { } @Override - protected RaiseException invalidUtf8() { + protected RaiseException invalidUtf8(ThreadContext context) { ByteList message = new ByteList( ByteList.plain("partial character in source, " + "but hit end near ")); int start = surrogatePairStart != -1 ? surrogatePairStart : charStart; message.append(src, start, srcEnd - start); return Utils.newException(context, Utils.M_PARSER_ERROR, - context.getRuntime().newString(message)); + context.runtime.newString(message)); } } diff --git a/java/src/json/ext/StringEncoder.java b/java/src/json/ext/StringEncoder.java index 290aa249..68fd81e3 100644 --- a/java/src/json/ext/StringEncoder.java +++ b/java/src/json/ext/StringEncoder.java @@ -9,6 +9,9 @@ import org.jruby.runtime.ThreadContext; import org.jruby.util.ByteList; +import java.io.IOException; +import java.io.OutputStream; + /** * An encoder that reads from the given source and outputs its representation * to another ByteList. The source string is fully checked for UTF-8 validity, @@ -17,10 +20,12 @@ final class StringEncoder extends ByteListTranscoder { private final boolean asciiOnly, scriptSafe; + private OutputStream out; + // Escaped characters will reuse this array, to avoid new allocations // or appending them byte-by-byte private final byte[] aux = - new byte[] {/* First unicode character */ + new byte[] {/* First Unicode character */ '\\', 'u', 0, 0, 0, 0, /* Second unicode character (for surrogate pairs) */ '\\', 'u', 0, 0, 0, 0, @@ -37,23 +42,42 @@ final class StringEncoder extends ByteListTranscoder { new byte[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - StringEncoder(ThreadContext context, boolean asciiOnly, boolean scriptSafe) { - super(context); + StringEncoder(boolean asciiOnly, boolean scriptSafe) { this.asciiOnly = asciiOnly; this.scriptSafe = scriptSafe; } - void encode(ByteList src, ByteList out) { - init(src, out); + void encode(ThreadContext context, ByteList src, OutputStream out) throws IOException { + init(src); + this.out = out; append('"'); while (hasNext()) { - handleChar(readUtf8Char()); + handleChar(readUtf8Char(context)); } quoteStop(pos); append('"'); } - private void handleChar(int c) { + void encodeASCII(ThreadContext context, ByteList src, OutputStream out) throws IOException { + init(src); + this.out = out; + append('"'); + while (hasNext()) { + handleChar(readASCIIChar()); + } + quoteStop(pos); + append('"'); + } + + protected void append(int b) throws IOException { + out.write(b); + } + + protected void append(byte[] origin, int start, int length) throws IOException { + out.write(origin, start, length); + } + + private void handleChar(int c) throws IOException { switch (c) { case '"': case '\\': @@ -97,13 +121,13 @@ private void handleChar(int c) { } } - private void escapeChar(char c) { + private void escapeChar(char c) throws IOException { quoteStop(charStart); aux[ESCAPE_CHAR_OFFSET + 1] = (byte)c; append(aux, ESCAPE_CHAR_OFFSET, 2); } - private void escapeUtf8Char(int codePoint) { + private void escapeUtf8Char(int codePoint) throws IOException { int numChars = Character.toChars(codePoint, utf16, 0); escapeCodeUnit(utf16[0], ESCAPE_UNI1_OFFSET + 2); if (numChars > 1) escapeCodeUnit(utf16[1], ESCAPE_UNI2_OFFSET + 2); @@ -117,8 +141,7 @@ private void escapeCodeUnit(char c, int auxOffset) { } @Override - protected RaiseException invalidUtf8() { - return Utils.newException(context, Utils.M_GENERATOR_ERROR, - "source sequence is illegal/malformed utf-8"); + protected RaiseException invalidUtf8(ThreadContext context) { + return Utils.newException(context, Utils.M_GENERATOR_ERROR, "source sequence is illegal/malformed utf-8"); } } diff --git a/java/src/json/ext/Utils.java b/java/src/json/ext/Utils.java index ed6f8329..87139cdb 100644 --- a/java/src/json/ext/Utils.java +++ b/java/src/json/ext/Utils.java @@ -9,7 +9,6 @@ import org.jruby.RubyArray; import org.jruby.RubyClass; import org.jruby.RubyException; -import org.jruby.RubyHash; import org.jruby.RubyString; import org.jruby.exceptions.RaiseException; import org.jruby.runtime.Block; @@ -44,12 +43,6 @@ static RubyArray ensureArray(IRubyObject object) throws RaiseException { throw runtime.newTypeError(object, runtime.getArray()); } - static RubyHash ensureHash(IRubyObject object) throws RaiseException { - if (object instanceof RubyHash) return (RubyHash)object; - Ruby runtime = object.getRuntime(); - throw runtime.newTypeError(object, runtime.getHash()); - } - static RubyString ensureString(IRubyObject object) throws RaiseException { if (object instanceof RubyString) return (RubyString)object; Ruby runtime = object.getRuntime(); @@ -59,17 +52,27 @@ static RubyString ensureString(IRubyObject object) throws RaiseException { static RaiseException newException(ThreadContext context, String className, String message) { return newException(context, className, - context.getRuntime().newString(message)); + context.runtime.newString(message)); } static RaiseException newException(ThreadContext context, String className, RubyString message) { - RuntimeInfo info = RuntimeInfo.forRuntime(context.getRuntime()); + RuntimeInfo info = RuntimeInfo.forRuntime(context.runtime); RubyClass klazz = info.jsonModule.get().getClass(className); - RubyException excptn = - (RubyException)klazz.newInstance(context, - new IRubyObject[] {message}, Block.NULL_BLOCK); - return new RaiseException(excptn); + RubyException excptn = (RubyException)klazz.newInstance(context, message, Block.NULL_BLOCK); + return excptn.toThrowable(); + } + + static RubyException buildGeneratorError(ThreadContext context, IRubyObject invalidObject, RubyString message) { + RuntimeInfo info = RuntimeInfo.forRuntime(context.runtime); + RubyClass klazz = info.jsonModule.get().getClass(M_GENERATOR_ERROR); + RubyException excptn = (RubyException)klazz.newInstance(context, message, Block.NULL_BLOCK); + excptn.setInstanceVariable("@invalid_object", invalidObject); + return excptn; + } + + static RubyException buildGeneratorError(ThreadContext context, IRubyObject invalidObject, String message) { + return buildGeneratorError(context, invalidObject, context.runtime.newString(message)); } static byte[] repeat(ByteList a, int n) { diff --git a/lib/json/common.rb b/lib/json/common.rb index 4c6b2e1a..197ae11f 100644 --- a/lib/json/common.rb +++ b/lib/json/common.rb @@ -143,7 +143,23 @@ class CircularDatastructure < NestingError; end # :startdoc: # This exception is raised if a generator or unparser error occurs. - class GeneratorError < JSONError; end + class GeneratorError < JSONError + attr_reader :invalid_object + + def initialize(message, invalid_object = nil) + super(message) + @invalid_object = invalid_object + end + + def detailed_message(...) + if @invalid_object.nil? + super + else + "#{super}\nInvalid object: #{@invalid_object.inspect}" + end + end + end + # For backwards compatibility UnparserError = GeneratorError # :nodoc: @@ -286,7 +302,7 @@ def generate(obj, opts = nil) if State === opts opts.generate(obj) else - State.generate(obj, opts) + State.generate(obj, opts, nil) end end @@ -411,6 +427,10 @@ class << self # # Returns the Ruby objects created by parsing the given +source+. # + # BEWARE: This method is meant to serialise data from trusted user input, + # like from your own database server or clients under your control, it could + # be dangerous to allow untrusted users to pass JSON sources into it. + # # - Argument +source+ must be, or be convertible to, a \String: # - If +source+ responds to instance method +to_str+, # source.to_str becomes the source. @@ -425,9 +445,6 @@ class << self # - Argument +proc+, if given, must be a \Proc that accepts one argument. # It will be called recursively with each result (depth-first order). # See details below. - # BEWARE: This method is meant to serialise data from trusted user input, - # like from your own database server or clients under your control, it could - # be dangerous to allow untrusted users to pass JSON sources into it. # - Argument +opts+, if given, contains a \Hash of options for the parsing. # See {Parsing Options}[#module-JSON-label-Parsing+Options]. # The default options can be changed via method JSON.unsafe_load_default_options=. @@ -564,6 +581,16 @@ def unsafe_load(source, proc = nil, options = nil) # # Returns the Ruby objects created by parsing the given +source+. # + # BEWARE: This method is meant to serialise data from trusted user input, + # like from your own database server or clients under your control, it could + # be dangerous to allow untrusted users to pass JSON sources into it. + # If you must use it, use JSON.unsafe_load instead to make it clear. + # + # Since JSON version 2.8.0, `load` emits a deprecation warning when a + # non native type is deserialized, without `create_additions` being explicitly + # enabled, and in JSON version 3.0, `load` will have `create_additions` disabled + # by default. + # # - Argument +source+ must be, or be convertible to, a \String: # - If +source+ responds to instance method +to_str+, # source.to_str becomes the source. @@ -578,10 +605,6 @@ def unsafe_load(source, proc = nil, options = nil) # - Argument +proc+, if given, must be a \Proc that accepts one argument. # It will be called recursively with each result (depth-first order). # See details below. - # BEWARE: This method is meant to serialise data from trusted user input, - # like from your own database server or clients under your control, it could - # be dangerous to allow untrusted users to pass JSON sources into it. - # If you must use it, use JSON.unsafe_load instead to make it clear. # - Argument +opts+, if given, contains a \Hash of options for the parsing. # See {Parsing Options}[#module-JSON-label-Parsing+Options]. # The default options can be changed via method JSON.load_default_options=. @@ -794,18 +817,15 @@ def dump(obj, anIO = nil, limit = nil, kwargs = nil) opts = opts.merge(:max_nesting => limit) if limit opts = merge_dump_options(opts, **kwargs) if kwargs - result = begin - generate(obj, opts) + begin + if State === opts + opts.generate(obj, anIO) + else + State.generate(obj, opts, anIO) + end rescue JSON::NestingError raise ArgumentError, "exceed depth limit" end - - if anIO.nil? - result - else - anIO.write result - anIO - end end # Encodes string using String.encode. diff --git a/lib/json/ext/generator/state.rb b/lib/json/ext/generator/state.rb index 6cd9496e..1e0d5245 100644 --- a/lib/json/ext/generator/state.rb +++ b/lib/json/ext/generator/state.rb @@ -47,6 +47,17 @@ def configure(opts) alias_method :merge, :configure + # call-seq: + # generate(obj) -> String + # generate(obj, anIO) -> anIO + # + # Generates a valid JSON document from object +obj+ and returns the + # result. If no valid JSON document can be created this method raises a + # GeneratorError exception. + def generate(obj, io = nil) + _generate(obj, io) + end + # call-seq: to_h # # Returns the configuration instance variables as a hash, that can be diff --git a/lib/json/truffle_ruby/generator.rb b/lib/json/truffle_ruby/generator.rb index b0f3e420..493ef263 100644 --- a/lib/json/truffle_ruby/generator.rb +++ b/lib/json/truffle_ruby/generator.rb @@ -62,8 +62,8 @@ def utf8_to_json(string, script_safe = false) # :nodoc: string end - def utf8_to_json_ascii(string, script_safe = false) # :nodoc: - string = string.b + def utf8_to_json_ascii(original_string, script_safe = false) # :nodoc: + string = original_string.b map = script_safe ? SCRIPT_SAFE_MAP : MAP string.gsub!(/[\/"\\\x0-\x1f]/n) { map[$&] || $& } string.gsub!(/( @@ -74,7 +74,7 @@ def utf8_to_json_ascii(string, script_safe = false) # :nodoc: )+ | [\x80-\xc1\xf5-\xff] # invalid )/nx) { |c| - c.size == 1 and raise GeneratorError, "invalid utf8 byte: '#{c}'" + c.size == 1 and raise GeneratorError.new("invalid utf8 byte: '#{c}'", original_string) s = c.encode(::Encoding::UTF_16BE, ::Encoding::UTF_8).unpack('H*')[0] s.force_encoding(::Encoding::BINARY) s.gsub!(/.{4}/n, '\\\\u\&') @@ -83,7 +83,7 @@ def utf8_to_json_ascii(string, script_safe = false) # :nodoc: string.force_encoding(::Encoding::UTF_8) string rescue => e - raise GeneratorError.wrap(e) + raise GeneratorError.new(e.message, original_string) end def valid_utf8?(string) @@ -96,8 +96,14 @@ def valid_utf8?(string) # This class is used to create State instances, that are use to hold data # while generating a JSON text from a Ruby data structure. class State - def self.generate(obj, opts = nil) - new(opts).generate(obj) + def self.generate(obj, opts = nil, io = nil) + string = new(opts).generate(obj) + if io + io.write(string) + io + else + string + end end # Creates a State object from _opts_, which ought to be Hash to create @@ -300,8 +306,10 @@ def generate(obj) else result = obj.to_json(self) end - JSON::TruffleRuby::Generator.valid_utf8?(result) or raise GeneratorError, - "source sequence #{result.inspect} is illegal/malformed utf-8" + JSON::TruffleRuby::Generator.valid_utf8?(result) or raise GeneratorError.new( + "source sequence #{result.inspect} is illegal/malformed utf-8", + obj + ) result end @@ -358,10 +366,10 @@ def generate(obj) begin string = string.encode(::Encoding::UTF_8) rescue Encoding::UndefinedConversionError => error - raise GeneratorError, error.message + raise GeneratorError.new(error.message, string) end end - raise GeneratorError, "source sequence is illegal/malformed utf-8" unless string.valid_encoding? + raise GeneratorError.new("source sequence is illegal/malformed utf-8", string) unless string.valid_encoding? if /["\\\x0-\x1f]/n.match?(string) buf << string.gsub(/["\\\x0-\x1f]/n, MAP) @@ -397,7 +405,7 @@ module Object # special method #to_json was defined for some object. def to_json(state = nil, *) if state && State.from_state(state).strict? - raise GeneratorError, "#{self.class} not allowed in JSON" + raise GeneratorError.new("#{self.class} not allowed in JSON", self) else to_s.to_json end @@ -448,7 +456,7 @@ def json_transform(state) result = +"#{result}#{key_json}#{state.space_before}:#{state.space}" if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value) - raise GeneratorError, "#{value.class} not allowed in JSON" + raise GeneratorError.new("#{value.class} not allowed in JSON", value) elsif value.respond_to?(:to_json) result << value.to_json(state) else @@ -501,7 +509,7 @@ def json_transform(state) result << delim unless first result << state.indent * depth if indent if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value) - raise GeneratorError, "#{value.class} not allowed in JSON" + raise GeneratorError.new("#{value.class} not allowed in JSON", value) elsif value.respond_to?(:to_json) result << value.to_json(state) else @@ -530,13 +538,13 @@ def to_json(state = nil, *) if state.allow_nan? to_s else - raise GeneratorError, "#{self} not allowed in JSON" + raise GeneratorError.new("#{self} not allowed in JSON", self) end when nan? if state.allow_nan? to_s else - raise GeneratorError, "#{self} not allowed in JSON" + raise GeneratorError.new("#{self} not allowed in JSON", self) end else to_s @@ -552,7 +560,7 @@ def to_json(state = nil, *args) state = State.from_state(state) if encoding == ::Encoding::UTF_8 unless valid_encoding? - raise GeneratorError, "source sequence is illegal/malformed utf-8" + raise GeneratorError.new("source sequence is illegal/malformed utf-8", self) end string = self else @@ -564,7 +572,7 @@ def to_json(state = nil, *args) %("#{JSON::TruffleRuby::Generator.utf8_to_json(string, state.script_safe)}") end rescue Encoding::UndefinedConversionError => error - raise ::JSON::GeneratorError, error.message + raise ::JSON::GeneratorError.new(error.message, self) end # Module that holds the extending methods if, the String module is diff --git a/test/json/json_common_interface_test.rb b/test/json/json_common_interface_test.rb index a5d62337..1f157da0 100644 --- a/test/json/json_common_interface_test.rb +++ b/test/json/json_common_interface_test.rb @@ -162,6 +162,17 @@ def test_dump assert_equal too_deep, dump(obj, strict: false) end + def test_dump_in_io + io = StringIO.new + assert_same io, JSON.dump([1], io) + assert_equal "[1]", io.string + + big_object = ["a" * 10, "b" * 40, { foo: 1.23 }] * 5000 + io.rewind + assert_same io, JSON.dump(big_object, io) + assert_equal JSON.dump(big_object), io.string + end + def test_dump_should_modify_defaults max_nesting = JSON.dump_default_options[:max_nesting] dump([], StringIO.new, 10) diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index 700220a1..8dd3913d 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -250,17 +250,20 @@ def test_fast_state end def test_allow_nan - assert_raise(GeneratorError) { generate([JSON::NaN]) } + error = assert_raise(GeneratorError) { generate([JSON::NaN]) } + assert_same JSON::NaN, error.invalid_object assert_equal '[NaN]', generate([JSON::NaN], :allow_nan => true) assert_raise(GeneratorError) { fast_generate([JSON::NaN]) } assert_raise(GeneratorError) { pretty_generate([JSON::NaN]) } assert_equal "[\n NaN\n]", pretty_generate([JSON::NaN], :allow_nan => true) - assert_raise(GeneratorError) { generate([JSON::Infinity]) } + error = assert_raise(GeneratorError) { generate([JSON::Infinity]) } + assert_same JSON::Infinity, error.invalid_object assert_equal '[Infinity]', generate([JSON::Infinity], :allow_nan => true) assert_raise(GeneratorError) { fast_generate([JSON::Infinity]) } assert_raise(GeneratorError) { pretty_generate([JSON::Infinity]) } assert_equal "[\n Infinity\n]", pretty_generate([JSON::Infinity], :allow_nan => true) - assert_raise(GeneratorError) { generate([JSON::MinusInfinity]) } + error = assert_raise(GeneratorError) { generate([JSON::MinusInfinity]) } + assert_same JSON::MinusInfinity, error.invalid_object assert_equal '[-Infinity]', generate([JSON::MinusInfinity], :allow_nan => true) assert_raise(GeneratorError) { fast_generate([JSON::MinusInfinity]) } assert_raise(GeneratorError) { pretty_generate([JSON::MinusInfinity]) } @@ -452,6 +455,10 @@ def test_backslash data = ["'"] json = '["\\\'"]' assert_equal '["\'"]', generate(data) + # + data = ["倩", "瀨"] + json = '["倩","瀨"]' + assert_equal json, generate(data, script_safe: true) end def test_string_subclass @@ -487,9 +494,13 @@ def test_invalid_encoding_string ["\x82\xAC\xEF".b].to_json end - assert_raise(JSON::GeneratorError) do - { foo: "\x82\xAC\xEF".b }.to_json + badly_encoded = "\x82\xAC\xEF".b + exception = assert_raise(JSON::GeneratorError) do + { foo: badly_encoded }.to_json end + + assert_kind_of EncodingError, exception.cause + assert_same badly_encoded, exception.invalid_object end class MyCustomString < String