From 2d62ec449f1c95004f82249e9fa6185bc51b2e29 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 16 Nov 2024 11:05:28 +0100 Subject: [PATCH 01/25] Fix the BEWARE documentation in `load` and `unsafe_load`. --- lib/json/common.rb | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/json/common.rb b/lib/json/common.rb index 4c6b2e1a..3b062020 100644 --- a/lib/json/common.rb +++ b/lib/json/common.rb @@ -411,6 +411,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 +429,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 +565,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 +589,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=. From f017af6c0aa5c9ab8165f213d84c84fc19fe6689 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 5 Nov 2024 19:57:46 +0100 Subject: [PATCH 02/25] JSON.dump: write directly into the provided IO Ref: https://github.com/ruby/json/issues/524 Rather than to buffer everything in memory. Unfortunately Ruby doesn't provide an API to write into and IO without first allocating a string, which is a bit wasteful. --- ext/json/ext/fbuffer/fbuffer.h | 62 ++++++++++++++++++------- ext/json/ext/generator/generator.c | 49 +++++++++---------- lib/json/common.rb | 17 +++---- lib/json/ext/generator/state.rb | 11 +++++ test/json/json_common_interface_test.rb | 11 +++++ 5 files changed, 98 insertions(+), 52 deletions(-) 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..503baca6 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); @@ -453,7 +453,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 +467,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 +480,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 +493,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 +505,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 +518,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 +543,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 +638,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) @@ -1045,12 +1045,14 @@ static VALUE generate_json_rescue(VALUE d, VALUE 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 +1064,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 +1497,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 +1518,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); } /* @@ -1583,9 +1580,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/lib/json/common.rb b/lib/json/common.rb index 3b062020..a88a3fff 100644 --- a/lib/json/common.rb +++ b/lib/json/common.rb @@ -286,7 +286,7 @@ def generate(obj, opts = nil) if State === opts opts.generate(obj) else - State.generate(obj, opts) + State.generate(obj, opts, nil) end end @@ -801,18 +801,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/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) From db393d695012b45fba830a401dd4a750aa3901cb Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 20 Nov 2024 15:02:58 -0600 Subject: [PATCH 03/25] Plumb OutputStream through generators When an IO is given, we should try to write directly to it. This patch moves that direction by always doing JSON dumping into an OutputStream, which can be implemented on top of a given IO or by producing a ByteList via a ByteArrayOutputStream. --- Rakefile | 4 +- .../json/ext/ByteListDirectOutputStream.java | 16 ++ java/src/json/ext/ByteListTranscoder.java | 21 ++- java/src/json/ext/Generator.java | 159 ++++++++++-------- java/src/json/ext/GeneratorState.java | 18 +- java/src/json/ext/StringDecoder.java | 27 +-- java/src/json/ext/StringEncoder.java | 11 +- 7 files changed, 156 insertions(+), 100 deletions(-) create mode 100644 java/src/json/ext/ByteListDirectOutputStream.java 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/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..0fedcabd 100644 --- a/java/src/json/ext/ByteListTranscoder.java +++ b/java/src/json/ext/ByteListTranscoder.java @@ -9,6 +9,9 @@ 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. @@ -23,7 +26,7 @@ abstract class ByteListTranscoder { /** Position of the next character to read */ protected int pos; - private ByteList out; + private OutputStream 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 @@ -37,11 +40,11 @@ protected ByteListTranscoder(ThreadContext context) { this.context = context; } - protected void init(ByteList src, ByteList out) { + protected void init(ByteList src, OutputStream 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, OutputStream out) { this.src = src; this.pos = start; this.charStart = start; @@ -142,19 +145,19 @@ 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); + out.write(src.bytes(), quoteStart, endPos - quoteStart); quoteStart = -1; } } - protected void append(int b) { - out.append(b); + protected void append(int b) throws IOException { + out.write(b); } - protected void append(byte[] origin, int start, int length) { - out.append(origin, start, length); + protected void append(byte[] origin, int start, int length) throws IOException { + out.write(origin, start, length); } diff --git a/java/src/json/ext/Generator.java b/java/src/json/ext/Generator.java index f76dcb38..15889969 100644 --- a/java/src/json/ext/Generator.java +++ b/java/src/json/ext/Generator.java @@ -5,6 +5,8 @@ */ package json.ext; +import org.jcodings.Encoding; +import org.jcodings.specific.UTF8Encoding; import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyBasicObject; @@ -13,11 +15,18 @@ import org.jruby.RubyFixnum; import org.jruby.RubyFloat; import org.jruby.RubyHash; +import org.jruby.RubyIO; import org.jruby.RubyString; +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.IOOutputStream; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; public final class Generator { private Generator() { @@ -48,11 +57,12 @@ private Generator() { * 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); Handler handler = getHandlerFor(context.runtime, object); + return handler.generateNew(session, object); } @@ -171,17 +181,20 @@ int guessSize(Session session, T object) { } 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; + ByteListDirectOutputStream buffer = new ByteListDirectOutputStream(guessSize(session, object)); + generateToBuffer(session, object, buffer); + return RubyString.newString(session.getRuntime(), buffer.toByteListDirect(UTF8Encoding.INSTANCE)); } - abstract void generate(Session session, T object, ByteList buffer); + void generateToBuffer(Session session, T object, OutputStream buffer) { + try { + generate(session, object, buffer); + } catch (IOException ioe) { + throw session.getRuntime().newIOErrorFromException(ioe); + } + } + + abstract void generate(Session session, T object, OutputStream buffer) throws IOException; } /** @@ -189,10 +202,10 @@ RubyString generateNew(Session session, T object) { */ private static class KeywordHandler extends Handler { - private final ByteList keyword; + private String keyword; private KeywordHandler(String keyword) { - this.keyword = new ByteList(ByteList.plain(keyword), false); + this.keyword = keyword; } @Override @@ -202,12 +215,12 @@ int guessSize(Session session, T object) { @Override RubyString generateNew(Session session, T object) { - return RubyString.newStringShared(session.getRuntime(), keyword); + return RubyString.newString(session.getRuntime(), keyword); } @Override - void generate(Session session, T object, ByteList buffer) { - buffer.append(keyword); + void generate(Session session, T object, OutputStream buffer) throws IOException { + buffer.write(keyword.getBytes(StandardCharsets.UTF_8)); } } @@ -217,39 +230,43 @@ void generate(Session session, T object, ByteList buffer) { static final Handler BIGNUM_HANDLER = new Handler() { @Override - void generate(Session session, RubyBignum object, ByteList buffer) { + void generate(Session session, RubyBignum object, OutputStream buffer) throws IOException { // 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()); + ByteList bytes = ((RubyString) object.to_s(IRubyObject.NULL_ARRAY)).getByteList(); + buffer.write(bytes.unsafeBytes(), bytes.begin(), bytes.length()); } }; static final Handler FIXNUM_HANDLER = new Handler() { @Override - void generate(Session session, RubyFixnum object, ByteList buffer) { - buffer.append(object.to_s().getByteList()); + void generate(Session session, RubyFixnum object, OutputStream buffer) throws IOException { + ByteList bytes = object.to_s().getByteList(); + buffer.write(bytes.unsafeBytes(), bytes.begin(), bytes.length()); } }; static final Handler FLOAT_HANDLER = new Handler() { @Override - void generate(Session session, RubyFloat object, ByteList buffer) { - double value = RubyFloat.num2dbl(object); - - if (Double.isInfinite(value) || Double.isNaN(value)) { + void generate(Session session, RubyFloat object, OutputStream buffer) throws IOException { + if (object.isInfinite() || object.isNaN()) { if (!session.getState().allowNaN()) { throw Utils.newException(session.getContext(), Utils.M_GENERATOR_ERROR, object + " not allowed in JSON"); } } - buffer.append(((RubyString)object.to_s()).getByteList()); + + double value = RubyFloat.num2dbl(object); + + buffer.write(Double.toString(value).getBytes(StandardCharsets.UTF_8)); } }; + private static final byte[] EMPTY_ARRAY_BYTES = "[]".getBytes(); static final Handler ARRAY_HANDLER = new Handler() { @Override @@ -264,14 +281,14 @@ int guessSize(Session session, RubyArray object) { } @Override - void generate(Session session, RubyArray object, ByteList buffer) { + void generate(Session session, RubyArray object, OutputStream buffer) throws IOException { ThreadContext context = session.getContext(); Ruby runtime = context.getRuntime(); GeneratorState state = session.getState(); int depth = state.increaseDepth(); if (object.isEmpty()) { - buffer.append("[]".getBytes()); + buffer.write(EMPTY_ARRAY_BYTES); state.decreaseDepth(); return; } @@ -287,8 +304,8 @@ void generate(Session session, RubyArray object, ByteList buffer) { 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); @@ -296,23 +313,24 @@ void generate(Session session, RubyArray object, ByteList buffer) { if (firstItem) { firstItem = false; } else { - buffer.append(delim); + buffer.write(delim); } - buffer.append(shift); + buffer.write(shift); Handler handler = (Handler) getHandlerFor(runtime, element); handler.generate(session, element, buffer); } state.decreaseDepth(); if (arrayNl.length() != 0) { - buffer.append(arrayNl); - buffer.append(shift, 0, state.getDepth() * indentUnit.length()); + 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 @@ -328,14 +346,14 @@ int guessSize(Session session, RubyHash object) { @Override void generate(final Session session, RubyHash object, - final ByteList buffer) { + final OutputStream buffer) throws IOException { ThreadContext context = session.getContext(); final Ruby runtime = context.getRuntime(); final GeneratorState state = session.getState(); final int depth = state.increaseDepth(); if (object.isEmpty()) { - buffer.append("{}".getBytes()); + buffer.write(EMPTY_HASH_BYTES); state.decreaseDepth(); return; } @@ -345,46 +363,50 @@ void generate(final Session session, RubyHash object, final ByteList spaceBefore = state.getSpaceBefore(); final ByteList space = state.getSpace(); - buffer.append((byte)'{'); - buffer.append(objectNl); + buffer.write((byte)'{'); + buffer.write(objectNl.bytes()); final boolean[] firstPair = new boolean[]{true}; object.visitAll(new RubyHash.Visitor() { @Override public void visit(IRubyObject key, IRubyObject value) { - if (firstPair[0]) { - firstPair[0] = false; - } else { - buffer.append((byte)','); - buffer.append(objectNl); + try { + if (firstPair[0]) { + firstPair[0] = false; + } else { + buffer.write((byte) ','); + buffer.write(objectNl.bytes()); + } + if (objectNl.length() != 0) buffer.write(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); + } + session.infectBy(key); + + buffer.write(spaceBefore.bytes()); + buffer.write((byte) ':'); + buffer.write(space.bytes()); + + Handler valueHandler = (Handler) getHandlerFor(runtime, value); + valueHandler.generate(session, value, buffer); + session.infectBy(value); + } catch (Throwable t) { + Helpers.throwException(t); } - 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); - } - 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); } }); state.decreaseDepth(); if (!firstPair[0] && objectNl.length() != 0) { - buffer.append(objectNl); + buffer.write(objectNl.bytes()); } - buffer.append(Utils.repeat(state.getIndent(), state.getDepth())); - buffer.append((byte)'}'); + buffer.write(Utils.repeat(state.getIndent(), state.getDepth())); + buffer.write((byte)'}'); } }; @@ -399,7 +421,7 @@ int guessSize(Session session, RubyString object) { } @Override - void generate(Session session, RubyString object, ByteList buffer) { + void generate(Session session, RubyString object, OutputStream buffer) throws IOException { RuntimeInfo info = session.getInfo(); RubyString src; @@ -439,7 +461,7 @@ RubyString generateNew(Session session, IRubyObject object) { } @Override - void generate(Session session, IRubyObject object, ByteList buffer) { + void generate(Session session, IRubyObject object, OutputStream buffer) throws IOException { RubyString str = object.asString(); STRING_HANDLER.generate(session, str, buffer); } @@ -468,9 +490,10 @@ RubyString generateNew(Session session, IRubyObject object) { } @Override - void generate(Session session, IRubyObject object, ByteList buffer) { + void generate(Session session, IRubyObject object, OutputStream buffer) throws IOException { RubyString result = generateNew(session, object); - buffer.append(result.getByteList()); + ByteList bytes = result.getByteList(); + buffer.write(bytes.unsafeBytes(), bytes.begin(), bytes.length()); } }; } diff --git a/java/src/json/ext/GeneratorState.java b/java/src/json/ext/GeneratorState.java index 1600b04a..28797f71 100644 --- a/java/src/json/ext/GeneratorState.java +++ b/java/src/json/ext/GeneratorState.java @@ -230,15 +230,21 @@ public IRubyObject initialize_copy(ThreadContext context, IRubyObject vOrig) { */ @JRubyMethod public IRubyObject generate(ThreadContext context, IRubyObject obj) { - RubyString result = Generator.generateJson(context, obj, this); + IRubyObject 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()); + if (!(result instanceof RubyString)) { + return result; + } + + RubyString resultString = result.convertToString(); + if (resultString.getEncoding() != UTF8Encoding.INSTANCE) { + if (resultString.isFrozen()) { + resultString = resultString.strDup(context.getRuntime()); } - result.force_encoding(context, info.utf8.get()); + resultString.force_encoding(context, info.utf8.get()); } - return result; + + return resultString; } private static boolean matchClosingBrace(ByteList bl, int pos, int len, diff --git a/java/src/json/ext/StringDecoder.java b/java/src/json/ext/StringDecoder.java index 76cf1837..f4877e93 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 @@ -29,17 +31,20 @@ final class StringDecoder extends ByteListTranscoder { } 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()); + try { + ByteListDirectOutputStream out = new ByteListDirectOutputStream(end - start); + init(src, start, end, out); + while (hasNext()) { + handleChar(readUtf8Char()); + } + quoteStop(pos); + return out.toByteListDirect(src.getEncoding()); + } catch (IOException e) { + throw context.runtime.newIOErrorFromException(e); } - quoteStop(pos); - return out; } - private void handleChar(int c) { + private void handleChar(int c) throws IOException { if (c == '\\') { quoteStop(charStart); handleEscapeSequence(); @@ -48,7 +53,7 @@ private void handleChar(int c) { } } - private void handleEscapeSequence() { + private void handleEscapeSequence() throws IOException { ensureMin(1); switch (readUtf8Char()) { case 'b': @@ -83,7 +88,7 @@ private void handleEscapeSequence() { } } - private void handleLowSurrogate(char highSurrogate) { + private void handleLowSurrogate(char highSurrogate) throws IOException { surrogatePairStart = charStart; ensureMin(1); int lowSurrogate = readUtf8Char(); @@ -103,7 +108,7 @@ private void handleLowSurrogate(char highSurrogate) { } } - private void writeUtf8Char(int codePoint) { + private void writeUtf8Char(int codePoint) throws IOException { if (codePoint < 0x80) { append(codePoint); } else if (codePoint < 0x800) { diff --git a/java/src/json/ext/StringEncoder.java b/java/src/json/ext/StringEncoder.java index 290aa249..b1e7096e 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, @@ -43,7 +46,7 @@ final class StringEncoder extends ByteListTranscoder { this.scriptSafe = scriptSafe; } - void encode(ByteList src, ByteList out) { + void encode(ByteList src, OutputStream out) throws IOException { init(src, out); append('"'); while (hasNext()) { @@ -53,7 +56,7 @@ void encode(ByteList src, ByteList out) { append('"'); } - private void handleChar(int c) { + private void handleChar(int c) throws IOException { switch (c) { case '"': case '\\': @@ -97,13 +100,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); From 9ad958329e822020e6c375cc99dc2d7af4675506 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 20 Nov 2024 17:15:44 -0600 Subject: [PATCH 04/25] Connect OutputStream-based generator to Ruby bits This connects up the OutputStream-based generator logic to the incoming IO parameter (an IO or nil). Also included here are some small changes to support the new state.rb: * Require the state.rb file at the end of ext init. * Move State#configure to _configure. * Add State#strict? as an alias for strict. --- java/src/json/ext/Generator.java | 9 +++++++-- java/src/json/ext/GeneratorService.java | 2 ++ java/src/json/ext/GeneratorState.java | 18 +++++++++--------- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/java/src/json/ext/Generator.java b/java/src/json/ext/Generator.java index 15889969..65c30ffa 100644 --- a/java/src/json/ext/Generator.java +++ b/java/src/json/ext/Generator.java @@ -59,11 +59,16 @@ private Generator() { */ public static IRubyObject generateJson(ThreadContext context, T object, - GeneratorState config) { + GeneratorState config, IRubyObject io) { Session session = new Session(context, config); Handler handler = getHandlerFor(context.runtime, object); - return handler.generateNew(session, object); + if (io.isNil()) { + return handler.generateNew(session, object); + } + + handler.generateToBuffer(session, object, new IOOutputStream(io)); + return io; } /** diff --git a/java/src/json/ext/GeneratorService.java b/java/src/json/ext/GeneratorService.java index e665ad14..1500c412 100644 --- a/java/src/json/ext/GeneratorService.java +++ b/java/src/json/ext/GeneratorService.java @@ -37,6 +37,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 28797f71..0d8a3617 100644 --- a/java/src/json/ext/GeneratorState.java +++ b/java/src/json/ext/GeneratorState.java @@ -139,8 +139,8 @@ public static IRubyObject from_state(ThreadContext context, } @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) { @@ -196,7 +196,7 @@ static GeneratorState fromState(ThreadContext context, RuntimeInfo info, */ @JRubyMethod(optional=1, visibility=Visibility.PRIVATE) public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { - configure(context, args.length > 0 ? args[0] : null); + _configure(context, args.length > 0 ? args[0] : null); return this; } @@ -228,9 +228,9 @@ 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) { - IRubyObject result = Generator.generateJson(context, obj, this); + @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.getRuntime()); if (!(result instanceof RubyString)) { return result; @@ -411,7 +411,7 @@ public boolean strict() { return strict; } - @JRubyMethod(name="strict") + @JRubyMethod(name={"strict","strict?"}) public RubyBoolean strict_get(ThreadContext context) { return context.getRuntime().newBoolean(strict); } @@ -484,8 +484,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"); From 0824b62657cb6b879b943061f3bf15fee3407036 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 21 Nov 2024 09:43:30 +0100 Subject: [PATCH 05/25] Update TruffleRuby::Generator to follow the new generate interface --- lib/json/truffle_ruby/generator.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/json/truffle_ruby/generator.rb b/lib/json/truffle_ruby/generator.rb index b0f3e420..84cfd53d 100644 --- a/lib/json/truffle_ruby/generator.rb +++ b/lib/json/truffle_ruby/generator.rb @@ -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 From 76ada33cbb42f7b2f064fe2820d5760b5658d5b1 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Thu, 21 Nov 2024 07:59:46 -0600 Subject: [PATCH 06/25] Flush buffer once generation is complete --- java/src/json/ext/Generator.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/src/json/ext/Generator.java b/java/src/json/ext/Generator.java index 65c30ffa..2454f10c 100644 --- a/java/src/json/ext/Generator.java +++ b/java/src/json/ext/Generator.java @@ -194,6 +194,7 @@ RubyString generateNew(Session session, T object) { void generateToBuffer(Session session, T object, OutputStream buffer) { try { generate(session, object, buffer); + buffer.flush(); } catch (IOException ioe) { throw session.getRuntime().newIOErrorFromException(ioe); } From 351e11d40fe98aa93eb91a86448a2252e9c75fc7 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Thu, 21 Nov 2024 08:11:50 -0600 Subject: [PATCH 07/25] Buffer output to an IO This improves performance by having fewer dynamic writes. --- java/src/json/ext/Generator.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/java/src/json/ext/Generator.java b/java/src/json/ext/Generator.java index 2454f10c..c909a110 100644 --- a/java/src/json/ext/Generator.java +++ b/java/src/json/ext/Generator.java @@ -24,11 +24,15 @@ import org.jruby.exceptions.RaiseException; import org.jruby.util.IOOutputStream; +import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; public final class Generator { + + private static final int IO_BUFFER_SIZE = 8192; + private Generator() { throw new RuntimeException(); } @@ -67,7 +71,8 @@ private Generator() { return handler.generateNew(session, object); } - handler.generateToBuffer(session, object, new IOOutputStream(io)); + BufferedOutputStream buffer = new BufferedOutputStream(new IOOutputStream(io), IO_BUFFER_SIZE); + handler.generateToBuffer(session, object, buffer); return io; } From adb8e56b5253361ecbc83b52436f98a3f2fcea8b Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Thu, 21 Nov 2024 08:33:53 -0600 Subject: [PATCH 08/25] Split generator to_json methods by arity --- java/src/json/ext/Generator.java | 21 ++-- java/src/json/ext/GeneratorMethods.java | 142 ++++++++++++++---------- 2 files changed, 100 insertions(+), 63 deletions(-) diff --git a/java/src/json/ext/Generator.java b/java/src/json/ext/Generator.java index c909a110..72918cd5 100644 --- a/java/src/json/ext/Generator.java +++ b/java/src/json/ext/Generator.java @@ -40,10 +40,13 @@ 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); + static RubyString generateJson(ThreadContext context, T object, Handler handler) { + Session session = new Session(context, null); + return session.infect(handler.generateNew(session, object)); + } + + static RubyString generateJson(ThreadContext context, T object, Handler handler, IRubyObject arg0) { + Session session = new Session(context, arg0); return session.infect(handler.generateNew(session, object)); } @@ -51,10 +54,14 @@ private Generator() { * 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); + } + + static RubyString generateJson(ThreadContext context, T object, IRubyObject arg0) { Handler handler = getHandlerFor(context.runtime, object); - return generateJson(context, object, handler, args); + return generateJson(context, object, handler, arg0); } /** diff --git a/java/src/json/ext/GeneratorMethods.java b/java/src/json/ext/GeneratorMethods.java index bde7a18d..ec44f4b3 100644 --- a/java/src/json/ext/GeneratorMethods.java +++ b/java/src/json/ext/GeneratorMethods.java @@ -62,48 +62,63 @@ private static void defineMethods(RubyModule parentModule, 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 +127,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,9 +147,8 @@ 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)); } @@ -154,9 +172,8 @@ private static RubyHash toJsonRawObject(ThreadContext context, return result; } - @JRubyMethod(required=1, module=true) - public static IRubyObject included(ThreadContext context, - IRubyObject vSelf, IRubyObject module) { + @JRubyMethod(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()); } @@ -170,7 +187,7 @@ 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(); @@ -195,37 +212,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); } } } From 420bb5049c0d77ba4b8f62424c1777e6d3281de1 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Thu, 21 Nov 2024 08:34:07 -0600 Subject: [PATCH 09/25] Hard reference the JSON module in case of GC --- java/src/json/ext/GeneratorService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/src/json/ext/GeneratorService.java b/java/src/json/ext/GeneratorService.java index 1500c412..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"); From d01c9a24296cd154460962977abf1f63c913f22c Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Thu, 21 Nov 2024 08:58:01 -0600 Subject: [PATCH 10/25] Reduce alloc for numeric and keyword handlers --- java/src/json/ext/Generator.java | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/java/src/json/ext/Generator.java b/java/src/json/ext/Generator.java index 72918cd5..d4de15c2 100644 --- a/java/src/json/ext/Generator.java +++ b/java/src/json/ext/Generator.java @@ -22,11 +22,13 @@ 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 java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.math.BigInteger; import java.nio.charset.StandardCharsets; public final class Generator { @@ -220,25 +222,25 @@ void generateToBuffer(Session session, T object, OutputStream buffer) { */ private static class KeywordHandler extends Handler { - private String keyword; + private byte[] keyword; private KeywordHandler(String keyword) { - this.keyword = keyword; + this.keyword = keyword.getBytes(StandardCharsets.UTF_8); } @Override int guessSize(Session session, T object) { - return keyword.length(); + return keyword.length; } @Override RubyString generateNew(Session session, T object) { - return RubyString.newString(session.getRuntime(), keyword); + return RubyString.newStringShared(session.getRuntime(), keyword); } @Override void generate(Session session, T object, OutputStream buffer) throws IOException { - buffer.write(keyword.getBytes(StandardCharsets.UTF_8)); + buffer.write(keyword); } } @@ -249,11 +251,8 @@ void generate(Session session, T object, OutputStream buffer) throws IOException new Handler() { @Override void generate(Session session, RubyBignum object, OutputStream buffer) throws IOException { - // JRUBY-4751: RubyBignum.to_s() returns generic object - // representation (fixed in 1.5, but we maintain backwards - // compatibility; call to_s(IRubyObject[]) then - ByteList bytes = ((RubyString) object.to_s(IRubyObject.NULL_ARRAY)).getByteList(); - buffer.write(bytes.unsafeBytes(), bytes.begin(), bytes.length()); + BigInteger bigInt = object.getValue(); + buffer.write(bigInt.toString().getBytes(StandardCharsets.UTF_8)); } }; @@ -261,8 +260,7 @@ void generate(Session session, RubyBignum object, OutputStream buffer) throws IO new Handler() { @Override void generate(Session session, RubyFixnum object, OutputStream buffer) throws IOException { - ByteList bytes = object.to_s().getByteList(); - buffer.write(bytes.unsafeBytes(), bytes.begin(), bytes.length()); + buffer.write(ConvertBytes.longToCharBytes(object.getLongValue())); } }; @@ -270,16 +268,14 @@ void generate(Session session, RubyFixnum object, OutputStream buffer) throws IO new Handler() { @Override void generate(Session session, RubyFloat object, OutputStream buffer) throws IOException { - if (object.isInfinite() || object.isNaN()) { + 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"); + throw Utils.newException(session.getContext(), Utils.M_GENERATOR_ERROR, object + " not allowed in JSON"); } } - double value = RubyFloat.num2dbl(object); - buffer.write(Double.toString(value).getBytes(StandardCharsets.UTF_8)); } }; From 5d498fba550c3782a111d149c03612775dc9c479 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Thu, 21 Nov 2024 09:39:36 -0600 Subject: [PATCH 11/25] Plumb ThreadContext through parser and generator This eliminates the context field on ParserSession, ByteListTranscoder, and Generator.Session as well as accesses of the runtime through it. These runtime structures should generally be passed on the stack rather than being stored into long-lived objects, even if they may be GCed quickly. --- java/src/json/ext/ByteListTranscoder.java | 52 ++-- java/src/json/ext/Generator.java | 157 ++++++------ java/src/json/ext/Parser.java | 282 +++++++++++----------- java/src/json/ext/Parser.rl | 136 +++++------ java/src/json/ext/StringDecoder.java | 46 ++-- java/src/json/ext/StringEncoder.java | 12 +- 6 files changed, 322 insertions(+), 363 deletions(-) diff --git a/java/src/json/ext/ByteListTranscoder.java b/java/src/json/ext/ByteListTranscoder.java index 0fedcabd..dc294f05 100644 --- a/java/src/json/ext/ByteListTranscoder.java +++ b/java/src/json/ext/ByteListTranscoder.java @@ -17,8 +17,6 @@ * 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 */ @@ -36,10 +34,6 @@ abstract class ByteListTranscoder { */ private int quoteStart = -1; - protected ByteListTranscoder(ThreadContext context) { - this.context = context; - } - protected void init(ByteList src, OutputStream out) { this.init(src, 0, src.length(), out); } @@ -70,52 +64,52 @@ 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); } /** * 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); } /** @@ -124,10 +118,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; } @@ -161,9 +155,9 @@ protected 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 d4de15c2..2b3cd581 100644 --- a/java/src/json/ext/Generator.java +++ b/java/src/json/ext/Generator.java @@ -43,13 +43,13 @@ private Generator() { * Encodes the given object as a JSON string, using the given handler. */ static RubyString generateJson(ThreadContext context, T object, Handler handler) { - Session session = new Session(context, null); - return session.infect(handler.generateNew(session, object)); + Session session = new Session(null); + return session.infect(handler.generateNew(context, session, object)); } static RubyString generateJson(ThreadContext context, T object, Handler handler, IRubyObject arg0) { - Session session = new Session(context, arg0); - return session.infect(handler.generateNew(session, object)); + Session session = new Session(arg0); + return session.infect(handler.generateNew(context, session, object)); } /** @@ -73,15 +73,15 @@ static RubyString generateJson(ThreadContext context, T public static IRubyObject generateJson(ThreadContext context, T object, GeneratorState config, IRubyObject io) { - Session session = new Session(context, config); + Session session = new Session(config); Handler handler = getHandlerFor(context.runtime, object); if (io.isNil()) { - return handler.generateNew(session, object); + return handler.generateNew(context, session, object); } BufferedOutputStream buffer = new BufferedOutputStream(new IOOutputStream(io), IO_BUFFER_SIZE); - handler.generateToBuffer(session, object, buffer); + handler.generateToBuffer(context, session, object, buffer); return io; } @@ -123,11 +123,10 @@ 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; @@ -136,40 +135,31 @@ static class Session { 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; } @@ -195,26 +185,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) { - ByteListDirectOutputStream buffer = new ByteListDirectOutputStream(guessSize(session, object)); - generateToBuffer(session, object, buffer); - return RubyString.newString(session.getRuntime(), buffer.toByteListDirect(UTF8Encoding.INSTANCE)); + 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)); } - void generateToBuffer(Session session, T object, OutputStream buffer) { + void generateToBuffer(ThreadContext context, Session session, T object, OutputStream buffer) { try { - generate(session, object, buffer); + generate(context, session, object, buffer); buffer.flush(); } catch (IOException ioe) { - throw session.getRuntime().newIOErrorFromException(ioe); + throw context.runtime.newIOErrorFromException(ioe); } } - abstract void generate(Session session, T object, OutputStream buffer) throws IOException; + abstract void generate(ThreadContext context, Session session, T object, OutputStream buffer) throws IOException; } /** @@ -229,17 +219,17 @@ private KeywordHandler(String keyword) { } @Override - int guessSize(Session session, T object) { + 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, OutputStream buffer) throws IOException { + void generate(ThreadContext context, Session session, T object, OutputStream buffer) throws IOException { buffer.write(keyword); } } @@ -250,7 +240,7 @@ void generate(Session session, T object, OutputStream buffer) throws IOException static final Handler BIGNUM_HANDLER = new Handler() { @Override - void generate(Session session, RubyBignum object, OutputStream buffer) throws IOException { + void generate(ThreadContext context, Session session, RubyBignum object, OutputStream buffer) throws IOException { BigInteger bigInt = object.getValue(); buffer.write(bigInt.toString().getBytes(StandardCharsets.UTF_8)); } @@ -259,7 +249,7 @@ void generate(Session session, RubyBignum object, OutputStream buffer) throws IO static final Handler FIXNUM_HANDLER = new Handler() { @Override - void generate(Session session, RubyFixnum object, OutputStream buffer) throws IOException { + void generate(ThreadContext context, Session session, RubyFixnum object, OutputStream buffer) throws IOException { buffer.write(ConvertBytes.longToCharBytes(object.getLongValue())); } }; @@ -267,12 +257,12 @@ void generate(Session session, RubyFixnum object, OutputStream buffer) throws IO static final Handler FLOAT_HANDLER = new Handler() { @Override - void generate(Session session, RubyFloat object, OutputStream buffer) throws IOException { + 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.newException(context, Utils.M_GENERATOR_ERROR, object + " not allowed in JSON"); } } @@ -284,8 +274,8 @@ void generate(Session session, RubyFloat object, OutputStream buffer) throws IOE 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 @@ -295,10 +285,8 @@ int guessSize(Session session, RubyArray object) { } @Override - void generate(Session session, RubyArray object, OutputStream buffer) throws IOException { - ThreadContext context = session.getContext(); - Ruby runtime = context.getRuntime(); - GeneratorState state = session.getState(); + void generate(ThreadContext context, Session session, RubyArray object, OutputStream buffer) throws IOException { + GeneratorState state = session.getState(context); int depth = state.increaseDepth(); if (object.isEmpty()) { @@ -307,6 +295,8 @@ void generate(Session session, RubyArray object, OutputStream buffer) throws IOE return; } + Ruby runtime = context.runtime; + ByteList indentUnit = state.getIndent(); byte[] shift = Utils.repeat(indentUnit, depth); @@ -321,6 +311,7 @@ void generate(Session session, RubyArray object, OutputStream buffer) throws IOE 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); @@ -331,7 +322,7 @@ void generate(Session session, RubyArray object, OutputStream buffer) throws IOE } buffer.write(shift); Handler handler = (Handler) getHandlerFor(runtime, element); - handler.generate(session, element, buffer); + handler.generate(context, session, element, buffer); } state.decreaseDepth(); @@ -348,8 +339,8 @@ void generate(Session session, RubyArray object, OutputStream buffer) throws IOE 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() @@ -359,11 +350,8 @@ int guessSize(Session session, RubyHash object) { } @Override - void generate(final Session session, RubyHash object, - final OutputStream buffer) throws IOException { - ThreadContext context = session.getContext(); - final Ruby runtime = context.getRuntime(); - final GeneratorState state = session.getState(); + 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(); if (object.isEmpty()) { @@ -372,6 +360,8 @@ void generate(final Session session, RubyHash object, return; } + final Ruby runtime = context.runtime; + final ByteList objectNl = state.getObjectNl(); final byte[] indent = Utils.repeat(state.getIndent(), depth); final ByteList spaceBefore = state.getSpaceBefore(); @@ -395,11 +385,11 @@ public void visit(IRubyObject key, IRubyObject value) { IRubyObject keyStr = key.callMethod(context, "to_s"); if (keyStr.getMetaClass() == runtime.getString()) { - STRING_HANDLER.generate(session, (RubyString) keyStr, buffer); + STRING_HANDLER.generate(context, session, (RubyString) keyStr, buffer); } else { Utils.ensureString(keyStr); Handler keyHandler = (Handler) getHandlerFor(runtime, keyStr); - keyHandler.generate(session, keyStr, buffer); + keyHandler.generate(context, session, keyStr, buffer); } session.infectBy(key); @@ -408,7 +398,7 @@ public void visit(IRubyObject key, IRubyObject value) { buffer.write(space.bytes()); Handler valueHandler = (Handler) getHandlerFor(runtime, value); - valueHandler.generate(session, value, buffer); + valueHandler.generate(context, session, value, buffer); session.infectBy(value); } catch (Throwable t) { Helpers.throwException(t); @@ -427,7 +417,7 @@ public void visit(IRubyObject key, IRubyObject value) { 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 @@ -435,21 +425,20 @@ int guessSize(Session session, RubyString object) { } @Override - void generate(Session session, RubyString object, OutputStream buffer) throws IOException { - RuntimeInfo info = session.getInfo(); + void generate(ThreadContext context, Session session, RubyString object, OutputStream buffer) throws IOException { + RuntimeInfo info = session.getInfo(context); RubyString src; try { - if (object.encoding(session.getContext()) != info.utf8.get()) { - src = (RubyString)object.encode(session.getContext(), - info.utf8.get()); + if (object.encoding(context) != info.utf8.get()) { + src = (RubyString)object.encode(context, info.utf8.get()); } else { src = object; } - session.getStringEncoder().encode(src.getByteList(), buffer); + session.getStringEncoder(context).encode(context, src.getByteList(), buffer); } catch (RaiseException re) { - throw Utils.newException(session.getContext(), Utils.M_GENERATOR_ERROR, + throw Utils.newException(context, Utils.M_GENERATOR_ERROR, re.getMessage()); } } @@ -469,15 +458,15 @@ void generate(Session session, RubyString object, OutputStream buffer) throws IO 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, OutputStream buffer) throws IOException { + 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); } }; @@ -488,24 +477,22 @@ void generate(Session session, IRubyObject object, OutputStream buffer) throws I 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.newException(context, Utils.M_GENERATOR_ERROR, object + " not allowed in JSON"); } 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, OutputStream buffer) throws IOException { - RubyString result = generateNew(session, object); + 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/Parser.java b/java/src/json/ext/Parser.java index 74037d37..b1bd5c8f 100644 --- a/java/src/json/ext/Parser.java +++ b/java/src/json/ext/Parser.java @@ -41,7 +41,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 @@ -175,7 +175,7 @@ public static IRubyObject parse(ThreadContext context, IRubyObject clazz, IRubyO @JRubyMethod(required = 1, optional = 1, visibility = Visibility.PRIVATE) public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { - Ruby runtime = context.getRuntime(); + Ruby runtime = context.runtime; if (this.vSource != null) { throw runtime.newTypeError("already initialized instance"); } @@ -278,7 +278,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 +288,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,7 +335,6 @@ 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; @@ -350,32 +349,27 @@ private static class ParserSession { 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.decoder = new StringDecoder(); this.dc = new DoubleConverter(); } - 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 391 "Parser.rl" -// line 379 "Parser.java" +// line 373 "Parser.java" private static byte[] init__JSON_value_actions_0() { return new byte [] { @@ -489,22 +483,22 @@ private static byte[] init__JSON_value_from_state_actions_0() static final int JSON_value_en_main = 1; -// line 503 "Parser.rl" +// line 497 "Parser.rl" - void parseValue(ParserResult res, int p, int pe) { + void parseValue(ThreadContext context, ParserResult res, int p, int pe) { int cs = EVIL; IRubyObject result = null; -// line 501 "Parser.java" +// line 495 "Parser.java" { cs = JSON_value_start; } -// line 510 "Parser.rl" +// line 504 "Parser.rl" -// line 508 "Parser.java" +// line 502 "Parser.java" { int _klen; int _trans = 0; @@ -530,13 +524,13 @@ void parseValue(ParserResult res, int p, int pe) { while ( _nacts-- > 0 ) { switch ( _JSON_value_actions[_acts++] ) { case 9: -// line 488 "Parser.rl" +// line 482 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 540 "Parser.java" +// line 534 "Parser.java" } } @@ -599,45 +593,45 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) switch ( _JSON_value_actions[_acts++] ) { case 0: -// line 405 "Parser.rl" +// line 399 "Parser.rl" { - result = getRuntime().getNil(); + result = context.nil; } break; case 1: -// line 408 "Parser.rl" +// line 402 "Parser.rl" { - result = getRuntime().getFalse(); + result = context.fals; } break; case 2: -// line 411 "Parser.rl" +// line 405 "Parser.rl" { - result = getRuntime().getTrue(); + result = context.tru; } break; case 3: -// line 414 "Parser.rl" +// line 408 "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 415 "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 422 "Parser.rl" { if (pe > p + 8 && absSubSequence(p, p + 9).equals(JSON_MINUS_INFINITY)) { @@ -648,15 +642,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 +660,9 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 6: -// line 454 "Parser.rl" +// line 448 "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 +673,10 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 7: -// line 464 "Parser.rl" +// line 458 "Parser.rl" { currentNesting++; - parseArray(res, p, pe); + parseArray(context, res, p, pe); currentNesting--; if (res.result == null) { p--; @@ -694,10 +688,10 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 8: -// line 476 "Parser.rl" +// line 470 "Parser.rl" { currentNesting++; - parseObject(res, p, pe); + parseObject(context, res, p, pe); currentNesting--; if (res.result == null) { p--; @@ -708,7 +702,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } } break; -// line 712 "Parser.java" +// line 706 "Parser.java" } } } @@ -728,7 +722,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) break; } } -// line 511 "Parser.rl" +// line 505 "Parser.rl" if (cs >= JSON_value_first_final && result != null) { if (parser.freeze) { @@ -741,7 +735,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } -// line 745 "Parser.java" +// line 739 "Parser.java" private static byte[] init__JSON_integer_actions_0() { return new byte [] { @@ -840,33 +834,33 @@ private static byte[] init__JSON_integer_trans_actions_0() static final int JSON_integer_en_main = 1; -// line 533 "Parser.rl" +// line 527 "Parser.rl" - void parseInteger(ParserResult res, int p, int pe) { - int new_p = parseIntegerInternal(p, pe); + void parseInteger(ThreadContext context, ParserResult res, int p, int pe) { + int new_p = parseIntegerInternal(context, 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 parseIntegerInternal(ThreadContext context, int p, int pe) { int cs = EVIL; -// line 862 "Parser.java" +// line 856 "Parser.java" { cs = JSON_integer_start; } -// line 550 "Parser.rl" +// line 544 "Parser.rl" int memo = p; -// line 870 "Parser.java" +// line 864 "Parser.java" { int _klen; int _trans = 0; @@ -947,13 +941,13 @@ else if ( data[p] > _JSON_integer_trans_keys[_mid+1] ) switch ( _JSON_integer_actions[_acts++] ) { case 0: -// line 527 "Parser.rl" +// line 521 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 957 "Parser.java" +// line 951 "Parser.java" } } } @@ -973,7 +967,7 @@ else if ( data[p] > _JSON_integer_trans_keys[_mid+1] ) break; } } -// line 552 "Parser.rl" +// line 546 "Parser.rl" if (cs < JSON_integer_first_final) { return -1; @@ -982,8 +976,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 +987,7 @@ RubyInteger bytesToInum(Ruby runtime, ByteList num) { } -// line 997 "Parser.java" +// line 991 "Parser.java" private static byte[] init__JSON_float_actions_0() { return new byte [] { @@ -1095,11 +1089,11 @@ private static byte[] init__JSON_float_trans_actions_0() static final int JSON_float_en_main = 1; -// line 585 "Parser.rl" +// line 579 "Parser.rl" - void parseFloat(ParserResult res, int p, int pe) { - int new_p = parseFloatInternal(p, pe); + void parseFloat(ThreadContext context, ParserResult res, int p, int pe) { + int new_p = parseFloatInternal(context, p, pe); if (new_p == -1) { res.update(null, p); return; @@ -1110,19 +1104,19 @@ void parseFloat(ParserResult res, int p, int pe) { res.update(number, new_p + 1); } - int parseFloatInternal(int p, int pe) { + int parseFloatInternal(ThreadContext context, int p, int pe) { int cs = EVIL; -// line 1118 "Parser.java" +// line 1112 "Parser.java" { cs = JSON_float_start; } -// line 603 "Parser.rl" +// line 597 "Parser.rl" int memo = p; -// line 1126 "Parser.java" +// line 1120 "Parser.java" { int _klen; int _trans = 0; @@ -1203,13 +1197,13 @@ else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) switch ( _JSON_float_actions[_acts++] ) { case 0: -// line 576 "Parser.rl" +// line 570 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1213 "Parser.java" +// line 1207 "Parser.java" } } } @@ -1229,7 +1223,7 @@ else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) break; } } -// line 605 "Parser.rl" +// line 599 "Parser.rl" if (cs < JSON_float_first_final) { return -1; @@ -1239,7 +1233,7 @@ else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) } -// line 1243 "Parser.java" +// line 1237 "Parser.java" private static byte[] init__JSON_string_actions_0() { return new byte [] { @@ -1341,23 +1335,23 @@ private static byte[] init__JSON_string_trans_actions_0() static final int JSON_string_en_main = 1; -// line 644 "Parser.rl" +// line 638 "Parser.rl" - void parseString(ParserResult res, int p, int pe) { + void parseString(ThreadContext context, ParserResult res, int p, int pe) { int cs = EVIL; IRubyObject result = null; -// line 1353 "Parser.java" +// line 1347 "Parser.java" { cs = JSON_string_start; } -// line 651 "Parser.rl" +// line 645 "Parser.rl" int memo = p; -// line 1361 "Parser.java" +// line 1355 "Parser.java" { int _klen; int _trans = 0; @@ -1438,12 +1432,12 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) switch ( _JSON_string_actions[_acts++] ) { case 0: -// line 619 "Parser.rl" +// line 613 "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 +1447,13 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) } break; case 1: -// line 632 "Parser.rl" +// line 626 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1463 "Parser.java" +// line 1457 "Parser.java" } } } @@ -1479,7 +1473,7 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) break; } } -// line 653 "Parser.rl" +// line 647 "Parser.rl" if (parser.createAdditions) { RubyHash matchString = parser.match_string; @@ -1501,7 +1495,7 @@ public void visit(IRubyObject pattern, IRubyObject klass) { 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); } @@ -1515,7 +1509,7 @@ public void visit(IRubyObject pattern, IRubyObject klass) { string.force_encoding(context, info.utf8.get()); if (parser.freeze) { string.setFrozen(true); - string = getRuntime().freezeAndDedupString(string); + string = context.runtime.freezeAndDedupString(string); } res.update(string, p + 1); } else { @@ -1527,7 +1521,7 @@ public void visit(IRubyObject pattern, IRubyObject klass) { } -// line 1531 "Parser.java" +// line 1525 "Parser.java" private static byte[] init__JSON_array_actions_0() { return new byte [] { @@ -1694,34 +1688,34 @@ private static byte[] init__JSON_array_trans_actions_0() static final int JSON_array_en_main = 1; -// line 738 "Parser.rl" +// line 732 "Parser.rl" - void parseArray(ParserResult res, int p, int pe) { + void parseArray(ThreadContext context, ParserResult res, int p, int pe) { int cs = EVIL; 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 1712 "Parser.java" { cs = JSON_array_start; } -// line 757 "Parser.rl" +// line 751 "Parser.rl" -// line 1725 "Parser.java" +// line 1719 "Parser.java" { int _klen; int _trans = 0; @@ -1764,7 +1758,7 @@ else if ( _widec > _JSON_array_cond_keys[_mid+1] ) case 0: { _widec = 65536 + (data[p] - 0); if ( -// line 705 "Parser.rl" +// line 699 "Parser.rl" parser.allowTrailingComma ) _widec += 65536; break; } @@ -1834,14 +1828,14 @@ else if ( _widec > _JSON_array_trans_keys[_mid+1] ) switch ( _JSON_array_actions[_acts++] ) { case 0: -// line 707 "Parser.rl" +// line 701 "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 +1845,13 @@ else if ( _widec > _JSON_array_trans_keys[_mid+1] ) } break; case 1: -// line 722 "Parser.rl" +// line 716 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1861 "Parser.java" +// line 1855 "Parser.java" } } } @@ -1877,17 +1871,17 @@ else if ( _widec > _JSON_array_trans_keys[_mid+1] ) break; } } -// line 758 "Parser.rl" +// line 752 "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 1885 "Parser.java" private static byte[] init__JSON_object_actions_0() { return new byte [] { @@ -2064,24 +2058,24 @@ private static byte[] init__JSON_object_trans_actions_0() static final int JSON_object_en_main = 1; -// line 819 "Parser.rl" +// line 813 "Parser.rl" - void parseObject(ParserResult res, int p, int pe) { + void parseObject(ThreadContext context, ParserResult res, int p, int pe) { int cs = EVIL; 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 +2083,14 @@ void parseObject(ParserResult res, int p, int pe) { } -// line 2093 "Parser.java" +// line 2087 "Parser.java" { cs = JSON_object_start; } -// line 843 "Parser.rl" +// line 837 "Parser.rl" -// line 2100 "Parser.java" +// line 2094 "Parser.java" { int _klen; int _trans = 0; @@ -2139,7 +2133,7 @@ else if ( _widec > _JSON_object_cond_keys[_mid+1] ) case 0: { _widec = 65536 + (data[p] - 0); if ( -// line 772 "Parser.rl" +// line 766 "Parser.rl" parser.allowTrailingComma ) _widec += 65536; break; } @@ -2209,14 +2203,14 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) switch ( _JSON_object_actions[_acts++] ) { case 0: -// line 774 "Parser.rl" +// line 768 "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 }); @@ -2226,9 +2220,9 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) } break; case 1: -// line 789 "Parser.rl" +// line 783 "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 +2238,13 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) } break; case 2: -// line 805 "Parser.rl" +// line 799 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 2254 "Parser.java" +// line 2248 "Parser.java" } } } @@ -2270,7 +2264,7 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) break; } } -// line 844 "Parser.rl" +// line 838 "Parser.rl" if (cs < JSON_object_first_final) { res.update(null, p + 1); @@ -2295,7 +2289,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 +2300,7 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) } -// line 2310 "Parser.java" +// line 2304 "Parser.java" private static byte[] init__JSON_actions_0() { return new byte [] { @@ -2409,26 +2403,26 @@ private static byte[] init__JSON_trans_actions_0() static final int JSON_en_main = 1; -// line 898 "Parser.rl" +// line 892 "Parser.rl" - public IRubyObject parseImplemetation() { + public IRubyObject parseImplementation(ThreadContext context) { int cs = EVIL; int p, pe; IRubyObject result = null; ParserResult res = new ParserResult(); -// line 2423 "Parser.java" +// line 2417 "Parser.java" { cs = JSON_start; } -// line 907 "Parser.rl" +// line 901 "Parser.rl" p = byteList.begin(); pe = p + byteList.length(); -// line 2432 "Parser.java" +// line 2426 "Parser.java" { int _klen; int _trans = 0; @@ -2509,9 +2503,9 @@ else if ( data[p] > _JSON_trans_keys[_mid+1] ) switch ( _JSON_actions[_acts++] ) { case 0: -// line 884 "Parser.rl" +// line 878 "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 +2515,7 @@ else if ( data[p] > _JSON_trans_keys[_mid+1] ) } } break; -// line 2525 "Parser.java" +// line 2519 "Parser.java" } } } @@ -2541,23 +2535,23 @@ else if ( data[p] > _JSON_trans_keys[_mid+1] ) break; } } -// line 910 "Parser.rl" +// line 904 "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 +2567,16 @@ 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)); + private RaiseException newException(ThreadContext context, String className, String messageBegin, ByteList messageEnd) { + return newException(context, className, context.runtime.newString(messageBegin).cat(messageEnd)); } } } diff --git a/java/src/json/ext/Parser.rl b/java/src/json/ext/Parser.rl index 9d2b96d6..84d75a33 100644 --- a/java/src/json/ext/Parser.rl +++ b/java/src/json/ext/Parser.rl @@ -39,7 +39,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 @@ -173,7 +173,7 @@ public class Parser extends RubyObject { @JRubyMethod(required = 1, optional = 1, visibility = Visibility.PRIVATE) public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { - Ruby runtime = context.getRuntime(); + Ruby runtime = context.runtime; if (this.vSource != null) { throw runtime.newTypeError("already initialized instance"); } @@ -276,7 +276,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 +286,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,7 +333,6 @@ 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; @@ -348,24 +347,19 @@ public class Parser extends RubyObject { 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.decoder = new StringDecoder(); this.dc = new DoubleConverter(); } - 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 +397,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 +429,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 +446,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 +457,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 +469,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,7 +496,7 @@ public class Parser extends RubyObject { ) %*exit; }%% - void parseValue(ParserResult res, int p, int pe) { + void parseValue(ThreadContext context, ParserResult res, int p, int pe) { int cs = EVIL; IRubyObject result = null; @@ -532,18 +526,18 @@ public class Parser extends RubyObject { main := '-'? ( '0' | [1-9][0-9]* ) ( ^[0-9]? @exit ); }%% - void parseInteger(ParserResult res, int p, int pe) { - int new_p = parseIntegerInternal(p, pe); + void parseInteger(ThreadContext context, ParserResult res, int p, int pe) { + int new_p = parseIntegerInternal(context, 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 parseIntegerInternal(ThreadContext context, int p, int pe) { int cs = EVIL; %% write init; @@ -557,8 +551,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,8 +578,8 @@ public class Parser extends RubyObject { ( ^[0-9Ee.\-]? @exit ); }%% - void parseFloat(ParserResult res, int p, int pe) { - int new_p = parseFloatInternal(p, pe); + void parseFloat(ThreadContext context, ParserResult res, int p, int pe) { + int new_p = parseFloatInternal(context, p, pe); if (new_p == -1) { res.update(null, p); return; @@ -596,7 +590,7 @@ public class Parser extends RubyObject { res.update(number, new_p + 1); } - int parseFloatInternal(int p, int pe) { + int parseFloatInternal(ThreadContext context, int p, int pe) { int cs = EVIL; %% write init; @@ -618,9 +612,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,7 +637,7 @@ public class Parser extends RubyObject { ) '"' @exit; }%% - void parseString(ParserResult res, int p, int pe) { + void parseString(ThreadContext context, ParserResult res, int p, int pe) { int cs = EVIL; IRubyObject result = null; @@ -671,7 +665,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`"); } result = klass.callMethod(context, "json_create", result); } @@ -685,7 +679,7 @@ public class Parser extends RubyObject { string.force_encoding(context, info.utf8.get()); if (parser.freeze) { string.setFrozen(true); - string = getRuntime().freezeAndDedupString(string); + string = context.runtime.freezeAndDedupString(string); } res.update(string, p + 1); } else { @@ -705,12 +699,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 +731,17 @@ public class Parser extends RubyObject { end_array @exit; }%% - void parseArray(ParserResult res, int p, int pe) { + void parseArray(ThreadContext context, ParserResult res, int p, int pe) { int cs = EVIL; 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 +753,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,12 +766,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.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 }); @@ -787,7 +781,7 @@ public class Parser extends RubyObject { } action parse_name { - parseString(res, fpc, pe); + parseString(context, res, fpc, pe); if (res.result == null) { fhold; fbreak; @@ -818,21 +812,21 @@ public class Parser extends RubyObject { ) @exit; }%% - void parseObject(ParserResult res, int p, int pe) { + void parseObject(ThreadContext context, ParserResult res, int p, int pe) { int cs = EVIL; 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 +859,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 +876,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,7 +891,7 @@ public class Parser extends RubyObject { ignore*; }%% - public IRubyObject parseImplemetation() { + public IRubyObject parseImplementation(ThreadContext context) { int cs = EVIL; int p, pe; IRubyObject result = null; @@ -911,18 +905,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 +932,16 @@ 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)); + private RaiseException newException(ThreadContext context, String className, String messageBegin, ByteList messageEnd) { + return newException(context, className, context.runtime.newString(messageBegin).cat(messageEnd)); } } } diff --git a/java/src/json/ext/StringDecoder.java b/java/src/json/ext/StringDecoder.java index f4877e93..e2b49efb 100644 --- a/java/src/json/ext/StringDecoder.java +++ b/java/src/json/ext/StringDecoder.java @@ -26,16 +26,12 @@ final class StringDecoder extends ByteListTranscoder { // Array used for writing multi-byte characters into the buffer at once private final byte[] aux = new byte[4]; - StringDecoder(ThreadContext context) { - super(context); - } - - ByteList decode(ByteList src, int start, int end) { + ByteList decode(ThreadContext context, ByteList src, int start, int end) { try { ByteListDirectOutputStream out = new ByteListDirectOutputStream(end - start); init(src, start, end, out); while (hasNext()) { - handleChar(readUtf8Char()); + handleChar(context, readUtf8Char(context)); } quoteStop(pos); return out.toByteListDirect(src.getEncoding()); @@ -44,18 +40,18 @@ ByteList decode(ByteList src, int start, int end) { } } - private void handleChar(int c) throws IOException { + private void handleChar(ThreadContext context, int c) throws IOException { if (c == '\\') { quoteStop(charStart); - handleEscapeSequence(); + handleEscapeSequence(context); } else { quoteStart(); } } - private void handleEscapeSequence() throws IOException { - ensureMin(1); - switch (readUtf8Char()) { + private void handleEscapeSequence(ThreadContext context) throws IOException { + ensureMin(context, 1); + switch (readUtf8Char(context)) { case 'b': append('\b'); break; @@ -72,13 +68,13 @@ private void handleEscapeSequence() throws IOException { 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); } @@ -88,15 +84,15 @@ private void handleEscapeSequence() throws IOException { } } - private void handleLowSurrogate(char highSurrogate) throws IOException { + 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)) { @@ -104,7 +100,7 @@ private void handleLowSurrogate(char highSurrogate) throws IOException { (char)lowSurrogate)); surrogatePairStart = -1; } else { - throw invalidUtf8(); + throw invalidUtf8(context); } } @@ -136,12 +132,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'; @@ -159,7 +155,7 @@ 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 ")); diff --git a/java/src/json/ext/StringEncoder.java b/java/src/json/ext/StringEncoder.java index b1e7096e..b211c4ec 100644 --- a/java/src/json/ext/StringEncoder.java +++ b/java/src/json/ext/StringEncoder.java @@ -40,17 +40,16 @@ 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, OutputStream out) throws IOException { + void encode(ThreadContext context, ByteList src, OutputStream out) throws IOException { init(src, out); append('"'); while (hasNext()) { - handleChar(readUtf8Char()); + handleChar(readUtf8Char(context)); } quoteStop(pos); append('"'); @@ -120,8 +119,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"); } } From b9d21a84317c6114433c839d9fe45f68edc6cc42 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Thu, 21 Nov 2024 10:06:44 -0600 Subject: [PATCH 12/25] Eliminate remaining argument list boxes --- java/src/json/ext/GeneratorState.java | 15 +- java/src/json/ext/Parser.java | 208 ++++++++++++++------------ java/src/json/ext/Parser.rl | 62 ++++---- java/src/json/ext/Utils.java | 4 +- 4 files changed, 158 insertions(+), 131 deletions(-) diff --git a/java/src/json/ext/GeneratorState.java b/java/src/json/ext/GeneratorState.java index 0d8a3617..977444aa 100644 --- a/java/src/json/ext/GeneratorState.java +++ b/java/src/json/ext/GeneratorState.java @@ -156,8 +156,7 @@ static GeneratorState fromState(ThreadContext context, RuntimeInfo info, // 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); + return (GeneratorState)klass.newInstance(context, opts, Block.NULL_BLOCK); } } @@ -194,9 +193,15 @@ 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; } diff --git a/java/src/json/ext/Parser.java b/java/src/json/ext/Parser.java index b1bd5c8f..f21897d4 100644 --- a/java/src/json/ext/Parser.java +++ b/java/src/json/ext/Parser.java @@ -20,13 +20,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; @@ -156,31 +156,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) { + @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 +228,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; @@ -365,11 +375,11 @@ private RaiseException unexpectedToken(ThreadContext context, int absStart, int } -// line 391 "Parser.rl" +// line 401 "Parser.rl" -// line 373 "Parser.java" +// line 383 "Parser.java" private static byte[] init__JSON_value_actions_0() { return new byte [] { @@ -483,7 +493,7 @@ private static byte[] init__JSON_value_from_state_actions_0() static final int JSON_value_en_main = 1; -// line 497 "Parser.rl" +// line 507 "Parser.rl" void parseValue(ThreadContext context, ParserResult res, int p, int pe) { @@ -491,14 +501,14 @@ void parseValue(ThreadContext context, ParserResult res, int p, int pe) { IRubyObject result = null; -// line 495 "Parser.java" +// line 505 "Parser.java" { cs = JSON_value_start; } -// line 504 "Parser.rl" +// line 514 "Parser.rl" -// line 502 "Parser.java" +// line 512 "Parser.java" { int _klen; int _trans = 0; @@ -524,13 +534,13 @@ void parseValue(ThreadContext context, ParserResult res, int p, int pe) { while ( _nacts-- > 0 ) { switch ( _JSON_value_actions[_acts++] ) { case 9: -// line 482 "Parser.rl" +// line 492 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 534 "Parser.java" +// line 544 "Parser.java" } } @@ -593,25 +603,25 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) switch ( _JSON_value_actions[_acts++] ) { case 0: -// line 399 "Parser.rl" +// line 409 "Parser.rl" { result = context.nil; } break; case 1: -// line 402 "Parser.rl" +// line 412 "Parser.rl" { result = context.fals; } break; case 2: -// line 405 "Parser.rl" +// line 415 "Parser.rl" { result = context.tru; } break; case 3: -// line 408 "Parser.rl" +// line 418 "Parser.rl" { if (parser.allowNaN) { result = getConstant(CONST_NAN); @@ -621,7 +631,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 4: -// line 415 "Parser.rl" +// line 425 "Parser.rl" { if (parser.allowNaN) { result = getConstant(CONST_INFINITY); @@ -631,7 +641,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 5: -// line 422 "Parser.rl" +// line 432 "Parser.rl" { if (pe > p + 8 && absSubSequence(p, p + 9).equals(JSON_MINUS_INFINITY)) { @@ -660,7 +670,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 6: -// line 448 "Parser.rl" +// line 458 "Parser.rl" { parseString(context, res, p, pe); if (res.result == null) { @@ -673,7 +683,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 7: -// line 458 "Parser.rl" +// line 468 "Parser.rl" { currentNesting++; parseArray(context, res, p, pe); @@ -688,7 +698,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 8: -// line 470 "Parser.rl" +// line 480 "Parser.rl" { currentNesting++; parseObject(context, res, p, pe); @@ -702,7 +712,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } } break; -// line 706 "Parser.java" +// line 716 "Parser.java" } } } @@ -722,7 +732,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) break; } } -// line 505 "Parser.rl" +// line 515 "Parser.rl" if (cs >= JSON_value_first_final && result != null) { if (parser.freeze) { @@ -735,7 +745,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } -// line 739 "Parser.java" +// line 749 "Parser.java" private static byte[] init__JSON_integer_actions_0() { return new byte [] { @@ -834,7 +844,7 @@ private static byte[] init__JSON_integer_trans_actions_0() static final int JSON_integer_en_main = 1; -// line 527 "Parser.rl" +// line 537 "Parser.rl" void parseInteger(ThreadContext context, ParserResult res, int p, int pe) { @@ -852,15 +862,15 @@ int parseIntegerInternal(ThreadContext context, int p, int pe) { int cs = EVIL; -// line 856 "Parser.java" +// line 866 "Parser.java" { cs = JSON_integer_start; } -// line 544 "Parser.rl" +// line 554 "Parser.rl" int memo = p; -// line 864 "Parser.java" +// line 874 "Parser.java" { int _klen; int _trans = 0; @@ -941,13 +951,13 @@ else if ( data[p] > _JSON_integer_trans_keys[_mid+1] ) switch ( _JSON_integer_actions[_acts++] ) { case 0: -// line 521 "Parser.rl" +// line 531 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 951 "Parser.java" +// line 961 "Parser.java" } } } @@ -967,7 +977,7 @@ else if ( data[p] > _JSON_integer_trans_keys[_mid+1] ) break; } } -// line 546 "Parser.rl" +// line 556 "Parser.rl" if (cs < JSON_integer_first_final) { return -1; @@ -987,7 +997,7 @@ RubyInteger bytesToInum(Ruby runtime, ByteList num) { } -// line 991 "Parser.java" +// line 1001 "Parser.java" private static byte[] init__JSON_float_actions_0() { return new byte [] { @@ -1089,7 +1099,7 @@ private static byte[] init__JSON_float_trans_actions_0() static final int JSON_float_en_main = 1; -// line 579 "Parser.rl" +// line 589 "Parser.rl" void parseFloat(ThreadContext context, ParserResult res, int p, int pe) { @@ -1108,15 +1118,15 @@ int parseFloatInternal(ThreadContext context, int p, int pe) { int cs = EVIL; -// line 1112 "Parser.java" +// line 1122 "Parser.java" { cs = JSON_float_start; } -// line 597 "Parser.rl" +// line 607 "Parser.rl" int memo = p; -// line 1120 "Parser.java" +// line 1130 "Parser.java" { int _klen; int _trans = 0; @@ -1197,13 +1207,13 @@ else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) switch ( _JSON_float_actions[_acts++] ) { case 0: -// line 570 "Parser.rl" +// line 580 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1207 "Parser.java" +// line 1217 "Parser.java" } } } @@ -1223,7 +1233,7 @@ else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) break; } } -// line 599 "Parser.rl" +// line 609 "Parser.rl" if (cs < JSON_float_first_final) { return -1; @@ -1233,7 +1243,7 @@ else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) } -// line 1237 "Parser.java" +// line 1247 "Parser.java" private static byte[] init__JSON_string_actions_0() { return new byte [] { @@ -1335,7 +1345,7 @@ private static byte[] init__JSON_string_trans_actions_0() static final int JSON_string_en_main = 1; -// line 638 "Parser.rl" +// line 648 "Parser.rl" void parseString(ThreadContext context, ParserResult res, int p, int pe) { @@ -1343,15 +1353,15 @@ void parseString(ThreadContext context, ParserResult res, int p, int pe) { IRubyObject result = null; -// line 1347 "Parser.java" +// line 1357 "Parser.java" { cs = JSON_string_start; } -// line 645 "Parser.rl" +// line 655 "Parser.rl" int memo = p; -// line 1355 "Parser.java" +// line 1365 "Parser.java" { int _klen; int _trans = 0; @@ -1432,7 +1442,7 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) switch ( _JSON_string_actions[_acts++] ) { case 0: -// line 613 "Parser.rl" +// line 623 "Parser.rl" { int offset = byteList.begin(); ByteList decoded = decoder.decode(context, byteList, memo + 1 - offset, @@ -1447,13 +1457,13 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) } break; case 1: -// line 626 "Parser.rl" +// line 636 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1457 "Parser.java" +// line 1467 "Parser.java" } } } @@ -1473,22 +1483,14 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) break; } } -// line 647 "Parser.rl" +// line 657 "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]; @@ -1521,7 +1523,7 @@ public void visit(IRubyObject pattern, IRubyObject klass) { } -// line 1525 "Parser.java" +// line 1527 "Parser.java" private static byte[] init__JSON_array_actions_0() { return new byte [] { @@ -1688,7 +1690,7 @@ private static byte[] init__JSON_array_trans_actions_0() static final int JSON_array_en_main = 1; -// line 732 "Parser.rl" +// line 734 "Parser.rl" void parseArray(ThreadContext context, ParserResult res, int p, int pe) { @@ -1708,14 +1710,14 @@ void parseArray(ThreadContext context, ParserResult res, int p, int pe) { } -// line 1712 "Parser.java" +// line 1714 "Parser.java" { cs = JSON_array_start; } -// line 751 "Parser.rl" +// line 753 "Parser.rl" -// line 1719 "Parser.java" +// line 1721 "Parser.java" { int _klen; int _trans = 0; @@ -1758,7 +1760,7 @@ else if ( _widec > _JSON_array_cond_keys[_mid+1] ) case 0: { _widec = 65536 + (data[p] - 0); if ( -// line 699 "Parser.rl" +// line 701 "Parser.rl" parser.allowTrailingComma ) _widec += 65536; break; } @@ -1828,7 +1830,7 @@ else if ( _widec > _JSON_array_trans_keys[_mid+1] ) switch ( _JSON_array_actions[_acts++] ) { case 0: -// line 701 "Parser.rl" +// line 703 "Parser.rl" { parseValue(context, res, p, pe); if (res.result == null) { @@ -1845,13 +1847,13 @@ else if ( _widec > _JSON_array_trans_keys[_mid+1] ) } break; case 1: -// line 716 "Parser.rl" +// line 718 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1855 "Parser.java" +// line 1857 "Parser.java" } } } @@ -1871,7 +1873,7 @@ else if ( _widec > _JSON_array_trans_keys[_mid+1] ) break; } } -// line 752 "Parser.rl" +// line 754 "Parser.rl" if (cs >= JSON_array_first_final) { res.update(result, p + 1); @@ -1881,7 +1883,7 @@ else if ( _widec > _JSON_array_trans_keys[_mid+1] ) } -// line 1885 "Parser.java" +// line 1887 "Parser.java" private static byte[] init__JSON_object_actions_0() { return new byte [] { @@ -2058,7 +2060,7 @@ private static byte[] init__JSON_object_trans_actions_0() static final int JSON_object_en_main = 1; -// line 813 "Parser.rl" +// line 815 "Parser.rl" void parseObject(ThreadContext context, ParserResult res, int p, int pe) { @@ -2083,14 +2085,14 @@ void parseObject(ThreadContext context, ParserResult res, int p, int pe) { } -// line 2087 "Parser.java" +// line 2089 "Parser.java" { cs = JSON_object_start; } -// line 837 "Parser.rl" +// line 839 "Parser.rl" -// line 2094 "Parser.java" +// line 2096 "Parser.java" { int _klen; int _trans = 0; @@ -2133,7 +2135,7 @@ else if ( _widec > _JSON_object_cond_keys[_mid+1] ) case 0: { _widec = 65536 + (data[p] - 0); if ( -// line 766 "Parser.rl" +// line 768 "Parser.rl" parser.allowTrailingComma ) _widec += 65536; break; } @@ -2203,7 +2205,7 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) switch ( _JSON_object_actions[_acts++] ) { case 0: -// line 768 "Parser.rl" +// line 770 "Parser.rl" { parseValue(context, res, p, pe); if (res.result == null) { @@ -2213,14 +2215,14 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) 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 783 "Parser.rl" +// line 785 "Parser.rl" { parseString(context, res, p, pe); if (res.result == null) { @@ -2238,13 +2240,13 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) } break; case 2: -// line 799 "Parser.rl" +// line 801 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 2248 "Parser.java" +// line 2250 "Parser.java" } } } @@ -2264,7 +2266,7 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) break; } } -// line 838 "Parser.rl" +// line 840 "Parser.rl" if (cs < JSON_object_first_final) { res.update(null, p + 1); @@ -2300,7 +2302,7 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) } -// line 2304 "Parser.java" +// line 2306 "Parser.java" private static byte[] init__JSON_actions_0() { return new byte [] { @@ -2403,7 +2405,7 @@ private static byte[] init__JSON_trans_actions_0() static final int JSON_en_main = 1; -// line 892 "Parser.rl" +// line 894 "Parser.rl" public IRubyObject parseImplementation(ThreadContext context) { @@ -2413,16 +2415,16 @@ public IRubyObject parseImplementation(ThreadContext context) { ParserResult res = new ParserResult(); -// line 2417 "Parser.java" +// line 2419 "Parser.java" { cs = JSON_start; } -// line 901 "Parser.rl" +// line 903 "Parser.rl" p = byteList.begin(); pe = p + byteList.length(); -// line 2426 "Parser.java" +// line 2428 "Parser.java" { int _klen; int _trans = 0; @@ -2503,7 +2505,7 @@ else if ( data[p] > _JSON_trans_keys[_mid+1] ) switch ( _JSON_actions[_acts++] ) { case 0: -// line 878 "Parser.rl" +// line 880 "Parser.rl" { parseValue(context, res, p, pe); if (res.result == null) { @@ -2515,7 +2517,7 @@ else if ( data[p] > _JSON_trans_keys[_mid+1] ) } } break; -// line 2519 "Parser.java" +// line 2521 "Parser.java" } } } @@ -2535,7 +2537,7 @@ else if ( data[p] > _JSON_trans_keys[_mid+1] ) break; } } -// line 904 "Parser.rl" +// line 906 "Parser.rl" if (cs >= JSON_first_final && p == pe) { return result; @@ -2578,5 +2580,15 @@ private RaiseException newException(ThreadContext context, String className, Rub private RaiseException newException(ThreadContext context, String className, String messageBegin, ByteList messageEnd) { return newException(context, className, context.runtime.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 84d75a33..14451fab 100644 --- a/java/src/json/ext/Parser.rl +++ b/java/src/json/ext/Parser.rl @@ -18,13 +18,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; @@ -154,31 +154,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) { + @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 +226,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; @@ -650,15 +660,7 @@ 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]; @@ -774,7 +776,7 @@ public class Parser extends RubyObject { 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; } @@ -943,5 +945,15 @@ public class Parser extends RubyObject { private RaiseException newException(ThreadContext context, String className, String messageBegin, ByteList messageEnd) { return newException(context, className, context.runtime.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/Utils.java b/java/src/json/ext/Utils.java index ed6f8329..f8c70608 100644 --- a/java/src/json/ext/Utils.java +++ b/java/src/json/ext/Utils.java @@ -66,9 +66,7 @@ static RaiseException newException(ThreadContext context, String className, RubyString message) { RuntimeInfo info = RuntimeInfo.forRuntime(context.getRuntime()); RubyClass klazz = info.jsonModule.get().getClass(className); - RubyException excptn = - (RubyException)klazz.newInstance(context, - new IRubyObject[] {message}, Block.NULL_BLOCK); + RubyException excptn = (RubyException)klazz.newInstance(context, message, Block.NULL_BLOCK); return new RaiseException(excptn); } From c29d9ae08f6491261e77e20637e77795a1e06048 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Thu, 21 Nov 2024 11:08:42 -0600 Subject: [PATCH 13/25] Cleanups and suggestions from IntelliJ --- java/src/json/ext/Generator.java | 111 ++++++++++-------------- java/src/json/ext/GeneratorMethods.java | 26 +++--- java/src/json/ext/GeneratorState.java | 101 ++++++++------------- java/src/json/ext/OptionsReader.java | 6 +- java/src/json/ext/Parser.java | 69 +++------------ java/src/json/ext/Parser.rl | 83 +++++------------- java/src/json/ext/RuntimeInfo.java | 32 ++----- java/src/json/ext/StringDecoder.java | 4 +- java/src/json/ext/StringEncoder.java | 2 +- java/src/json/ext/Utils.java | 13 +-- 10 files changed, 145 insertions(+), 302 deletions(-) diff --git a/java/src/json/ext/Generator.java b/java/src/json/ext/Generator.java index 2b3cd581..18d46f18 100644 --- a/java/src/json/ext/Generator.java +++ b/java/src/json/ext/Generator.java @@ -5,7 +5,6 @@ */ package json.ext; -import org.jcodings.Encoding; import org.jcodings.specific.UTF8Encoding; import org.jruby.Ruby; import org.jruby.RubyArray; @@ -15,7 +14,6 @@ import org.jruby.RubyFixnum; import org.jruby.RubyFloat; import org.jruby.RubyHash; -import org.jruby.RubyIO; import org.jruby.RubyString; import org.jruby.runtime.Helpers; import org.jruby.runtime.ThreadContext; @@ -29,7 +27,8 @@ import java.io.IOException; import java.io.OutputStream; import java.math.BigInteger; -import java.nio.charset.StandardCharsets; + +import static java.nio.charset.StandardCharsets.*; public final class Generator { @@ -44,12 +43,12 @@ private Generator() { */ static RubyString generateJson(ThreadContext context, T object, Handler handler) { Session session = new Session(null); - return session.infect(handler.generateNew(context, session, object)); + return handler.generateNew(context, session, object); } static RubyString generateJson(ThreadContext context, T object, Handler handler, IRubyObject arg0) { Session session = new Session(arg0); - return session.infect(handler.generateNew(context, session, object)); + return handler.generateNew(context, session, object); } /** @@ -93,21 +92,21 @@ static RubyString generateJson(ThreadContext context, T @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; } @@ -132,9 +131,6 @@ static class Session { private RuntimeInfo info; private StringEncoder stringEncoder; - private boolean tainted = false; - private boolean untrusted = false; - Session(GeneratorState state) { this.state = state; } @@ -163,17 +159,6 @@ public StringEncoder getStringEncoder(ThreadContext context) { } 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; - } } @@ -212,10 +197,10 @@ void generateToBuffer(ThreadContext context, Session session, T object, OutputSt */ private static class KeywordHandler extends Handler { - private byte[] keyword; + private final byte[] keyword; private KeywordHandler(String keyword) { - this.keyword = keyword.getBytes(StandardCharsets.UTF_8); + this.keyword = keyword.getBytes(UTF_8); } @Override @@ -242,7 +227,7 @@ void generate(ThreadContext context, Session session, T object, OutputStream buf @Override void generate(ThreadContext context, Session session, RubyBignum object, OutputStream buffer) throws IOException { BigInteger bigInt = object.getValue(); - buffer.write(bigInt.toString().getBytes(StandardCharsets.UTF_8)); + buffer.write(bigInt.toString().getBytes(UTF_8)); } }; @@ -266,15 +251,15 @@ void generate(ThreadContext context, Session session, RubyFloat object, OutputSt } } - buffer.write(Double.toString(value).getBytes(StandardCharsets.UTF_8)); + buffer.write(Double.toString(value).getBytes(UTF_8)); } }; private static final byte[] EMPTY_ARRAY_BYTES = "[]".getBytes(); - static final Handler ARRAY_HANDLER = - new Handler() { + static final Handler> ARRAY_HANDLER = + new Handler>() { @Override - int guessSize(ThreadContext context, Session session, RubyArray object) { + int guessSize(ThreadContext context, Session session, RubyArray object) { GeneratorState state = session.getState(context); int depth = state.getDepth(); int perItem = @@ -285,9 +270,9 @@ int guessSize(ThreadContext context, Session session, RubyArray object) { } @Override - void generate(ThreadContext context, Session session, RubyArray object, OutputStream buffer) throws IOException { + void generate(ThreadContext context, Session session, RubyArray object, OutputStream buffer) throws IOException { GeneratorState state = session.getState(context); - int depth = state.increaseDepth(); + int depth = state.increaseDepth(context); if (object.isEmpty()) { buffer.write(EMPTY_ARRAY_BYTES); @@ -306,27 +291,24 @@ void generate(ThreadContext context, Session session, RubyArray object, OutputSt System.arraycopy(arrayNl.unsafeBytes(), arrayNl.begin(), delim, 1, arrayNl.length()); - session.infectBy(object); - 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.write(delim); } buffer.write(shift); - Handler handler = (Handler) getHandlerFor(runtime, element); + Handler handler = getHandlerFor(runtime, element); handler.generate(context, session, element, buffer); } state.decreaseDepth(); - if (arrayNl.length() != 0) { + if (!arrayNl.isEmpty()) { buffer.write(arrayNl.bytes()); buffer.write(shift, 0, state.getDepth() * indentUnit.length()); } @@ -352,7 +334,7 @@ int guessSize(ThreadContext context, Session session, RubyHash object) { @Override 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(); + final int depth = state.increaseDepth(context); if (object.isEmpty()) { buffer.write(EMPTY_HASH_BYTES); @@ -360,54 +342,53 @@ void generate(ThreadContext context, final Session session, RubyHash object, fin return; } - final Ruby runtime = context.runtime; - 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.write((byte)'{'); - buffer.write(objectNl.bytes()); + 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) { + 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(objectNl.bytes()); + buffer.write(objectNLBytes); } - if (objectNl.length() != 0) buffer.write(indent); + if (!objectNl.isEmpty()) buffer.write(indent); + + Ruby runtime = context.runtime; IRubyObject keyStr = key.callMethod(context, "to_s"); if (keyStr.getMetaClass() == runtime.getString()) { STRING_HANDLER.generate(context, session, (RubyString) keyStr, buffer); } else { Utils.ensureString(keyStr); - Handler keyHandler = (Handler) getHandlerFor(runtime, keyStr); + Handler keyHandler = getHandlerFor(runtime, keyStr); keyHandler.generate(context, session, keyStr, buffer); } - session.infectBy(key); - buffer.write(spaceBefore.bytes()); + buffer.write(spaceBefore.unsafeBytes()); buffer.write((byte) ':'); - buffer.write(space.bytes()); + buffer.write(space.unsafeBytes()); - Handler valueHandler = (Handler) getHandlerFor(runtime, value); + Handler valueHandler = getHandlerFor(runtime, value); valueHandler.generate(context, session, value, buffer); - session.infectBy(value); } catch (Throwable t) { Helpers.throwException(t); } } - }); + }, firstPair); state.decreaseDepth(); - if (!firstPair[0] && objectNl.length() != 0) { - buffer.write(objectNl.bytes()); + if (!firstPair[0] && !objectNl.isEmpty()) { + buffer.write(objectNLBytes); } buffer.write(Utils.repeat(state.getIndent(), state.getDepth())); buffer.write((byte)'}'); @@ -445,11 +426,11 @@ void generate(ThreadContext context, Session session, RubyString object, OutputS }; 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 diff --git a/java/src/json/ext/GeneratorMethods.java b/java/src/json/ext/GeneratorMethods.java index ec44f4b3..81f01162 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,19 +44,18 @@ 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 = new WeakReference<>(module.defineModuleUnder("String").defineModuleUnder("Extend")); info.stringExtendModule.get().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); } @@ -77,12 +75,12 @@ public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf, IRub public static class RbArray { @JRubyMethod public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf) { - return Generator.generateJson(context, (RubyArray)vSelf, Generator.ARRAY_HANDLER); + 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); + return Generator.generateJson(context, (RubyArray)vSelf, Generator.ARRAY_HANDLER, arg0); } } @@ -154,7 +152,7 @@ public static IRubyObject to_json_raw_object(ThreadContext context, IRubyObject 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) @@ -174,7 +172,7 @@ private static RubyHash toJsonRawObject(ThreadContext context, @JRubyMethod(module=true) public static IRubyObject included(ThreadContext context, IRubyObject vSelf, IRubyObject module) { - RuntimeInfo info = RuntimeInfo.forRuntime(context.getRuntime()); + RuntimeInfo info = RuntimeInfo.forRuntime(context.runtime); return module.callMethod(context, "extend", info.stringExtendModule.get()); } } @@ -190,7 +188,7 @@ public static class StringExtend { @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) { diff --git a/java/src/json/ext/GeneratorState.java b/java/src/json/ext/GeneratorState.java index 977444aa..34a3f9b8 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,15 +122,13 @@ 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); } @@ -144,7 +138,7 @@ public static IRubyObject generate(ThreadContext context, IRubyObject klass, IRu } 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,7 +149,7 @@ 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)) { + if (context.runtime.getHash().isInstance(opts)) { return (GeneratorState)klass.newInstance(context, opts, Block.NULL_BLOCK); } } @@ -166,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: * *

