json 2.19.3 → 2.21.1
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 +61 -1
- data/LEGAL +3 -3
- data/README.md +16 -7
- data/ext/json/ext/fbuffer/fbuffer.h +32 -24
- data/ext/json/ext/generator/extconf.rb +3 -0
- data/ext/json/ext/generator/generator.c +116 -35
- data/ext/json/ext/json.h +78 -0
- data/ext/json/ext/parser/extconf.rb +21 -0
- data/ext/json/ext/parser/parser.c +1580 -360
- data/ext/json/ext/vendor/fast_float_parser.h +814 -0
- data/lib/json/common.rb +9 -0
- data/lib/json/ext/generator/state.rb +1 -0
- data/lib/json/ext.rb +26 -0
- data/lib/json/truffle_ruby/generator.rb +47 -9
- data/lib/json/version.rb +1 -1
- data/lib/json.rb +23 -5
- metadata +3 -3
- data/ext/json/ext/vendor/ryu.h +0 -819
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bd45f22d143e0edbca7ee9ba8209d10fd58f1b21f5e095d7a4c2938d12ea1041
|
|
4
|
+
data.tar.gz: 7104d723fe198b21508897a5583fcb513407595df0fdb0240c0343481cac7e06
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 554e73a89035e122a9643ecdd611ca893dbcfee74d86b9dd1ed8ea1fb2d0ddfc84416458efeff5b71925922e8e59c1c60aa90a172a411c6a33e3069a41e328d5
|
|
7
|
+
data.tar.gz: 4746639e1e86fff01e23ecbc036bae1410e6e944687ad576daf297f4d50f792334d0bef5a2864b0f2fe7e32495d7c205be2b666c811985f7168d0c2c8b1892f8
|
data/CHANGES.md
CHANGED
|
@@ -2,13 +2,65 @@
|
|
|
2
2
|
|
|
3
3
|
### Unreleased
|
|
4
4
|
|
|
5
|
+
### 2026-07-13 (2.21.1)
|
|
6
|
+
|
|
7
|
+
* Fix a compilation issue on Window and Microsoft Visual C++.
|
|
8
|
+
|
|
9
|
+
### 2026-07-12 (2.21.0)
|
|
10
|
+
|
|
11
|
+
* `JSON.generate` now accept a `sort_keys` option, which takes either a boolean or a block.
|
|
12
|
+
* Added `#empty?` and `#partial_value?` methods on `JSON::ResumableParser`.
|
|
13
|
+
* Numerous correctness and performance fixes for `JSON::ResumableParser`.
|
|
14
|
+
* Avoid triggering Ruby's `float out of range` warning when parsing out of range numbers.
|
|
15
|
+
* Declare C types with Ruby 4.1 `RUBY_TYPED_THREAD_SAFE_FREE`.
|
|
16
|
+
|
|
17
|
+
### 2026-06-23 (2.20.0)
|
|
18
|
+
|
|
19
|
+
* Both C and Java parsers are no longer recursive, so parsing very deep documents with `max_nesting: false` will no longer
|
|
20
|
+
result in `SystemStackError stack level too deep` errors.
|
|
21
|
+
* The `:max_nesting` option still defaults to `100`.
|
|
22
|
+
* Optimized floating point number parsing further by replacing the ryu algorithm by a port of Eisel-Lemire Fast Float.
|
|
23
|
+
* Added `JSON::ResumableParser` to parse streams of JSON documents. Not yet available on JRuby.
|
|
24
|
+
* Deprecate default support of JavaScript comments in the parser and add `allow_comments: true` parsing option.
|
|
25
|
+
* Integrate with Ruby 4.1 `ruby_sized_xfree`.
|
|
26
|
+
|
|
27
|
+
### 2026-06-11 (2.19.9)
|
|
28
|
+
|
|
29
|
+
* Fix buffer overflow that could lead to a crash when writing JSON directly into an IO
|
|
30
|
+
with `JSON.generate(object, io)`. [CVE-2026-54696].
|
|
31
|
+
|
|
32
|
+
### 2026-06-03 (2.19.8)
|
|
33
|
+
|
|
34
|
+
* Fix 1-byte buffer overread on EOS errors.
|
|
35
|
+
* Handle invalid types passed as `max_nesting` option.
|
|
36
|
+
|
|
37
|
+
### 2026-05-28 (2.19.7)
|
|
38
|
+
|
|
39
|
+
* Fix some more edge cases with out of range floats.
|
|
40
|
+
* Ensure the string provided to `JSON.parse` can't be mutated during parsing.
|
|
41
|
+
* Add missing write barriers in `State#dup`.
|
|
42
|
+
* Further validate generator `depth` config.
|
|
43
|
+
|
|
44
|
+
### 2026-05-28 (2.19.6)
|
|
45
|
+
|
|
46
|
+
* Cleanly handle overly large `depth` generator argument.
|
|
47
|
+
* Add missing write barrier in `ParserConfig`.
|
|
48
|
+
|
|
49
|
+
### 2026-05-04 (2.19.5)
|
|
50
|
+
|
|
51
|
+
* Cap the parser to emit a maximum of 5 deprecation warnings per document. Emitting more is not helpful.
|
|
52
|
+
|
|
53
|
+
### 2026-04-19 (2.19.4)
|
|
54
|
+
|
|
55
|
+
* Fix parsing of out of range floats (very large exponents that lead to either `0.0` or `Inf`).
|
|
56
|
+
|
|
5
57
|
### 2026-03-25 (2.19.3)
|
|
6
58
|
|
|
7
59
|
* Fix handling of unescaped control characters preceeded by a backslash.
|
|
8
60
|
|
|
9
61
|
### 2026-03-18 (2.19.2)
|
|
10
62
|
|
|
11
|
-
* Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`.
|
|
63
|
+
* Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
|
|
12
64
|
|
|
13
65
|
### 2026-03-08 (2.19.1)
|
|
14
66
|
|
|
@@ -28,6 +80,10 @@
|
|
|
28
80
|
|
|
29
81
|
* Add `:allow_control_characters` parser options, to allow JSON strings containing unescaped ASCII control characters (e.g. newlines).
|
|
30
82
|
|
|
83
|
+
### 2026-03-18 (2.17.1.2) - Security Backport
|
|
84
|
+
|
|
85
|
+
* Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
|
|
86
|
+
|
|
31
87
|
### 2025-12-04 (2.17.1)
|
|
32
88
|
|
|
33
89
|
* Fix a regression in parsing of unicode surogate pairs (`\uXX\uXX`) that could cause an invalid string to be returned.
|
|
@@ -54,6 +110,10 @@
|
|
|
54
110
|
* Optimized numbers parsing using SWAR (thanks to Scott Myron).
|
|
55
111
|
* Optimized parsing of pretty printed documents using SWAR (thanks to Scott Myron).
|
|
56
112
|
|
|
113
|
+
### 2026-03-18 (2.15.2.1) - Security Backport
|
|
114
|
+
|
|
115
|
+
* Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
|
|
116
|
+
|
|
57
117
|
### 2025-10-25 (2.15.2)
|
|
58
118
|
|
|
59
119
|
* Fix `JSON::Coder` to have one dedicated depth counter per invocation.
|
data/LEGAL
CHANGED
|
@@ -15,6 +15,6 @@ ext/json/ext/vendor/jeaiii-ltoa.h::
|
|
|
15
15
|
This file is adapted from https://github.com/jeaiii/itoa
|
|
16
16
|
It is licensed under the MIT License
|
|
17
17
|
|
|
18
|
-
ext/json/ext/vendor/
|
|
19
|
-
This file is adapted from the
|
|
20
|
-
It is
|
|
18
|
+
ext/json/ext/vendor/fast_float_parser.h::
|
|
19
|
+
This file is adapted from the Fast Float C++ library by The fast_float authors https://github.com/fastfloat/fast_float
|
|
20
|
+
It is licensed under the MIT License
|
data/README.md
CHANGED
|
@@ -85,7 +85,7 @@ Both of these behavior can be disabled using the `strict: true` option:
|
|
|
85
85
|
|
|
86
86
|
```ruby
|
|
87
87
|
JSON.generate(Object.new, strict: true) # => Object not allowed in JSON (JSON::GeneratorError)
|
|
88
|
-
JSON.generate(Position.new(1, 2)) # => Position not allowed in JSON (JSON::GeneratorError)
|
|
88
|
+
JSON.generate(Position.new(1, 2), strict: true) # => Position not allowed in JSON (JSON::GeneratorError)
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
## JSON::Coder
|
|
@@ -117,13 +117,13 @@ It is also called for objects that do have a JSON equivalent, but are used as Ha
|
|
|
117
117
|
as well as for strings that aren't valid UTF-8:
|
|
118
118
|
|
|
119
119
|
```ruby
|
|
120
|
-
coder = JSON::
|
|
120
|
+
coder = JSON::Coder.new do |object, is_object_key|
|
|
121
121
|
case object
|
|
122
122
|
when String
|
|
123
|
-
if !
|
|
124
|
-
Base64.encode64(
|
|
123
|
+
if !object.valid_encoding? || object.encoding != Encoding::UTF_8
|
|
124
|
+
Base64.encode64(object)
|
|
125
125
|
else
|
|
126
|
-
|
|
126
|
+
object
|
|
127
127
|
end
|
|
128
128
|
else
|
|
129
129
|
object
|
|
@@ -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
|
|
@@ -295,5 +306,3 @@ The latest version of this library can be downloaded at
|
|
|
295
306
|
Online Documentation should be located at
|
|
296
307
|
|
|
297
308
|
* https://www.rubydoc.info/gems/json
|
|
298
|
-
|
|
299
|
-
[Ragel]: http://www.colm.net/open-source/ragel/
|
|
@@ -37,13 +37,17 @@ static void fbuffer_append_long(FBuffer *fb, long number);
|
|
|
37
37
|
static inline void fbuffer_append_char(FBuffer *fb, char newchr);
|
|
38
38
|
static VALUE fbuffer_finalize(FBuffer *fb);
|
|
39
39
|
|
|
40
|
-
static void
|
|
40
|
+
static void fbuffer_init(FBuffer *fb, size_t initial_length, VALUE io, char *stack_buffer, size_t stack_buffer_size)
|
|
41
41
|
{
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
if (RTEST(io)) {
|
|
43
|
+
JSON_ASSERT(fb->type == FBUFFER_HEAP_ALLOCATED);
|
|
44
|
+
fb->io = io;
|
|
45
|
+
fb->initial_length = (initial_length > 0) ? initial_length : FBUFFER_IO_BUFFER_SIZE;
|
|
46
|
+
} else {
|
|
44
47
|
fb->type = FBUFFER_STACK_ALLOCATED;
|
|
45
48
|
fb->ptr = stack_buffer;
|
|
46
49
|
fb->capa = stack_buffer_size;
|
|
50
|
+
fb->initial_length = (initial_length > 0) ? initial_length : FBUFFER_INITIAL_LENGTH_DEFAULT;
|
|
47
51
|
}
|
|
48
52
|
#if JSON_DEBUG
|
|
49
53
|
fb->requested = 0;
|
|
@@ -64,7 +68,7 @@ static inline void fbuffer_consumed(FBuffer *fb, size_t consumed)
|
|
|
64
68
|
static void fbuffer_free(FBuffer *fb)
|
|
65
69
|
{
|
|
66
70
|
if (fb->ptr && fb->type == FBUFFER_HEAP_ALLOCATED) {
|
|
67
|
-
|
|
71
|
+
JSON_SIZED_FREE_N(fb->ptr, fb->capa);
|
|
68
72
|
}
|
|
69
73
|
}
|
|
70
74
|
|
|
@@ -79,45 +83,40 @@ static void fbuffer_flush(FBuffer *fb)
|
|
|
79
83
|
fbuffer_clear(fb);
|
|
80
84
|
}
|
|
81
85
|
|
|
82
|
-
static void fbuffer_realloc(FBuffer *fb, size_t
|
|
86
|
+
static void fbuffer_realloc(FBuffer *fb, size_t new_capa)
|
|
83
87
|
{
|
|
84
|
-
if (
|
|
88
|
+
if (new_capa > fb->capa) {
|
|
85
89
|
if (fb->type == FBUFFER_STACK_ALLOCATED) {
|
|
86
90
|
const char *old_buffer = fb->ptr;
|
|
87
|
-
fb->ptr = ALLOC_N(char,
|
|
91
|
+
fb->ptr = ALLOC_N(char, new_capa);
|
|
88
92
|
fb->type = FBUFFER_HEAP_ALLOCATED;
|
|
89
93
|
MEMCPY(fb->ptr, old_buffer, char, fb->len);
|
|
90
94
|
} else {
|
|
91
|
-
|
|
95
|
+
JSON_SIZED_REALLOC_N(fb->ptr, char, new_capa, fb->capa);
|
|
92
96
|
}
|
|
93
|
-
fb->capa =
|
|
97
|
+
fb->capa = new_capa;
|
|
94
98
|
}
|
|
95
99
|
}
|
|
96
100
|
|
|
97
101
|
static void fbuffer_do_inc_capa(FBuffer *fb, size_t requested)
|
|
98
102
|
{
|
|
99
103
|
if (RB_UNLIKELY(fb->io)) {
|
|
100
|
-
if (fb->capa
|
|
101
|
-
fbuffer_realloc(fb, FBUFFER_IO_BUFFER_SIZE);
|
|
102
|
-
} else {
|
|
104
|
+
if (fb->capa != 0) {
|
|
103
105
|
fbuffer_flush(fb);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return;
|
|
106
|
+
if (RB_LIKELY(requested < fb->capa)) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
108
109
|
}
|
|
109
110
|
}
|
|
110
111
|
|
|
111
|
-
size_t
|
|
112
|
+
size_t new_capa = fb->capa ? fb->capa : fb->initial_length;
|
|
113
|
+
size_t needed_capa = requested + fb->len;
|
|
112
114
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
fb->capa = fb->initial_length;
|
|
115
|
+
while (new_capa < needed_capa) {
|
|
116
|
+
new_capa *= 2;
|
|
116
117
|
}
|
|
117
118
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
fbuffer_realloc(fb, required);
|
|
119
|
+
fbuffer_realloc(fb, new_capa);
|
|
121
120
|
}
|
|
122
121
|
|
|
123
122
|
static inline void fbuffer_inc_capa(FBuffer *fb, size_t requested)
|
|
@@ -131,6 +130,15 @@ static inline void fbuffer_inc_capa(FBuffer *fb, size_t requested)
|
|
|
131
130
|
}
|
|
132
131
|
}
|
|
133
132
|
|
|
133
|
+
static inline size_t fbuffer_size_mul_or_raise(size_t a, size_t b)
|
|
134
|
+
{
|
|
135
|
+
size_t result = a * b;
|
|
136
|
+
if (RB_UNLIKELY(a != 0 && (result / a) != b)) {
|
|
137
|
+
rb_raise(rb_eArgError, "Buffer overflow, the resulting document is too large to be generated");
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
|
|
134
142
|
static inline void fbuffer_append_reserved(FBuffer *fb, const char *newstr, size_t len)
|
|
135
143
|
{
|
|
136
144
|
MEMCPY(fb->ptr + fb->len, newstr, char, len);
|
|
@@ -175,7 +183,7 @@ static void fbuffer_append_str_repeat(FBuffer *fb, VALUE str, size_t repeat)
|
|
|
175
183
|
size_t len;
|
|
176
184
|
RSTRING_GETMEM(str, ptr, len);
|
|
177
185
|
|
|
178
|
-
fbuffer_inc_capa(fb, repeat
|
|
186
|
+
fbuffer_inc_capa(fb, fbuffer_size_mul_or_raise(repeat, len));
|
|
179
187
|
while (repeat) {
|
|
180
188
|
#if JSON_DEBUG
|
|
181
189
|
fb->requested = len;
|
|
@@ -5,6 +5,9 @@ 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
|
+
have_func("ruby_xfree_sized", "ruby.h") # RUBY_VERSION >= 4.1
|
|
10
|
+
|
|
8
11
|
$defs << "-DJSON_GENERATOR"
|
|
9
12
|
$defs << "-DJSON_DEBUG" if ENV.fetch("JSON_DEBUG", "0") != "0"
|
|
10
13
|
|
|
@@ -34,13 +34,14 @@ typedef struct JSON_Generator_StateStruct {
|
|
|
34
34
|
bool ascii_only;
|
|
35
35
|
bool script_safe;
|
|
36
36
|
bool strict;
|
|
37
|
+
VALUE sort_keys;
|
|
37
38
|
} JSON_Generator_State;
|
|
38
39
|
|
|
39
|
-
static VALUE mJSON, cState, cFragment, eGeneratorError, eNestingError, Encoding_UTF_8;
|
|
40
|
+
static VALUE mJSON, cState, cFragment, eGeneratorError, eNestingError, Encoding_UTF_8, default_sort_keys_proc;
|
|
40
41
|
|
|
41
42
|
static ID i_to_s, i_to_json, i_new, i_encode;
|
|
42
43
|
static VALUE sym_indent, sym_space, sym_space_before, sym_object_nl, sym_array_nl, sym_max_nesting, sym_allow_nan, sym_allow_duplicate_key,
|
|
43
|
-
sym_ascii_only, sym_depth, sym_buffer_initial_length, sym_script_safe, sym_escape_slash, sym_strict, sym_as_json;
|
|
44
|
+
sym_ascii_only, sym_depth, sym_buffer_initial_length, sym_script_safe, sym_escape_slash, sym_strict, sym_as_json, sym_sort_keys;
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
#define GET_STATE_TO(self, state) \
|
|
@@ -709,6 +710,7 @@ static void State_mark(void *ptr)
|
|
|
709
710
|
rb_gc_mark_movable(state->object_nl);
|
|
710
711
|
rb_gc_mark_movable(state->array_nl);
|
|
711
712
|
rb_gc_mark_movable(state->as_json);
|
|
713
|
+
rb_gc_mark_movable(state->sort_keys);
|
|
712
714
|
}
|
|
713
715
|
|
|
714
716
|
static void State_compact(void *ptr)
|
|
@@ -720,29 +722,27 @@ static void State_compact(void *ptr)
|
|
|
720
722
|
state->object_nl = rb_gc_location(state->object_nl);
|
|
721
723
|
state->array_nl = rb_gc_location(state->array_nl);
|
|
722
724
|
state->as_json = rb_gc_location(state->as_json);
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
static void State_free(void *ptr)
|
|
726
|
-
{
|
|
727
|
-
JSON_Generator_State *state = ptr;
|
|
728
|
-
ruby_xfree(state);
|
|
725
|
+
state->sort_keys = rb_gc_location(state->sort_keys);
|
|
729
726
|
}
|
|
730
727
|
|
|
731
728
|
static size_t State_memsize(const void *ptr)
|
|
732
729
|
{
|
|
730
|
+
#ifdef HAVE_RUBY_TYPED_EMBEDDABLE
|
|
731
|
+
return 0;
|
|
732
|
+
#else
|
|
733
733
|
return sizeof(JSON_Generator_State);
|
|
734
|
+
#endif
|
|
734
735
|
}
|
|
735
736
|
|
|
736
737
|
static const rb_data_type_t JSON_Generator_State_type = {
|
|
737
|
-
"JSON/Generator/State",
|
|
738
|
-
{
|
|
738
|
+
.wrap_struct_name = "JSON/Generator/State",
|
|
739
|
+
.function = {
|
|
739
740
|
.dmark = State_mark,
|
|
740
|
-
.dfree =
|
|
741
|
+
.dfree = RUBY_DEFAULT_FREE,
|
|
741
742
|
.dsize = State_memsize,
|
|
742
743
|
.dcompact = State_compact,
|
|
743
744
|
},
|
|
744
|
-
|
|
745
|
-
RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE,
|
|
745
|
+
.flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_EMBEDDABLE,
|
|
746
746
|
};
|
|
747
747
|
|
|
748
748
|
static void state_init(JSON_Generator_State *state)
|
|
@@ -772,6 +772,7 @@ static void vstate_spill(struct generate_json_data *data)
|
|
|
772
772
|
RB_OBJ_WRITTEN(vstate, Qundef, state->object_nl);
|
|
773
773
|
RB_OBJ_WRITTEN(vstate, Qundef, state->array_nl);
|
|
774
774
|
RB_OBJ_WRITTEN(vstate, Qundef, state->as_json);
|
|
775
|
+
RB_OBJ_WRITTEN(vstate, Qundef, state->sort_keys);
|
|
775
776
|
}
|
|
776
777
|
|
|
777
778
|
static inline VALUE json_call_to_json(struct generate_json_data *data, VALUE obj)
|
|
@@ -826,9 +827,17 @@ static VALUE encode_json_string_rescue(VALUE str, VALUE exception)
|
|
|
826
827
|
return Qundef;
|
|
827
828
|
}
|
|
828
829
|
|
|
830
|
+
static inline int json_str_coderange(VALUE str) {
|
|
831
|
+
int coderange = RB_ENC_CODERANGE(str);
|
|
832
|
+
if (coderange == RUBY_ENC_CODERANGE_UNKNOWN) {
|
|
833
|
+
coderange = rb_enc_str_coderange(str);
|
|
834
|
+
}
|
|
835
|
+
return coderange;
|
|
836
|
+
}
|
|
837
|
+
|
|
829
838
|
static inline bool valid_json_string_p(VALUE str)
|
|
830
839
|
{
|
|
831
|
-
int coderange =
|
|
840
|
+
int coderange = json_str_coderange(str);
|
|
832
841
|
|
|
833
842
|
if (RB_LIKELY(coderange == ENC_CODERANGE_7BIT)) {
|
|
834
843
|
return true;
|
|
@@ -841,12 +850,8 @@ static inline bool valid_json_string_p(VALUE str)
|
|
|
841
850
|
return false;
|
|
842
851
|
}
|
|
843
852
|
|
|
844
|
-
static
|
|
853
|
+
NOINLINE(static) VALUE convert_invalid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key)
|
|
845
854
|
{
|
|
846
|
-
if (RB_LIKELY(valid_json_string_p(str))) {
|
|
847
|
-
return str;
|
|
848
|
-
}
|
|
849
|
-
|
|
850
855
|
if (!as_json_called && data->state->strict && RTEST(data->state->as_json)) {
|
|
851
856
|
VALUE coerced_str = json_call_as_json(data->state, str, Qfalse);
|
|
852
857
|
if (coerced_str != str) {
|
|
@@ -882,6 +887,16 @@ static inline VALUE ensure_valid_encoding(struct generate_json_data *data, VALUE
|
|
|
882
887
|
return rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str);
|
|
883
888
|
}
|
|
884
889
|
|
|
890
|
+
ALWAYS_INLINE(static) VALUE ensure_valid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key)
|
|
891
|
+
{
|
|
892
|
+
if (RB_LIKELY(valid_json_string_p(str))) {
|
|
893
|
+
return str;
|
|
894
|
+
}
|
|
895
|
+
else {
|
|
896
|
+
return convert_invalid_encoding(data, str, as_json_called, is_key);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
885
900
|
static void raw_generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
886
901
|
{
|
|
887
902
|
fbuffer_append_char(buffer, '"');
|
|
@@ -900,7 +915,7 @@ static void raw_generate_json_string(FBuffer *buffer, struct generate_json_data
|
|
|
900
915
|
search.chunk_end = NULL;
|
|
901
916
|
#endif /* HAVE_SIMD */
|
|
902
917
|
|
|
903
|
-
switch (
|
|
918
|
+
switch (json_str_coderange(obj)) {
|
|
904
919
|
case ENC_CODERANGE_7BIT:
|
|
905
920
|
case ENC_CODERANGE_VALID:
|
|
906
921
|
if (RB_UNLIKELY(data->state->ascii_only)) {
|
|
@@ -1039,6 +1054,11 @@ static inline long increase_depth(struct generate_json_data *data)
|
|
|
1039
1054
|
|
|
1040
1055
|
static void generate_json_object(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
1041
1056
|
{
|
|
1057
|
+
if (RB_UNLIKELY(data->state->sort_keys)) {
|
|
1058
|
+
obj = rb_proc_call_with_block(data->state->sort_keys, 1, &obj, Qnil);
|
|
1059
|
+
Check_Type(obj, T_HASH);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1042
1062
|
long depth = increase_depth(data);
|
|
1043
1063
|
|
|
1044
1064
|
if (RHASH_SIZE(obj) == 0) {
|
|
@@ -1297,10 +1317,8 @@ static inline VALUE cState_partial_generate(VALUE self, VALUE obj, generator_fun
|
|
|
1297
1317
|
GET_STATE(self);
|
|
1298
1318
|
|
|
1299
1319
|
char stack_buffer[FBUFFER_STACK_SIZE];
|
|
1300
|
-
FBuffer buffer = {
|
|
1301
|
-
|
|
1302
|
-
};
|
|
1303
|
-
fbuffer_stack_init(&buffer, state->buffer_initial_length, stack_buffer, FBUFFER_STACK_SIZE);
|
|
1320
|
+
FBuffer buffer = { 0 };
|
|
1321
|
+
fbuffer_init(&buffer, state->buffer_initial_length, io, stack_buffer, FBUFFER_STACK_SIZE);
|
|
1304
1322
|
|
|
1305
1323
|
struct generate_json_data data = {
|
|
1306
1324
|
.buffer = &buffer,
|
|
@@ -1360,12 +1378,15 @@ static VALUE cState_init_copy(VALUE obj, VALUE orig)
|
|
|
1360
1378
|
if (!objState) rb_raise(rb_eArgError, "unallocated JSON::State");
|
|
1361
1379
|
|
|
1362
1380
|
MEMCPY(objState, origState, JSON_Generator_State, 1);
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1381
|
+
|
|
1382
|
+
RB_OBJ_WRITTEN(obj, Qundef, objState->indent);
|
|
1383
|
+
RB_OBJ_WRITTEN(obj, Qundef, objState->space);
|
|
1384
|
+
RB_OBJ_WRITTEN(obj, Qundef, objState->space_before);
|
|
1385
|
+
RB_OBJ_WRITTEN(obj, Qundef, objState->object_nl);
|
|
1386
|
+
RB_OBJ_WRITTEN(obj, Qundef, objState->array_nl);
|
|
1387
|
+
RB_OBJ_WRITTEN(obj, Qundef, objState->as_json);
|
|
1388
|
+
RB_OBJ_WRITTEN(obj, Qundef, objState->sort_keys);
|
|
1389
|
+
|
|
1369
1390
|
return obj;
|
|
1370
1391
|
}
|
|
1371
1392
|
|
|
@@ -1572,7 +1593,7 @@ static VALUE cState_max_nesting(VALUE self)
|
|
|
1572
1593
|
|
|
1573
1594
|
static long long_config(VALUE num)
|
|
1574
1595
|
{
|
|
1575
|
-
return RTEST(num) ?
|
|
1596
|
+
return RTEST(num) ? NUM2LONG(num) : 0;
|
|
1576
1597
|
}
|
|
1577
1598
|
|
|
1578
1599
|
// depth must never be negative; reject early with a clear error.
|
|
@@ -1583,6 +1604,9 @@ static long depth_config(VALUE num)
|
|
|
1583
1604
|
if (RB_UNLIKELY(d < 0)) {
|
|
1584
1605
|
rb_raise(rb_eArgError, "depth must be >= 0 (got %ld)", d);
|
|
1585
1606
|
}
|
|
1607
|
+
if (RB_UNLIKELY(d > INT_MAX)) {
|
|
1608
|
+
rb_raise(rb_eArgError, "depth is too large (got %ld)", d);
|
|
1609
|
+
}
|
|
1586
1610
|
return d;
|
|
1587
1611
|
}
|
|
1588
1612
|
|
|
@@ -1708,6 +1732,55 @@ static VALUE cState_ascii_only_set(VALUE self, VALUE enable)
|
|
|
1708
1732
|
return Qnil;
|
|
1709
1733
|
}
|
|
1710
1734
|
|
|
1735
|
+
static VALUE cState_set_default_sort_keys_proc(VALUE self, VALUE proc)
|
|
1736
|
+
{
|
|
1737
|
+
if (!rb_obj_is_proc(proc)) {
|
|
1738
|
+
rb_raise(rb_eTypeError, "sort_key_proc must be a Proc");
|
|
1739
|
+
}
|
|
1740
|
+
return default_sort_keys_proc = proc;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
static VALUE normalize_sort_keys(VALUE value)
|
|
1744
|
+
{
|
|
1745
|
+
if (rb_obj_is_proc(value)) {
|
|
1746
|
+
return value;
|
|
1747
|
+
} else if (value == Qtrue) {
|
|
1748
|
+
return default_sort_keys_proc;
|
|
1749
|
+
} else if (RTEST(value)) {
|
|
1750
|
+
rb_raise(rb_eTypeError, "The `sort_keys` argument must be a boolean or a Proc");
|
|
1751
|
+
} else {
|
|
1752
|
+
return Qfalse;
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
/*
|
|
1757
|
+
* call-seq: sort_keys
|
|
1758
|
+
*
|
|
1759
|
+
* Get the value of sort_keys.
|
|
1760
|
+
*/
|
|
1761
|
+
static VALUE cState_sort_keys_p(VALUE self)
|
|
1762
|
+
{
|
|
1763
|
+
GET_STATE(self);
|
|
1764
|
+
return state->sort_keys;
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
/*
|
|
1768
|
+
* call-seq: sort_keys=(value)
|
|
1769
|
+
*
|
|
1770
|
+
* value is a boolean or a proc. If the value is the boolean true, object keys
|
|
1771
|
+
* will be sorted lexicographically in ascending order.
|
|
1772
|
+
*
|
|
1773
|
+
* If the value is a proc, it receives the entire Hash and must return a Hash
|
|
1774
|
+
* with its pairs in the desired order, allowing for arbitrary sorting.
|
|
1775
|
+
*/
|
|
1776
|
+
static VALUE cState_sort_keys_set(VALUE self, VALUE value)
|
|
1777
|
+
{
|
|
1778
|
+
rb_check_frozen(self);
|
|
1779
|
+
GET_STATE(self);
|
|
1780
|
+
RB_OBJ_WRITE(self, &state->sort_keys, normalize_sort_keys(value));
|
|
1781
|
+
return Qnil;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1711
1784
|
static VALUE cState_allow_duplicate_key_p(VALUE self)
|
|
1712
1785
|
{
|
|
1713
1786
|
GET_STATE(self);
|
|
@@ -1818,6 +1891,9 @@ static int configure_state_i(VALUE key, VALUE val, VALUE _arg)
|
|
|
1818
1891
|
state->as_json_single_arg = proc && rb_proc_arity(proc) == 1;
|
|
1819
1892
|
state_write_value(data, &state->as_json, proc);
|
|
1820
1893
|
}
|
|
1894
|
+
else if (key == sym_sort_keys) {
|
|
1895
|
+
state_write_value(data, &state->sort_keys, normalize_sort_keys(val));
|
|
1896
|
+
}
|
|
1821
1897
|
return ST_CONTINUE;
|
|
1822
1898
|
}
|
|
1823
1899
|
|
|
@@ -1854,10 +1930,8 @@ static VALUE cState_m_do_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io,
|
|
|
1854
1930
|
configure_state(&state, Qfalse, opts);
|
|
1855
1931
|
|
|
1856
1932
|
char stack_buffer[FBUFFER_STACK_SIZE];
|
|
1857
|
-
FBuffer buffer = {
|
|
1858
|
-
|
|
1859
|
-
};
|
|
1860
|
-
fbuffer_stack_init(&buffer, state.buffer_initial_length, stack_buffer, FBUFFER_STACK_SIZE);
|
|
1933
|
+
FBuffer buffer = { 0 };
|
|
1934
|
+
fbuffer_init(&buffer, state.buffer_initial_length, io, stack_buffer, FBUFFER_STACK_SIZE);
|
|
1861
1935
|
|
|
1862
1936
|
struct generate_json_data data = {
|
|
1863
1937
|
.buffer = &buffer,
|
|
@@ -1897,6 +1971,8 @@ void Init_generator(void)
|
|
|
1897
1971
|
VALUE mExt = rb_define_module_under(mJSON, "Ext");
|
|
1898
1972
|
VALUE mGenerator = rb_define_module_under(mExt, "Generator");
|
|
1899
1973
|
|
|
1974
|
+
rb_global_variable(&default_sort_keys_proc);
|
|
1975
|
+
|
|
1900
1976
|
rb_global_variable(&eGeneratorError);
|
|
1901
1977
|
eGeneratorError = rb_path2class("JSON::GeneratorError");
|
|
1902
1978
|
|
|
@@ -1906,6 +1982,8 @@ void Init_generator(void)
|
|
|
1906
1982
|
cState = rb_define_class_under(mGenerator, "State", rb_cObject);
|
|
1907
1983
|
rb_define_alloc_func(cState, cState_s_allocate);
|
|
1908
1984
|
rb_define_singleton_method(cState, "from_state", cState_from_state_s, 1);
|
|
1985
|
+
rb_define_singleton_method(cState, "default_sort_keys_proc=", cState_set_default_sort_keys_proc, 1);
|
|
1986
|
+
|
|
1909
1987
|
rb_define_method(cState, "initialize", cState_initialize, -1);
|
|
1910
1988
|
rb_define_alias(cState, "initialize", "initialize"); // avoid method redefinition warnings
|
|
1911
1989
|
rb_define_private_method(cState, "_configure", cState_configure, 1);
|
|
@@ -1945,6 +2023,8 @@ void Init_generator(void)
|
|
|
1945
2023
|
rb_define_method(cState, "buffer_initial_length=", cState_buffer_initial_length_set, 1);
|
|
1946
2024
|
rb_define_method(cState, "generate", cState_generate, -1);
|
|
1947
2025
|
rb_define_method(cState, "_generate_no_fallback", cState_generate_no_fallback, -1);
|
|
2026
|
+
rb_define_method(cState, "sort_keys", cState_sort_keys_p, 0);
|
|
2027
|
+
rb_define_method(cState, "sort_keys=", cState_sort_keys_set, 1);
|
|
1948
2028
|
|
|
1949
2029
|
rb_define_private_method(cState, "allow_duplicate_key?", cState_allow_duplicate_key_p, 0);
|
|
1950
2030
|
|
|
@@ -1974,6 +2054,7 @@ void Init_generator(void)
|
|
|
1974
2054
|
sym_strict = ID2SYM(rb_intern("strict"));
|
|
1975
2055
|
sym_as_json = ID2SYM(rb_intern("as_json"));
|
|
1976
2056
|
sym_allow_duplicate_key = ID2SYM(rb_intern("allow_duplicate_key"));
|
|
2057
|
+
sym_sort_keys = ID2SYM(rb_intern("sort_keys"));
|
|
1977
2058
|
|
|
1978
2059
|
usascii_encindex = rb_usascii_encindex();
|
|
1979
2060
|
utf8_encindex = rb_utf8_encindex();
|