json 2.19.3 → 2.19.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGES.md +34 -1
- data/README.md +11 -0
- data/ext/json/ext/fbuffer/fbuffer.h +10 -1
- data/ext/json/ext/generator/extconf.rb +2 -0
- data/ext/json/ext/generator/generator.c +37 -25
- data/ext/json/ext/json.h +11 -0
- data/ext/json/ext/parser/extconf.rb +4 -0
- data/ext/json/ext/parser/parser.c +120 -60
- data/lib/json/truffle_ruby/generator.rb +11 -8
- data/lib/json/version.rb +1 -1
- data/lib/json.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5f07e2772537eccf97069c6a7d7290aceba9a18c6b635e8e822bc7b92137c678
|
|
4
|
+
data.tar.gz: cbb25a9d0e3434eba5d9fa2be9af5a69e1fe062c5113c461d854d129122adeed
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2783e5483b100728ae73451008d3d5f880cc6e9dc663d773ed9dcdf27c23c3523c345f7ca70ae0e3687ab3b11262b380a7ecf455906d72b996f07eedbb1b14f8
|
|
7
|
+
data.tar.gz: 84ab5ff3feb2d961b4bd5d759d444b006e8ccaa6802465203ed04a8718db43b4473859fdd613c106b0cc53d4e96d1da8b4a16550361f455964a4a1a75ae3cb23
|
data/CHANGES.md
CHANGED
|
@@ -2,13 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
### Unreleased
|
|
4
4
|
|
|
5
|
+
### 2026-06-03 (2.19.8)
|
|
6
|
+
|
|
7
|
+
* Fix 1-byte buffer overread on EOS errors.
|
|
8
|
+
* Handle invalid types passed as `max_nesting` option.
|
|
9
|
+
|
|
10
|
+
### 2026-05-28 (2.19.7)
|
|
11
|
+
|
|
12
|
+
* Fix some more edge cases with out of range floats.
|
|
13
|
+
* Ensure the string provided to `JSON.parse` can't be mutated during parsing.
|
|
14
|
+
* Add missing write barriers in `State#dup`.
|
|
15
|
+
* Further validate generator `depth` config.
|
|
16
|
+
|
|
17
|
+
### 2026-05-28 (2.19.6)
|
|
18
|
+
|
|
19
|
+
* Cleanly handle overly large `depth` generator argument.
|
|
20
|
+
* Add missing write barrier in `ParserConfig`.
|
|
21
|
+
|
|
22
|
+
### 2026-05-04 (2.19.5)
|
|
23
|
+
|
|
24
|
+
* Cap the parser to emit a maximum of 5 deprecation warnings per document. Emitting more is not helpful.
|
|
25
|
+
|
|
26
|
+
### 2026-04-19 (2.19.4)
|
|
27
|
+
|
|
28
|
+
* Fix parsing of out of range floats (very large exponents that lead to either `0.0` or `Inf`).
|
|
29
|
+
|
|
5
30
|
### 2026-03-25 (2.19.3)
|
|
6
31
|
|
|
7
32
|
* Fix handling of unescaped control characters preceeded by a backslash.
|
|
8
33
|
|
|
9
34
|
### 2026-03-18 (2.19.2)
|
|
10
35
|
|
|
11
|
-
* Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`.
|
|
36
|
+
* Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
|
|
12
37
|
|
|
13
38
|
### 2026-03-08 (2.19.1)
|
|
14
39
|
|
|
@@ -28,6 +53,10 @@
|
|
|
28
53
|
|
|
29
54
|
* Add `:allow_control_characters` parser options, to allow JSON strings containing unescaped ASCII control characters (e.g. newlines).
|
|
30
55
|
|
|
56
|
+
### 2026-03-18 (2.17.1.2) - Security Backport
|
|
57
|
+
|
|
58
|
+
* Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
|
|
59
|
+
|
|
31
60
|
### 2025-12-04 (2.17.1)
|
|
32
61
|
|
|
33
62
|
* Fix a regression in parsing of unicode surogate pairs (`\uXX\uXX`) that could cause an invalid string to be returned.
|
|
@@ -54,6 +83,10 @@
|
|
|
54
83
|
* Optimized numbers parsing using SWAR (thanks to Scott Myron).
|
|
55
84
|
* Optimized parsing of pretty printed documents using SWAR (thanks to Scott Myron).
|
|
56
85
|
|
|
86
|
+
### 2026-03-18 (2.15.2.1) - Security Backport
|
|
87
|
+
|
|
88
|
+
* Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
|
|
89
|
+
|
|
57
90
|
### 2025-10-25 (2.15.2)
|
|
58
91
|
|
|
59
92
|
* Fix `JSON::Coder` to have one dedicated depth counter per invocation.
|
data/README.md
CHANGED
|
@@ -249,6 +249,17 @@ There are also the methods `Kernel#j` for generate, and `Kernel#jj` for
|
|
|
249
249
|
`pretty_generate` output to the console, that work analogous to Core Ruby's `p` and
|
|
250
250
|
the `pp` library's `pp` methods.
|
|
251
251
|
|
|
252
|
+
## Security
|
|
253
|
+
|
|
254
|
+
When parsing or serializing untrusted input, parser and generator options should never be user controlled.
|
|
255
|
+
|
|
256
|
+
```ruby
|
|
257
|
+
# Dangerous, DO NOT DO THIS.
|
|
258
|
+
JSON.generate(params[:data], params[:options])
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Security vulnerability reports relying on attacker controlled parsing or generator options will be handled as regular bug fixes.
|
|
262
|
+
|
|
252
263
|
## Development
|
|
253
264
|
|
|
254
265
|
### Prerequisites
|
|
@@ -131,6 +131,15 @@ static inline void fbuffer_inc_capa(FBuffer *fb, size_t requested)
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
static inline size_t fbuffer_size_mul_or_raise(size_t a, size_t b)
|
|
135
|
+
{
|
|
136
|
+
size_t result = a * b;
|
|
137
|
+
if (RB_UNLIKELY(a != 0 && (result / a) != b)) {
|
|
138
|
+
rb_raise(rb_eArgError, "Buffer overflow, the resulting document is too large to be generated");
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
|
|
134
143
|
static inline void fbuffer_append_reserved(FBuffer *fb, const char *newstr, size_t len)
|
|
135
144
|
{
|
|
136
145
|
MEMCPY(fb->ptr + fb->len, newstr, char, len);
|
|
@@ -175,7 +184,7 @@ static void fbuffer_append_str_repeat(FBuffer *fb, VALUE str, size_t repeat)
|
|
|
175
184
|
size_t len;
|
|
176
185
|
RSTRING_GETMEM(str, ptr, len);
|
|
177
186
|
|
|
178
|
-
fbuffer_inc_capa(fb, repeat
|
|
187
|
+
fbuffer_inc_capa(fb, fbuffer_size_mul_or_raise(repeat, len));
|
|
179
188
|
while (repeat) {
|
|
180
189
|
#if JSON_DEBUG
|
|
181
190
|
fb->requested = len;
|
|
@@ -5,6 +5,8 @@ if RUBY_ENGINE == 'truffleruby'
|
|
|
5
5
|
File.write('Makefile', dummy_makefile("").join)
|
|
6
6
|
else
|
|
7
7
|
append_cflags("-std=c99")
|
|
8
|
+
have_const("RUBY_TYPED_EMBEDDABLE", "ruby.h") # RUBY_VERSION >= 3.3
|
|
9
|
+
|
|
8
10
|
$defs << "-DJSON_GENERATOR"
|
|
9
11
|
$defs << "-DJSON_DEBUG" if ENV.fetch("JSON_DEBUG", "0") != "0"
|
|
10
12
|
|
|
@@ -722,27 +722,20 @@ static void State_compact(void *ptr)
|
|
|
722
722
|
state->as_json = rb_gc_location(state->as_json);
|
|
723
723
|
}
|
|
724
724
|
|
|
725
|
-
static void State_free(void *ptr)
|
|
726
|
-
{
|
|
727
|
-
JSON_Generator_State *state = ptr;
|
|
728
|
-
ruby_xfree(state);
|
|
729
|
-
}
|
|
730
|
-
|
|
731
725
|
static size_t State_memsize(const void *ptr)
|
|
732
726
|
{
|
|
733
727
|
return sizeof(JSON_Generator_State);
|
|
734
728
|
}
|
|
735
729
|
|
|
736
730
|
static const rb_data_type_t JSON_Generator_State_type = {
|
|
737
|
-
"JSON/Generator/State",
|
|
738
|
-
{
|
|
731
|
+
.wrap_struct_name = "JSON/Generator/State",
|
|
732
|
+
.function = {
|
|
739
733
|
.dmark = State_mark,
|
|
740
|
-
.dfree =
|
|
734
|
+
.dfree = RUBY_DEFAULT_FREE,
|
|
741
735
|
.dsize = State_memsize,
|
|
742
736
|
.dcompact = State_compact,
|
|
743
737
|
},
|
|
744
|
-
|
|
745
|
-
RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE,
|
|
738
|
+
.flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_EMBEDDABLE,
|
|
746
739
|
};
|
|
747
740
|
|
|
748
741
|
static void state_init(JSON_Generator_State *state)
|
|
@@ -826,9 +819,17 @@ static VALUE encode_json_string_rescue(VALUE str, VALUE exception)
|
|
|
826
819
|
return Qundef;
|
|
827
820
|
}
|
|
828
821
|
|
|
822
|
+
static inline int json_str_coderange(VALUE str) {
|
|
823
|
+
int coderange = RB_ENC_CODERANGE(str);
|
|
824
|
+
if (coderange == RUBY_ENC_CODERANGE_UNKNOWN) {
|
|
825
|
+
coderange = rb_enc_str_coderange(str);
|
|
826
|
+
}
|
|
827
|
+
return coderange;
|
|
828
|
+
}
|
|
829
|
+
|
|
829
830
|
static inline bool valid_json_string_p(VALUE str)
|
|
830
831
|
{
|
|
831
|
-
int coderange =
|
|
832
|
+
int coderange = json_str_coderange(str);
|
|
832
833
|
|
|
833
834
|
if (RB_LIKELY(coderange == ENC_CODERANGE_7BIT)) {
|
|
834
835
|
return true;
|
|
@@ -841,12 +842,8 @@ static inline bool valid_json_string_p(VALUE str)
|
|
|
841
842
|
return false;
|
|
842
843
|
}
|
|
843
844
|
|
|
844
|
-
static
|
|
845
|
+
NOINLINE(static) VALUE convert_invalid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key)
|
|
845
846
|
{
|
|
846
|
-
if (RB_LIKELY(valid_json_string_p(str))) {
|
|
847
|
-
return str;
|
|
848
|
-
}
|
|
849
|
-
|
|
850
847
|
if (!as_json_called && data->state->strict && RTEST(data->state->as_json)) {
|
|
851
848
|
VALUE coerced_str = json_call_as_json(data->state, str, Qfalse);
|
|
852
849
|
if (coerced_str != str) {
|
|
@@ -882,6 +879,16 @@ static inline VALUE ensure_valid_encoding(struct generate_json_data *data, VALUE
|
|
|
882
879
|
return rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str);
|
|
883
880
|
}
|
|
884
881
|
|
|
882
|
+
ALWAYS_INLINE(static) VALUE ensure_valid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key)
|
|
883
|
+
{
|
|
884
|
+
if (RB_LIKELY(valid_json_string_p(str))) {
|
|
885
|
+
return str;
|
|
886
|
+
}
|
|
887
|
+
else {
|
|
888
|
+
return convert_invalid_encoding(data, str, as_json_called, is_key);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
885
892
|
static void raw_generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
886
893
|
{
|
|
887
894
|
fbuffer_append_char(buffer, '"');
|
|
@@ -900,7 +907,7 @@ static void raw_generate_json_string(FBuffer *buffer, struct generate_json_data
|
|
|
900
907
|
search.chunk_end = NULL;
|
|
901
908
|
#endif /* HAVE_SIMD */
|
|
902
909
|
|
|
903
|
-
switch (
|
|
910
|
+
switch (json_str_coderange(obj)) {
|
|
904
911
|
case ENC_CODERANGE_7BIT:
|
|
905
912
|
case ENC_CODERANGE_VALID:
|
|
906
913
|
if (RB_UNLIKELY(data->state->ascii_only)) {
|
|
@@ -1360,12 +1367,14 @@ static VALUE cState_init_copy(VALUE obj, VALUE orig)
|
|
|
1360
1367
|
if (!objState) rb_raise(rb_eArgError, "unallocated JSON::State");
|
|
1361
1368
|
|
|
1362
1369
|
MEMCPY(objState, origState, JSON_Generator_State, 1);
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1370
|
+
|
|
1371
|
+
RB_OBJ_WRITTEN(obj, Qundef, objState->indent);
|
|
1372
|
+
RB_OBJ_WRITTEN(obj, Qundef, objState->space);
|
|
1373
|
+
RB_OBJ_WRITTEN(obj, Qundef, objState->space_before);
|
|
1374
|
+
RB_OBJ_WRITTEN(obj, Qundef, objState->object_nl);
|
|
1375
|
+
RB_OBJ_WRITTEN(obj, Qundef, objState->array_nl);
|
|
1376
|
+
RB_OBJ_WRITTEN(obj, Qundef, objState->as_json);
|
|
1377
|
+
|
|
1369
1378
|
return obj;
|
|
1370
1379
|
}
|
|
1371
1380
|
|
|
@@ -1572,7 +1581,7 @@ static VALUE cState_max_nesting(VALUE self)
|
|
|
1572
1581
|
|
|
1573
1582
|
static long long_config(VALUE num)
|
|
1574
1583
|
{
|
|
1575
|
-
return RTEST(num) ?
|
|
1584
|
+
return RTEST(num) ? NUM2LONG(num) : 0;
|
|
1576
1585
|
}
|
|
1577
1586
|
|
|
1578
1587
|
// depth must never be negative; reject early with a clear error.
|
|
@@ -1583,6 +1592,9 @@ static long depth_config(VALUE num)
|
|
|
1583
1592
|
if (RB_UNLIKELY(d < 0)) {
|
|
1584
1593
|
rb_raise(rb_eArgError, "depth must be >= 0 (got %ld)", d);
|
|
1585
1594
|
}
|
|
1595
|
+
if (RB_UNLIKELY(d > INT_MAX)) {
|
|
1596
|
+
rb_raise(rb_eArgError, "depth is too large (got %ld)", d);
|
|
1597
|
+
}
|
|
1586
1598
|
return d;
|
|
1587
1599
|
}
|
|
1588
1600
|
|
data/ext/json/ext/json.h
CHANGED
|
@@ -54,6 +54,17 @@ typedef unsigned char _Bool;
|
|
|
54
54
|
# define RUBY_TYPED_FROZEN_SHAREABLE 0
|
|
55
55
|
#endif
|
|
56
56
|
|
|
57
|
+
#ifdef RUBY_TYPED_EMBEDDABLE
|
|
58
|
+
# define HAVE_RUBY_TYPED_EMBEDDABLE 1
|
|
59
|
+
#else
|
|
60
|
+
# ifdef HAVE_CONST_RUBY_TYPED_EMBEDDABLE
|
|
61
|
+
# define RUBY_TYPED_EMBEDDABLE RUBY_TYPED_EMBEDDABLE
|
|
62
|
+
# define HAVE_RUBY_TYPED_EMBEDDABLE 1
|
|
63
|
+
# else
|
|
64
|
+
# define RUBY_TYPED_EMBEDDABLE 0
|
|
65
|
+
# endif
|
|
66
|
+
#endif
|
|
67
|
+
|
|
57
68
|
#ifndef NORETURN
|
|
58
69
|
#if defined(__has_attribute) && __has_attribute(noreturn)
|
|
59
70
|
#define NORETURN(x) __attribute__((noreturn)) x
|
|
@@ -7,6 +7,10 @@ have_func("rb_str_to_interned_str", "ruby.h") # RUBY_VERSION >= 3.0
|
|
|
7
7
|
have_func("rb_hash_new_capa", "ruby.h") # RUBY_VERSION >= 3.2
|
|
8
8
|
have_func("rb_hash_bulk_insert", "ruby.h") # Missing on TruffleRuby
|
|
9
9
|
|
|
10
|
+
if RUBY_ENGINE == "ruby"
|
|
11
|
+
have_const("RUBY_TYPED_EMBEDDABLE", "ruby.h") # RUBY_VERSION >= 3.3
|
|
12
|
+
end
|
|
13
|
+
|
|
10
14
|
append_cflags("-std=c99")
|
|
11
15
|
|
|
12
16
|
if enable_config('parser-use-simd', default=!ENV["JSON_DISABLE_SIMD"])
|
|
@@ -241,17 +241,27 @@ static void rvalue_stack_mark(void *ptr)
|
|
|
241
241
|
{
|
|
242
242
|
rvalue_stack *stack = (rvalue_stack *)ptr;
|
|
243
243
|
long index;
|
|
244
|
-
|
|
245
|
-
|
|
244
|
+
if (stack && stack->ptr) {
|
|
245
|
+
for (index = 0; index < stack->head; index++) {
|
|
246
|
+
rb_gc_mark(stack->ptr[index]);
|
|
247
|
+
}
|
|
246
248
|
}
|
|
247
249
|
}
|
|
248
250
|
|
|
251
|
+
static void rvalue_stack_free_buffer(rvalue_stack *stack)
|
|
252
|
+
{
|
|
253
|
+
ruby_xfree(stack->ptr);
|
|
254
|
+
stack->ptr = NULL;
|
|
255
|
+
}
|
|
256
|
+
|
|
249
257
|
static void rvalue_stack_free(void *ptr)
|
|
250
258
|
{
|
|
251
259
|
rvalue_stack *stack = (rvalue_stack *)ptr;
|
|
252
260
|
if (stack) {
|
|
253
|
-
|
|
261
|
+
rvalue_stack_free_buffer(stack);
|
|
262
|
+
#ifndef HAVE_RUBY_TYPED_EMBEDDABLE
|
|
254
263
|
ruby_xfree(stack);
|
|
264
|
+
#endif
|
|
255
265
|
}
|
|
256
266
|
}
|
|
257
267
|
|
|
@@ -262,14 +272,13 @@ static size_t rvalue_stack_memsize(const void *ptr)
|
|
|
262
272
|
}
|
|
263
273
|
|
|
264
274
|
static const rb_data_type_t JSON_Parser_rvalue_stack_type = {
|
|
265
|
-
"JSON::Ext::Parser/rvalue_stack",
|
|
266
|
-
{
|
|
275
|
+
.wrap_struct_name = "JSON::Ext::Parser/rvalue_stack",
|
|
276
|
+
.function = {
|
|
267
277
|
.dmark = rvalue_stack_mark,
|
|
268
278
|
.dfree = rvalue_stack_free,
|
|
269
279
|
.dsize = rvalue_stack_memsize,
|
|
270
280
|
},
|
|
271
|
-
|
|
272
|
-
RUBY_TYPED_FREE_IMMEDIATELY,
|
|
281
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_EMBEDDABLE,
|
|
273
282
|
};
|
|
274
283
|
|
|
275
284
|
static rvalue_stack *rvalue_stack_spill(rvalue_stack *old_stack, VALUE *handle, rvalue_stack **stack_ref)
|
|
@@ -291,8 +300,12 @@ static void rvalue_stack_eagerly_release(VALUE handle)
|
|
|
291
300
|
if (handle) {
|
|
292
301
|
rvalue_stack *stack;
|
|
293
302
|
TypedData_Get_Struct(handle, rvalue_stack, &JSON_Parser_rvalue_stack_type, stack);
|
|
294
|
-
|
|
303
|
+
#ifdef HAVE_RUBY_TYPED_EMBEDDABLE
|
|
304
|
+
rvalue_stack_free_buffer(stack);
|
|
305
|
+
#else
|
|
295
306
|
rvalue_stack_free(stack);
|
|
307
|
+
RTYPEDDATA_DATA(handle) = NULL;
|
|
308
|
+
#endif
|
|
296
309
|
}
|
|
297
310
|
}
|
|
298
311
|
|
|
@@ -343,7 +356,7 @@ typedef struct JSON_ParserStruct {
|
|
|
343
356
|
} JSON_ParserConfig;
|
|
344
357
|
|
|
345
358
|
typedef struct JSON_ParserStateStruct {
|
|
346
|
-
VALUE stack_handle;
|
|
359
|
+
VALUE *stack_handle;
|
|
347
360
|
const char *start;
|
|
348
361
|
const char *cursor;
|
|
349
362
|
const char *end;
|
|
@@ -351,6 +364,7 @@ typedef struct JSON_ParserStateStruct {
|
|
|
351
364
|
rvalue_cache name_cache;
|
|
352
365
|
int in_array;
|
|
353
366
|
int current_nesting;
|
|
367
|
+
unsigned int emitted_deprecations;
|
|
354
368
|
} JSON_ParserState;
|
|
355
369
|
|
|
356
370
|
static inline size_t rest(JSON_ParserState *state) {
|
|
@@ -371,6 +385,13 @@ static inline char peek(JSON_ParserState *state)
|
|
|
371
385
|
|
|
372
386
|
static void cursor_position(JSON_ParserState *state, long *line_out, long *column_out)
|
|
373
387
|
{
|
|
388
|
+
JSON_ASSERT(state->cursor <= state->end);
|
|
389
|
+
|
|
390
|
+
// Redundant but helpful for hardening
|
|
391
|
+
if (RB_UNLIKELY(state->cursor > state->end)) {
|
|
392
|
+
state->cursor = state->end;
|
|
393
|
+
}
|
|
394
|
+
|
|
374
395
|
const char *cursor = state->cursor;
|
|
375
396
|
long column = 0;
|
|
376
397
|
long line = 1;
|
|
@@ -436,9 +457,8 @@ static VALUE build_parse_error_message(const char *format, JSON_ParserState *sta
|
|
|
436
457
|
}
|
|
437
458
|
}
|
|
438
459
|
|
|
439
|
-
VALUE
|
|
440
|
-
|
|
441
|
-
RB_GC_GUARD(msg);
|
|
460
|
+
VALUE message = rb_enc_sprintf(enc_utf8, format, ptr);
|
|
461
|
+
rb_str_catf(message, " at line %ld column %ld", line, column);
|
|
442
462
|
return message;
|
|
443
463
|
}
|
|
444
464
|
|
|
@@ -843,7 +863,7 @@ NOINLINE(static) VALUE json_decode_large_float(const char *start, long len)
|
|
|
843
863
|
/* Ruby JSON optimized float decoder using vendored Ryu algorithm
|
|
844
864
|
* Accepts pre-extracted mantissa and exponent from first-pass validation
|
|
845
865
|
*/
|
|
846
|
-
static inline VALUE json_decode_float(JSON_ParserConfig *config, uint64_t mantissa, int mantissa_digits,
|
|
866
|
+
static inline VALUE json_decode_float(JSON_ParserConfig *config, uint64_t mantissa, int mantissa_digits, int64_t exponent, bool negative,
|
|
847
867
|
const char *start, const char *end)
|
|
848
868
|
{
|
|
849
869
|
if (RB_UNLIKELY(config->decimal_class)) {
|
|
@@ -851,13 +871,21 @@ static inline VALUE json_decode_float(JSON_ParserConfig *config, uint64_t mantis
|
|
|
851
871
|
return rb_funcallv(config->decimal_class, config->decimal_method_id, 1, &text);
|
|
852
872
|
}
|
|
853
873
|
|
|
874
|
+
if (RB_UNLIKELY(exponent > INT32_MAX)) {
|
|
875
|
+
return negative ? CMinusInfinity : CInfinity;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
if (RB_UNLIKELY(exponent < INT32_MIN)) {
|
|
879
|
+
return rb_float_new(negative ? -0.0 : 0.0);
|
|
880
|
+
}
|
|
881
|
+
|
|
854
882
|
// Fall back to rb_cstr_to_dbl for potential subnormals (rare edge case)
|
|
855
883
|
// Ryu has rounding issues with subnormals around 1e-310 (< 2.225e-308)
|
|
856
884
|
if (RB_UNLIKELY(mantissa_digits > 17 || mantissa_digits + exponent < -307)) {
|
|
857
885
|
return json_decode_large_float(start, end - start);
|
|
858
886
|
}
|
|
859
887
|
|
|
860
|
-
return DBL2NUM(ryu_s2d_from_parts(mantissa, mantissa_digits, exponent, negative));
|
|
888
|
+
return DBL2NUM(ryu_s2d_from_parts(mantissa, mantissa_digits, (int32_t)exponent, negative));
|
|
861
889
|
}
|
|
862
890
|
|
|
863
891
|
static inline VALUE json_decode_array(JSON_ParserState *state, JSON_ParserConfig *config, long count)
|
|
@@ -911,9 +939,6 @@ NORETURN(static) void raise_duplicate_key_error(JSON_ParserState *state, VALUE d
|
|
|
911
939
|
cursor_position(state, &line, &column);
|
|
912
940
|
rb_str_concat(message, build_parse_error_message("", state, line, column)) ;
|
|
913
941
|
rb_exc_raise(parse_error_new(message, line, column));
|
|
914
|
-
|
|
915
|
-
raise_parse_error(RSTRING_PTR(message), state);
|
|
916
|
-
RB_GC_GUARD(message);
|
|
917
942
|
}
|
|
918
943
|
|
|
919
944
|
static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfig *config, size_t count)
|
|
@@ -928,7 +953,12 @@ static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfi
|
|
|
928
953
|
case JSON_IGNORE:
|
|
929
954
|
break;
|
|
930
955
|
case JSON_DEPRECATED:
|
|
931
|
-
|
|
956
|
+
// Only emit the first few deprecations to avoid spamming.
|
|
957
|
+
if (state->emitted_deprecations < 5) {
|
|
958
|
+
emit_duplicate_key_warning(state, json_find_duplicated_key(count, pairs));
|
|
959
|
+
state->emitted_deprecations++;
|
|
960
|
+
}
|
|
961
|
+
|
|
932
962
|
break;
|
|
933
963
|
case JSON_RAISE:
|
|
934
964
|
raise_duplicate_key_error(state, json_find_duplicated_key(count, pairs));
|
|
@@ -950,7 +980,7 @@ static inline VALUE json_push_value(JSON_ParserState *state, JSON_ParserConfig *
|
|
|
950
980
|
if (RB_UNLIKELY(config->on_load_proc)) {
|
|
951
981
|
value = rb_proc_call_with_block(config->on_load_proc, 1, &value, Qnil);
|
|
952
982
|
}
|
|
953
|
-
rvalue_stack_push(state->stack, value,
|
|
983
|
+
rvalue_stack_push(state->stack, value, state->stack_handle, &state->stack);
|
|
954
984
|
return value;
|
|
955
985
|
}
|
|
956
986
|
|
|
@@ -999,6 +1029,13 @@ ALWAYS_INLINE(static) bool string_scan(JSON_ParserState *state)
|
|
|
999
1029
|
}
|
|
1000
1030
|
state->cursor++;
|
|
1001
1031
|
}
|
|
1032
|
+
|
|
1033
|
+
// If the string ended with an unterminated escape sequence, we might
|
|
1034
|
+
// have gone past the end.
|
|
1035
|
+
if (RB_UNLIKELY(state->cursor > state->end)) {
|
|
1036
|
+
state->cursor = state->end;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1002
1039
|
return false;
|
|
1003
1040
|
}
|
|
1004
1041
|
|
|
@@ -1135,7 +1172,7 @@ static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig
|
|
|
1135
1172
|
const char first_digit = *state->cursor;
|
|
1136
1173
|
|
|
1137
1174
|
// Variables for Ryu optimization - extract digits during parsing
|
|
1138
|
-
|
|
1175
|
+
int64_t exponent = 0;
|
|
1139
1176
|
int decimal_point_pos = -1;
|
|
1140
1177
|
uint64_t mantissa = 0;
|
|
1141
1178
|
|
|
@@ -1179,7 +1216,11 @@ static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig
|
|
|
1179
1216
|
raise_parse_error_at("invalid number: %s", state, start);
|
|
1180
1217
|
}
|
|
1181
1218
|
|
|
1182
|
-
|
|
1219
|
+
if (RB_UNLIKELY(exponent_digits >= 20 || abs_exponent > (uint64_t)INT64_MAX)) {
|
|
1220
|
+
exponent = negative_exponent ? INT64_MIN : INT64_MAX;
|
|
1221
|
+
} else {
|
|
1222
|
+
exponent = negative_exponent ? -(int64_t)abs_exponent : (int64_t)abs_exponent;
|
|
1223
|
+
}
|
|
1183
1224
|
}
|
|
1184
1225
|
|
|
1185
1226
|
if (integer) {
|
|
@@ -1434,23 +1475,39 @@ static void json_ensure_eof(JSON_ParserState *state)
|
|
|
1434
1475
|
|
|
1435
1476
|
static VALUE convert_encoding(VALUE source)
|
|
1436
1477
|
{
|
|
1437
|
-
|
|
1478
|
+
StringValue(source);
|
|
1479
|
+
int encindex = RB_ENCODING_GET(source);
|
|
1480
|
+
|
|
1481
|
+
if (RB_LIKELY(encindex == utf8_encindex)) {
|
|
1482
|
+
return source;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
if (encindex == binary_encindex) {
|
|
1486
|
+
// For historical reason, we silently reinterpret binary strings as UTF-8
|
|
1487
|
+
return rb_enc_associate_index(rb_str_dup(source), utf8_encindex);
|
|
1488
|
+
}
|
|
1438
1489
|
|
|
1439
|
-
|
|
1490
|
+
source = rb_funcall(source, i_encode, 1, Encoding_UTF_8);
|
|
1491
|
+
StringValue(source);
|
|
1440
1492
|
return source;
|
|
1441
|
-
|
|
1493
|
+
}
|
|
1442
1494
|
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1495
|
+
struct parser_config_init_args {
|
|
1496
|
+
JSON_ParserConfig *config;
|
|
1497
|
+
VALUE self;
|
|
1498
|
+
};
|
|
1447
1499
|
|
|
1448
|
-
|
|
1500
|
+
static void parser_config_wb_write(VALUE self, VALUE *dest, VALUE val)
|
|
1501
|
+
{
|
|
1502
|
+
*dest = val;
|
|
1503
|
+
if (self) RB_OBJ_WRITTEN(self, Qundef, val);
|
|
1449
1504
|
}
|
|
1450
1505
|
|
|
1451
1506
|
static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
|
|
1452
1507
|
{
|
|
1453
|
-
|
|
1508
|
+
struct parser_config_init_args *args = (struct parser_config_init_args *)data;
|
|
1509
|
+
JSON_ParserConfig *config = args->config;
|
|
1510
|
+
VALUE self = args->self;
|
|
1454
1511
|
|
|
1455
1512
|
if (key == sym_max_nesting) { config->max_nesting = RTEST(val) ? FIX2INT(val) : 0; }
|
|
1456
1513
|
else if (key == sym_allow_nan) { config->allow_nan = RTEST(val); }
|
|
@@ -1459,15 +1516,15 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
|
|
|
1459
1516
|
else if (key == sym_allow_invalid_escape) { config->allow_invalid_escape = RTEST(val); }
|
|
1460
1517
|
else if (key == sym_symbolize_names) { config->symbolize_names = RTEST(val); }
|
|
1461
1518
|
else if (key == sym_freeze) { config->freeze = RTEST(val); }
|
|
1462
|
-
else if (key == sym_on_load) { config->on_load_proc
|
|
1519
|
+
else if (key == sym_on_load) { parser_config_wb_write(self, &config->on_load_proc, RTEST(val) ? val : Qfalse); }
|
|
1463
1520
|
else if (key == sym_allow_duplicate_key) { config->on_duplicate_key = RTEST(val) ? JSON_IGNORE : JSON_RAISE; }
|
|
1464
1521
|
else if (key == sym_decimal_class) {
|
|
1465
1522
|
if (RTEST(val)) {
|
|
1466
1523
|
if (rb_respond_to(val, i_try_convert)) {
|
|
1467
|
-
config->decimal_class
|
|
1524
|
+
parser_config_wb_write(self, &config->decimal_class, val);
|
|
1468
1525
|
config->decimal_method_id = i_try_convert;
|
|
1469
1526
|
} else if (rb_respond_to(val, i_new)) {
|
|
1470
|
-
config->decimal_class
|
|
1527
|
+
parser_config_wb_write(self, &config->decimal_class, val);
|
|
1471
1528
|
config->decimal_method_id = i_new;
|
|
1472
1529
|
} else if (RB_TYPE_P(val, T_CLASS)) {
|
|
1473
1530
|
VALUE name = rb_class_name(val);
|
|
@@ -1476,7 +1533,7 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
|
|
|
1476
1533
|
if (last_colon) {
|
|
1477
1534
|
const char *mod_path_end = last_colon - 1;
|
|
1478
1535
|
VALUE mod_path = rb_str_substr(name, 0, mod_path_end - name_cstr);
|
|
1479
|
-
config->decimal_class
|
|
1536
|
+
parser_config_wb_write(self, &config->decimal_class, rb_path_to_class(mod_path));
|
|
1480
1537
|
|
|
1481
1538
|
const char *method_name_beg = last_colon + 1;
|
|
1482
1539
|
long before_len = method_name_beg - name_cstr;
|
|
@@ -1484,7 +1541,7 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
|
|
|
1484
1541
|
VALUE method_name = rb_str_substr(name, before_len, len);
|
|
1485
1542
|
config->decimal_method_id = SYM2ID(rb_str_intern(method_name));
|
|
1486
1543
|
} else {
|
|
1487
|
-
config->decimal_class
|
|
1544
|
+
parser_config_wb_write(self, &config->decimal_class, rb_mKernel);
|
|
1488
1545
|
config->decimal_method_id = SYM2ID(rb_str_intern(name));
|
|
1489
1546
|
}
|
|
1490
1547
|
}
|
|
@@ -1494,16 +1551,21 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
|
|
|
1494
1551
|
return ST_CONTINUE;
|
|
1495
1552
|
}
|
|
1496
1553
|
|
|
1497
|
-
static void parser_config_init(JSON_ParserConfig *config, VALUE opts)
|
|
1554
|
+
static void parser_config_init(JSON_ParserConfig *config, VALUE opts, VALUE self)
|
|
1498
1555
|
{
|
|
1499
1556
|
config->max_nesting = 100;
|
|
1500
1557
|
|
|
1558
|
+
struct parser_config_init_args args = {
|
|
1559
|
+
.config = config,
|
|
1560
|
+
.self = self,
|
|
1561
|
+
};
|
|
1562
|
+
|
|
1501
1563
|
if (!NIL_P(opts)) {
|
|
1502
1564
|
Check_Type(opts, T_HASH);
|
|
1503
1565
|
if (RHASH_SIZE(opts) > 0) {
|
|
1504
1566
|
// We assume in most cases few keys are set so it's faster to go over
|
|
1505
1567
|
// the provided keys than to check all possible keys.
|
|
1506
|
-
rb_hash_foreach(opts, parser_config_init_i, (VALUE)
|
|
1568
|
+
rb_hash_foreach(opts, parser_config_init_i, (VALUE)&args);
|
|
1507
1569
|
}
|
|
1508
1570
|
|
|
1509
1571
|
}
|
|
@@ -1537,17 +1599,21 @@ static VALUE cParserConfig_initialize(VALUE self, VALUE opts)
|
|
|
1537
1599
|
rb_check_frozen(self);
|
|
1538
1600
|
GET_PARSER_CONFIG;
|
|
1539
1601
|
|
|
1540
|
-
parser_config_init(config, opts);
|
|
1541
|
-
|
|
1542
|
-
RB_OBJ_WRITTEN(self, Qundef, config->decimal_class);
|
|
1602
|
+
parser_config_init(config, opts, self);
|
|
1543
1603
|
|
|
1544
1604
|
return self;
|
|
1545
1605
|
}
|
|
1546
1606
|
|
|
1547
|
-
static VALUE cParser_parse(JSON_ParserConfig *config, VALUE
|
|
1607
|
+
static VALUE cParser_parse(JSON_ParserConfig *config, VALUE src)
|
|
1548
1608
|
{
|
|
1549
|
-
Vsource = convert_encoding(
|
|
1550
|
-
|
|
1609
|
+
VALUE Vsource = convert_encoding(src);
|
|
1610
|
+
|
|
1611
|
+
// Ensure the string isn't mutated under us.
|
|
1612
|
+
// The classic API to use is `rb_str_locktmp`, but then we'd
|
|
1613
|
+
// need to use `rb_protect` to make sure we always unlock.
|
|
1614
|
+
if (Vsource == src) {
|
|
1615
|
+
Vsource = rb_str_new_frozen(Vsource);
|
|
1616
|
+
}
|
|
1551
1617
|
|
|
1552
1618
|
VALUE rvalue_stack_buffer[RVALUE_STACK_INITIAL_CAPA];
|
|
1553
1619
|
rvalue_stack stack = {
|
|
@@ -1558,13 +1624,16 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE Vsource)
|
|
|
1558
1624
|
|
|
1559
1625
|
long len;
|
|
1560
1626
|
const char *start;
|
|
1627
|
+
|
|
1561
1628
|
RSTRING_GETMEM(Vsource, start, len);
|
|
1562
1629
|
|
|
1630
|
+
VALUE stack_handle = 0;
|
|
1563
1631
|
JSON_ParserState _state = {
|
|
1564
1632
|
.start = start,
|
|
1565
1633
|
.cursor = start,
|
|
1566
1634
|
.end = start + len,
|
|
1567
1635
|
.stack = &stack,
|
|
1636
|
+
.stack_handle = &stack_handle,
|
|
1568
1637
|
};
|
|
1569
1638
|
JSON_ParserState *state = &_state;
|
|
1570
1639
|
|
|
@@ -1572,8 +1641,9 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE Vsource)
|
|
|
1572
1641
|
|
|
1573
1642
|
// This may be skipped in case of exception, but
|
|
1574
1643
|
// it won't cause a leak.
|
|
1575
|
-
rvalue_stack_eagerly_release(
|
|
1576
|
-
|
|
1644
|
+
rvalue_stack_eagerly_release(stack_handle);
|
|
1645
|
+
RB_GC_GUARD(stack_handle);
|
|
1646
|
+
RB_GC_GUARD(Vsource);
|
|
1577
1647
|
json_ensure_eof(state);
|
|
1578
1648
|
|
|
1579
1649
|
return result;
|
|
@@ -1594,12 +1664,9 @@ static VALUE cParserConfig_parse(VALUE self, VALUE Vsource)
|
|
|
1594
1664
|
|
|
1595
1665
|
static VALUE cParser_m_parse(VALUE klass, VALUE Vsource, VALUE opts)
|
|
1596
1666
|
{
|
|
1597
|
-
Vsource = convert_encoding(StringValue(Vsource));
|
|
1598
|
-
StringValue(Vsource);
|
|
1599
|
-
|
|
1600
1667
|
JSON_ParserConfig _config = {0};
|
|
1601
1668
|
JSON_ParserConfig *config = &_config;
|
|
1602
|
-
parser_config_init(config, opts);
|
|
1669
|
+
parser_config_init(config, opts, false);
|
|
1603
1670
|
|
|
1604
1671
|
return cParser_parse(config, Vsource);
|
|
1605
1672
|
}
|
|
@@ -1611,26 +1678,19 @@ static void JSON_ParserConfig_mark(void *ptr)
|
|
|
1611
1678
|
rb_gc_mark(config->decimal_class);
|
|
1612
1679
|
}
|
|
1613
1680
|
|
|
1614
|
-
static void JSON_ParserConfig_free(void *ptr)
|
|
1615
|
-
{
|
|
1616
|
-
JSON_ParserConfig *config = ptr;
|
|
1617
|
-
ruby_xfree(config);
|
|
1618
|
-
}
|
|
1619
|
-
|
|
1620
1681
|
static size_t JSON_ParserConfig_memsize(const void *ptr)
|
|
1621
1682
|
{
|
|
1622
1683
|
return sizeof(JSON_ParserConfig);
|
|
1623
1684
|
}
|
|
1624
1685
|
|
|
1625
1686
|
static const rb_data_type_t JSON_ParserConfig_type = {
|
|
1626
|
-
"JSON::Ext::Parser/ParserConfig",
|
|
1627
|
-
{
|
|
1687
|
+
.wrap_struct_name = "JSON::Ext::Parser/ParserConfig",
|
|
1688
|
+
.function = {
|
|
1628
1689
|
JSON_ParserConfig_mark,
|
|
1629
|
-
|
|
1690
|
+
RUBY_DEFAULT_FREE,
|
|
1630
1691
|
JSON_ParserConfig_memsize,
|
|
1631
1692
|
},
|
|
1632
|
-
|
|
1633
|
-
RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE,
|
|
1693
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_EMBEDDABLE,
|
|
1634
1694
|
};
|
|
1635
1695
|
|
|
1636
1696
|
static VALUE cJSON_parser_s_allocate(VALUE klass)
|
|
@@ -48,7 +48,7 @@ module JSON
|
|
|
48
48
|
SCRIPT_SAFE_ESCAPE_PATTERN = /[\/"\\\x0-\x1f\u2028-\u2029]/
|
|
49
49
|
|
|
50
50
|
def self.native_type?(value) # :nodoc:
|
|
51
|
-
(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value)
|
|
51
|
+
(false == value || true == value || nil == value || String === value || Symbol === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
def self.native_key?(key) # :nodoc:
|
|
@@ -307,6 +307,9 @@ module JSON
|
|
|
307
307
|
if !opts.key?(:max_nesting) # defaults to 100
|
|
308
308
|
@max_nesting = 100
|
|
309
309
|
elsif opts[:max_nesting]
|
|
310
|
+
unless opts[:max_nesting].is_a?(Integer)
|
|
311
|
+
raise TypeError, ":max_nesting must be an Integer, got: #{opts[:max_nesting].class}"
|
|
312
|
+
end
|
|
310
313
|
@max_nesting = opts[:max_nesting]
|
|
311
314
|
else
|
|
312
315
|
@max_nesting = 0
|
|
@@ -517,11 +520,11 @@ module JSON
|
|
|
517
520
|
|
|
518
521
|
if empty?
|
|
519
522
|
state.depth -= 1
|
|
520
|
-
return '{}'
|
|
523
|
+
return +'{}'
|
|
521
524
|
end
|
|
522
525
|
|
|
523
526
|
delim = ",#{state.object_nl}"
|
|
524
|
-
result =
|
|
527
|
+
result = "{#{state.object_nl}"
|
|
525
528
|
first = true
|
|
526
529
|
key_type = nil
|
|
527
530
|
indent = !state.object_nl.empty?
|
|
@@ -558,7 +561,7 @@ module JSON
|
|
|
558
561
|
raise TypeError, "#{key.class}#to_s returns an instance of #{key_str.class}, expected a String"
|
|
559
562
|
end
|
|
560
563
|
|
|
561
|
-
result =
|
|
564
|
+
result = "#{result}#{key_json}#{state.space_before}:#{state.space}"
|
|
562
565
|
if state.strict? && !Generator.native_type?(value)
|
|
563
566
|
if state.as_json
|
|
564
567
|
value = state.as_json.call(value, false)
|
|
@@ -609,7 +612,7 @@ module JSON
|
|
|
609
612
|
|
|
610
613
|
if empty?
|
|
611
614
|
state.depth -= 1
|
|
612
|
-
return '[]'
|
|
615
|
+
return +'[]'
|
|
613
616
|
end
|
|
614
617
|
|
|
615
618
|
result = '['.dup
|
|
@@ -734,17 +737,17 @@ module JSON
|
|
|
734
737
|
|
|
735
738
|
module TrueClass
|
|
736
739
|
# Returns a JSON string for true: 'true'.
|
|
737
|
-
def to_json(*) 'true' end
|
|
740
|
+
def to_json(*) +'true' end
|
|
738
741
|
end
|
|
739
742
|
|
|
740
743
|
module FalseClass
|
|
741
744
|
# Returns a JSON string for false: 'false'.
|
|
742
|
-
def to_json(*) 'false' end
|
|
745
|
+
def to_json(*) +'false' end
|
|
743
746
|
end
|
|
744
747
|
|
|
745
748
|
module NilClass
|
|
746
749
|
# Returns a JSON string for nil: 'null'.
|
|
747
|
-
def to_json(*) 'null' end
|
|
750
|
+
def to_json(*) +'null' end
|
|
748
751
|
end
|
|
749
752
|
end
|
|
750
753
|
end
|
data/lib/json/version.rb
CHANGED
data/lib/json.rb
CHANGED
|
@@ -335,8 +335,8 @@ require 'json/common'
|
|
|
335
335
|
# JSON.generate(JSON::MinusInfinity)
|
|
336
336
|
#
|
|
337
337
|
# Allow:
|
|
338
|
-
# ruby = [Float::
|
|
339
|
-
# JSON.generate(ruby, allow_nan: true) # => '[NaN,Infinity,-Infinity]'
|
|
338
|
+
# ruby = [Float::NAN, Float::INFINITY, JSON::NaN, JSON::Infinity, JSON::MinusInfinity]
|
|
339
|
+
# JSON.generate(ruby, allow_nan: true) # => '[NaN,Infinity,NaN,Infinity,-Infinity]'
|
|
340
340
|
#
|
|
341
341
|
# ---
|
|
342
342
|
#
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: json
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.19.
|
|
4
|
+
version: 2.19.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Florian Frank
|
|
@@ -84,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
84
84
|
- !ruby/object:Gem::Version
|
|
85
85
|
version: '0'
|
|
86
86
|
requirements: []
|
|
87
|
-
rubygems_version: 4.0.
|
|
87
|
+
rubygems_version: 4.0.12
|
|
88
88
|
specification_version: 4
|
|
89
89
|
summary: JSON Implementation for Ruby
|
|
90
90
|
test_files: []
|