@@ -207,7 +201,7 @@ public IRubyObject initialize(ThreadContext context, IRubyObject arg0) { @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()); } @@ -236,7 +230,7 @@ public IRubyObject initialize_copy(ThreadContext context, IRubyObject vOrig) { @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.getRuntime()); + RuntimeInfo info = RuntimeInfo.forRuntime(context.runtime); if (!(result instanceof RubyString)) { return result; } @@ -244,7 +238,7 @@ public IRubyObject _generate(ThreadContext context, IRubyObject obj, IRubyObject RubyString resultString = result.convertToString(); if (resultString.getEncoding() != UTF8Encoding.INSTANCE) { if (resultString.isFrozen()) { - resultString = resultString.strDup(context.getRuntime()); + resultString = resultString.strDup(context.runtime); } resultString.force_encoding(context, info.utf8.get()); } @@ -252,17 +246,7 @@ public IRubyObject _generate(ThreadContext context, IRubyObject obj, IRubyObject return resultString; } - 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; - } - return false; - } - - @JRubyMethod(name="[]", required=1) + @JRubyMethod(name="[]") public IRubyObject op_aref(ThreadContext context, IRubyObject vName) { String name = vName.asJavaString(); if (getMetaClass().isMethodBound(name, true)) { @@ -273,16 +257,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() { @@ -291,7 +275,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=") @@ -306,7 +290,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=") @@ -321,7 +305,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=") @@ -337,7 +321,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=") @@ -353,7 +337,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=") @@ -365,19 +349,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=") @@ -395,7 +372,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=") @@ -406,7 +383,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); } /** @@ -418,7 +395,7 @@ public boolean strict() { @JRubyMethod(name={"strict","strict?"}) public RubyBoolean strict_get(ThreadContext context) { - return context.getRuntime().newBoolean(strict); + return RubyBoolean.newBoolean(context, strict); } @JRubyMethod(name="strict=") @@ -433,7 +410,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() { @@ -442,12 +419,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=") @@ -463,7 +440,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=") @@ -474,7 +451,7 @@ public IRubyObject depth_set(IRubyObject vDepth) { private ByteList prepareByteList(ThreadContext context, IRubyObject value) { RubyString str = value.convertToString(); - RuntimeInfo info = RuntimeInfo.forRuntime(context.getRuntime()); + RuntimeInfo info = RuntimeInfo.forRuntime(context.runtime); if (str.encoding(context) != info.utf8.get()) { str = (RubyString)str.encode(context, info.utf8.get()); } @@ -532,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)); @@ -553,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..be19e609 100644 --- a/java/src/json/ext/OptionsReader.java +++ b/java/src/json/ext/OptionsReader.java @@ -22,7 +22,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 +41,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 +69,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 */ diff --git a/java/src/json/ext/Parser.java b/java/src/json/ext/Parser.java index f21897d4..4988db0c 100644 --- a/java/src/json/ext/Parser.java +++ b/java/src/json/ext/Parser.java @@ -71,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. @@ -119,7 +115,7 @@ public Parser(Ruby runtime, RubyClass metaClass) { *
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 @@ -252,34 +248,6 @@ private RubyString convertEncoding(ThreadContext context, RubyString source) { 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() * @@ -351,11 +319,6 @@ private static class ParserSession { 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; @@ -364,7 +327,6 @@ private ParserSession(Parser parser, ThreadContext context, RuntimeInfo info) { this.data = byteList.unsafeBytes(); this.view = new ByteList(data, false); this.decoder = new StringDecoder(); - this.dc = new DoubleConverter(); } private RaiseException unexpectedToken(ThreadContext context, int absStart, int absEnd) { @@ -497,7 +459,7 @@ private static byte[] init__JSON_value_from_state_actions_0() void parseValue(ThreadContext context, ParserResult res, int p, int pe) { - int cs = EVIL; + int cs; IRubyObject result = null; @@ -848,18 +810,17 @@ private static byte[] init__JSON_integer_trans_actions_0() void parseInteger(ThreadContext context, ParserResult res, int p, int pe) { - int new_p = parseIntegerInternal(context, p, pe); + int new_p = parseIntegerInternal(p, pe); if (new_p == -1) { res.update(null, p); return; } RubyInteger number = createInteger(context, p, new_p); res.update(number, new_p + 1); - return; } - int parseIntegerInternal(ThreadContext context, int p, int pe) { - int cs = EVIL; + int parseIntegerInternal(int p, int pe) { + int cs; // line 866 "Parser.java" @@ -1103,7 +1064,7 @@ private static byte[] init__JSON_float_trans_actions_0() void parseFloat(ThreadContext context, ParserResult res, int p, int pe) { - int new_p = parseFloatInternal(context, p, pe); + int new_p = parseFloatInternal(p, pe); if (new_p == -1) { res.update(null, p); return; @@ -1114,8 +1075,8 @@ void parseFloat(ThreadContext context, ParserResult res, int p, int pe) { res.update(number, new_p + 1); } - int parseFloatInternal(ThreadContext context, int p, int pe) { - int cs = EVIL; + int parseFloatInternal(int p, int pe) { + int cs; // line 1122 "Parser.java" @@ -1349,7 +1310,7 @@ private static byte[] init__JSON_string_trans_actions_0() void parseString(ThreadContext context, ParserResult res, int p, int pe) { - int cs = EVIL; + int cs; IRubyObject result = null; @@ -1694,7 +1655,7 @@ private static byte[] init__JSON_array_trans_actions_0() void parseArray(ThreadContext context, ParserResult res, int p, int pe) { - int cs = EVIL; + int cs; if (parser.maxNesting > 0 && currentNesting > parser.maxNesting) { throw newException(context, Utils.M_NESTING_ERROR, @@ -2064,7 +2025,7 @@ private static byte[] init__JSON_object_trans_actions_0() void parseObject(ThreadContext context, ParserResult res, int p, int pe) { - int cs = EVIL; + int cs; IRubyObject lastName = null; boolean objectDefault = true; @@ -2409,7 +2370,7 @@ private static byte[] init__JSON_trans_actions_0() public IRubyObject parseImplementation(ThreadContext context) { - int cs = EVIL; + int cs; int p, pe; IRubyObject result = null; ParserResult res = new ParserResult(); @@ -2577,10 +2538,6 @@ private RaiseException newException(ThreadContext context, String className, Rub return Utils.newException(context, className, message); } - private RaiseException newException(ThreadContext context, String className, String messageBegin, ByteList messageEnd) { - return newException(context, className, context.runtime.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) { diff --git a/java/src/json/ext/Parser.rl b/java/src/json/ext/Parser.rl index 14451fab..f40b94e2 100644 --- a/java/src/json/ext/Parser.rl +++ b/java/src/json/ext/Parser.rl @@ -69,11 +69,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 +107,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 @@ -250,34 +246,6 @@ public class Parser extends RubyObject { 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() * @@ -349,11 +317,6 @@ public class Parser extends RubyObject { 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; @@ -362,7 +325,6 @@ public class Parser extends RubyObject { this.data = byteList.unsafeBytes(); this.view = new ByteList(data, false); this.decoder = new StringDecoder(); - this.dc = new DoubleConverter(); } private RaiseException unexpectedToken(ThreadContext context, int absStart, int absEnd) { @@ -507,7 +469,7 @@ public class Parser extends RubyObject { }%% void parseValue(ThreadContext context, ParserResult res, int p, int pe) { - int cs = EVIL; + int cs; IRubyObject result = null; %% write init; @@ -537,18 +499,17 @@ public class Parser extends RubyObject { }%% void parseInteger(ThreadContext context, ParserResult res, int p, int pe) { - int new_p = parseIntegerInternal(context, p, pe); + int new_p = parseIntegerInternal(p, pe); if (new_p == -1) { res.update(null, p); return; } RubyInteger number = createInteger(context, p, new_p); res.update(number, new_p + 1); - return; } - int parseIntegerInternal(ThreadContext context, int p, int pe) { - int cs = EVIL; + int parseIntegerInternal(int p, int pe) { + int cs; %% write init; int memo = p; @@ -589,7 +550,7 @@ public class Parser extends RubyObject { }%% void parseFloat(ThreadContext context, ParserResult res, int p, int pe) { - int new_p = parseFloatInternal(context, p, pe); + int new_p = parseFloatInternal(p, pe); if (new_p == -1) { res.update(null, p); return; @@ -600,8 +561,8 @@ public class Parser extends RubyObject { res.update(number, new_p + 1); } - int parseFloatInternal(ThreadContext context, int p, int pe) { - int cs = EVIL; + int parseFloatInternal(int p, int pe) { + int cs; %% write init; int memo = p; @@ -648,7 +609,7 @@ public class Parser extends RubyObject { }%% void parseString(ThreadContext context, ParserResult res, int p, int pe) { - int cs = EVIL; + int cs; IRubyObject result = null; %% write init; @@ -734,7 +695,7 @@ public class Parser extends RubyObject { }%% void parseArray(ThreadContext context, ParserResult res, int p, int pe) { - int cs = EVIL; + int cs; if (parser.maxNesting > 0 && currentNesting > parser.maxNesting) { throw newException(context, Utils.M_NESTING_ERROR, @@ -815,7 +776,7 @@ public class Parser extends RubyObject { }%% void parseObject(ThreadContext context, ParserResult res, int p, int pe) { - int cs = EVIL; + int cs; IRubyObject lastName = null; boolean objectDefault = true; @@ -894,7 +855,7 @@ public class Parser extends RubyObject { }%% public IRubyObject parseImplementation(ThreadContext context) { - int cs = EVIL; + int cs; int p, pe; IRubyObject result = null; ParserResult res = new ParserResult(); @@ -942,10 +903,6 @@ public class Parser extends RubyObject { return Utils.newException(context, className, message); } - private RaiseException newException(ThreadContext context, String className, String messageBegin, ByteList messageEnd) { - return newException(context, className, context.runtime.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) { diff --git a/java/src/json/ext/RuntimeInfo.java b/java/src/json/ext/RuntimeInfo.java index 2323bd94..fcfaee3e 100644 --- a/java/src/json/ext/RuntimeInfo.java +++ b/java/src/json/ext/RuntimeInfo.java @@ -6,7 +6,6 @@ package json.ext; import java.lang.ref.WeakReference; -import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; import org.jruby.Ruby; @@ -20,7 +19,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; @@ -39,22 +38,18 @@ final class RuntimeInfo { 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, + utf8 = new WeakReference<>((RubyEncoding) RubyEncoding.find(context, encodingClass, runtime.newString("utf-8"))); - ascii8bit = new WeakReference((RubyEncoding)RubyEncoding.find(context, + ascii8bit = new WeakReference<>((RubyEncoding) RubyEncoding.find(context, encodingClass, runtime.newString("ascii-8bit"))); - encodings = new HashMap>(); } } @@ -63,12 +58,12 @@ static RuntimeInfo initRuntime(Ruby runtime) { if (runtime1.get() == runtime) { return info1; } else if (runtime1.get() == null) { - runtime1 = new WeakReference(runtime); + runtime1 = new WeakReference<>(runtime); info1 = new RuntimeInfo(runtime); return info1; } else { if (runtimes == null) { - runtimes = new WeakHashMap(1); + runtimes = new WeakHashMap<>(1); } RuntimeInfo cache = runtimes.get(runtime); if (cache == null) { @@ -90,26 +85,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 e2b49efb..ff984935 100644 --- a/java/src/json/ext/StringDecoder.java +++ b/java/src/json/ext/StringDecoder.java @@ -23,7 +23,7 @@ final class StringDecoder extends ByteListTranscoder { */ private int surrogatePairStart = -1; - // Array used for writing multi-byte characters into the buffer at once + // Array used for writing multibyte characters into the buffer at once private final byte[] aux = new byte[4]; ByteList decode(ThreadContext context, ByteList src, int start, int end) { @@ -162,6 +162,6 @@ protected RaiseException invalidUtf8(ThreadContext context) { 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 b211c4ec..8d23dfb9 100644 --- a/java/src/json/ext/StringEncoder.java +++ b/java/src/json/ext/StringEncoder.java @@ -23,7 +23,7 @@ final class StringEncoder extends ByteListTranscoder { // 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, diff --git a/java/src/json/ext/Utils.java b/java/src/json/ext/Utils.java index f8c70608..a33652d7 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,15 +52,15 @@ 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, message, Block.NULL_BLOCK); - return new RaiseException(excptn); + return excptn.toThrowable(); } static byte[] repeat(ByteList a, int n) { From 5c41cd6df347bf9196592ccd3ba182918e1cb48a Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Thu, 21 Nov 2024 16:58:58 -0600 Subject: [PATCH 14/25] Javadoc cleanup --- java/src/json/ext/Parser.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/java/src/json/ext/Parser.java b/java/src/json/ext/Parser.java index 4988db0c..4923d664 100644 --- a/java/src/json/ext/Parser.java +++ b/java/src/json/ext/Parser.java @@ -109,7 +109,7 @@ 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 @@ -120,31 +120,31 @@ public Parser(Ruby runtime, RubyClass metaClass) { *
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 From cbcc74b0631e5561f8db5d695929440aea590c6a Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Thu, 21 Nov 2024 16:59:09 -0600 Subject: [PATCH 15/25] Eliminate weak refs to RubyEncoding objects It's more efficient to compare the Encoding object and for the rare cases where we need to transcode to UTF-8 just look it up again. --- java/src/json/ext/Generator.java | 8 +- java/src/json/ext/GeneratorState.java | 8 +- java/src/json/ext/OptionsReader.java | 6 +- java/src/json/ext/Parser.java | 164 +++++++++++++------------- java/src/json/ext/Parser.rl | 18 +-- java/src/json/ext/RuntimeInfo.java | 21 +--- 6 files changed, 108 insertions(+), 117 deletions(-) diff --git a/java/src/json/ext/Generator.java b/java/src/json/ext/Generator.java index 18d46f18..6db539a3 100644 --- a/java/src/json/ext/Generator.java +++ b/java/src/json/ext/Generator.java @@ -407,20 +407,18 @@ int guessSize(ThreadContext context, Session session, RubyString object) { @Override void generate(ThreadContext context, Session session, RubyString object, OutputStream buffer) throws IOException { - RuntimeInfo info = session.getInfo(context); RubyString src; try { - if (object.encoding(context) != info.utf8.get()) { - src = (RubyString)object.encode(context, info.utf8.get()); + if (object.getEncoding() != UTF8Encoding.INSTANCE) { + src = (RubyString)object.encode(context, context.runtime.getEncodingService().convertEncodingToRubyEncoding(UTF8Encoding.INSTANCE)); } else { src = object; } session.getStringEncoder(context).encode(context, src.getByteList(), buffer); } catch (RaiseException re) { - throw Utils.newException(context, Utils.M_GENERATOR_ERROR, - re.getMessage()); + throw Utils.newException(context, Utils.M_GENERATOR_ERROR, re.getMessage()); } } }; diff --git a/java/src/json/ext/GeneratorState.java b/java/src/json/ext/GeneratorState.java index 34a3f9b8..fdd433c6 100644 --- a/java/src/json/ext/GeneratorState.java +++ b/java/src/json/ext/GeneratorState.java @@ -240,7 +240,8 @@ public IRubyObject _generate(ThreadContext context, IRubyObject obj, IRubyObject if (resultString.isFrozen()) { resultString = resultString.strDup(context.runtime); } - resultString.force_encoding(context, info.utf8.get()); + resultString.setEncoding(UTF8Encoding.INSTANCE); + resultString.clearCodeRange(); } return resultString; @@ -451,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.runtime); - 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(); } diff --git a/java/src/json/ext/OptionsReader.java b/java/src/json/ext/OptionsReader.java index be19e609..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; @@ -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 4923d664..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; @@ -238,12 +240,13 @@ public IRubyObject initialize(ThreadContext context, IRubyObject arg0, IRubyObje * 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; } @@ -337,11 +340,11 @@ private RaiseException unexpectedToken(ThreadContext context, int absStart, int } -// line 401 "Parser.rl" +// line 366 "Parser.rl" -// line 383 "Parser.java" +// line 348 "Parser.java" private static byte[] init__JSON_value_actions_0() { return new byte [] { @@ -455,7 +458,7 @@ private static byte[] init__JSON_value_from_state_actions_0() static final int JSON_value_en_main = 1; -// line 507 "Parser.rl" +// line 472 "Parser.rl" void parseValue(ThreadContext context, ParserResult res, int p, int pe) { @@ -463,14 +466,14 @@ void parseValue(ThreadContext context, ParserResult res, int p, int pe) { IRubyObject result = null; -// line 505 "Parser.java" +// line 470 "Parser.java" { cs = JSON_value_start; } -// line 514 "Parser.rl" +// line 479 "Parser.rl" -// line 512 "Parser.java" +// line 477 "Parser.java" { int _klen; int _trans = 0; @@ -496,13 +499,13 @@ void parseValue(ThreadContext context, ParserResult res, int p, int pe) { while ( _nacts-- > 0 ) { switch ( _JSON_value_actions[_acts++] ) { case 9: -// line 492 "Parser.rl" +// line 457 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 544 "Parser.java" +// line 509 "Parser.java" } } @@ -565,25 +568,25 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) switch ( _JSON_value_actions[_acts++] ) { case 0: -// line 409 "Parser.rl" +// line 374 "Parser.rl" { result = context.nil; } break; case 1: -// line 412 "Parser.rl" +// line 377 "Parser.rl" { result = context.fals; } break; case 2: -// line 415 "Parser.rl" +// line 380 "Parser.rl" { result = context.tru; } break; case 3: -// line 418 "Parser.rl" +// line 383 "Parser.rl" { if (parser.allowNaN) { result = getConstant(CONST_NAN); @@ -593,7 +596,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 4: -// line 425 "Parser.rl" +// line 390 "Parser.rl" { if (parser.allowNaN) { result = getConstant(CONST_INFINITY); @@ -603,7 +606,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 5: -// line 432 "Parser.rl" +// line 397 "Parser.rl" { if (pe > p + 8 && absSubSequence(p, p + 9).equals(JSON_MINUS_INFINITY)) { @@ -632,7 +635,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 6: -// line 458 "Parser.rl" +// line 423 "Parser.rl" { parseString(context, res, p, pe); if (res.result == null) { @@ -645,7 +648,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 7: -// line 468 "Parser.rl" +// line 433 "Parser.rl" { currentNesting++; parseArray(context, res, p, pe); @@ -660,7 +663,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 8: -// line 480 "Parser.rl" +// line 445 "Parser.rl" { currentNesting++; parseObject(context, res, p, pe); @@ -674,7 +677,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } } break; -// line 716 "Parser.java" +// line 681 "Parser.java" } } } @@ -694,7 +697,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) break; } } -// line 515 "Parser.rl" +// line 480 "Parser.rl" if (cs >= JSON_value_first_final && result != null) { if (parser.freeze) { @@ -707,7 +710,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } -// line 749 "Parser.java" +// line 714 "Parser.java" private static byte[] init__JSON_integer_actions_0() { return new byte [] { @@ -806,7 +809,7 @@ private static byte[] init__JSON_integer_trans_actions_0() static final int JSON_integer_en_main = 1; -// line 537 "Parser.rl" +// line 502 "Parser.rl" void parseInteger(ThreadContext context, ParserResult res, int p, int pe) { @@ -823,15 +826,15 @@ int parseIntegerInternal(int p, int pe) { int cs; -// line 866 "Parser.java" +// line 830 "Parser.java" { cs = JSON_integer_start; } -// line 554 "Parser.rl" +// line 518 "Parser.rl" int memo = p; -// line 874 "Parser.java" +// line 838 "Parser.java" { int _klen; int _trans = 0; @@ -912,13 +915,13 @@ else if ( data[p] > _JSON_integer_trans_keys[_mid+1] ) switch ( _JSON_integer_actions[_acts++] ) { case 0: -// line 531 "Parser.rl" +// line 496 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 961 "Parser.java" +// line 925 "Parser.java" } } } @@ -938,7 +941,7 @@ else if ( data[p] > _JSON_integer_trans_keys[_mid+1] ) break; } } -// line 556 "Parser.rl" +// line 520 "Parser.rl" if (cs < JSON_integer_first_final) { return -1; @@ -958,7 +961,7 @@ RubyInteger bytesToInum(Ruby runtime, ByteList num) { } -// line 1001 "Parser.java" +// line 965 "Parser.java" private static byte[] init__JSON_float_actions_0() { return new byte [] { @@ -1060,7 +1063,7 @@ private static byte[] init__JSON_float_trans_actions_0() static final int JSON_float_en_main = 1; -// line 589 "Parser.rl" +// line 553 "Parser.rl" void parseFloat(ThreadContext context, ParserResult res, int p, int pe) { @@ -1079,15 +1082,15 @@ int parseFloatInternal(int p, int pe) { int cs; -// line 1122 "Parser.java" +// line 1086 "Parser.java" { cs = JSON_float_start; } -// line 607 "Parser.rl" +// line 571 "Parser.rl" int memo = p; -// line 1130 "Parser.java" +// line 1094 "Parser.java" { int _klen; int _trans = 0; @@ -1168,13 +1171,13 @@ else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) switch ( _JSON_float_actions[_acts++] ) { case 0: -// line 580 "Parser.rl" +// line 544 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1217 "Parser.java" +// line 1181 "Parser.java" } } } @@ -1194,7 +1197,7 @@ else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) break; } } -// line 609 "Parser.rl" +// line 573 "Parser.rl" if (cs < JSON_float_first_final) { return -1; @@ -1204,7 +1207,7 @@ else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) } -// line 1247 "Parser.java" +// line 1211 "Parser.java" private static byte[] init__JSON_string_actions_0() { return new byte [] { @@ -1306,7 +1309,7 @@ private static byte[] init__JSON_string_trans_actions_0() static final int JSON_string_en_main = 1; -// line 648 "Parser.rl" +// line 612 "Parser.rl" void parseString(ThreadContext context, ParserResult res, int p, int pe) { @@ -1314,15 +1317,15 @@ void parseString(ThreadContext context, ParserResult res, int p, int pe) { IRubyObject result = null; -// line 1357 "Parser.java" +// line 1321 "Parser.java" { cs = JSON_string_start; } -// line 655 "Parser.rl" +// line 619 "Parser.rl" int memo = p; -// line 1365 "Parser.java" +// line 1329 "Parser.java" { int _klen; int _trans = 0; @@ -1403,7 +1406,7 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) switch ( _JSON_string_actions[_acts++] ) { case 0: -// line 623 "Parser.rl" +// line 587 "Parser.rl" { int offset = byteList.begin(); ByteList decoded = decoder.decode(context, byteList, memo + 1 - offset, @@ -1418,13 +1421,13 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) } break; case 1: -// line 636 "Parser.rl" +// line 600 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1467 "Parser.java" +// line 1431 "Parser.java" } } } @@ -1444,7 +1447,7 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) break; } } -// line 657 "Parser.rl" +// line 621 "Parser.rl" if (parser.createAdditions) { RubyHash matchString = parser.match_string; @@ -1469,7 +1472,8 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) 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 = context.runtime.freezeAndDedupString(string); @@ -1484,7 +1488,7 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) } -// line 1527 "Parser.java" +// line 1492 "Parser.java" private static byte[] init__JSON_array_actions_0() { return new byte [] { @@ -1651,7 +1655,7 @@ private static byte[] init__JSON_array_trans_actions_0() static final int JSON_array_en_main = 1; -// line 734 "Parser.rl" +// line 699 "Parser.rl" void parseArray(ThreadContext context, ParserResult res, int p, int pe) { @@ -1671,14 +1675,14 @@ void parseArray(ThreadContext context, ParserResult res, int p, int pe) { } -// line 1714 "Parser.java" +// line 1679 "Parser.java" { cs = JSON_array_start; } -// line 753 "Parser.rl" +// line 718 "Parser.rl" -// line 1721 "Parser.java" +// line 1686 "Parser.java" { int _klen; int _trans = 0; @@ -1721,7 +1725,7 @@ else if ( _widec > _JSON_array_cond_keys[_mid+1] ) case 0: { _widec = 65536 + (data[p] - 0); if ( -// line 701 "Parser.rl" +// line 666 "Parser.rl" parser.allowTrailingComma ) _widec += 65536; break; } @@ -1791,7 +1795,7 @@ else if ( _widec > _JSON_array_trans_keys[_mid+1] ) switch ( _JSON_array_actions[_acts++] ) { case 0: -// line 703 "Parser.rl" +// line 668 "Parser.rl" { parseValue(context, res, p, pe); if (res.result == null) { @@ -1808,13 +1812,13 @@ else if ( _widec > _JSON_array_trans_keys[_mid+1] ) } break; case 1: -// line 718 "Parser.rl" +// line 683 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1857 "Parser.java" +// line 1822 "Parser.java" } } } @@ -1834,7 +1838,7 @@ else if ( _widec > _JSON_array_trans_keys[_mid+1] ) break; } } -// line 754 "Parser.rl" +// line 719 "Parser.rl" if (cs >= JSON_array_first_final) { res.update(result, p + 1); @@ -1844,7 +1848,7 @@ else if ( _widec > _JSON_array_trans_keys[_mid+1] ) } -// line 1887 "Parser.java" +// line 1852 "Parser.java" private static byte[] init__JSON_object_actions_0() { return new byte [] { @@ -2021,7 +2025,7 @@ private static byte[] init__JSON_object_trans_actions_0() static final int JSON_object_en_main = 1; -// line 815 "Parser.rl" +// line 780 "Parser.rl" void parseObject(ThreadContext context, ParserResult res, int p, int pe) { @@ -2046,14 +2050,14 @@ void parseObject(ThreadContext context, ParserResult res, int p, int pe) { } -// line 2089 "Parser.java" +// line 2054 "Parser.java" { cs = JSON_object_start; } -// line 839 "Parser.rl" +// line 804 "Parser.rl" -// line 2096 "Parser.java" +// line 2061 "Parser.java" { int _klen; int _trans = 0; @@ -2096,7 +2100,7 @@ else if ( _widec > _JSON_object_cond_keys[_mid+1] ) case 0: { _widec = 65536 + (data[p] - 0); if ( -// line 768 "Parser.rl" +// line 733 "Parser.rl" parser.allowTrailingComma ) _widec += 65536; break; } @@ -2166,7 +2170,7 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) switch ( _JSON_object_actions[_acts++] ) { case 0: -// line 770 "Parser.rl" +// line 735 "Parser.rl" { parseValue(context, res, p, pe); if (res.result == null) { @@ -2183,7 +2187,7 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) } break; case 1: -// line 785 "Parser.rl" +// line 750 "Parser.rl" { parseString(context, res, p, pe); if (res.result == null) { @@ -2201,13 +2205,13 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) } break; case 2: -// line 801 "Parser.rl" +// line 766 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 2250 "Parser.java" +// line 2215 "Parser.java" } } } @@ -2227,7 +2231,7 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) break; } } -// line 840 "Parser.rl" +// line 805 "Parser.rl" if (cs < JSON_object_first_final) { res.update(null, p + 1); @@ -2263,7 +2267,7 @@ else if ( _widec > _JSON_object_trans_keys[_mid+1] ) } -// line 2306 "Parser.java" +// line 2271 "Parser.java" private static byte[] init__JSON_actions_0() { return new byte [] { @@ -2366,7 +2370,7 @@ private static byte[] init__JSON_trans_actions_0() static final int JSON_en_main = 1; -// line 894 "Parser.rl" +// line 859 "Parser.rl" public IRubyObject parseImplementation(ThreadContext context) { @@ -2376,16 +2380,16 @@ public IRubyObject parseImplementation(ThreadContext context) { ParserResult res = new ParserResult(); -// line 2419 "Parser.java" +// line 2384 "Parser.java" { cs = JSON_start; } -// line 903 "Parser.rl" +// line 868 "Parser.rl" p = byteList.begin(); pe = p + byteList.length(); -// line 2428 "Parser.java" +// line 2393 "Parser.java" { int _klen; int _trans = 0; @@ -2466,7 +2470,7 @@ else if ( data[p] > _JSON_trans_keys[_mid+1] ) switch ( _JSON_actions[_acts++] ) { case 0: -// line 880 "Parser.rl" +// line 845 "Parser.rl" { parseValue(context, res, p, pe); if (res.result == null) { @@ -2478,7 +2482,7 @@ else if ( data[p] > _JSON_trans_keys[_mid+1] ) } } break; -// line 2521 "Parser.java" +// line 2486 "Parser.java" } } } @@ -2498,7 +2502,7 @@ else if ( data[p] > _JSON_trans_keys[_mid+1] ) break; } } -// line 906 "Parser.rl" +// line 871 "Parser.rl" if (cs >= JSON_first_final && p == pe) { return result; diff --git a/java/src/json/ext/Parser.rl b/java/src/json/ext/Parser.rl index f40b94e2..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; @@ -236,12 +238,13 @@ 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; } @@ -639,7 +642,8 @@ 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 = context.runtime.freezeAndDedupString(string); diff --git a/java/src/json/ext/RuntimeInfo.java b/java/src/json/ext/RuntimeInfo.java index fcfaee3e..f430a64c 100644 --- a/java/src/json/ext/RuntimeInfo.java +++ b/java/src/json/ext/RuntimeInfo.java @@ -10,7 +10,6 @@ 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; @@ -36,21 +35,7 @@ final class RuntimeInfo { /** JSON::SAFE_STATE_PROTOTYPE */ WeakReference safeStatePrototype; - final WeakReference utf8; - final WeakReference ascii8bit; - - private RuntimeInfo(Ruby runtime) { - RubyClass encodingClass = runtime.getEncoding(); - if (encodingClass == null) { // 1.8 mode - utf8 = ascii8bit = 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"))); - } + private RuntimeInfo() { } static RuntimeInfo initRuntime(Ruby runtime) { @@ -59,7 +44,7 @@ static RuntimeInfo initRuntime(Ruby runtime) { return info1; } else if (runtime1.get() == null) { runtime1 = new WeakReference<>(runtime); - info1 = new RuntimeInfo(runtime); + info1 = new RuntimeInfo(); return info1; } else { if (runtimes == null) { @@ -67,7 +52,7 @@ static RuntimeInfo initRuntime(Ruby runtime) { } RuntimeInfo cache = runtimes.get(runtime); if (cache == null) { - cache = new RuntimeInfo(runtime); + cache = new RuntimeInfo(); runtimes.put(runtime, cache); } return cache; From d798d799e7f5ff907730596510acf3ac0ae8ee27 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Thu, 21 Nov 2024 17:21:41 -0600 Subject: [PATCH 16/25] Eliminate String::Extend weak reference Rarely used, mostly at boot, and probably just as quick to look up the constant as go through the weak reference. --- java/src/json/ext/GeneratorMethods.java | 8 ++++---- java/src/json/ext/RuntimeInfo.java | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/java/src/json/ext/GeneratorMethods.java b/java/src/json/ext/GeneratorMethods.java index 81f01162..eb31b9ef 100644 --- a/java/src/json/ext/GeneratorMethods.java +++ b/java/src/json/ext/GeneratorMethods.java @@ -44,8 +44,8 @@ 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); } /** @@ -171,9 +171,9 @@ private static RubyHash toJsonRawObject(ThreadContext context, } @JRubyMethod(module=true) - public static IRubyObject included(ThreadContext context, IRubyObject vSelf, IRubyObject module) { + public static IRubyObject included(ThreadContext context, IRubyObject extendModule, IRubyObject module) { RuntimeInfo info = RuntimeInfo.forRuntime(context.runtime); - return module.callMethod(context, "extend", info.stringExtendModule.get()); + return module.callMethod(context, "extend", ((RubyModule) extendModule).getConstant("Extend")); } } diff --git a/java/src/json/ext/RuntimeInfo.java b/java/src/json/ext/RuntimeInfo.java index f430a64c..f712dc25 100644 --- a/java/src/json/ext/RuntimeInfo.java +++ b/java/src/json/ext/RuntimeInfo.java @@ -28,8 +28,6 @@ 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 */ From 5b77eed4a67bd9ac4987b0518e7d060a64d62686 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Fri, 22 Nov 2024 09:09:54 -0600 Subject: [PATCH 17/25] Write bytes directly --- java/src/json/ext/ByteListTranscoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/src/json/ext/ByteListTranscoder.java b/java/src/json/ext/ByteListTranscoder.java index dc294f05..41e4e393 100644 --- a/java/src/json/ext/ByteListTranscoder.java +++ b/java/src/json/ext/ByteListTranscoder.java @@ -141,7 +141,7 @@ protected void quoteStart() { */ protected void quoteStop(int endPos) throws IOException { if (quoteStart != -1) { - out.write(src.bytes(), quoteStart, endPos - quoteStart); + out.write(src.unsafeBytes(), src.begin() + quoteStart, endPos - quoteStart); quoteStart = -1; } } From f30ade0fbcdd91a7b981c6c2b998899dcc0cd2ab Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Fri, 22 Nov 2024 09:13:37 -0600 Subject: [PATCH 18/25] Duplicate to_s shortcut from CRuby --- java/src/json/ext/Generator.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/java/src/json/ext/Generator.java b/java/src/json/ext/Generator.java index 6db539a3..a502b7c9 100644 --- a/java/src/json/ext/Generator.java +++ b/java/src/json/ext/Generator.java @@ -11,10 +11,12 @@ 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.runtime.Helpers; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; @@ -22,6 +24,7 @@ import org.jruby.exceptions.RaiseException; import org.jruby.util.ConvertBytes; import org.jruby.util.IOOutputStream; +import org.jruby.util.TypeConverter; import java.io.BufferedOutputStream; import java.io.IOException; @@ -366,7 +369,20 @@ public void visit(ThreadContext context, RubyHash self, IRubyObject key, IRubyOb Ruby runtime = context.runtime; - IRubyObject keyStr = key.callMethod(context, "to_s"); + 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 { From 6d852f175072bb02c986b4cca3260b6b7d66bd86 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Fri, 22 Nov 2024 11:08:19 -0600 Subject: [PATCH 19/25] Duplicate CRuby handling of UTF-8 compatible strings Previous logic would only pass the string through if its encoding was exactly UTF-8. New logic passes through UTF-8 and US-ASCII and handles other encodings like CRuby. --- java/src/json/ext/ByteListTranscoder.java | 5 +++ java/src/json/ext/Generator.java | 47 +++++++++++++++++++---- java/src/json/ext/StringEncoder.java | 10 +++++ 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/java/src/json/ext/ByteListTranscoder.java b/java/src/json/ext/ByteListTranscoder.java index 41e4e393..883cb407 100644 --- a/java/src/json/ext/ByteListTranscoder.java +++ b/java/src/json/ext/ByteListTranscoder.java @@ -104,6 +104,11 @@ protected int readUtf8Char(ThreadContext context) { 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. diff --git a/java/src/json/ext/Generator.java b/java/src/json/ext/Generator.java index a502b7c9..9f5f7e86 100644 --- a/java/src/json/ext/Generator.java +++ b/java/src/json/ext/Generator.java @@ -5,6 +5,9 @@ */ 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; @@ -24,6 +27,7 @@ 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; @@ -423,22 +427,49 @@ int guessSize(ThreadContext context, Session session, RubyString object) { @Override void generate(ThreadContext context, Session session, RubyString object, OutputStream buffer) throws IOException { - RubyString src; - try { - if (object.getEncoding() != UTF8Encoding.INSTANCE) { - src = (RubyString)object.encode(context, context.runtime.getEncodingService().convertEncodingToRubyEncoding(UTF8Encoding.INSTANCE)); - } else { - src = object; + object = ensureValidEncoding(context, object); + 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 stringEncoder.invalidUtf8(context); } - - session.getStringEncoder(context).encode(context, src.getByteList(), buffer); } catch (RaiseException re) { throw Utils.newException(context, Utils.M_GENERATOR_ERROR, re.getMessage()); } } }; + 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"); static final Handler FALSE_HANDLER = diff --git a/java/src/json/ext/StringEncoder.java b/java/src/json/ext/StringEncoder.java index 8d23dfb9..9ffb9043 100644 --- a/java/src/json/ext/StringEncoder.java +++ b/java/src/json/ext/StringEncoder.java @@ -55,6 +55,16 @@ void encode(ThreadContext context, ByteList src, OutputStream out) throws IOExce append('"'); } + void encodeASCII(ThreadContext context, ByteList src, OutputStream out) throws IOException { + init(src, out); + append('"'); + while (hasNext()) { + handleChar(readASCIIChar()); + } + quoteStop(pos); + append('"'); + } + private void handleChar(int c) throws IOException { switch (c) { case '"': From 0ce2119592ecce9ee3731149748442ad995667ba Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Fri, 22 Nov 2024 14:32:02 -0600 Subject: [PATCH 20/25] Restore direct-to-ByteList for decoding String Using an output stream here is just added overhead compared to writing directly to a ByteList. Instead, we move the OutputStream logic into StringEncoder and restore direct ByteList writing to StringDecoder. --- java/src/json/ext/ByteListTranscoder.java | 18 ++++++------------ java/src/json/ext/StringDecoder.java | 16 +++++++++++++--- java/src/json/ext/StringEncoder.java | 16 ++++++++++++++-- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/java/src/json/ext/ByteListTranscoder.java b/java/src/json/ext/ByteListTranscoder.java index 883cb407..78d8037c 100644 --- a/java/src/json/ext/ByteListTranscoder.java +++ b/java/src/json/ext/ByteListTranscoder.java @@ -24,7 +24,6 @@ abstract class ByteListTranscoder { /** Position of the next character to read */ protected int pos; - private OutputStream 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 @@ -34,16 +33,15 @@ abstract class ByteListTranscoder { */ private int quoteStart = -1; - protected void init(ByteList src, OutputStream out) { - this.init(src, 0, src.length(), out); + protected void init(ByteList src) { + this.init(src, 0, src.length()); } - protected void init(ByteList src, int start, int end, OutputStream 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; } /** @@ -146,18 +144,14 @@ protected void quoteStart() { */ protected void quoteStop(int endPos) throws IOException { if (quoteStart != -1) { - out.write(src.unsafeBytes(), src.begin() + quoteStart, endPos - quoteStart); + append(src.unsafeBytes(), src.begin() + quoteStart, endPos - quoteStart); quoteStart = -1; } } - protected void append(int b) throws IOException { - out.write(b); - } + protected abstract void append(int b) throws IOException; - protected void append(byte[] origin, int start, int length) throws IOException { - out.write(origin, start, length); - } + protected abstract void append(byte[] origin, int start, int length) throws IOException; protected abstract RaiseException invalidUtf8(ThreadContext context); diff --git a/java/src/json/ext/StringDecoder.java b/java/src/json/ext/StringDecoder.java index ff984935..bf616c1e 100644 --- a/java/src/json/ext/StringDecoder.java +++ b/java/src/json/ext/StringDecoder.java @@ -23,18 +23,20 @@ final class StringDecoder extends ByteListTranscoder { */ private int surrogatePairStart = -1; + private ByteList out; + // Array used for writing multibyte characters into the buffer at once private final byte[] aux = new byte[4]; ByteList decode(ThreadContext context, ByteList src, int start, int end) { try { - ByteListDirectOutputStream out = new ByteListDirectOutputStream(end - start); - init(src, start, end, out); + init(src, start, end); + this.out = new ByteList(end - start); while (hasNext()) { handleChar(context, readUtf8Char(context)); } quoteStop(pos); - return out.toByteListDirect(src.getEncoding()); + return out; } catch (IOException e) { throw context.runtime.newIOErrorFromException(e); } @@ -84,6 +86,14 @@ private void handleEscapeSequence(ThreadContext context) throws IOException { } } + 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(context, 1); diff --git a/java/src/json/ext/StringEncoder.java b/java/src/json/ext/StringEncoder.java index 9ffb9043..63df459d 100644 --- a/java/src/json/ext/StringEncoder.java +++ b/java/src/json/ext/StringEncoder.java @@ -20,6 +20,8 @@ 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 = @@ -46,7 +48,8 @@ final class StringEncoder extends ByteListTranscoder { } void encode(ThreadContext context, ByteList src, OutputStream out) throws IOException { - init(src, out); + init(src); + this.out = out; append('"'); while (hasNext()) { handleChar(readUtf8Char(context)); @@ -56,7 +59,8 @@ void encode(ThreadContext context, ByteList src, OutputStream out) throws IOExce } void encodeASCII(ThreadContext context, ByteList src, OutputStream out) throws IOException { - init(src, out); + init(src); + this.out = out; append('"'); while (hasNext()) { handleChar(readASCIIChar()); @@ -65,6 +69,14 @@ void encodeASCII(ThreadContext context, ByteList src, OutputStream out) throws I 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 '"': From e10d0bffcd7df857f69ba251213d0cb568481f73 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 23 Nov 2024 09:51:57 +0100 Subject: [PATCH 21/25] Stop using `rb_gc_mark_locations` It's using `rb_gc_mark_maybe` under the hood, which isn't what we need. --- ext/json/ext/parser/extconf.rb | 1 - ext/json/ext/parser/parser.c | 256 ++++++++++++++++----------------- ext/json/ext/parser/parser.rl | 24 ++-- 3 files changed, 132 insertions(+), 149 deletions(-) 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) From 03d74147b6db49715542ed5bf3aaec894d823eca Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 25 Nov 2024 11:15:53 +0100 Subject: [PATCH 22/25] JSON::GeneratorError expose invalid object MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix: https://github.com/ruby/json/issues/710 Makes it easier to debug why a given tree of objects can't be dumped as JSON. Co-Authored-By: Étienne Barrié --- ext/json/ext/generator/generator.c | 54 +++++++++++++++++++++------- java/src/json/ext/Generator.java | 34 ++++++++++-------- java/src/json/ext/StringEncoder.java | 2 +- java/src/json/ext/Utils.java | 12 +++++++ lib/json/common.rb | 18 +++++++++- lib/json/truffle_ruby/generator.rb | 32 +++++++++-------- test/json/json_generator_test.rb | 17 ++++++--- 7 files changed, 119 insertions(+), 50 deletions(-) diff --git a/ext/json/ext/generator/generator.c b/ext/json/ext/generator/generator.c index 503baca6..e4761fcb 100644 --- a/ext/json/ext/generator/generator.c +++ b/ext/json/ext/generator/generator.c @@ -71,6 +71,28 @@ 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 +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. * @@ -867,6 +889,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 +919,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 +942,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 +990,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 +1039,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,10 +1067,6 @@ 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; @@ -1537,10 +1564,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); diff --git a/java/src/json/ext/Generator.java b/java/src/json/ext/Generator.java index 9f5f7e86..4ab92805 100644 --- a/java/src/json/ext/Generator.java +++ b/java/src/json/ext/Generator.java @@ -20,6 +20,7 @@ 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; @@ -254,7 +255,7 @@ void generate(ThreadContext context, Session session, RubyFloat object, OutputSt if (Double.isInfinite(value) || Double.isNaN(value)) { if (!session.getState(context).allowNaN()) { - throw Utils.newException(context, Utils.M_GENERATOR_ERROR, object + " not allowed in JSON"); + throw Utils.buildGeneratorError(context, object, object + " not allowed in JSON").toThrowable(); } } @@ -429,20 +430,23 @@ int guessSize(ThreadContext context, Session session, RubyString object) { void generate(ThreadContext context, Session session, RubyString object, OutputStream buffer) throws IOException { try { object = ensureValidEncoding(context, object); - 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 stringEncoder.invalidUtf8(context); - } } catch (RaiseException re) { - throw Utils.newException(context, 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(); } } }; @@ -506,7 +510,7 @@ void generate(ThreadContext context, Session session, IRubyObject object, Output RubyString generateNew(ThreadContext context, Session session, IRubyObject object) { GeneratorState state = session.getState(context); if (state.strict()) { - throw Utils.newException(context, Utils.M_GENERATOR_ERROR, object + " not allowed in JSON"); + throw Utils.buildGeneratorError(context, object, object + " not allowed in JSON").toThrowable(); } else if (object.respondsTo("to_json")) { IRubyObject result = object.callMethod(context, "to_json", state); if (result instanceof RubyString) return (RubyString)result; diff --git a/java/src/json/ext/StringEncoder.java b/java/src/json/ext/StringEncoder.java index 63df459d..68fd81e3 100644 --- a/java/src/json/ext/StringEncoder.java +++ b/java/src/json/ext/StringEncoder.java @@ -142,6 +142,6 @@ private void escapeCodeUnit(char c, int auxOffset) { @Override protected RaiseException invalidUtf8(ThreadContext context) { - return Utils.newException(context, Utils.M_GENERATOR_ERROR, "source sequence is illegal/malformed utf-8"); + 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 a33652d7..87139cdb 100644 --- a/java/src/json/ext/Utils.java +++ b/java/src/json/ext/Utils.java @@ -63,6 +63,18 @@ static RaiseException newException(ThreadContext context, 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) { return repeat(a.unsafeBytes(), a.begin(), a.length(), n); } diff --git a/lib/json/common.rb b/lib/json/common.rb index a88a3fff..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: diff --git a/lib/json/truffle_ruby/generator.rb b/lib/json/truffle_ruby/generator.rb index 84cfd53d..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) @@ -306,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 @@ -364,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) @@ -403,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 @@ -454,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 @@ -507,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 @@ -536,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 @@ -558,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 @@ -570,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_generator_test.rb b/test/json/json_generator_test.rb index 700220a1..6e4e293d 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]) } @@ -487,9 +490,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 From b8c1490846280c796ac9d7acbe0791a133f1be80 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Wed, 27 Nov 2024 11:18:04 +0900 Subject: [PATCH 23/25] Prevent a warning of "a candidate for gnu_printf format attribute" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GCC 13 prints the following warning. https://rubyci.s3.amazonaws.com/ubuntu/ruby-master/log/20241127T001003Z.log.html.gz ``` compiling generator.c generator.c: In function ‘raise_generator_error’: generator.c:91:5: warning: function ‘raise_generator_error’ might be a candidate for ‘gnu_printf’ format attribute [-Wsuggest-attribute=format] 91 | VALUE str = rb_vsprintf(fmt, args); | ^~~~~ ``` This change prevents the warning by specifying the format attribute. --- ext/json/ext/generator/generator.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ext/json/ext/generator/generator.c b/ext/json/ext/generator/generator.c index e4761fcb..67d2ea32 100644 --- a/ext/json/ext/generator/generator.c +++ b/ext/json/ext/generator/generator.c @@ -84,6 +84,9 @@ static void raise_generator_error_str(VALUE invalid_object, VALUE str) #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; From d0c38f274e211c29cd94cc3ff5c894c38f48e7be Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 29 Nov 2024 08:46:09 +0100 Subject: [PATCH 24/25] Add missing entry in changelog Fix: https://github.com/ruby/json/issues/714 --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) 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) From 93a7f8717d92013aca42a337e26ba9b85a7da317 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 3 Dec 2024 09:11:31 +0100 Subject: [PATCH 25/25] Fix generate(script_safe: true) to not confuse unrelated characters Fix: https://github.com/ruby/json/issues/715 The first byte check was missing. --- ext/json/ext/generator/generator.c | 2 +- test/json/json_generator_test.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ext/json/ext/generator/generator.c b/ext/json/ext/generator/generator.c index 67d2ea32..d5c8bfd4 100644 --- a/ext/json/ext/generator/generator.c +++ b/ext/json/ext/generator/generator.c @@ -155,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); diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index 6e4e293d..8dd3913d 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -455,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