[ruby/json] Resync
This commit is contained in:
parent
e8522f06b5
commit
ca8f21ace8
Notes:
git
2024-11-05 17:00:58 +00:00
@ -4,9 +4,40 @@
|
|||||||
#include "ruby.h"
|
#include "ruby.h"
|
||||||
#include "ruby/encoding.h"
|
#include "ruby/encoding.h"
|
||||||
|
|
||||||
|
/* shims */
|
||||||
|
/* This is the fallback definition from Ruby 3.4 */
|
||||||
|
|
||||||
|
#ifndef RBIMPL_STDBOOL_H
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
# if defined(HAVE_STDBOOL_H) && (__cplusplus >= 201103L)
|
||||||
|
# include <cstdbool>
|
||||||
|
# endif
|
||||||
|
#elif defined(HAVE_STDBOOL_H)
|
||||||
|
# include <stdbool.h>
|
||||||
|
#elif !defined(HAVE__BOOL)
|
||||||
|
typedef unsigned char _Bool;
|
||||||
|
# define bool _Bool
|
||||||
|
# define true ((_Bool)+1)
|
||||||
|
# define false ((_Bool)+0)
|
||||||
|
# define __bool_true_false_are_defined
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef RB_UNLIKELY
|
||||||
|
#define RB_UNLIKELY(expr) expr
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef RB_LIKELY
|
||||||
|
#define RB_LIKELY(expr) expr
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MAYBE_UNUSED
|
||||||
|
# define MAYBE_UNUSED(x) x
|
||||||
|
#endif
|
||||||
|
|
||||||
enum fbuffer_type {
|
enum fbuffer_type {
|
||||||
HEAP = 0,
|
FBUFFER_HEAP_ALLOCATED = 0,
|
||||||
STACK = 1,
|
FBUFFER_STACK_ALLOCATED = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct FBufferStruct {
|
typedef struct FBufferStruct {
|
||||||
@ -38,19 +69,11 @@ static inline void fbuffer_append_char(FBuffer *fb, char newchr);
|
|||||||
static VALUE fbuffer_to_s(FBuffer *fb);
|
static VALUE fbuffer_to_s(FBuffer *fb);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef RB_UNLIKELY
|
|
||||||
#define RB_UNLIKELY(expr) expr
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef RB_LIKELY
|
|
||||||
#define RB_LIKELY(expr) expr
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void fbuffer_stack_init(FBuffer *fb, unsigned long initial_length, char *stack_buffer, long stack_buffer_size)
|
static void fbuffer_stack_init(FBuffer *fb, unsigned long initial_length, char *stack_buffer, long stack_buffer_size)
|
||||||
{
|
{
|
||||||
fb->initial_length = (initial_length > 0) ? initial_length : FBUFFER_INITIAL_LENGTH_DEFAULT;
|
fb->initial_length = (initial_length > 0) ? initial_length : FBUFFER_INITIAL_LENGTH_DEFAULT;
|
||||||
if (stack_buffer) {
|
if (stack_buffer) {
|
||||||
fb->type = STACK;
|
fb->type = FBUFFER_STACK_ALLOCATED;
|
||||||
fb->ptr = stack_buffer;
|
fb->ptr = stack_buffer;
|
||||||
fb->capa = stack_buffer_size;
|
fb->capa = stack_buffer_size;
|
||||||
}
|
}
|
||||||
@ -58,7 +81,7 @@ static void fbuffer_stack_init(FBuffer *fb, unsigned long initial_length, char *
|
|||||||
|
|
||||||
static void fbuffer_free(FBuffer *fb)
|
static void fbuffer_free(FBuffer *fb)
|
||||||
{
|
{
|
||||||
if (fb->ptr && fb->type == HEAP) {
|
if (fb->ptr && fb->type == FBUFFER_HEAP_ALLOCATED) {
|
||||||
ruby_xfree(fb->ptr);
|
ruby_xfree(fb->ptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,10 +105,10 @@ static void fbuffer_do_inc_capa(FBuffer *fb, unsigned long requested)
|
|||||||
for (required = fb->capa; requested > required - fb->len; required <<= 1);
|
for (required = fb->capa; requested > required - fb->len; required <<= 1);
|
||||||
|
|
||||||
if (required > fb->capa) {
|
if (required > fb->capa) {
|
||||||
if (fb->type == STACK) {
|
if (fb->type == FBUFFER_STACK_ALLOCATED) {
|
||||||
const char *old_buffer = fb->ptr;
|
const char *old_buffer = fb->ptr;
|
||||||
fb->ptr = ALLOC_N(char, required);
|
fb->ptr = ALLOC_N(char, required);
|
||||||
fb->type = HEAP;
|
fb->type = FBUFFER_HEAP_ALLOCATED;
|
||||||
MEMCPY(fb->ptr, old_buffer, char, fb->len);
|
MEMCPY(fb->ptr, old_buffer, char, fb->len);
|
||||||
} else {
|
} else {
|
||||||
REALLOC_N(fb->ptr, char, required);
|
REALLOC_N(fb->ptr, char, required);
|
||||||
|
@ -1,5 +1,27 @@
|
|||||||
|
#include "ruby.h"
|
||||||
#include "../fbuffer/fbuffer.h"
|
#include "../fbuffer/fbuffer.h"
|
||||||
#include "generator.h"
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
/* ruby api and some helpers */
|
||||||
|
|
||||||
|
typedef struct JSON_Generator_StateStruct {
|
||||||
|
VALUE indent;
|
||||||
|
VALUE space;
|
||||||
|
VALUE space_before;
|
||||||
|
VALUE object_nl;
|
||||||
|
VALUE array_nl;
|
||||||
|
|
||||||
|
long max_nesting;
|
||||||
|
long depth;
|
||||||
|
long buffer_initial_length;
|
||||||
|
|
||||||
|
bool allow_nan;
|
||||||
|
bool ascii_only;
|
||||||
|
bool script_safe;
|
||||||
|
bool strict;
|
||||||
|
} JSON_Generator_State;
|
||||||
|
|
||||||
#ifndef RB_UNLIKELY
|
#ifndef RB_UNLIKELY
|
||||||
#define RB_UNLIKELY(cond) (cond)
|
#define RB_UNLIKELY(cond) (cond)
|
||||||
@ -31,6 +53,7 @@ struct generate_json_data {
|
|||||||
generator_func func;
|
generator_func func;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
static void generate_json(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj);
|
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_object(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj);
|
||||||
@ -1013,6 +1036,10 @@ static VALUE generate_json_rescue(VALUE d, VALUE exc)
|
|||||||
struct generate_json_data *data = (struct generate_json_data *)d;
|
struct generate_json_data *data = (struct generate_json_data *)d;
|
||||||
fbuffer_free(data->buffer);
|
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);
|
rb_exc_raise(exc);
|
||||||
|
|
||||||
return Qundef;
|
return Qundef;
|
||||||
|
@ -1,91 +0,0 @@
|
|||||||
#ifndef _GENERATOR_H_
|
|
||||||
#define _GENERATOR_H_
|
|
||||||
|
|
||||||
#include <math.h>
|
|
||||||
#include <ctype.h>
|
|
||||||
|
|
||||||
#include "ruby.h"
|
|
||||||
|
|
||||||
/* This is the fallback definition from Ruby 3.4 */
|
|
||||||
#ifndef RBIMPL_STDBOOL_H
|
|
||||||
#if defined(__cplusplus)
|
|
||||||
# if defined(HAVE_STDBOOL_H) && (__cplusplus >= 201103L)
|
|
||||||
# include <cstdbool>
|
|
||||||
# endif
|
|
||||||
#elif defined(HAVE_STDBOOL_H)
|
|
||||||
# include <stdbool.h>
|
|
||||||
#elif !defined(HAVE__BOOL)
|
|
||||||
typedef unsigned char _Bool;
|
|
||||||
# define bool _Bool
|
|
||||||
# define true ((_Bool)+1)
|
|
||||||
# define false ((_Bool)+0)
|
|
||||||
# define __bool_true_false_are_defined
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* ruby api and some helpers */
|
|
||||||
|
|
||||||
typedef struct JSON_Generator_StateStruct {
|
|
||||||
VALUE indent;
|
|
||||||
VALUE space;
|
|
||||||
VALUE space_before;
|
|
||||||
VALUE object_nl;
|
|
||||||
VALUE array_nl;
|
|
||||||
|
|
||||||
long max_nesting;
|
|
||||||
long depth;
|
|
||||||
long buffer_initial_length;
|
|
||||||
|
|
||||||
bool allow_nan;
|
|
||||||
bool ascii_only;
|
|
||||||
bool script_safe;
|
|
||||||
bool strict;
|
|
||||||
} JSON_Generator_State;
|
|
||||||
|
|
||||||
static VALUE mHash_to_json(int argc, VALUE *argv, VALUE self);
|
|
||||||
static VALUE mArray_to_json(int argc, VALUE *argv, VALUE self);
|
|
||||||
#ifdef RUBY_INTEGER_UNIFICATION
|
|
||||||
static VALUE mInteger_to_json(int argc, VALUE *argv, VALUE self);
|
|
||||||
#else
|
|
||||||
static VALUE mFixnum_to_json(int argc, VALUE *argv, VALUE self);
|
|
||||||
static VALUE mBignum_to_json(int argc, VALUE *argv, VALUE self);
|
|
||||||
#endif
|
|
||||||
static VALUE mFloat_to_json(int argc, VALUE *argv, VALUE self);
|
|
||||||
static VALUE mString_included_s(VALUE self, VALUE modul);
|
|
||||||
static VALUE mString_to_json(int argc, VALUE *argv, VALUE self);
|
|
||||||
static VALUE mString_to_json_raw_object(VALUE self);
|
|
||||||
static VALUE mString_to_json_raw(int argc, VALUE *argv, VALUE self);
|
|
||||||
static VALUE mString_Extend_json_create(VALUE self, VALUE o);
|
|
||||||
static VALUE mTrueClass_to_json(int argc, VALUE *argv, VALUE self);
|
|
||||||
static VALUE mFalseClass_to_json(int argc, VALUE *argv, VALUE self);
|
|
||||||
static VALUE mNilClass_to_json(int argc, VALUE *argv, VALUE self);
|
|
||||||
static VALUE mObject_to_json(int argc, VALUE *argv, VALUE self);
|
|
||||||
static void State_free(void *state);
|
|
||||||
static VALUE cState_s_allocate(VALUE klass);
|
|
||||||
|
|
||||||
static VALUE cState_generate(VALUE self, VALUE obj);
|
|
||||||
static VALUE cState_from_state_s(VALUE self, VALUE opts);
|
|
||||||
static VALUE cState_indent(VALUE self);
|
|
||||||
static VALUE cState_indent_set(VALUE self, VALUE indent);
|
|
||||||
static VALUE cState_space(VALUE self);
|
|
||||||
static VALUE cState_space_set(VALUE self, VALUE space);
|
|
||||||
static VALUE cState_space_before(VALUE self);
|
|
||||||
static VALUE cState_space_before_set(VALUE self, VALUE space_before);
|
|
||||||
static VALUE cState_object_nl(VALUE self);
|
|
||||||
static VALUE cState_object_nl_set(VALUE self, VALUE object_nl);
|
|
||||||
static VALUE cState_array_nl(VALUE self);
|
|
||||||
static VALUE cState_array_nl_set(VALUE self, VALUE array_nl);
|
|
||||||
static VALUE cState_max_nesting(VALUE self);
|
|
||||||
static VALUE cState_max_nesting_set(VALUE self, VALUE depth);
|
|
||||||
static VALUE cState_allow_nan_p(VALUE self);
|
|
||||||
static VALUE cState_ascii_only_p(VALUE self);
|
|
||||||
static VALUE cState_depth(VALUE self);
|
|
||||||
static VALUE cState_depth_set(VALUE self, VALUE depth);
|
|
||||||
static VALUE cState_script_safe(VALUE self);
|
|
||||||
static VALUE cState_script_safe_set(VALUE self, VALUE depth);
|
|
||||||
static VALUE cState_strict(VALUE self);
|
|
||||||
static VALUE cState_strict_set(VALUE self, VALUE strict);
|
|
||||||
|
|
||||||
static const rb_data_type_t JSON_Generator_State_type;
|
|
||||||
|
|
||||||
#endif
|
|
@ -583,10 +583,5 @@ require 'json/common'
|
|||||||
#
|
#
|
||||||
module JSON
|
module JSON
|
||||||
require 'json/version'
|
require 'json/version'
|
||||||
|
require 'json/ext'
|
||||||
begin
|
|
||||||
require 'json/ext'
|
|
||||||
rescue LoadError
|
|
||||||
require 'json/pure'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@ -32,9 +32,7 @@ module JSON
|
|||||||
JSON.generate(object, opts)
|
JSON.generate(object, opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the JSON parser class that is used by JSON. This is either
|
# Returns the JSON parser class that is used by JSON.
|
||||||
# JSON::Ext::Parser or JSON::Pure::Parser:
|
|
||||||
# JSON.parser # => JSON::Ext::Parser
|
|
||||||
attr_reader :parser
|
attr_reader :parser
|
||||||
|
|
||||||
# Set the JSON parser class _parser_ to be used by JSON.
|
# Set the JSON parser class _parser_ to be used by JSON.
|
||||||
@ -97,14 +95,10 @@ module JSON
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the JSON generator module that is used by JSON. This is
|
# Returns the JSON generator module that is used by JSON.
|
||||||
# either JSON::Ext::Generator or JSON::Pure::Generator:
|
|
||||||
# JSON.generator # => JSON::Ext::Generator
|
|
||||||
attr_reader :generator
|
attr_reader :generator
|
||||||
|
|
||||||
# Sets or Returns the JSON generator state class that is used by JSON. This is
|
# Sets or Returns the JSON generator state class that is used by JSON.
|
||||||
# either JSON::Ext::Generator::State or JSON::Pure::Generator::State:
|
|
||||||
# JSON.state # => JSON::Ext::Generator::State
|
|
||||||
attr_accessor :state
|
attr_accessor :state
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -207,16 +201,7 @@ module JSON
|
|||||||
# JSON.parse('')
|
# JSON.parse('')
|
||||||
#
|
#
|
||||||
def parse(source, opts = nil)
|
def parse(source, opts = nil)
|
||||||
if opts.nil?
|
Parser.parse(source, opts)
|
||||||
Parser.new(source).parse
|
|
||||||
else
|
|
||||||
# NB: The ** shouldn't be required, but we have to deal with
|
|
||||||
# different versions of the `json` and `json_pure` gems being
|
|
||||||
# loaded concurrently.
|
|
||||||
# Prior to 2.7.3, `JSON::Ext::Parser` would only take kwargs.
|
|
||||||
# Ref: https://github.com/ruby/json/issues/650
|
|
||||||
Parser.new(source, **opts).parse
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# :call-seq:
|
# :call-seq:
|
||||||
|
@ -8,14 +8,12 @@ module JSON
|
|||||||
module Ext
|
module Ext
|
||||||
if RUBY_ENGINE == 'truffleruby'
|
if RUBY_ENGINE == 'truffleruby'
|
||||||
require 'json/ext/parser'
|
require 'json/ext/parser'
|
||||||
require 'json/pure'
|
require 'json/truffle_ruby/generator'
|
||||||
$DEBUG and warn "Using Ext extension for JSON parser and Pure library for JSON generator."
|
|
||||||
JSON.parser = Parser
|
JSON.parser = Parser
|
||||||
JSON.generator = JSON::Pure::Generator
|
JSON.generator = ::JSON::TruffleRuby::Generator
|
||||||
else
|
else
|
||||||
require 'json/ext/parser'
|
require 'json/ext/parser'
|
||||||
require 'json/ext/generator'
|
require 'json/ext/generator'
|
||||||
$DEBUG and warn "Using Ext extension for JSON."
|
|
||||||
JSON.parser = Parser
|
JSON.parser = Parser
|
||||||
JSON.generator = Generator
|
JSON.generator = Generator
|
||||||
end
|
end
|
||||||
|
@ -2,7 +2,10 @@
|
|||||||
require 'mkmf'
|
require 'mkmf'
|
||||||
|
|
||||||
have_func("rb_enc_interned_str", "ruby.h") # RUBY_VERSION >= 3.0
|
have_func("rb_enc_interned_str", "ruby.h") # RUBY_VERSION >= 3.0
|
||||||
have_func("rb_gc_mark_locations") # Missing on TruffleRuby
|
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
|
||||||
|
|
||||||
append_cflags("-std=c99")
|
append_cflags("-std=c99")
|
||||||
|
|
||||||
create_makefile 'json/ext/parser'
|
create_makefile 'json/ext/parser'
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,78 +0,0 @@
|
|||||||
#ifndef _PARSER_H_
|
|
||||||
#define _PARSER_H_
|
|
||||||
|
|
||||||
#include "ruby.h"
|
|
||||||
|
|
||||||
/* This is the fallback definition from Ruby 3.4 */
|
|
||||||
#ifndef RBIMPL_STDBOOL_H
|
|
||||||
#if defined(__cplusplus)
|
|
||||||
# if defined(HAVE_STDBOOL_H) && (__cplusplus >= 201103L)
|
|
||||||
# include <cstdbool>
|
|
||||||
# endif
|
|
||||||
#elif defined(HAVE_STDBOOL_H)
|
|
||||||
# include <stdbool.h>
|
|
||||||
#elif !defined(HAVE__BOOL)
|
|
||||||
typedef unsigned char _Bool;
|
|
||||||
# define bool _Bool
|
|
||||||
# define true ((_Bool)+1)
|
|
||||||
# define false ((_Bool)+0)
|
|
||||||
# define __bool_true_false_are_defined
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef MAYBE_UNUSED
|
|
||||||
# define MAYBE_UNUSED(x) x
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define option_given_p(opts, key) (rb_hash_lookup2(opts, key, Qundef) != Qundef)
|
|
||||||
|
|
||||||
typedef struct JSON_ParserStruct {
|
|
||||||
VALUE Vsource;
|
|
||||||
char *source;
|
|
||||||
long len;
|
|
||||||
char *memo;
|
|
||||||
VALUE create_id;
|
|
||||||
VALUE object_class;
|
|
||||||
VALUE array_class;
|
|
||||||
VALUE decimal_class;
|
|
||||||
VALUE match_string;
|
|
||||||
FBuffer fbuffer;
|
|
||||||
int max_nesting;
|
|
||||||
char allow_nan;
|
|
||||||
char parsing_name;
|
|
||||||
char symbolize_names;
|
|
||||||
char freeze;
|
|
||||||
char create_additions;
|
|
||||||
char deprecated_create_additions;
|
|
||||||
} JSON_Parser;
|
|
||||||
|
|
||||||
#define GET_PARSER \
|
|
||||||
GET_PARSER_INIT; \
|
|
||||||
if (!json->Vsource) rb_raise(rb_eTypeError, "uninitialized instance")
|
|
||||||
#define GET_PARSER_INIT \
|
|
||||||
JSON_Parser *json; \
|
|
||||||
TypedData_Get_Struct(self, JSON_Parser, &JSON_Parser_type, json)
|
|
||||||
|
|
||||||
#define MinusInfinity "-Infinity"
|
|
||||||
#define EVIL 0x666
|
|
||||||
|
|
||||||
static uint32_t unescape_unicode(const unsigned char *p);
|
|
||||||
static int convert_UTF32_to_UTF8(char *buf, uint32_t ch);
|
|
||||||
static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting);
|
|
||||||
static char *JSON_parse_value(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting);
|
|
||||||
static char *JSON_parse_integer(JSON_Parser *json, char *p, char *pe, VALUE *result);
|
|
||||||
static char *JSON_parse_float(JSON_Parser *json, char *p, char *pe, VALUE *result);
|
|
||||||
static char *JSON_parse_array(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting);
|
|
||||||
static VALUE json_string_unescape(char *string, char *stringEnd, bool intern, bool symbolize);
|
|
||||||
static char *JSON_parse_string(JSON_Parser *json, char *p, char *pe, VALUE *result);
|
|
||||||
static VALUE convert_encoding(VALUE source);
|
|
||||||
static VALUE cParser_initialize(int argc, VALUE *argv, VALUE self);
|
|
||||||
static VALUE cParser_parse(VALUE self);
|
|
||||||
static void JSON_mark(void *json);
|
|
||||||
static void JSON_free(void *json);
|
|
||||||
static VALUE cJSON_parser_s_allocate(VALUE klass);
|
|
||||||
static VALUE cParser_source(VALUE self);
|
|
||||||
|
|
||||||
static const rb_data_type_t JSON_Parser_type;
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,5 +1,308 @@
|
|||||||
|
#include "ruby.h"
|
||||||
#include "../fbuffer/fbuffer.h"
|
#include "../fbuffer/fbuffer.h"
|
||||||
#include "parser.h"
|
|
||||||
|
static VALUE mJSON, mExt, cParser, eNestingError, Encoding_UTF_8;
|
||||||
|
static VALUE CNaN, CInfinity, CMinusInfinity;
|
||||||
|
|
||||||
|
static ID i_json_creatable_p, i_json_create, i_create_id,
|
||||||
|
i_chr, i_deep_const_get, i_match, i_aset, i_aref,
|
||||||
|
i_leftshift, i_new, i_try_convert, i_uminus, i_encode;
|
||||||
|
|
||||||
|
static VALUE sym_max_nesting, sym_allow_nan, sym_allow_trailing_comma, sym_symbolize_names, sym_freeze,
|
||||||
|
sym_create_additions, sym_create_id, sym_object_class, sym_array_class,
|
||||||
|
sym_decimal_class, sym_match_string;
|
||||||
|
|
||||||
|
static int binary_encindex;
|
||||||
|
static int utf8_encindex;
|
||||||
|
|
||||||
|
#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)
|
||||||
|
{
|
||||||
|
long index = 0;
|
||||||
|
while (index < count) {
|
||||||
|
VALUE name = pairs[index++];
|
||||||
|
VALUE value = pairs[index++];
|
||||||
|
rb_hash_aset(hash, name, value);
|
||||||
|
}
|
||||||
|
RB_GC_GUARD(hash);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* name cache */
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
// Object names are likely to be repeated, and are frozen.
|
||||||
|
// As such we can re-use them if we keep a cache of the ones we've seen so far,
|
||||||
|
// and save much more expensive lookups into the global fstring table.
|
||||||
|
// This cache implementation is deliberately simple, as we're optimizing for compactness,
|
||||||
|
// to be able to fit safely on the stack.
|
||||||
|
// As such, binary search into a sorted array gives a good tradeoff between compactness and
|
||||||
|
// performance.
|
||||||
|
#define JSON_RVALUE_CACHE_CAPA 63
|
||||||
|
typedef struct rvalue_cache_struct {
|
||||||
|
int length;
|
||||||
|
VALUE entries[JSON_RVALUE_CACHE_CAPA];
|
||||||
|
} rvalue_cache;
|
||||||
|
|
||||||
|
static rb_encoding *enc_utf8;
|
||||||
|
|
||||||
|
#define JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH 55
|
||||||
|
|
||||||
|
static inline VALUE build_interned_string(const char *str, const long length)
|
||||||
|
{
|
||||||
|
# ifdef HAVE_RB_ENC_INTERNED_STR
|
||||||
|
return rb_enc_interned_str(str, length, enc_utf8);
|
||||||
|
# else
|
||||||
|
VALUE rstring = rb_utf8_str_new(str, length);
|
||||||
|
return rb_funcall(rb_str_freeze(rstring), i_uminus, 0);
|
||||||
|
# endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline VALUE build_symbol(const char *str, const long length)
|
||||||
|
{
|
||||||
|
return rb_str_intern(build_interned_string(str, length));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rvalue_cache_insert_at(rvalue_cache *cache, int index, VALUE rstring)
|
||||||
|
{
|
||||||
|
MEMMOVE(&cache->entries[index + 1], &cache->entries[index], VALUE, cache->length - index);
|
||||||
|
cache->length++;
|
||||||
|
cache->entries[index] = rstring;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int rstring_cache_cmp(const char *str, const long length, VALUE rstring)
|
||||||
|
{
|
||||||
|
long rstring_length = RSTRING_LEN(rstring);
|
||||||
|
if (length == rstring_length) {
|
||||||
|
return memcmp(str, RSTRING_PTR(rstring), length);
|
||||||
|
} else {
|
||||||
|
return (int)(length - rstring_length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE rstring_cache_fetch(rvalue_cache *cache, const char *str, const long length)
|
||||||
|
{
|
||||||
|
if (RB_UNLIKELY(length > JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH)) {
|
||||||
|
// Common names aren't likely to be very long. So we just don't
|
||||||
|
// cache names above an arbitrary threshold.
|
||||||
|
return Qfalse;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RB_UNLIKELY(!isalpha(str[0]))) {
|
||||||
|
// Simple heuristic, if the first character isn't a letter,
|
||||||
|
// we're much less likely to see this string again.
|
||||||
|
// We mostly want to cache strings that are likely to be repeated.
|
||||||
|
return Qfalse;
|
||||||
|
}
|
||||||
|
|
||||||
|
int low = 0;
|
||||||
|
int high = cache->length - 1;
|
||||||
|
int mid = 0;
|
||||||
|
int last_cmp = 0;
|
||||||
|
|
||||||
|
while (low <= high) {
|
||||||
|
mid = (high + low) >> 1;
|
||||||
|
VALUE entry = cache->entries[mid];
|
||||||
|
last_cmp = rstring_cache_cmp(str, length, entry);
|
||||||
|
|
||||||
|
if (last_cmp == 0) {
|
||||||
|
return entry;
|
||||||
|
} else if (last_cmp > 0) {
|
||||||
|
low = mid + 1;
|
||||||
|
} else {
|
||||||
|
high = mid - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RB_UNLIKELY(memchr(str, '\\', length))) {
|
||||||
|
// We assume the overwhelming majority of names don't need to be escaped.
|
||||||
|
// But if they do, we have to fallback to the slow path.
|
||||||
|
return Qfalse;
|
||||||
|
}
|
||||||
|
|
||||||
|
VALUE rstring = build_interned_string(str, length);
|
||||||
|
|
||||||
|
if (cache->length < JSON_RVALUE_CACHE_CAPA) {
|
||||||
|
if (last_cmp > 0) {
|
||||||
|
mid += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
rvalue_cache_insert_at(cache, mid, rstring);
|
||||||
|
}
|
||||||
|
return rstring;
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE rsymbol_cache_fetch(rvalue_cache *cache, const char *str, const long length)
|
||||||
|
{
|
||||||
|
if (RB_UNLIKELY(length > JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH)) {
|
||||||
|
// Common names aren't likely to be very long. So we just don't
|
||||||
|
// cache names above an arbitrary threshold.
|
||||||
|
return Qfalse;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RB_UNLIKELY(!isalpha(str[0]))) {
|
||||||
|
// Simple heuristic, if the first character isn't a letter,
|
||||||
|
// we're much less likely to see this string again.
|
||||||
|
// We mostly want to cache strings that are likely to be repeated.
|
||||||
|
return Qfalse;
|
||||||
|
}
|
||||||
|
|
||||||
|
int low = 0;
|
||||||
|
int high = cache->length - 1;
|
||||||
|
int mid = 0;
|
||||||
|
int last_cmp = 0;
|
||||||
|
|
||||||
|
while (low <= high) {
|
||||||
|
mid = (high + low) >> 1;
|
||||||
|
VALUE entry = cache->entries[mid];
|
||||||
|
last_cmp = rstring_cache_cmp(str, length, rb_sym2str(entry));
|
||||||
|
|
||||||
|
if (last_cmp == 0) {
|
||||||
|
return entry;
|
||||||
|
} else if (last_cmp > 0) {
|
||||||
|
low = mid + 1;
|
||||||
|
} else {
|
||||||
|
high = mid - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RB_UNLIKELY(memchr(str, '\\', length))) {
|
||||||
|
// We assume the overwhelming majority of names don't need to be escaped.
|
||||||
|
// But if they do, we have to fallback to the slow path.
|
||||||
|
return Qfalse;
|
||||||
|
}
|
||||||
|
|
||||||
|
VALUE rsymbol = build_symbol(str, length);
|
||||||
|
|
||||||
|
if (cache->length < JSON_RVALUE_CACHE_CAPA) {
|
||||||
|
if (last_cmp > 0) {
|
||||||
|
mid += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
rvalue_cache_insert_at(cache, mid, rsymbol);
|
||||||
|
}
|
||||||
|
return rsymbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* rvalue stack */
|
||||||
|
|
||||||
|
#define RVALUE_STACK_INITIAL_CAPA 128
|
||||||
|
|
||||||
|
enum rvalue_stack_type {
|
||||||
|
RVALUE_STACK_HEAP_ALLOCATED = 0,
|
||||||
|
RVALUE_STACK_STACK_ALLOCATED = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct rvalue_stack_struct {
|
||||||
|
enum rvalue_stack_type type;
|
||||||
|
long capa;
|
||||||
|
long head;
|
||||||
|
VALUE *ptr;
|
||||||
|
} rvalue_stack;
|
||||||
|
|
||||||
|
static rvalue_stack *rvalue_stack_spill(rvalue_stack *old_stack, VALUE *handle, rvalue_stack **stack_ref);
|
||||||
|
|
||||||
|
static rvalue_stack *rvalue_stack_grow(rvalue_stack *stack, VALUE *handle, rvalue_stack **stack_ref)
|
||||||
|
{
|
||||||
|
long required = stack->capa * 2;
|
||||||
|
|
||||||
|
if (stack->type == RVALUE_STACK_STACK_ALLOCATED) {
|
||||||
|
stack = rvalue_stack_spill(stack, handle, stack_ref);
|
||||||
|
} else {
|
||||||
|
REALLOC_N(stack->ptr, VALUE, required);
|
||||||
|
stack->capa = required;
|
||||||
|
}
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rvalue_stack_push(rvalue_stack *stack, VALUE value, VALUE *handle, rvalue_stack **stack_ref)
|
||||||
|
{
|
||||||
|
if (RB_UNLIKELY(stack->head >= stack->capa)) {
|
||||||
|
stack = rvalue_stack_grow(stack, handle, stack_ref);
|
||||||
|
}
|
||||||
|
stack->ptr[stack->head] = value;
|
||||||
|
stack->head++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline VALUE *rvalue_stack_peek(rvalue_stack *stack, long count)
|
||||||
|
{
|
||||||
|
return stack->ptr + (stack->head - count);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void rvalue_stack_pop(rvalue_stack *stack, long count)
|
||||||
|
{
|
||||||
|
stack->head -= count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rvalue_stack_mark(void *ptr)
|
||||||
|
{
|
||||||
|
rvalue_stack *stack = (rvalue_stack *)ptr;
|
||||||
|
rb_gc_mark_locations(stack->ptr, stack->ptr + stack->head);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rvalue_stack_free(void *ptr)
|
||||||
|
{
|
||||||
|
rvalue_stack *stack = (rvalue_stack *)ptr;
|
||||||
|
if (stack) {
|
||||||
|
ruby_xfree(stack->ptr);
|
||||||
|
ruby_xfree(stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t rvalue_stack_memsize(const void *ptr)
|
||||||
|
{
|
||||||
|
const rvalue_stack *stack = (const rvalue_stack *)ptr;
|
||||||
|
return sizeof(rvalue_stack) + sizeof(VALUE) * stack->capa;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const rb_data_type_t JSON_Parser_rvalue_stack_type = {
|
||||||
|
"JSON::Ext::Parser/rvalue_stack",
|
||||||
|
{
|
||||||
|
.dmark = rvalue_stack_mark,
|
||||||
|
.dfree = rvalue_stack_free,
|
||||||
|
.dsize = rvalue_stack_memsize,
|
||||||
|
},
|
||||||
|
0, 0,
|
||||||
|
RUBY_TYPED_FREE_IMMEDIATELY,
|
||||||
|
};
|
||||||
|
|
||||||
|
static rvalue_stack *rvalue_stack_spill(rvalue_stack *old_stack, VALUE *handle, rvalue_stack **stack_ref)
|
||||||
|
{
|
||||||
|
rvalue_stack *stack;
|
||||||
|
*handle = TypedData_Make_Struct(0, rvalue_stack, &JSON_Parser_rvalue_stack_type, stack);
|
||||||
|
*stack_ref = stack;
|
||||||
|
MEMCPY(stack, old_stack, rvalue_stack, 1);
|
||||||
|
|
||||||
|
stack->capa = old_stack->capa << 1;
|
||||||
|
stack->ptr = ALLOC_N(VALUE, stack->capa);
|
||||||
|
stack->type = RVALUE_STACK_HEAP_ALLOCATED;
|
||||||
|
MEMCPY(stack->ptr, old_stack->ptr, VALUE, old_stack->head);
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rvalue_stack_eagerly_release(VALUE handle)
|
||||||
|
{
|
||||||
|
rvalue_stack *stack;
|
||||||
|
TypedData_Get_Struct(handle, rvalue_stack, &JSON_Parser_rvalue_stack_type, stack);
|
||||||
|
RTYPEDDATA_DATA(handle) = NULL;
|
||||||
|
rvalue_stack_free(stack);
|
||||||
|
}
|
||||||
|
|
||||||
/* unicode */
|
/* unicode */
|
||||||
|
|
||||||
@ -67,6 +370,50 @@ static int convert_UTF32_to_UTF8(char *buf, uint32_t ch)
|
|||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef struct JSON_ParserStruct {
|
||||||
|
VALUE Vsource;
|
||||||
|
char *source;
|
||||||
|
long len;
|
||||||
|
char *memo;
|
||||||
|
VALUE create_id;
|
||||||
|
VALUE object_class;
|
||||||
|
VALUE array_class;
|
||||||
|
VALUE decimal_class;
|
||||||
|
VALUE match_string;
|
||||||
|
FBuffer fbuffer;
|
||||||
|
int max_nesting;
|
||||||
|
bool allow_nan;
|
||||||
|
bool allow_trailing_comma;
|
||||||
|
bool parsing_name;
|
||||||
|
bool symbolize_names;
|
||||||
|
bool freeze;
|
||||||
|
bool create_additions;
|
||||||
|
bool deprecated_create_additions;
|
||||||
|
rvalue_cache name_cache;
|
||||||
|
rvalue_stack *stack;
|
||||||
|
VALUE stack_handle;
|
||||||
|
} JSON_Parser;
|
||||||
|
|
||||||
|
#define GET_PARSER \
|
||||||
|
GET_PARSER_INIT; \
|
||||||
|
if (!json->Vsource) rb_raise(rb_eTypeError, "uninitialized instance")
|
||||||
|
|
||||||
|
#define GET_PARSER_INIT \
|
||||||
|
JSON_Parser *json; \
|
||||||
|
TypedData_Get_Struct(self, JSON_Parser, &JSON_Parser_type, json)
|
||||||
|
|
||||||
|
#define MinusInfinity "-Infinity"
|
||||||
|
#define EVIL 0x666
|
||||||
|
|
||||||
|
static const rb_data_type_t JSON_Parser_type;
|
||||||
|
static char *JSON_parse_string(JSON_Parser *json, char *p, char *pe, VALUE *result);
|
||||||
|
static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting);
|
||||||
|
static char *JSON_parse_value(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting);
|
||||||
|
static char *JSON_parse_integer(JSON_Parser *json, char *p, char *pe, VALUE *result);
|
||||||
|
static char *JSON_parse_float(JSON_Parser *json, char *p, char *pe, VALUE *result);
|
||||||
|
static char *JSON_parse_array(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting);
|
||||||
|
|
||||||
|
|
||||||
#define PARSE_ERROR_FRAGMENT_LEN 32
|
#define PARSE_ERROR_FRAGMENT_LEN 32
|
||||||
#ifdef RBIMPL_ATTR_NORETURN
|
#ifdef RBIMPL_ATTR_NORETURN
|
||||||
RBIMPL_ATTR_NORETURN()
|
RBIMPL_ATTR_NORETURN()
|
||||||
@ -84,21 +431,9 @@ static void raise_parse_error(const char *format, const char *start)
|
|||||||
ptr = buffer;
|
ptr = buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
rb_enc_raise(rb_utf8_encoding(), rb_path2class("JSON::ParserError"), format, ptr);
|
rb_enc_raise(enc_utf8, rb_path2class("JSON::ParserError"), format, ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE mJSON, mExt, cParser, eNestingError, Encoding_UTF_8;
|
|
||||||
static VALUE CNaN, CInfinity, CMinusInfinity;
|
|
||||||
|
|
||||||
static ID i_json_creatable_p, i_json_create, i_create_id, i_create_additions,
|
|
||||||
i_chr, i_max_nesting, i_allow_nan, i_symbolize_names,
|
|
||||||
i_object_class, i_array_class, i_decimal_class,
|
|
||||||
i_deep_const_get, i_match, i_match_string, i_aset, i_aref,
|
|
||||||
i_leftshift, i_new, i_try_convert, i_freeze, i_uminus, i_encode;
|
|
||||||
|
|
||||||
static int binary_encindex;
|
|
||||||
static int utf8_encindex;
|
|
||||||
|
|
||||||
|
|
||||||
%%{
|
%%{
|
||||||
machine JSON_common;
|
machine JSON_common;
|
||||||
@ -135,27 +470,25 @@ static int utf8_encindex;
|
|||||||
write data;
|
write data;
|
||||||
|
|
||||||
action parse_value {
|
action parse_value {
|
||||||
VALUE v = Qnil;
|
char *np = JSON_parse_value(json, fpc, pe, result, current_nesting);
|
||||||
char *np = JSON_parse_value(json, fpc, pe, &v, current_nesting);
|
|
||||||
if (np == NULL) {
|
if (np == NULL) {
|
||||||
fhold; fbreak;
|
fhold; fbreak;
|
||||||
} else {
|
} else {
|
||||||
if (NIL_P(json->object_class)) {
|
|
||||||
OBJ_FREEZE(last_name);
|
|
||||||
rb_hash_aset(*result, last_name, v);
|
|
||||||
} else {
|
|
||||||
rb_funcall(*result, i_aset, 2, last_name, v);
|
|
||||||
}
|
|
||||||
fexec np;
|
fexec np;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
action allow_trailing_comma { json->allow_trailing_comma }
|
||||||
|
|
||||||
action parse_name {
|
action parse_name {
|
||||||
char *np;
|
char *np;
|
||||||
json->parsing_name = 1;
|
json->parsing_name = true;
|
||||||
np = JSON_parse_string(json, fpc, pe, &last_name);
|
np = JSON_parse_string(json, fpc, pe, result);
|
||||||
json->parsing_name = 0;
|
json->parsing_name = false;
|
||||||
if (np == NULL) { fhold; fbreak; } else fexec np;
|
if (np == NULL) { fhold; fbreak; } else {
|
||||||
|
PUSH(*result);
|
||||||
|
fexec np;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
action exit { fhold; fbreak; }
|
action exit { fhold; fbreak; }
|
||||||
@ -165,33 +498,57 @@ static int utf8_encindex;
|
|||||||
|
|
||||||
main := (
|
main := (
|
||||||
begin_object
|
begin_object
|
||||||
(pair (next_pair)*)? ignore*
|
(pair (next_pair)*((ignore* value_separator) when allow_trailing_comma)?)? ignore*
|
||||||
end_object
|
end_object
|
||||||
) @exit;
|
) @exit;
|
||||||
}%%
|
}%%
|
||||||
|
|
||||||
|
#define PUSH(result) rvalue_stack_push(json->stack, result, &json->stack_handle, &json->stack)
|
||||||
|
|
||||||
static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting)
|
static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting)
|
||||||
{
|
{
|
||||||
int cs = EVIL;
|
int cs = EVIL;
|
||||||
VALUE last_name = Qnil;
|
|
||||||
VALUE object_class = json->object_class;
|
|
||||||
|
|
||||||
if (json->max_nesting && current_nesting > json->max_nesting) {
|
if (json->max_nesting && current_nesting > json->max_nesting) {
|
||||||
rb_raise(eNestingError, "nesting of %d is too deep", current_nesting);
|
rb_raise(eNestingError, "nesting of %d is too deep", current_nesting);
|
||||||
}
|
}
|
||||||
|
|
||||||
*result = NIL_P(object_class) ? rb_hash_new() : rb_class_new_instance(0, 0, object_class);
|
long stack_head = json->stack->head;
|
||||||
|
|
||||||
%% write init;
|
%% write init;
|
||||||
%% write exec;
|
%% write exec;
|
||||||
|
|
||||||
if (cs >= JSON_object_first_final) {
|
if (cs >= JSON_object_first_final) {
|
||||||
if (json->create_additions) {
|
long count = json->stack->head - stack_head;
|
||||||
|
|
||||||
|
if (RB_UNLIKELY(json->object_class)) {
|
||||||
|
VALUE object = rb_class_new_instance(0, 0, json->object_class);
|
||||||
|
long index = 0;
|
||||||
|
VALUE *items = rvalue_stack_peek(json->stack, count);
|
||||||
|
while (index < count) {
|
||||||
|
VALUE name = items[index++];
|
||||||
|
VALUE value = items[index++];
|
||||||
|
rb_funcall(object, i_aset, 2, name, value);
|
||||||
|
}
|
||||||
|
*result = object;
|
||||||
|
} else {
|
||||||
|
VALUE hash;
|
||||||
|
#ifdef HAVE_RB_HASH_NEW_CAPA
|
||||||
|
hash = rb_hash_new_capa(count >> 1);
|
||||||
|
#else
|
||||||
|
hash = rb_hash_new();
|
||||||
|
#endif
|
||||||
|
rb_hash_bulk_insert(count, rvalue_stack_peek(json->stack, count), hash);
|
||||||
|
*result = hash;
|
||||||
|
}
|
||||||
|
rvalue_stack_pop(json->stack, count);
|
||||||
|
|
||||||
|
if (RB_UNLIKELY(json->create_additions)) {
|
||||||
VALUE klassname;
|
VALUE klassname;
|
||||||
if (NIL_P(json->object_class)) {
|
if (json->object_class) {
|
||||||
klassname = rb_hash_aref(*result, json->create_id);
|
klassname = rb_funcall(*result, i_aref, 1, json->create_id);
|
||||||
} else {
|
} else {
|
||||||
klassname = rb_funcall(*result, i_aref, 1, json->create_id);
|
klassname = rb_hash_aref(*result, json->create_id);
|
||||||
}
|
}
|
||||||
if (!NIL_P(klassname)) {
|
if (!NIL_P(klassname)) {
|
||||||
VALUE klass = rb_funcall(mJSON, i_deep_const_get, 1, klassname);
|
VALUE klass = rb_funcall(mJSON, i_deep_const_get, 1, klassname);
|
||||||
@ -209,7 +566,6 @@ static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *resu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
%%{
|
%%{
|
||||||
machine JSON_value;
|
machine JSON_value;
|
||||||
include JSON_common;
|
include JSON_common;
|
||||||
@ -241,7 +597,12 @@ static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *resu
|
|||||||
}
|
}
|
||||||
action parse_string {
|
action parse_string {
|
||||||
char *np = JSON_parse_string(json, fpc, pe, result);
|
char *np = JSON_parse_string(json, fpc, pe, result);
|
||||||
if (np == NULL) { fhold; fbreak; } else fexec np;
|
if (np == NULL) {
|
||||||
|
fhold;
|
||||||
|
fbreak;
|
||||||
|
} else {
|
||||||
|
fexec np;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
action parse_number {
|
action parse_number {
|
||||||
@ -256,9 +617,13 @@ static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *resu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
np = JSON_parse_float(json, fpc, pe, result);
|
np = JSON_parse_float(json, fpc, pe, result);
|
||||||
if (np != NULL) fexec np;
|
if (np != NULL) {
|
||||||
|
fexec np;
|
||||||
|
}
|
||||||
np = JSON_parse_integer(json, fpc, pe, result);
|
np = JSON_parse_integer(json, fpc, pe, result);
|
||||||
if (np != NULL) fexec np;
|
if (np != NULL) {
|
||||||
|
fexec np;
|
||||||
|
}
|
||||||
fhold; fbreak;
|
fhold; fbreak;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,6 +666,7 @@ static char *JSON_parse_value(JSON_Parser *json, char *p, char *pe, VALUE *resul
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cs >= JSON_value_first_final) {
|
if (cs >= JSON_value_first_final) {
|
||||||
|
PUSH(*result);
|
||||||
return p;
|
return p;
|
||||||
} else {
|
} else {
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -362,7 +728,7 @@ static char *JSON_parse_float(JSON_Parser *json, char *p, char *pe, VALUE *resul
|
|||||||
if (cs >= JSON_float_first_final) {
|
if (cs >= JSON_float_first_final) {
|
||||||
VALUE mod = Qnil;
|
VALUE mod = Qnil;
|
||||||
ID method_id = 0;
|
ID method_id = 0;
|
||||||
if (!NIL_P(json->decimal_class)) {
|
if (json->decimal_class) {
|
||||||
if (rb_respond_to(json->decimal_class, i_try_convert)) {
|
if (rb_respond_to(json->decimal_class, i_try_convert)) {
|
||||||
mod = json->decimal_class;
|
mod = json->decimal_class;
|
||||||
method_id = i_try_convert;
|
method_id = i_try_convert;
|
||||||
@ -421,39 +787,51 @@ static char *JSON_parse_float(JSON_Parser *json, char *p, char *pe, VALUE *resul
|
|||||||
if (np == NULL) {
|
if (np == NULL) {
|
||||||
fhold; fbreak;
|
fhold; fbreak;
|
||||||
} else {
|
} else {
|
||||||
if (NIL_P(json->array_class)) {
|
|
||||||
rb_ary_push(*result, v);
|
|
||||||
} else {
|
|
||||||
rb_funcall(*result, i_leftshift, 1, v);
|
|
||||||
}
|
|
||||||
fexec np;
|
fexec np;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
action allow_trailing_comma { json->allow_trailing_comma }
|
||||||
|
|
||||||
action exit { fhold; fbreak; }
|
action exit { fhold; fbreak; }
|
||||||
|
|
||||||
next_element = value_separator ignore* begin_value >parse_value;
|
next_element = value_separator ignore* begin_value >parse_value;
|
||||||
|
|
||||||
main := begin_array ignore*
|
main := begin_array ignore*
|
||||||
((begin_value >parse_value ignore*)
|
((begin_value >parse_value ignore*)
|
||||||
(ignore* next_element ignore*)*)?
|
(ignore* next_element ignore*)*((value_separator ignore*) when allow_trailing_comma)?)?
|
||||||
end_array @exit;
|
end_array @exit;
|
||||||
}%%
|
}%%
|
||||||
|
|
||||||
static char *JSON_parse_array(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting)
|
static char *JSON_parse_array(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting)
|
||||||
{
|
{
|
||||||
int cs = EVIL;
|
int cs = EVIL;
|
||||||
VALUE array_class = json->array_class;
|
|
||||||
|
|
||||||
if (json->max_nesting && current_nesting > json->max_nesting) {
|
if (json->max_nesting && current_nesting > json->max_nesting) {
|
||||||
rb_raise(eNestingError, "nesting of %d is too deep", current_nesting);
|
rb_raise(eNestingError, "nesting of %d is too deep", current_nesting);
|
||||||
}
|
}
|
||||||
*result = NIL_P(array_class) ? rb_ary_new() : rb_class_new_instance(0, 0, array_class);
|
long stack_head = json->stack->head;
|
||||||
|
|
||||||
%% write init;
|
%% write init;
|
||||||
%% write exec;
|
%% write exec;
|
||||||
|
|
||||||
if(cs >= JSON_array_first_final) {
|
if(cs >= JSON_array_first_final) {
|
||||||
|
long count = json->stack->head - stack_head;
|
||||||
|
|
||||||
|
if (RB_UNLIKELY(json->array_class)) {
|
||||||
|
VALUE array = rb_class_new_instance(0, 0, json->array_class);
|
||||||
|
VALUE *items = rvalue_stack_peek(json->stack, count);
|
||||||
|
long index;
|
||||||
|
for (index = 0; index < count; index++) {
|
||||||
|
rb_funcall(array, i_leftshift, 1, items[index]);
|
||||||
|
}
|
||||||
|
*result = array;
|
||||||
|
} else {
|
||||||
|
VALUE array = rb_ary_new_from_values(count, rvalue_stack_peek(json->stack, count));
|
||||||
|
*result = array;
|
||||||
|
}
|
||||||
|
rvalue_stack_pop(json->stack, count);
|
||||||
|
|
||||||
return p + 1;
|
return p + 1;
|
||||||
} else {
|
} else {
|
||||||
raise_parse_error("unexpected token at '%s'", p);
|
raise_parse_error("unexpected token at '%s'", p);
|
||||||
@ -469,7 +847,7 @@ static inline VALUE build_string(const char *start, const char *end, bool intern
|
|||||||
VALUE result;
|
VALUE result;
|
||||||
# ifdef HAVE_RB_ENC_INTERNED_STR
|
# ifdef HAVE_RB_ENC_INTERNED_STR
|
||||||
if (intern) {
|
if (intern) {
|
||||||
result = rb_enc_interned_str(start, (long)(end - start), rb_utf8_encoding());
|
result = rb_enc_interned_str(start, (long)(end - start), enc_utf8);
|
||||||
} else {
|
} else {
|
||||||
result = rb_utf8_str_new(start, (long)(end - start));
|
result = rb_utf8_str_new(start, (long)(end - start));
|
||||||
}
|
}
|
||||||
@ -487,13 +865,26 @@ static inline VALUE build_string(const char *start, const char *end, bool intern
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE json_string_unescape(char *string, char *stringEnd, bool intern, bool symbolize)
|
static VALUE json_string_unescape(JSON_Parser *json, char *string, char *stringEnd, bool is_name, bool intern, bool symbolize)
|
||||||
{
|
{
|
||||||
size_t bufferSize = stringEnd - string;
|
size_t bufferSize = stringEnd - string;
|
||||||
char *p = string, *pe = string, *unescape, *bufferStart, *buffer;
|
char *p = string, *pe = string, *unescape, *bufferStart, *buffer;
|
||||||
int unescape_len;
|
int unescape_len;
|
||||||
char buf[4];
|
char buf[4];
|
||||||
|
|
||||||
|
if (is_name) {
|
||||||
|
VALUE cached_key;
|
||||||
|
if (RB_UNLIKELY(symbolize)) {
|
||||||
|
cached_key = rsymbol_cache_fetch(&json->name_cache, string, bufferSize);
|
||||||
|
} else {
|
||||||
|
cached_key = rstring_cache_fetch(&json->name_cache, string, bufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RB_LIKELY(cached_key)) {
|
||||||
|
return cached_key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pe = memchr(p, '\\', bufferSize);
|
pe = memchr(p, '\\', bufferSize);
|
||||||
if (RB_LIKELY(pe == NULL)) {
|
if (RB_LIKELY(pe == NULL)) {
|
||||||
return build_string(string, stringEnd, intern, symbolize);
|
return build_string(string, stringEnd, intern, symbolize);
|
||||||
@ -602,7 +993,7 @@ static VALUE json_string_unescape(char *string, char *stringEnd, bool intern, bo
|
|||||||
write data;
|
write data;
|
||||||
|
|
||||||
action parse_string {
|
action parse_string {
|
||||||
*result = json_string_unescape(json->memo + 1, p, json->parsing_name || json-> freeze, json->parsing_name && json->symbolize_names);
|
*result = json_string_unescape(json, json->memo + 1, p, json->parsing_name, json->parsing_name || json-> freeze, json->parsing_name && json->symbolize_names);
|
||||||
if (NIL_P(*result)) {
|
if (NIL_P(*result)) {
|
||||||
fhold;
|
fhold;
|
||||||
fbreak;
|
fbreak;
|
||||||
@ -671,7 +1062,7 @@ static VALUE convert_encoding(VALUE source)
|
|||||||
{
|
{
|
||||||
int encindex = RB_ENCODING_GET(source);
|
int encindex = RB_ENCODING_GET(source);
|
||||||
|
|
||||||
if (encindex == utf8_encindex) {
|
if (RB_LIKELY(encindex == utf8_encindex)) {
|
||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -683,6 +1074,68 @@ static VALUE convert_encoding(VALUE source)
|
|||||||
return rb_funcall(source, i_encode, 1, Encoding_UTF_8);
|
return rb_funcall(source, i_encode, 1, Encoding_UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int configure_parser_i(VALUE key, VALUE val, VALUE data)
|
||||||
|
{
|
||||||
|
JSON_Parser *json = (JSON_Parser *)data;
|
||||||
|
|
||||||
|
if (key == sym_max_nesting) { json->max_nesting = RTEST(val) ? FIX2INT(val) : 0; }
|
||||||
|
else if (key == sym_allow_nan) { json->allow_nan = RTEST(val); }
|
||||||
|
else if (key == sym_allow_trailing_comma) { json->allow_trailing_comma = RTEST(val); }
|
||||||
|
else if (key == sym_symbolize_names) { json->symbolize_names = RTEST(val); }
|
||||||
|
else if (key == sym_freeze) { json->freeze = RTEST(val); }
|
||||||
|
else if (key == sym_create_id) { json->create_id = RTEST(val) ? val : Qfalse; }
|
||||||
|
else if (key == sym_object_class) { json->object_class = RTEST(val) ? val : Qfalse; }
|
||||||
|
else if (key == sym_array_class) { json->array_class = RTEST(val) ? val : Qfalse; }
|
||||||
|
else if (key == sym_decimal_class) { json->decimal_class = RTEST(val) ? val : Qfalse; }
|
||||||
|
else if (key == sym_match_string) { json->match_string = RTEST(val) ? val : Qfalse; }
|
||||||
|
else if (key == sym_create_additions) {
|
||||||
|
if (NIL_P(val)) {
|
||||||
|
json->create_additions = true;
|
||||||
|
json->deprecated_create_additions = true;
|
||||||
|
} else {
|
||||||
|
json->create_additions = RTEST(val);
|
||||||
|
json->deprecated_create_additions = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ST_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parser_init(JSON_Parser *json, VALUE source, VALUE opts)
|
||||||
|
{
|
||||||
|
if (json->Vsource) {
|
||||||
|
rb_raise(rb_eTypeError, "already initialized instance");
|
||||||
|
}
|
||||||
|
|
||||||
|
json->fbuffer.initial_length = FBUFFER_INITIAL_LENGTH_DEFAULT;
|
||||||
|
json->max_nesting = 100;
|
||||||
|
|
||||||
|
if (!NIL_P(opts)) {
|
||||||
|
Check_Type(opts, T_HASH);
|
||||||
|
if (RHASH_SIZE(opts) > 0) {
|
||||||
|
// We assume in most cases few keys are set so it's faster to go over
|
||||||
|
// the provided keys than to check all possible keys.
|
||||||
|
rb_hash_foreach(opts, configure_parser_i, (VALUE)json);
|
||||||
|
|
||||||
|
if (json->symbolize_names && json->create_additions) {
|
||||||
|
rb_raise(rb_eArgError,
|
||||||
|
"options :symbolize_names and :create_additions cannot be "
|
||||||
|
" used in conjunction");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json->create_additions && !json->create_id) {
|
||||||
|
json->create_id = rb_funcall(mJSON, i_create_id, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
source = convert_encoding(StringValue(source));
|
||||||
|
StringValue(source);
|
||||||
|
json->len = RSTRING_LEN(source);
|
||||||
|
json->source = RSTRING_PTR(source);
|
||||||
|
json->Vsource = source;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* call-seq: new(source, opts => {})
|
* call-seq: new(source, opts => {})
|
||||||
*
|
*
|
||||||
@ -717,117 +1170,11 @@ static VALUE convert_encoding(VALUE source)
|
|||||||
*/
|
*/
|
||||||
static VALUE cParser_initialize(int argc, VALUE *argv, VALUE self)
|
static VALUE cParser_initialize(int argc, VALUE *argv, VALUE self)
|
||||||
{
|
{
|
||||||
VALUE source, opts;
|
|
||||||
GET_PARSER_INIT;
|
GET_PARSER_INIT;
|
||||||
|
|
||||||
if (json->Vsource) {
|
|
||||||
rb_raise(rb_eTypeError, "already initialized instance");
|
|
||||||
}
|
|
||||||
|
|
||||||
rb_check_arity(argc, 1, 2);
|
rb_check_arity(argc, 1, 2);
|
||||||
source = argv[0];
|
|
||||||
opts = Qnil;
|
|
||||||
if (argc == 2) {
|
|
||||||
opts = argv[1];
|
|
||||||
Check_Type(argv[1], T_HASH);
|
|
||||||
if (RHASH_SIZE(argv[1]) > 0) {
|
|
||||||
opts = argv[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!NIL_P(opts)) {
|
parser_init(json, argv[0], argc == 2 ? argv[1] : Qnil);
|
||||||
VALUE tmp = ID2SYM(i_max_nesting);
|
|
||||||
if (option_given_p(opts, tmp)) {
|
|
||||||
VALUE max_nesting = rb_hash_aref(opts, tmp);
|
|
||||||
if (RTEST(max_nesting)) {
|
|
||||||
Check_Type(max_nesting, T_FIXNUM);
|
|
||||||
json->max_nesting = FIX2INT(max_nesting);
|
|
||||||
} else {
|
|
||||||
json->max_nesting = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
json->max_nesting = 100;
|
|
||||||
}
|
|
||||||
tmp = ID2SYM(i_allow_nan);
|
|
||||||
if (option_given_p(opts, tmp)) {
|
|
||||||
json->allow_nan = RTEST(rb_hash_aref(opts, tmp)) ? 1 : 0;
|
|
||||||
} else {
|
|
||||||
json->allow_nan = 0;
|
|
||||||
}
|
|
||||||
tmp = ID2SYM(i_symbolize_names);
|
|
||||||
if (option_given_p(opts, tmp)) {
|
|
||||||
json->symbolize_names = RTEST(rb_hash_aref(opts, tmp)) ? 1 : 0;
|
|
||||||
} else {
|
|
||||||
json->symbolize_names = 0;
|
|
||||||
}
|
|
||||||
tmp = ID2SYM(i_freeze);
|
|
||||||
if (option_given_p(opts, tmp)) {
|
|
||||||
json->freeze = RTEST(rb_hash_aref(opts, tmp)) ? 1 : 0;
|
|
||||||
} else {
|
|
||||||
json->freeze = 0;
|
|
||||||
}
|
|
||||||
tmp = ID2SYM(i_create_additions);
|
|
||||||
if (option_given_p(opts, tmp)) {
|
|
||||||
tmp = rb_hash_aref(opts, tmp);
|
|
||||||
if (NIL_P(tmp)) {
|
|
||||||
json->create_additions = 1;
|
|
||||||
json->deprecated_create_additions = 1;
|
|
||||||
} else {
|
|
||||||
json->create_additions = RTEST(tmp);
|
|
||||||
json->deprecated_create_additions = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (json->symbolize_names && json->create_additions) {
|
|
||||||
rb_raise(rb_eArgError,
|
|
||||||
"options :symbolize_names and :create_additions cannot be "
|
|
||||||
" used in conjunction");
|
|
||||||
}
|
|
||||||
tmp = ID2SYM(i_create_id);
|
|
||||||
if (option_given_p(opts, tmp)) {
|
|
||||||
json->create_id = rb_hash_aref(opts, tmp);
|
|
||||||
} else {
|
|
||||||
json->create_id = rb_funcall(mJSON, i_create_id, 0);
|
|
||||||
}
|
|
||||||
tmp = ID2SYM(i_object_class);
|
|
||||||
if (option_given_p(opts, tmp)) {
|
|
||||||
json->object_class = rb_hash_aref(opts, tmp);
|
|
||||||
} else {
|
|
||||||
json->object_class = Qnil;
|
|
||||||
}
|
|
||||||
tmp = ID2SYM(i_array_class);
|
|
||||||
if (option_given_p(opts, tmp)) {
|
|
||||||
json->array_class = rb_hash_aref(opts, tmp);
|
|
||||||
} else {
|
|
||||||
json->array_class = Qnil;
|
|
||||||
}
|
|
||||||
tmp = ID2SYM(i_decimal_class);
|
|
||||||
if (option_given_p(opts, tmp)) {
|
|
||||||
json->decimal_class = rb_hash_aref(opts, tmp);
|
|
||||||
} else {
|
|
||||||
json->decimal_class = Qnil;
|
|
||||||
}
|
|
||||||
tmp = ID2SYM(i_match_string);
|
|
||||||
if (option_given_p(opts, tmp)) {
|
|
||||||
VALUE match_string = rb_hash_aref(opts, tmp);
|
|
||||||
json->match_string = RTEST(match_string) ? match_string : Qnil;
|
|
||||||
} else {
|
|
||||||
json->match_string = Qnil;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
json->max_nesting = 100;
|
|
||||||
json->allow_nan = 0;
|
|
||||||
json->create_additions = 0;
|
|
||||||
json->create_id = Qnil;
|
|
||||||
json->object_class = Qnil;
|
|
||||||
json->array_class = Qnil;
|
|
||||||
json->decimal_class = Qnil;
|
|
||||||
}
|
|
||||||
source = convert_encoding(StringValue(source));
|
|
||||||
StringValue(source);
|
|
||||||
json->len = RSTRING_LEN(source);
|
|
||||||
json->source = RSTRING_PTR(source);
|
|
||||||
json->Vsource = source;
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -862,11 +1209,26 @@ static VALUE cParser_parse(VALUE self)
|
|||||||
VALUE result = Qnil;
|
VALUE result = Qnil;
|
||||||
GET_PARSER;
|
GET_PARSER;
|
||||||
|
|
||||||
|
char stack_buffer[FBUFFER_STACK_SIZE];
|
||||||
|
fbuffer_stack_init(&json->fbuffer, FBUFFER_INITIAL_LENGTH_DEFAULT, stack_buffer, FBUFFER_STACK_SIZE);
|
||||||
|
|
||||||
|
VALUE rvalue_stack_buffer[RVALUE_STACK_INITIAL_CAPA];
|
||||||
|
rvalue_stack stack = {
|
||||||
|
.type = RVALUE_STACK_STACK_ALLOCATED,
|
||||||
|
.ptr = rvalue_stack_buffer,
|
||||||
|
.capa = RVALUE_STACK_INITIAL_CAPA,
|
||||||
|
};
|
||||||
|
json->stack = &stack;
|
||||||
|
|
||||||
%% write init;
|
%% write init;
|
||||||
p = json->source;
|
p = json->source;
|
||||||
pe = p + json->len;
|
pe = p + json->len;
|
||||||
%% write exec;
|
%% write exec;
|
||||||
|
|
||||||
|
if (json->stack_handle) {
|
||||||
|
rvalue_stack_eagerly_release(json->stack_handle);
|
||||||
|
}
|
||||||
|
|
||||||
if (cs >= JSON_first_final && p == pe) {
|
if (cs >= JSON_first_final && p == pe) {
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
@ -875,18 +1237,43 @@ static VALUE cParser_parse(VALUE self)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef HAVE_RB_GC_MARK_LOCATIONS
|
static VALUE cParser_m_parse(VALUE klass, VALUE source, VALUE opts)
|
||||||
// For TruffleRuby
|
|
||||||
void rb_gc_mark_locations(const VALUE *start, const VALUE *end)
|
|
||||||
{
|
{
|
||||||
VALUE *value = start;
|
char *p, *pe;
|
||||||
|
int cs = EVIL;
|
||||||
|
VALUE result = Qnil;
|
||||||
|
|
||||||
while (value < end) {
|
JSON_Parser _parser = {0};
|
||||||
rb_gc_mark(*value);
|
JSON_Parser *json = &_parser;
|
||||||
value++;
|
parser_init(json, source, opts);
|
||||||
|
|
||||||
|
char stack_buffer[FBUFFER_STACK_SIZE];
|
||||||
|
fbuffer_stack_init(&json->fbuffer, FBUFFER_INITIAL_LENGTH_DEFAULT, stack_buffer, FBUFFER_STACK_SIZE);
|
||||||
|
|
||||||
|
VALUE rvalue_stack_buffer[RVALUE_STACK_INITIAL_CAPA];
|
||||||
|
rvalue_stack stack = {
|
||||||
|
.type = RVALUE_STACK_STACK_ALLOCATED,
|
||||||
|
.ptr = rvalue_stack_buffer,
|
||||||
|
.capa = RVALUE_STACK_INITIAL_CAPA,
|
||||||
|
};
|
||||||
|
json->stack = &stack;
|
||||||
|
|
||||||
|
%% write init;
|
||||||
|
p = json->source;
|
||||||
|
pe = p + json->len;
|
||||||
|
%% write exec;
|
||||||
|
|
||||||
|
if (json->stack_handle) {
|
||||||
|
rvalue_stack_eagerly_release(json->stack_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cs >= JSON_first_final && p == pe) {
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
raise_parse_error("unexpected token at '%s'", p);
|
||||||
|
return Qnil;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
static void JSON_mark(void *ptr)
|
static void JSON_mark(void *ptr)
|
||||||
{
|
{
|
||||||
@ -897,6 +1284,8 @@ static void JSON_mark(void *ptr)
|
|||||||
rb_gc_mark(json->array_class);
|
rb_gc_mark(json->array_class);
|
||||||
rb_gc_mark(json->decimal_class);
|
rb_gc_mark(json->decimal_class);
|
||||||
rb_gc_mark(json->match_string);
|
rb_gc_mark(json->match_string);
|
||||||
|
rb_gc_mark(json->stack_handle);
|
||||||
|
|
||||||
const VALUE *name_cache_entries = &json->name_cache.entries[0];
|
const VALUE *name_cache_entries = &json->name_cache.entries[0];
|
||||||
rb_gc_mark_locations(name_cache_entries, name_cache_entries + json->name_cache.length);
|
rb_gc_mark_locations(name_cache_entries, name_cache_entries + json->name_cache.length);
|
||||||
}
|
}
|
||||||
@ -959,6 +1348,8 @@ void Init_parser(void)
|
|||||||
rb_define_method(cParser, "parse", cParser_parse, 0);
|
rb_define_method(cParser, "parse", cParser_parse, 0);
|
||||||
rb_define_method(cParser, "source", cParser_source, 0);
|
rb_define_method(cParser, "source", cParser_source, 0);
|
||||||
|
|
||||||
|
rb_define_singleton_method(cParser, "parse", cParser_m_parse, 2);
|
||||||
|
|
||||||
CNaN = rb_const_get(mJSON, rb_intern("NaN"));
|
CNaN = rb_const_get(mJSON, rb_intern("NaN"));
|
||||||
rb_gc_register_mark_object(CNaN);
|
rb_gc_register_mark_object(CNaN);
|
||||||
|
|
||||||
@ -971,31 +1362,35 @@ void Init_parser(void)
|
|||||||
rb_global_variable(&Encoding_UTF_8);
|
rb_global_variable(&Encoding_UTF_8);
|
||||||
Encoding_UTF_8 = rb_const_get(rb_path2class("Encoding"), rb_intern("UTF_8"));
|
Encoding_UTF_8 = rb_const_get(rb_path2class("Encoding"), rb_intern("UTF_8"));
|
||||||
|
|
||||||
|
sym_max_nesting = ID2SYM(rb_intern("max_nesting"));
|
||||||
|
sym_allow_nan = ID2SYM(rb_intern("allow_nan"));
|
||||||
|
sym_allow_trailing_comma = ID2SYM(rb_intern("allow_trailing_comma"));
|
||||||
|
sym_symbolize_names = ID2SYM(rb_intern("symbolize_names"));
|
||||||
|
sym_freeze = ID2SYM(rb_intern("freeze"));
|
||||||
|
sym_create_additions = ID2SYM(rb_intern("create_additions"));
|
||||||
|
sym_create_id = ID2SYM(rb_intern("create_id"));
|
||||||
|
sym_object_class = ID2SYM(rb_intern("object_class"));
|
||||||
|
sym_array_class = ID2SYM(rb_intern("array_class"));
|
||||||
|
sym_decimal_class = ID2SYM(rb_intern("decimal_class"));
|
||||||
|
sym_match_string = ID2SYM(rb_intern("match_string"));
|
||||||
|
|
||||||
|
i_create_id = rb_intern("create_id");
|
||||||
i_json_creatable_p = rb_intern("json_creatable?");
|
i_json_creatable_p = rb_intern("json_creatable?");
|
||||||
i_json_create = rb_intern("json_create");
|
i_json_create = rb_intern("json_create");
|
||||||
i_create_id = rb_intern("create_id");
|
|
||||||
i_create_additions = rb_intern("create_additions");
|
|
||||||
i_chr = rb_intern("chr");
|
i_chr = rb_intern("chr");
|
||||||
i_max_nesting = rb_intern("max_nesting");
|
|
||||||
i_allow_nan = rb_intern("allow_nan");
|
|
||||||
i_symbolize_names = rb_intern("symbolize_names");
|
|
||||||
i_object_class = rb_intern("object_class");
|
|
||||||
i_array_class = rb_intern("array_class");
|
|
||||||
i_decimal_class = rb_intern("decimal_class");
|
|
||||||
i_match = rb_intern("match");
|
i_match = rb_intern("match");
|
||||||
i_match_string = rb_intern("match_string");
|
|
||||||
i_deep_const_get = rb_intern("deep_const_get");
|
i_deep_const_get = rb_intern("deep_const_get");
|
||||||
i_aset = rb_intern("[]=");
|
i_aset = rb_intern("[]=");
|
||||||
i_aref = rb_intern("[]");
|
i_aref = rb_intern("[]");
|
||||||
i_leftshift = rb_intern("<<");
|
i_leftshift = rb_intern("<<");
|
||||||
i_new = rb_intern("new");
|
i_new = rb_intern("new");
|
||||||
i_try_convert = rb_intern("try_convert");
|
i_try_convert = rb_intern("try_convert");
|
||||||
i_freeze = rb_intern("freeze");
|
|
||||||
i_uminus = rb_intern("-@");
|
i_uminus = rb_intern("-@");
|
||||||
i_encode = rb_intern("encode");
|
i_encode = rb_intern("encode");
|
||||||
|
|
||||||
binary_encindex = rb_ascii8bit_encindex();
|
binary_encindex = rb_ascii8bit_encindex();
|
||||||
utf8_encindex = rb_utf8_encindex();
|
utf8_encindex = rb_utf8_encindex();
|
||||||
|
enc_utf8 = rb_utf8_encoding();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1 +0,0 @@
|
|||||||
["extra comma",]
|
|
@ -1 +0,0 @@
|
|||||||
{"Extra comma": true,}
|
|
@ -52,11 +52,11 @@ class JSONCommonInterfaceTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_generator
|
def test_generator
|
||||||
assert_match(/::Generator\z/, JSON.generator.name)
|
assert_match(/::(TruffleRuby)?Generator\z/, JSON.generator.name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_state
|
def test_state
|
||||||
assert_match(/::Generator::State\z/, JSON.state.name)
|
assert_match(/::(TruffleRuby)?Generator::State\z/, JSON.state.name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_create_id
|
def test_create_id
|
||||||
|
@ -2,53 +2,51 @@
|
|||||||
require_relative 'test_helper'
|
require_relative 'test_helper'
|
||||||
|
|
||||||
class JSONExtParserTest < Test::Unit::TestCase
|
class JSONExtParserTest < Test::Unit::TestCase
|
||||||
if defined?(JSON::Ext::Parser)
|
include JSON
|
||||||
include JSON
|
|
||||||
|
|
||||||
def test_allocate
|
def test_allocate
|
||||||
parser = JSON::Ext::Parser.new("{}")
|
parser = JSON::Ext::Parser.new("{}")
|
||||||
assert_raise(TypeError, '[ruby-core:35079]') do
|
assert_raise(TypeError, '[ruby-core:35079]') do
|
||||||
parser.__send__(:initialize, "{}")
|
parser.__send__(:initialize, "{}")
|
||||||
end
|
end
|
||||||
parser = JSON::Ext::Parser.allocate
|
parser = JSON::Ext::Parser.allocate
|
||||||
assert_raise(TypeError, '[ruby-core:35079]') { parser.source }
|
assert_raise(TypeError, '[ruby-core:35079]') { parser.source }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_error_messages
|
||||||
|
ex = assert_raise(ParserError) { parse('Infinity') }
|
||||||
|
assert_equal "unexpected token at 'Infinity'", ex.message
|
||||||
|
|
||||||
|
unless RUBY_PLATFORM =~ /java/
|
||||||
|
ex = assert_raise(ParserError) { parse('-Infinity') }
|
||||||
|
assert_equal "unexpected token at '-Infinity'", ex.message
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_error_messages
|
ex = assert_raise(ParserError) { parse('NaN') }
|
||||||
ex = assert_raise(ParserError) { parse('Infinity') }
|
assert_equal "unexpected token at 'NaN'", ex.message
|
||||||
assert_equal "unexpected token at 'Infinity'", ex.message
|
end
|
||||||
|
|
||||||
unless RUBY_PLATFORM =~ /java/
|
if GC.respond_to?(:stress=)
|
||||||
ex = assert_raise(ParserError) { parse('-Infinity') }
|
def test_gc_stress_parser_new
|
||||||
assert_equal "unexpected token at '-Infinity'", ex.message
|
payload = JSON.dump([{ foo: 1, bar: 2, baz: 3, egg: { spam: 4 } }] * 10)
|
||||||
end
|
|
||||||
|
|
||||||
ex = assert_raise(ParserError) { parse('NaN') }
|
previous_stress = GC.stress
|
||||||
assert_equal "unexpected token at 'NaN'", ex.message
|
JSON::Parser.new(payload).parse
|
||||||
|
ensure
|
||||||
|
GC.stress = previous_stress
|
||||||
end
|
end
|
||||||
|
|
||||||
if GC.respond_to?(:stress=)
|
def test_gc_stress
|
||||||
def test_gc_stress_parser_new
|
payload = JSON.dump([{ foo: 1, bar: 2, baz: 3, egg: { spam: 4 } }] * 10)
|
||||||
payload = JSON.dump([{ foo: 1, bar: 2, baz: 3, egg: { spam: 4 } }] * 10)
|
|
||||||
|
|
||||||
previous_stress = GC.stress
|
previous_stress = GC.stress
|
||||||
JSON::Parser.new(payload).parse
|
JSON.parse(payload)
|
||||||
ensure
|
ensure
|
||||||
GC.stress = previous_stress
|
GC.stress = previous_stress
|
||||||
end
|
|
||||||
|
|
||||||
def test_gc_stress
|
|
||||||
payload = JSON.dump([{ foo: 1, bar: 2, baz: 3, egg: { spam: 4 } }] * 10)
|
|
||||||
|
|
||||||
previous_stress = GC.stress
|
|
||||||
JSON.parse(payload)
|
|
||||||
ensure
|
|
||||||
GC.stress = previous_stress
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse(json)
|
|
||||||
JSON::Ext::Parser.new(json).parse
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def parse(json)
|
||||||
|
JSON::Ext::Parser.new(json).parse
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -343,27 +343,25 @@ class JSONGeneratorTest < Test::Unit::TestCase
|
|||||||
assert_equal '2', state.indent
|
assert_equal '2', state.indent
|
||||||
end
|
end
|
||||||
|
|
||||||
if defined?(JSON::Ext::Generator)
|
def test_broken_bignum # [ruby-core:38867]
|
||||||
def test_broken_bignum # [ruby-core:38867]
|
pid = fork do
|
||||||
pid = fork do
|
x = 1 << 64
|
||||||
x = 1 << 64
|
x.class.class_eval do
|
||||||
x.class.class_eval do
|
def to_s
|
||||||
def to_s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
begin
|
|
||||||
JSON::Ext::Generator::State.new.generate(x)
|
|
||||||
exit 1
|
|
||||||
rescue TypeError
|
|
||||||
exit 0
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
_, status = Process.waitpid2(pid)
|
begin
|
||||||
assert status.success?
|
JSON::Ext::Generator::State.new.generate(x)
|
||||||
rescue NotImplementedError
|
exit 1
|
||||||
# forking to avoid modifying core class of a parent process and
|
rescue TypeError
|
||||||
# introducing race conditions of tests are run in parallel
|
exit 0
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
_, status = Process.waitpid2(pid)
|
||||||
|
assert status.success?
|
||||||
|
rescue NotImplementedError
|
||||||
|
# forking to avoid modifying core class of a parent process and
|
||||||
|
# introducing race conditions of tests are run in parallel
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_hash_likeness_set_symbol
|
def test_hash_likeness_set_symbol
|
||||||
@ -477,12 +475,20 @@ class JSONGeneratorTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
assert_includes error.message, "source sequence is illegal/malformed utf-8"
|
assert_includes error.message, "source sequence is illegal/malformed utf-8"
|
||||||
|
|
||||||
assert_raise(Encoding::UndefinedConversionError) do
|
assert_raise(JSON::GeneratorError) do
|
||||||
|
JSON.dump("\x82\xAC\xEF".b)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_raise(JSON::GeneratorError) do
|
||||||
"\x82\xAC\xEF".b.to_json
|
"\x82\xAC\xEF".b.to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_raise(Encoding::UndefinedConversionError) do
|
assert_raise(JSON::GeneratorError) do
|
||||||
JSON.dump("\x82\xAC\xEF".b)
|
["\x82\xAC\xEF".b].to_json
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_raise(JSON::GeneratorError) do
|
||||||
|
{ foo: "\x82\xAC\xEF".b }.to_json
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ class JSONParserTest < Test::Unit::TestCase
|
|||||||
}
|
}
|
||||||
assert_equal(Encoding::UTF_8, e.message.encoding, bug10705)
|
assert_equal(Encoding::UTF_8, e.message.encoding, bug10705)
|
||||||
assert_include(e.message, json, bug10705)
|
assert_include(e.message, json, bug10705)
|
||||||
end if defined?(JSON::Ext::Parser)
|
end
|
||||||
|
|
||||||
def test_parsing
|
def test_parsing
|
||||||
parser = JSON::Parser.new('"test"')
|
parser = JSON::Parser.new('"test"')
|
||||||
@ -180,7 +180,93 @@ class JSONParserTest < Test::Unit::TestCase
|
|||||||
assert parse('NaN', :allow_nan => true).nan?
|
assert parse('NaN', :allow_nan => true).nan?
|
||||||
assert parse('Infinity', :allow_nan => true).infinite?
|
assert parse('Infinity', :allow_nan => true).infinite?
|
||||||
assert parse('-Infinity', :allow_nan => true).infinite?
|
assert parse('-Infinity', :allow_nan => true).infinite?
|
||||||
assert_raise(JSON::ParserError) { parse('[ 1, ]') }
|
end
|
||||||
|
|
||||||
|
def test_parse_arrays_with_allow_trailing_comma
|
||||||
|
assert_equal([], parse('[]', allow_trailing_comma: true))
|
||||||
|
assert_equal([], parse('[]', allow_trailing_comma: false))
|
||||||
|
assert_raise(JSON::ParserError) { parse('[,]', allow_trailing_comma: true) }
|
||||||
|
assert_raise(JSON::ParserError) { parse('[,]', allow_trailing_comma: false) }
|
||||||
|
|
||||||
|
assert_equal([1], parse('[1]', allow_trailing_comma: true))
|
||||||
|
assert_equal([1], parse('[1]', allow_trailing_comma: false))
|
||||||
|
assert_equal([1], parse('[1,]', allow_trailing_comma: true))
|
||||||
|
assert_raise(JSON::ParserError) { parse('[1,]', allow_trailing_comma: false) }
|
||||||
|
|
||||||
|
assert_equal([1, 2, 3], parse('[1,2,3]', allow_trailing_comma: true))
|
||||||
|
assert_equal([1, 2, 3], parse('[1,2,3]', allow_trailing_comma: false))
|
||||||
|
assert_equal([1, 2, 3], parse('[1,2,3,]', allow_trailing_comma: true))
|
||||||
|
assert_raise(JSON::ParserError) { parse('[1,2,3,]', allow_trailing_comma: false) }
|
||||||
|
|
||||||
|
assert_equal([1, 2, 3], parse('[ 1 , 2 , 3 ]', allow_trailing_comma: true))
|
||||||
|
assert_equal([1, 2, 3], parse('[ 1 , 2 , 3 ]', allow_trailing_comma: false))
|
||||||
|
assert_equal([1, 2, 3], parse('[ 1 , 2 , 3 , ]', allow_trailing_comma: true))
|
||||||
|
assert_raise(JSON::ParserError) { parse('[ 1 , 2 , 3 , ]', allow_trailing_comma: false) }
|
||||||
|
|
||||||
|
assert_equal({'foo' => [1, 2, 3]}, parse('{ "foo": [1,2,3] }', allow_trailing_comma: true))
|
||||||
|
assert_equal({'foo' => [1, 2, 3]}, parse('{ "foo": [1,2,3] }', allow_trailing_comma: false))
|
||||||
|
assert_equal({'foo' => [1, 2, 3]}, parse('{ "foo": [1,2,3,] }', allow_trailing_comma: true))
|
||||||
|
assert_raise(JSON::ParserError) { parse('{ "foo": [1,2,3,] }', allow_trailing_comma: false) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_parse_object_with_allow_trailing_comma
|
||||||
|
assert_equal({}, parse('{}', allow_trailing_comma: true))
|
||||||
|
assert_equal({}, parse('{}', allow_trailing_comma: false))
|
||||||
|
assert_raise(JSON::ParserError) { parse('{,}', allow_trailing_comma: true) }
|
||||||
|
assert_raise(JSON::ParserError) { parse('{,}', allow_trailing_comma: false) }
|
||||||
|
|
||||||
|
assert_equal({'foo'=>'bar'}, parse('{"foo":"bar"}', allow_trailing_comma: true))
|
||||||
|
assert_equal({'foo'=>'bar'}, parse('{"foo":"bar"}', allow_trailing_comma: false))
|
||||||
|
assert_equal({'foo'=>'bar'}, parse('{"foo":"bar",}', allow_trailing_comma: true))
|
||||||
|
assert_raise(JSON::ParserError) { parse('{"foo":"bar",}', allow_trailing_comma: false) }
|
||||||
|
|
||||||
|
assert_equal(
|
||||||
|
{'foo'=>'bar', 'baz'=>'qux', 'quux'=>'garply'},
|
||||||
|
parse('{"foo":"bar","baz":"qux","quux":"garply"}', allow_trailing_comma: true)
|
||||||
|
)
|
||||||
|
assert_equal(
|
||||||
|
{'foo'=>'bar', 'baz'=>'qux', 'quux'=>'garply'},
|
||||||
|
parse('{"foo":"bar","baz":"qux","quux":"garply"}', allow_trailing_comma: false)
|
||||||
|
)
|
||||||
|
assert_equal(
|
||||||
|
{'foo'=>'bar', 'baz'=>'qux', 'quux'=>'garply'},
|
||||||
|
parse('{"foo":"bar","baz":"qux","quux":"garply",}', allow_trailing_comma: true)
|
||||||
|
)
|
||||||
|
assert_raise(JSON::ParserError) {
|
||||||
|
parse('{"foo":"bar","baz":"qux","quux":"garply",}', allow_trailing_comma: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal(
|
||||||
|
{'foo'=>'bar', 'baz'=>'qux', 'quux'=>'garply'},
|
||||||
|
parse('{ "foo":"bar" , "baz":"qux" , "quux":"garply" }', allow_trailing_comma: true)
|
||||||
|
)
|
||||||
|
assert_equal(
|
||||||
|
{'foo'=>'bar', 'baz'=>'qux', 'quux'=>'garply'},
|
||||||
|
parse('{ "foo":"bar" , "baz":"qux" , "quux":"garply" }', allow_trailing_comma: false)
|
||||||
|
)
|
||||||
|
assert_equal(
|
||||||
|
{'foo'=>'bar', 'baz'=>'qux', 'quux'=>'garply'},
|
||||||
|
parse('{ "foo":"bar" , "baz":"qux" , "quux":"garply" , }', allow_trailing_comma: true)
|
||||||
|
)
|
||||||
|
assert_raise(JSON::ParserError) {
|
||||||
|
parse('{ "foo":"bar" , "baz":"qux" , "quux":"garply" , }', allow_trailing_comma: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal(
|
||||||
|
[{'foo'=>'bar', 'baz'=>'qux', 'quux'=>'garply'}],
|
||||||
|
parse('[{"foo":"bar","baz":"qux","quux":"garply"}]', allow_trailing_comma: true)
|
||||||
|
)
|
||||||
|
assert_equal(
|
||||||
|
[{'foo'=>'bar', 'baz'=>'qux', 'quux'=>'garply'}],
|
||||||
|
parse('[{"foo":"bar","baz":"qux","quux":"garply"}]', allow_trailing_comma: false)
|
||||||
|
)
|
||||||
|
assert_equal(
|
||||||
|
[{'foo'=>'bar', 'baz'=>'qux', 'quux'=>'garply'}],
|
||||||
|
parse('[{"foo":"bar","baz":"qux","quux":"garply",}]', allow_trailing_comma: true)
|
||||||
|
)
|
||||||
|
assert_raise(JSON::ParserError) {
|
||||||
|
parse('[{"foo":"bar","baz":"qux","quux":"garply",}]', allow_trailing_comma: false)
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_parse_some_strings
|
def test_parse_some_strings
|
||||||
@ -533,7 +619,7 @@ class JSONParserTest < Test::Unit::TestCase
|
|||||||
error = assert_raise(JSON::ParserError) do
|
error = assert_raise(JSON::ParserError) do
|
||||||
JSON.parse('{"input":{"firstName":"Bob","lastName":"Mob","email":"bob@example.com"}')
|
JSON.parse('{"input":{"firstName":"Bob","lastName":"Mob","email":"bob@example.com"}')
|
||||||
end
|
end
|
||||||
if RUBY_ENGINE == "ruby" && defined?(JSON::Ext)
|
if RUBY_ENGINE == "ruby"
|
||||||
assert_equal %(unexpected token at '{"input":{"firstName":"Bob","las'), error.message
|
assert_equal %(unexpected token at '{"input":{"firstName":"Bob","las'), error.message
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,30 +1,14 @@
|
|||||||
case ENV['JSON']
|
$LOAD_PATH.unshift(File.expand_path('../../../ext', __FILE__), File.expand_path('../../../lib', __FILE__))
|
||||||
when 'pure'
|
|
||||||
$LOAD_PATH.unshift(File.expand_path('../../../lib', __FILE__))
|
|
||||||
$stderr.puts("Testing JSON::Pure")
|
|
||||||
require 'json/pure'
|
|
||||||
when 'ext'
|
|
||||||
$stderr.puts("Testing JSON::Ext")
|
|
||||||
$LOAD_PATH.unshift(File.expand_path('../../../ext', __FILE__), File.expand_path('../../../lib', __FILE__))
|
|
||||||
require 'json/ext'
|
|
||||||
else
|
|
||||||
$LOAD_PATH.unshift(File.expand_path('../../../ext', __FILE__), File.expand_path('../../../lib', __FILE__))
|
|
||||||
$stderr.puts("Testing JSON")
|
|
||||||
require 'json'
|
|
||||||
end
|
|
||||||
|
|
||||||
|
require 'json'
|
||||||
require 'test/unit'
|
require 'test/unit'
|
||||||
begin
|
|
||||||
require 'byebug'
|
|
||||||
rescue LoadError
|
|
||||||
end
|
|
||||||
|
|
||||||
if GC.respond_to?(:verify_compaction_references)
|
if GC.respond_to?(:verify_compaction_references)
|
||||||
# This method was added in Ruby 3.0.0. Calling it this way asks the GC to
|
# This method was added in Ruby 3.0.0. Calling it this way asks the GC to
|
||||||
# move objects around, helping to find object movement bugs.
|
# move objects around, helping to find object movement bugs.
|
||||||
begin
|
begin
|
||||||
GC.verify_compaction_references(double_heap: true, toward: :empty)
|
GC.verify_compaction_references(expand_heap: true, toward: :empty)
|
||||||
rescue NotImplementedError
|
rescue NotImplementedError, ArgumentError
|
||||||
# Some platforms don't support compaction
|
# Some platforms don't support compaction
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user