json 2.19.2 → 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 +39 -2
- 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 +123 -61
- 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,9 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
### Unreleased
|
|
4
4
|
|
|
5
|
-
### 2026-03
|
|
5
|
+
### 2026-06-03 (2.19.8)
|
|
6
6
|
|
|
7
|
-
* Fix
|
|
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
|
+
|
|
30
|
+
### 2026-03-25 (2.19.3)
|
|
31
|
+
|
|
32
|
+
* Fix handling of unescaped control characters preceeded by a backslash.
|
|
33
|
+
|
|
34
|
+
### 2026-03-18 (2.19.2)
|
|
35
|
+
|
|
36
|
+
* Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
|
|
8
37
|
|
|
9
38
|
### 2026-03-08 (2.19.1)
|
|
10
39
|
|
|
@@ -24,6 +53,10 @@
|
|
|
24
53
|
|
|
25
54
|
* Add `:allow_control_characters` parser options, to allow JSON strings containing unescaped ASCII control characters (e.g. newlines).
|
|
26
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
|
+
|
|
27
60
|
### 2025-12-04 (2.17.1)
|
|
28
61
|
|
|
29
62
|
* Fix a regression in parsing of unicode surogate pairs (`\uXX\uXX`) that could cause an invalid string to be returned.
|
|
@@ -50,6 +83,10 @@
|
|
|
50
83
|
* Optimized numbers parsing using SWAR (thanks to Scott Myron).
|
|
51
84
|
* Optimized parsing of pretty printed documents using SWAR (thanks to Scott Myron).
|
|
52
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
|
+
|
|
53
90
|
### 2025-10-25 (2.15.2)
|
|
54
91
|
|
|
55
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
|
|
|
@@ -758,7 +778,9 @@ NOINLINE(static) VALUE json_string_unescape(JSON_ParserState *state, JSON_Parser
|
|
|
758
778
|
}
|
|
759
779
|
raise_parse_error_at("invalid ASCII control character in string: %s", state, pe - 1);
|
|
760
780
|
}
|
|
761
|
-
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (config->allow_invalid_escape) {
|
|
762
784
|
APPEND_CHAR(*pe);
|
|
763
785
|
} else {
|
|
764
786
|
raise_parse_error_at("invalid escape character in string: %s", state, pe - 1);
|
|
@@ -841,7 +863,7 @@ NOINLINE(static) VALUE json_decode_large_float(const char *start, long len)
|
|
|
841
863
|
/* Ruby JSON optimized float decoder using vendored Ryu algorithm
|
|
842
864
|
* Accepts pre-extracted mantissa and exponent from first-pass validation
|
|
843
865
|
*/
|
|
844
|
-
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,
|
|
845
867
|
const char *start, const char *end)
|
|
846
868
|
{
|
|
847
869
|
if (RB_UNLIKELY(config->decimal_class)) {
|
|
@@ -849,13 +871,21 @@ static inline VALUE json_decode_float(JSON_ParserConfig *config, uint64_t mantis
|
|
|
849
871
|
return rb_funcallv(config->decimal_class, config->decimal_method_id, 1, &text);
|
|
850
872
|
}
|
|
851
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
|
+
|
|
852
882
|
// Fall back to rb_cstr_to_dbl for potential subnormals (rare edge case)
|
|
853
883
|
// Ryu has rounding issues with subnormals around 1e-310 (< 2.225e-308)
|
|
854
884
|
if (RB_UNLIKELY(mantissa_digits > 17 || mantissa_digits + exponent < -307)) {
|
|
855
885
|
return json_decode_large_float(start, end - start);
|
|
856
886
|
}
|
|
857
887
|
|
|
858
|
-
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));
|
|
859
889
|
}
|
|
860
890
|
|
|
861
891
|
static inline VALUE json_decode_array(JSON_ParserState *state, JSON_ParserConfig *config, long count)
|
|
@@ -909,9 +939,6 @@ NORETURN(static) void raise_duplicate_key_error(JSON_ParserState *state, VALUE d
|
|
|
909
939
|
cursor_position(state, &line, &column);
|
|
910
940
|
rb_str_concat(message, build_parse_error_message("", state, line, column)) ;
|
|
911
941
|
rb_exc_raise(parse_error_new(message, line, column));
|
|
912
|
-
|
|
913
|
-
raise_parse_error(RSTRING_PTR(message), state);
|
|
914
|
-
RB_GC_GUARD(message);
|
|
915
942
|
}
|
|
916
943
|
|
|
917
944
|
static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfig *config, size_t count)
|
|
@@ -926,7 +953,12 @@ static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfi
|
|
|
926
953
|
case JSON_IGNORE:
|
|
927
954
|
break;
|
|
928
955
|
case JSON_DEPRECATED:
|
|
929
|
-
|
|
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
|
+
|
|
930
962
|
break;
|
|
931
963
|
case JSON_RAISE:
|
|
932
964
|
raise_duplicate_key_error(state, json_find_duplicated_key(count, pairs));
|
|
@@ -948,7 +980,7 @@ static inline VALUE json_push_value(JSON_ParserState *state, JSON_ParserConfig *
|
|
|
948
980
|
if (RB_UNLIKELY(config->on_load_proc)) {
|
|
949
981
|
value = rb_proc_call_with_block(config->on_load_proc, 1, &value, Qnil);
|
|
950
982
|
}
|
|
951
|
-
rvalue_stack_push(state->stack, value,
|
|
983
|
+
rvalue_stack_push(state->stack, value, state->stack_handle, &state->stack);
|
|
952
984
|
return value;
|
|
953
985
|
}
|
|
954
986
|
|
|
@@ -997,6 +1029,13 @@ ALWAYS_INLINE(static) bool string_scan(JSON_ParserState *state)
|
|
|
997
1029
|
}
|
|
998
1030
|
state->cursor++;
|
|
999
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
|
+
|
|
1000
1039
|
return false;
|
|
1001
1040
|
}
|
|
1002
1041
|
|
|
@@ -1133,7 +1172,7 @@ static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig
|
|
|
1133
1172
|
const char first_digit = *state->cursor;
|
|
1134
1173
|
|
|
1135
1174
|
// Variables for Ryu optimization - extract digits during parsing
|
|
1136
|
-
|
|
1175
|
+
int64_t exponent = 0;
|
|
1137
1176
|
int decimal_point_pos = -1;
|
|
1138
1177
|
uint64_t mantissa = 0;
|
|
1139
1178
|
|
|
@@ -1177,7 +1216,11 @@ static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig
|
|
|
1177
1216
|
raise_parse_error_at("invalid number: %s", state, start);
|
|
1178
1217
|
}
|
|
1179
1218
|
|
|
1180
|
-
|
|
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
|
+
}
|
|
1181
1224
|
}
|
|
1182
1225
|
|
|
1183
1226
|
if (integer) {
|
|
@@ -1432,23 +1475,39 @@ static void json_ensure_eof(JSON_ParserState *state)
|
|
|
1432
1475
|
|
|
1433
1476
|
static VALUE convert_encoding(VALUE source)
|
|
1434
1477
|
{
|
|
1435
|
-
|
|
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
|
+
}
|
|
1436
1489
|
|
|
1437
|
-
|
|
1490
|
+
source = rb_funcall(source, i_encode, 1, Encoding_UTF_8);
|
|
1491
|
+
StringValue(source);
|
|
1438
1492
|
return source;
|
|
1439
|
-
|
|
1493
|
+
}
|
|
1440
1494
|
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1495
|
+
struct parser_config_init_args {
|
|
1496
|
+
JSON_ParserConfig *config;
|
|
1497
|
+
VALUE self;
|
|
1498
|
+
};
|
|
1445
1499
|
|
|
1446
|
-
|
|
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);
|
|
1447
1504
|
}
|
|
1448
1505
|
|
|
1449
1506
|
static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
|
|
1450
1507
|
{
|
|
1451
|
-
|
|
1508
|
+
struct parser_config_init_args *args = (struct parser_config_init_args *)data;
|
|
1509
|
+
JSON_ParserConfig *config = args->config;
|
|
1510
|
+
VALUE self = args->self;
|
|
1452
1511
|
|
|
1453
1512
|
if (key == sym_max_nesting) { config->max_nesting = RTEST(val) ? FIX2INT(val) : 0; }
|
|
1454
1513
|
else if (key == sym_allow_nan) { config->allow_nan = RTEST(val); }
|
|
@@ -1457,15 +1516,15 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
|
|
|
1457
1516
|
else if (key == sym_allow_invalid_escape) { config->allow_invalid_escape = RTEST(val); }
|
|
1458
1517
|
else if (key == sym_symbolize_names) { config->symbolize_names = RTEST(val); }
|
|
1459
1518
|
else if (key == sym_freeze) { config->freeze = RTEST(val); }
|
|
1460
|
-
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); }
|
|
1461
1520
|
else if (key == sym_allow_duplicate_key) { config->on_duplicate_key = RTEST(val) ? JSON_IGNORE : JSON_RAISE; }
|
|
1462
1521
|
else if (key == sym_decimal_class) {
|
|
1463
1522
|
if (RTEST(val)) {
|
|
1464
1523
|
if (rb_respond_to(val, i_try_convert)) {
|
|
1465
|
-
config->decimal_class
|
|
1524
|
+
parser_config_wb_write(self, &config->decimal_class, val);
|
|
1466
1525
|
config->decimal_method_id = i_try_convert;
|
|
1467
1526
|
} else if (rb_respond_to(val, i_new)) {
|
|
1468
|
-
config->decimal_class
|
|
1527
|
+
parser_config_wb_write(self, &config->decimal_class, val);
|
|
1469
1528
|
config->decimal_method_id = i_new;
|
|
1470
1529
|
} else if (RB_TYPE_P(val, T_CLASS)) {
|
|
1471
1530
|
VALUE name = rb_class_name(val);
|
|
@@ -1474,7 +1533,7 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
|
|
|
1474
1533
|
if (last_colon) {
|
|
1475
1534
|
const char *mod_path_end = last_colon - 1;
|
|
1476
1535
|
VALUE mod_path = rb_str_substr(name, 0, mod_path_end - name_cstr);
|
|
1477
|
-
config->decimal_class
|
|
1536
|
+
parser_config_wb_write(self, &config->decimal_class, rb_path_to_class(mod_path));
|
|
1478
1537
|
|
|
1479
1538
|
const char *method_name_beg = last_colon + 1;
|
|
1480
1539
|
long before_len = method_name_beg - name_cstr;
|
|
@@ -1482,7 +1541,7 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
|
|
|
1482
1541
|
VALUE method_name = rb_str_substr(name, before_len, len);
|
|
1483
1542
|
config->decimal_method_id = SYM2ID(rb_str_intern(method_name));
|
|
1484
1543
|
} else {
|
|
1485
|
-
config->decimal_class
|
|
1544
|
+
parser_config_wb_write(self, &config->decimal_class, rb_mKernel);
|
|
1486
1545
|
config->decimal_method_id = SYM2ID(rb_str_intern(name));
|
|
1487
1546
|
}
|
|
1488
1547
|
}
|
|
@@ -1492,16 +1551,21 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
|
|
|
1492
1551
|
return ST_CONTINUE;
|
|
1493
1552
|
}
|
|
1494
1553
|
|
|
1495
|
-
static void parser_config_init(JSON_ParserConfig *config, VALUE opts)
|
|
1554
|
+
static void parser_config_init(JSON_ParserConfig *config, VALUE opts, VALUE self)
|
|
1496
1555
|
{
|
|
1497
1556
|
config->max_nesting = 100;
|
|
1498
1557
|
|
|
1558
|
+
struct parser_config_init_args args = {
|
|
1559
|
+
.config = config,
|
|
1560
|
+
.self = self,
|
|
1561
|
+
};
|
|
1562
|
+
|
|
1499
1563
|
if (!NIL_P(opts)) {
|
|
1500
1564
|
Check_Type(opts, T_HASH);
|
|
1501
1565
|
if (RHASH_SIZE(opts) > 0) {
|
|
1502
1566
|
// We assume in most cases few keys are set so it's faster to go over
|
|
1503
1567
|
// the provided keys than to check all possible keys.
|
|
1504
|
-
rb_hash_foreach(opts, parser_config_init_i, (VALUE)
|
|
1568
|
+
rb_hash_foreach(opts, parser_config_init_i, (VALUE)&args);
|
|
1505
1569
|
}
|
|
1506
1570
|
|
|
1507
1571
|
}
|
|
@@ -1535,17 +1599,21 @@ static VALUE cParserConfig_initialize(VALUE self, VALUE opts)
|
|
|
1535
1599
|
rb_check_frozen(self);
|
|
1536
1600
|
GET_PARSER_CONFIG;
|
|
1537
1601
|
|
|
1538
|
-
parser_config_init(config, opts);
|
|
1539
|
-
|
|
1540
|
-
RB_OBJ_WRITTEN(self, Qundef, config->decimal_class);
|
|
1602
|
+
parser_config_init(config, opts, self);
|
|
1541
1603
|
|
|
1542
1604
|
return self;
|
|
1543
1605
|
}
|
|
1544
1606
|
|
|
1545
|
-
static VALUE cParser_parse(JSON_ParserConfig *config, VALUE
|
|
1607
|
+
static VALUE cParser_parse(JSON_ParserConfig *config, VALUE src)
|
|
1546
1608
|
{
|
|
1547
|
-
Vsource = convert_encoding(
|
|
1548
|
-
|
|
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
|
+
}
|
|
1549
1617
|
|
|
1550
1618
|
VALUE rvalue_stack_buffer[RVALUE_STACK_INITIAL_CAPA];
|
|
1551
1619
|
rvalue_stack stack = {
|
|
@@ -1556,13 +1624,16 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE Vsource)
|
|
|
1556
1624
|
|
|
1557
1625
|
long len;
|
|
1558
1626
|
const char *start;
|
|
1627
|
+
|
|
1559
1628
|
RSTRING_GETMEM(Vsource, start, len);
|
|
1560
1629
|
|
|
1630
|
+
VALUE stack_handle = 0;
|
|
1561
1631
|
JSON_ParserState _state = {
|
|
1562
1632
|
.start = start,
|
|
1563
1633
|
.cursor = start,
|
|
1564
1634
|
.end = start + len,
|
|
1565
1635
|
.stack = &stack,
|
|
1636
|
+
.stack_handle = &stack_handle,
|
|
1566
1637
|
};
|
|
1567
1638
|
JSON_ParserState *state = &_state;
|
|
1568
1639
|
|
|
@@ -1570,8 +1641,9 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE Vsource)
|
|
|
1570
1641
|
|
|
1571
1642
|
// This may be skipped in case of exception, but
|
|
1572
1643
|
// it won't cause a leak.
|
|
1573
|
-
rvalue_stack_eagerly_release(
|
|
1574
|
-
|
|
1644
|
+
rvalue_stack_eagerly_release(stack_handle);
|
|
1645
|
+
RB_GC_GUARD(stack_handle);
|
|
1646
|
+
RB_GC_GUARD(Vsource);
|
|
1575
1647
|
json_ensure_eof(state);
|
|
1576
1648
|
|
|
1577
1649
|
return result;
|
|
@@ -1592,12 +1664,9 @@ static VALUE cParserConfig_parse(VALUE self, VALUE Vsource)
|
|
|
1592
1664
|
|
|
1593
1665
|
static VALUE cParser_m_parse(VALUE klass, VALUE Vsource, VALUE opts)
|
|
1594
1666
|
{
|
|
1595
|
-
Vsource = convert_encoding(StringValue(Vsource));
|
|
1596
|
-
StringValue(Vsource);
|
|
1597
|
-
|
|
1598
1667
|
JSON_ParserConfig _config = {0};
|
|
1599
1668
|
JSON_ParserConfig *config = &_config;
|
|
1600
|
-
parser_config_init(config, opts);
|
|
1669
|
+
parser_config_init(config, opts, false);
|
|
1601
1670
|
|
|
1602
1671
|
return cParser_parse(config, Vsource);
|
|
1603
1672
|
}
|
|
@@ -1609,26 +1678,19 @@ static void JSON_ParserConfig_mark(void *ptr)
|
|
|
1609
1678
|
rb_gc_mark(config->decimal_class);
|
|
1610
1679
|
}
|
|
1611
1680
|
|
|
1612
|
-
static void JSON_ParserConfig_free(void *ptr)
|
|
1613
|
-
{
|
|
1614
|
-
JSON_ParserConfig *config = ptr;
|
|
1615
|
-
ruby_xfree(config);
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
1681
|
static size_t JSON_ParserConfig_memsize(const void *ptr)
|
|
1619
1682
|
{
|
|
1620
1683
|
return sizeof(JSON_ParserConfig);
|
|
1621
1684
|
}
|
|
1622
1685
|
|
|
1623
1686
|
static const rb_data_type_t JSON_ParserConfig_type = {
|
|
1624
|
-
"JSON::Ext::Parser/ParserConfig",
|
|
1625
|
-
{
|
|
1687
|
+
.wrap_struct_name = "JSON::Ext::Parser/ParserConfig",
|
|
1688
|
+
.function = {
|
|
1626
1689
|
JSON_ParserConfig_mark,
|
|
1627
|
-
|
|
1690
|
+
RUBY_DEFAULT_FREE,
|
|
1628
1691
|
JSON_ParserConfig_memsize,
|
|
1629
1692
|
},
|
|
1630
|
-
|
|
1631
|
-
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,
|
|
1632
1694
|
};
|
|
1633
1695
|
|
|
1634
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: []
|