json 2.21.1 → 3.0.0.rc1
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 +45 -0
- data/README.md +4 -84
- data/ext/json/ext/generator/generator.c +17 -27
- data/ext/json/ext/json.h +13 -0
- data/ext/json/ext/parser/extconf.rb +6 -0
- data/ext/json/ext/parser/parser.c +36 -71
- data/lib/json/common.rb +86 -402
- data/lib/json/ext/generator/state.rb +0 -27
- data/lib/json/ext.rb +0 -2
- data/lib/json/truffle_ruby/generator.rb +38 -80
- data/lib/json/version.rb +1 -1
- data/lib/json.rb +24 -265
- metadata +2 -18
- data/lib/json/add/bigdecimal.rb +0 -58
- data/lib/json/add/complex.rb +0 -51
- data/lib/json/add/core.rb +0 -13
- data/lib/json/add/date.rb +0 -54
- data/lib/json/add/date_time.rb +0 -67
- data/lib/json/add/exception.rb +0 -49
- data/lib/json/add/ostruct.rb +0 -54
- data/lib/json/add/range.rb +0 -54
- data/lib/json/add/rational.rb +0 -49
- data/lib/json/add/regexp.rb +0 -48
- data/lib/json/add/set.rb +0 -48
- data/lib/json/add/string.rb +0 -35
- data/lib/json/add/struct.rb +0 -52
- data/lib/json/add/symbol.rb +0 -52
- data/lib/json/add/time.rb +0 -52
- data/lib/json/generic_object.rb +0 -67
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 297c71ea2d3c5ecf3b479b5b3708cb36ff532bd700e05e7eea48c53c0e79d96f
|
|
4
|
+
data.tar.gz: ca9386834406f49385b0e6e091f3f9db7ef66ccfe2632b3c87d340d212872d4d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 616c27a9a4ca01fce4bc13dc85da3e38bfbc8c13daa57c56396b77e61dcc215b1a6cf2994e1b7ba3c7bdbacf9c6b21ce7b7a147a057c9968d447ebe2f19039ba
|
|
7
|
+
data.tar.gz: 3f79549799744cfcc517fedcd722e1644fe44e8497176b5864f882174b48247e2b5c89e14f68acae527c3ee807908b20dc799daf04071acdf3ce363f235b492c
|
data/CHANGES.md
CHANGED
|
@@ -2,6 +2,51 @@
|
|
|
2
2
|
|
|
3
3
|
### Unreleased
|
|
4
4
|
|
|
5
|
+
### 2026-08-11 (3.0.0.rc1)
|
|
6
|
+
|
|
7
|
+
With the removal of the insecure `create_additions` option, `JSON.load` and `JSON.dump` are
|
|
8
|
+
now safe to use. Them being unsafe by default caused multiple security vulnerabilites in the past.
|
|
9
|
+
|
|
10
|
+
If you did depend on `create_additions`, the recommended migration is to [implement a custom serializer using
|
|
11
|
+
`JSON::Coder`](https://byroot.github.io/ruby/json/2025/08/02/whats-wrong-with-the-json-gem-api.html#the-create_additions-option).
|
|
12
|
+
|
|
13
|
+
All the mutable default options, such as `JSON.load_default_options` have been removed.
|
|
14
|
+
They were preventing Ractor compatiblity, and causing bug in libraries using JSON expecting the default behavior.
|
|
15
|
+
`JSON` methods now always behave the same unless monkey patched.
|
|
16
|
+
|
|
17
|
+
All methods options are now either keyword arguments or checked like keyword arguments, meaning
|
|
18
|
+
unknown options such as typos raise `ArgumentError`.
|
|
19
|
+
|
|
20
|
+
Duplicated keys are now rejected by default.
|
|
21
|
+
|
|
22
|
+
JavaScript comments in documents are no longer supported by default.
|
|
23
|
+
|
|
24
|
+
Numerous rarely used aliases have been removed.
|
|
25
|
+
|
|
26
|
+
* `JSON.load` defaults are now safe to use.
|
|
27
|
+
* All unknown options are now cause an `ArgumentError` rather than to be ignored.
|
|
28
|
+
* The `allow_comments` parsing option now default to `false`.
|
|
29
|
+
* The `allow_duplicate_key` option now defaults to `false`, for both parsing and generating JSON.
|
|
30
|
+
* Removed the `limit` positional argument of `JSON.dump`.
|
|
31
|
+
* Removed the `escape_slash` alias of `script_safe`.
|
|
32
|
+
* Removed `Kernel#j` and `Kernel#jj`.
|
|
33
|
+
* Removed `JSON.load_default_options`.
|
|
34
|
+
* Removed `JSON.unsafe_load_default_options`.
|
|
35
|
+
* Removed `JSON.dump_default_options`.
|
|
36
|
+
* Removed `JSON::State#[]` and `JSON::State#[]=`.
|
|
37
|
+
* Removed `JSON.unparse`.
|
|
38
|
+
* Removed `JSON.fast_generate`.
|
|
39
|
+
* Removed `JSON.fast_unparse`.
|
|
40
|
+
* Removed `JSON.pretty_unparse`.
|
|
41
|
+
* Removed `JSON.restore`.
|
|
42
|
+
* Removed `JSON::PRETTY_STATE_PROTOTYPE`.
|
|
43
|
+
* Removed the insecure `create_additions` option.
|
|
44
|
+
* Removed `JSON::GenericObject`.
|
|
45
|
+
|
|
46
|
+
### 2026-07-31 (2.21.2)
|
|
47
|
+
|
|
48
|
+
* Fix a use-after-free bug in `JSON::ResumableParser`. [GHSA-9hj4-r449-hfvc][CVE-2026-71847].
|
|
49
|
+
|
|
5
50
|
### 2026-07-13 (2.21.1)
|
|
6
51
|
|
|
7
52
|
* Fix a compilation issue on Window and Microsoft Visual C++.
|
data/README.md
CHANGED
|
@@ -14,10 +14,7 @@ UTF-16 surrogate pairs in order to be able to generate the whole range of
|
|
|
14
14
|
unicode code points.
|
|
15
15
|
|
|
16
16
|
All strings, that are to be encoded as JSON strings, should be UTF-8 byte
|
|
17
|
-
sequences on the Ruby side.
|
|
18
|
-
encoded, please use the to\_json\_raw\_object method of String (which produces
|
|
19
|
-
an object, that contains a byte array) and decode the result on the receiving
|
|
20
|
-
endpoint.
|
|
17
|
+
sequences on the Ruby side.
|
|
21
18
|
|
|
22
19
|
## Installation
|
|
23
20
|
|
|
@@ -49,8 +46,7 @@ JSON.generate(data)
|
|
|
49
46
|
```
|
|
50
47
|
|
|
51
48
|
You can also use the `pretty_generate` method (which formats the output more
|
|
52
|
-
verbosely and nicely)
|
|
53
|
-
checks generate performs, e. g. nesting deepness checks).
|
|
49
|
+
verbosely and nicely).
|
|
54
50
|
|
|
55
51
|
## Casting non native types
|
|
56
52
|
|
|
@@ -143,84 +139,12 @@ posts_json.map! { |post_json| JSON::Fragment.new(post_json) }
|
|
|
143
139
|
JSON.generate({ posts: posts_json, count: posts_json.count })
|
|
144
140
|
```
|
|
145
141
|
|
|
146
|
-
##
|
|
147
|
-
|
|
148
|
-
> [!CAUTION]
|
|
149
|
-
> You should never use `JSON.unsafe_load` nor `JSON.parse(str, create_additions: true)` to parse untrusted user input,
|
|
150
|
-
> as it can lead to remote code execution vulnerabilities.
|
|
151
|
-
|
|
152
|
-
To create a JSON document from a ruby data structure, you can call
|
|
153
|
-
`JSON.generate` like that:
|
|
154
|
-
|
|
155
|
-
```ruby
|
|
156
|
-
json = JSON.generate [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
|
|
157
|
-
# => "[1,2,{\"a\":3.141},false,true,null,\"4..10\"]"
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
To get back a ruby data structure from a JSON document, you have to call
|
|
161
|
-
JSON.parse on it:
|
|
162
|
-
|
|
163
|
-
```ruby
|
|
164
|
-
JSON.parse json
|
|
165
|
-
# => [1, 2, {"a"=>3.141}, false, true, nil, "4..10"]
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
Note, that the range from the original data structure is a simple
|
|
169
|
-
string now. The reason for this is, that JSON doesn't support ranges
|
|
170
|
-
or arbitrary classes. In this case the json library falls back to call
|
|
171
|
-
`Object#to_json`, which is the same as `#to_s.to_json`.
|
|
172
|
-
|
|
173
|
-
It's possible to add JSON support serialization to arbitrary classes by
|
|
174
|
-
simply implementing a more specialized version of the `#to_json method`, that
|
|
175
|
-
should return a JSON object (a hash converted to JSON with `#to_json`) like
|
|
176
|
-
this (don't forget the `*a` for all the arguments):
|
|
177
|
-
|
|
178
|
-
```ruby
|
|
179
|
-
class Range
|
|
180
|
-
def to_json(*a)
|
|
181
|
-
{
|
|
182
|
-
'json_class' => self.class.name, # = 'Range'
|
|
183
|
-
'data' => [ first, last, exclude_end? ]
|
|
184
|
-
}.to_json(*a)
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
The hash key `json_class` is the class, that will be asked to deserialise the
|
|
190
|
-
JSON representation later. In this case it's `Range`, but any namespace of
|
|
191
|
-
the form `A::B` or `::A::B` will do. All other keys are arbitrary and can be
|
|
192
|
-
used to store the necessary data to configure the object to be deserialised.
|
|
193
|
-
|
|
194
|
-
If the key `json_class` is found in a JSON object, the JSON parser checks
|
|
195
|
-
if the given class responds to the `json_create` class method. If so, it is
|
|
196
|
-
called with the JSON object converted to a Ruby hash. So a range can
|
|
197
|
-
be deserialised by implementing `Range.json_create` like this:
|
|
198
|
-
|
|
199
|
-
```ruby
|
|
200
|
-
class Range
|
|
201
|
-
def self.json_create(o)
|
|
202
|
-
new(*o['data'])
|
|
203
|
-
end
|
|
204
|
-
end
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
Now it possible to serialise/deserialise ranges as well:
|
|
208
|
-
|
|
209
|
-
```ruby
|
|
210
|
-
json = JSON.generate [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
|
|
211
|
-
# => "[1,2,{\"a\":3.141},false,true,null,{\"json_class\":\"Range\",\"data\":[4,10,false]}]"
|
|
212
|
-
JSON.parse json
|
|
213
|
-
# => [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
|
|
214
|
-
json = JSON.generate [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
|
|
215
|
-
# => "[1,2,{\"a\":3.141},false,true,null,{\"json_class\":\"Range\",\"data\":[4,10,false]}]"
|
|
216
|
-
JSON.unsafe_load json
|
|
217
|
-
# => [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
|
|
218
|
-
```
|
|
142
|
+
## Pretty Printing
|
|
219
143
|
|
|
220
144
|
`JSON.generate` always creates the shortest possible string representation of a
|
|
221
145
|
ruby data structure in one line. This is good for data storage or network
|
|
222
146
|
protocols, but not so good for humans to read. Fortunately there's also
|
|
223
|
-
`JSON.pretty_generate`
|
|
147
|
+
`JSON.pretty_generate` that creates a more readable
|
|
224
148
|
output:
|
|
225
149
|
|
|
226
150
|
```ruby
|
|
@@ -245,10 +169,6 @@ output:
|
|
|
245
169
|
]
|
|
246
170
|
```
|
|
247
171
|
|
|
248
|
-
There are also the methods `Kernel#j` for generate, and `Kernel#jj` for
|
|
249
|
-
`pretty_generate` output to the console, that work analogous to Core Ruby's `p` and
|
|
250
|
-
the `pp` library's `pp` methods.
|
|
251
|
-
|
|
252
172
|
## Security
|
|
253
173
|
|
|
254
174
|
When parsing or serializing untrusted input, parser and generator options should never be user controlled.
|
|
@@ -9,12 +9,6 @@
|
|
|
9
9
|
|
|
10
10
|
/* ruby api and some helpers */
|
|
11
11
|
|
|
12
|
-
enum duplicate_key_action {
|
|
13
|
-
JSON_DEPRECATED = 0,
|
|
14
|
-
JSON_IGNORE,
|
|
15
|
-
JSON_RAISE,
|
|
16
|
-
};
|
|
17
|
-
|
|
18
12
|
typedef struct JSON_Generator_StateStruct {
|
|
19
13
|
VALUE indent;
|
|
20
14
|
VALUE space;
|
|
@@ -27,8 +21,7 @@ typedef struct JSON_Generator_StateStruct {
|
|
|
27
21
|
long depth;
|
|
28
22
|
long buffer_initial_length;
|
|
29
23
|
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
bool allow_duplicate_key;
|
|
32
25
|
bool as_json_single_arg;
|
|
33
26
|
bool allow_nan;
|
|
34
27
|
bool ascii_only;
|
|
@@ -41,7 +34,7 @@ static VALUE mJSON, cState, cFragment, eGeneratorError, eNestingError, Encoding_
|
|
|
41
34
|
|
|
42
35
|
static ID i_to_s, i_to_json, i_new, i_encode;
|
|
43
36
|
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,
|
|
44
|
-
sym_ascii_only, sym_depth, sym_buffer_initial_length, sym_script_safe,
|
|
37
|
+
sym_ascii_only, sym_depth, sym_buffer_initial_length, sym_script_safe, sym_strict, sym_as_json, sym_sort_keys;
|
|
45
38
|
|
|
46
39
|
|
|
47
40
|
#define GET_STATE_TO(self, state) \
|
|
@@ -956,9 +949,8 @@ json_inspect_hash_with_mixed_keys(struct hash_foreach_arg *arg)
|
|
|
956
949
|
arg->mixed_keys_encountered = true;
|
|
957
950
|
|
|
958
951
|
JSON_Generator_State *state = arg->data->state;
|
|
959
|
-
if (state->
|
|
960
|
-
|
|
961
|
-
rb_funcall(mJSON, rb_intern("on_mixed_keys_hash"), 2, arg->hash, do_raise);
|
|
952
|
+
if (!state->allow_duplicate_key) {
|
|
953
|
+
rb_funcall(mJSON, rb_intern("on_mixed_keys_hash"), 1, arg->hash);
|
|
962
954
|
}
|
|
963
955
|
}
|
|
964
956
|
|
|
@@ -1784,14 +1776,7 @@ static VALUE cState_sort_keys_set(VALUE self, VALUE value)
|
|
|
1784
1776
|
static VALUE cState_allow_duplicate_key_p(VALUE self)
|
|
1785
1777
|
{
|
|
1786
1778
|
GET_STATE(self);
|
|
1787
|
-
|
|
1788
|
-
case JSON_IGNORE:
|
|
1789
|
-
return Qtrue;
|
|
1790
|
-
case JSON_DEPRECATED:
|
|
1791
|
-
return Qnil;
|
|
1792
|
-
default:
|
|
1793
|
-
return Qfalse;
|
|
1794
|
-
}
|
|
1779
|
+
return state->allow_duplicate_key ? Qtrue : Qfalse;
|
|
1795
1780
|
}
|
|
1796
1781
|
|
|
1797
1782
|
/*
|
|
@@ -1856,6 +1841,7 @@ static VALUE cState_buffer_initial_length_set(VALUE self, VALUE buffer_initial_l
|
|
|
1856
1841
|
struct configure_state_data {
|
|
1857
1842
|
JSON_Generator_State *state;
|
|
1858
1843
|
VALUE vstate; // Ruby object that owns the state, or Qfalse if stack-allocated
|
|
1844
|
+
VALUE unknown_keywords;
|
|
1859
1845
|
};
|
|
1860
1846
|
|
|
1861
1847
|
static inline void state_write_value(struct configure_state_data *data, VALUE *field, VALUE value)
|
|
@@ -1883,9 +1869,8 @@ static int configure_state_i(VALUE key, VALUE val, VALUE _arg)
|
|
|
1883
1869
|
else if (key == sym_depth) { state->depth = depth_config(val); }
|
|
1884
1870
|
else if (key == sym_buffer_initial_length) { buffer_initial_length_set(state, val); }
|
|
1885
1871
|
else if (key == sym_script_safe) { state->script_safe = RTEST(val); }
|
|
1886
|
-
else if (key == sym_escape_slash) { state->script_safe = RTEST(val); }
|
|
1887
1872
|
else if (key == sym_strict) { state->strict = RTEST(val); }
|
|
1888
|
-
else if (key == sym_allow_duplicate_key) { state->
|
|
1873
|
+
else if (key == sym_allow_duplicate_key) { state->allow_duplicate_key = RTEST(val); }
|
|
1889
1874
|
else if (key == sym_as_json) {
|
|
1890
1875
|
VALUE proc = RTEST(val) ? rb_convert_type(val, T_DATA, "Proc", "to_proc") : Qfalse;
|
|
1891
1876
|
state->as_json_single_arg = proc && rb_proc_arity(proc) == 1;
|
|
@@ -1894,6 +1879,12 @@ static int configure_state_i(VALUE key, VALUE val, VALUE _arg)
|
|
|
1894
1879
|
else if (key == sym_sort_keys) {
|
|
1895
1880
|
state_write_value(data, &state->sort_keys, normalize_sort_keys(val));
|
|
1896
1881
|
}
|
|
1882
|
+
else {
|
|
1883
|
+
if (!data->unknown_keywords) {
|
|
1884
|
+
data->unknown_keywords = rb_obj_hide(rb_ary_new());
|
|
1885
|
+
}
|
|
1886
|
+
rb_ary_push(data->unknown_keywords, key);
|
|
1887
|
+
}
|
|
1897
1888
|
return ST_CONTINUE;
|
|
1898
1889
|
}
|
|
1899
1890
|
|
|
@@ -1907,12 +1898,15 @@ static void configure_state(JSON_Generator_State *state, VALUE vstate, VALUE con
|
|
|
1907
1898
|
|
|
1908
1899
|
struct configure_state_data data = {
|
|
1909
1900
|
.state = state,
|
|
1910
|
-
.vstate = vstate
|
|
1901
|
+
.vstate = vstate,
|
|
1902
|
+
.unknown_keywords = Qfalse,
|
|
1911
1903
|
};
|
|
1912
1904
|
|
|
1913
1905
|
// We assume in most cases few keys are set so it's faster to go over
|
|
1914
1906
|
// the provided keys than to check all possible keys.
|
|
1915
1907
|
rb_hash_foreach(config, configure_state_i, (VALUE)&data);
|
|
1908
|
+
|
|
1909
|
+
raise_argument_error_on_unknown_keywords(data.unknown_keywords);
|
|
1916
1910
|
}
|
|
1917
1911
|
|
|
1918
1912
|
static VALUE cState_configure(VALUE self, VALUE opts)
|
|
@@ -2006,9 +2000,6 @@ void Init_generator(void)
|
|
|
2006
2000
|
rb_define_method(cState, "script_safe", cState_script_safe, 0);
|
|
2007
2001
|
rb_define_method(cState, "script_safe?", cState_script_safe, 0);
|
|
2008
2002
|
rb_define_method(cState, "script_safe=", cState_script_safe_set, 1);
|
|
2009
|
-
rb_define_alias(cState, "escape_slash", "script_safe");
|
|
2010
|
-
rb_define_alias(cState, "escape_slash?", "script_safe?");
|
|
2011
|
-
rb_define_alias(cState, "escape_slash=", "script_safe=");
|
|
2012
2003
|
rb_define_method(cState, "strict", cState_strict, 0);
|
|
2013
2004
|
rb_define_method(cState, "strict?", cState_strict, 0);
|
|
2014
2005
|
rb_define_method(cState, "strict=", cState_strict_set, 1);
|
|
@@ -2050,7 +2041,6 @@ void Init_generator(void)
|
|
|
2050
2041
|
sym_depth = ID2SYM(rb_intern("depth"));
|
|
2051
2042
|
sym_buffer_initial_length = ID2SYM(rb_intern("buffer_initial_length"));
|
|
2052
2043
|
sym_script_safe = ID2SYM(rb_intern("script_safe"));
|
|
2053
|
-
sym_escape_slash = ID2SYM(rb_intern("escape_slash"));
|
|
2054
2044
|
sym_strict = ID2SYM(rb_intern("strict"));
|
|
2055
2045
|
sym_as_json = ID2SYM(rb_intern("as_json"));
|
|
2056
2046
|
sym_allow_duplicate_key = ID2SYM(rb_intern("allow_duplicate_key"));
|
data/ext/json/ext/json.h
CHANGED
|
@@ -180,4 +180,17 @@ static inline VALUE json_rb_catch_obj(VALUE tag, VALUE (*func)(VALUE args), VALU
|
|
|
180
180
|
|
|
181
181
|
#endif // JSON_TRUFFLERUBY_RB_CATCH_BUG
|
|
182
182
|
|
|
183
|
+
static inline void raise_argument_error_on_unknown_keywords(VALUE unknown_keywords)
|
|
184
|
+
{
|
|
185
|
+
if (RB_UNLIKELY(unknown_keywords)) {
|
|
186
|
+
if (RARRAY_LEN(unknown_keywords) == 1) {
|
|
187
|
+
rb_raise(rb_eArgError, "unknown keyword: %" PRIsVALUE, RARRAY_AREF(unknown_keywords, 0));
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
VALUE keywords = rb_ary_join(unknown_keywords, rb_utf8_str_new_cstr(", "));
|
|
191
|
+
rb_raise(rb_eArgError, "unknown keywords: %" PRIsVALUE, keywords);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
183
196
|
#endif // _JSON_H_
|
|
@@ -30,6 +30,12 @@ end
|
|
|
30
30
|
|
|
31
31
|
append_cflags("-std=c99")
|
|
32
32
|
|
|
33
|
+
# Disable function outlining on clang to prevent some of the repeated instructions
|
|
34
|
+
# in json_eat_whitespace being outlined into function calls.
|
|
35
|
+
# Note: This verifies '-mno-outline' is accepted as a valid compiler flag
|
|
36
|
+
# and will not pass it if unsupported.
|
|
37
|
+
append_cflags("-mno-outline")
|
|
38
|
+
|
|
33
39
|
if enable_config('parser-use-simd', default=!ENV["JSON_DISABLE_SIMD"])
|
|
34
40
|
load __dir__ + "/../simd/conf.rb"
|
|
35
41
|
end
|
|
@@ -403,19 +403,13 @@ typedef struct json_frame_stack_struct {
|
|
|
403
403
|
json_frame *ptr;
|
|
404
404
|
} json_frame_stack;
|
|
405
405
|
|
|
406
|
-
enum deprecatable_action {
|
|
407
|
-
JSON_DEPRECATED = 0,
|
|
408
|
-
JSON_IGNORE,
|
|
409
|
-
JSON_RAISE,
|
|
410
|
-
};
|
|
411
|
-
|
|
412
406
|
typedef struct JSON_ParserStruct {
|
|
413
407
|
VALUE on_load_proc;
|
|
414
408
|
VALUE decimal_class;
|
|
415
409
|
ID decimal_method_id;
|
|
416
|
-
enum deprecatable_action on_duplicate_key;
|
|
417
|
-
enum deprecatable_action on_comment;
|
|
418
410
|
int max_nesting;
|
|
411
|
+
bool allow_comments;
|
|
412
|
+
bool allow_duplicate_key;
|
|
419
413
|
bool allow_nan;
|
|
420
414
|
bool allow_trailing_comma;
|
|
421
415
|
bool allow_control_characters;
|
|
@@ -435,7 +429,6 @@ typedef struct JSON_ParserStateStruct {
|
|
|
435
429
|
rvalue_cache name_cache;
|
|
436
430
|
int in_array;
|
|
437
431
|
int current_nesting;
|
|
438
|
-
unsigned int emitted_deprecations;
|
|
439
432
|
VALUE parser;
|
|
440
433
|
} JSON_ParserState;
|
|
441
434
|
|
|
@@ -589,6 +582,8 @@ static inline char peek(JSON_ParserState *state)
|
|
|
589
582
|
|
|
590
583
|
static void cursor_position(JSON_ParserState *state, long *line_out, long *column_out)
|
|
591
584
|
{
|
|
585
|
+
JSON_ASSERT(!state->parser);
|
|
586
|
+
JSON_ASSERT(state->cursor);
|
|
592
587
|
JSON_ASSERT(state->cursor <= state->end);
|
|
593
588
|
|
|
594
589
|
// Redundant but helpful for hardening
|
|
@@ -617,17 +612,6 @@ static void cursor_position(JSON_ParserState *state, long *line_out, long *colum
|
|
|
617
612
|
*column_out = column;
|
|
618
613
|
}
|
|
619
614
|
|
|
620
|
-
static const unsigned int MAX_DEPRECATIONS = 5;
|
|
621
|
-
|
|
622
|
-
static void emit_parse_warning(const char *message, JSON_ParserState *state)
|
|
623
|
-
{
|
|
624
|
-
long line, column;
|
|
625
|
-
cursor_position(state, &line, &column);
|
|
626
|
-
|
|
627
|
-
VALUE warning = rb_sprintf("%s at line %ld column %ld", message, line, column);
|
|
628
|
-
rb_funcall(mJSON, rb_intern("deprecation_warning"), 1, warning);
|
|
629
|
-
}
|
|
630
|
-
|
|
631
615
|
#define PARSE_ERROR_FRAGMENT_LEN 32
|
|
632
616
|
|
|
633
617
|
static VALUE build_parse_error_message(const char *format, JSON_ParserState *state)
|
|
@@ -766,11 +750,10 @@ static uint32_t unescape_unicode(JSON_ParserState *state, const char *sp, const
|
|
|
766
750
|
|
|
767
751
|
static const rb_data_type_t JSON_ParserConfig_type;
|
|
768
752
|
|
|
769
|
-
const char *COMMENT_DEPRECATION_MESSAGE = "Encountered comment in JSON. This will raise an error in json 3.0 unless enabled via `allow_comments: true`";
|
|
770
753
|
NOINLINE(static) void
|
|
771
754
|
json_eat_comments(JSON_ParserState *state, JSON_ParserConfig *config, const char *resume_pos)
|
|
772
755
|
{
|
|
773
|
-
if (config->
|
|
756
|
+
if (!config->allow_comments) {
|
|
774
757
|
raise_syntax_error("unexpected token %s", state);
|
|
775
758
|
}
|
|
776
759
|
|
|
@@ -820,11 +803,6 @@ json_eat_comments(JSON_ParserState *state, JSON_ParserConfig *config, const char
|
|
|
820
803
|
raise_parse_error_at("unexpected token %s", state, eos(state) ? rewind_pos : start, eos(state));
|
|
821
804
|
break;
|
|
822
805
|
}
|
|
823
|
-
|
|
824
|
-
if (config->on_comment == JSON_DEPRECATED && state->emitted_deprecations < MAX_DEPRECATIONS) {
|
|
825
|
-
state->emitted_deprecations++;
|
|
826
|
-
emit_parse_warning(COMMENT_DEPRECATION_MESSAGE, state);
|
|
827
|
-
}
|
|
828
806
|
}
|
|
829
807
|
|
|
830
808
|
ALWAYS_INLINE(static) void
|
|
@@ -1076,7 +1054,8 @@ NOINLINE(static) VALUE json_string_unescape(JSON_ParserState *state, JSON_Parser
|
|
|
1076
1054
|
return result;
|
|
1077
1055
|
}
|
|
1078
1056
|
|
|
1079
|
-
#define MAX_FAST_INTEGER_SIZE
|
|
1057
|
+
#define MAX_FAST_INTEGER_SIZE 19
|
|
1058
|
+
#define MAX_FAST_UINT64_SIZE 20
|
|
1080
1059
|
#define MAX_NUMBER_STACK_BUFFER 128
|
|
1081
1060
|
|
|
1082
1061
|
typedef VALUE (*json_number_decode_func_t)(const char *ptr);
|
|
@@ -1111,11 +1090,30 @@ NOINLINE(static) VALUE json_decode_large_integer(const char *start, long len)
|
|
|
1111
1090
|
|
|
1112
1091
|
static inline VALUE json_decode_integer(uint64_t mantissa, int mantissa_digits, bool negative, const char *start, const char *end)
|
|
1113
1092
|
{
|
|
1114
|
-
if (RB_LIKELY(mantissa_digits
|
|
1115
|
-
if (negative) {
|
|
1093
|
+
if (RB_LIKELY(mantissa_digits <= MAX_FAST_INTEGER_SIZE)) {
|
|
1094
|
+
if (RB_LIKELY(!negative)) {
|
|
1095
|
+
return UINT64T2NUM(mantissa);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// For a negative number 19 digits in length, we only get half of the range,
|
|
1099
|
+
// so ensure this negative number is less than INT64_MAX.
|
|
1100
|
+
//
|
|
1101
|
+
// Note: This does miss INT64_MIN as it's value is one past INT64_MAX
|
|
1102
|
+
// when converted to a uint64_t. It will still be parsed correctly by
|
|
1103
|
+
// falling through to json_decode_large_integer.
|
|
1104
|
+
if (RB_LIKELY(mantissa <= (uint64_t)INT64_MAX)) {
|
|
1116
1105
|
return INT64T2NUM(-((int64_t)mantissa));
|
|
1117
1106
|
}
|
|
1118
|
-
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
if (!negative && mantissa_digits == MAX_FAST_UINT64_SIZE) {
|
|
1110
|
+
// Not all 20 digit integers can be safely represented by a uint64_t but
|
|
1111
|
+
// some can. The memcmp with uint64_max is safe as we've rejected leading
|
|
1112
|
+
// zeros and we have guaranteed we're comparing it with a 20 digit number.
|
|
1113
|
+
static const char uint64_max[] = "18446744073709551615";
|
|
1114
|
+
if (memcmp(end - MAX_FAST_UINT64_SIZE, uint64_max, MAX_FAST_UINT64_SIZE) <= 0) {
|
|
1115
|
+
return UINT64T2NUM(mantissa);
|
|
1116
|
+
}
|
|
1119
1117
|
}
|
|
1120
1118
|
|
|
1121
1119
|
return json_decode_large_integer(start, end - start);
|
|
@@ -1193,17 +1191,6 @@ static VALUE json_find_duplicated_key(size_t count, const VALUE *pairs)
|
|
|
1193
1191
|
return Qfalse;
|
|
1194
1192
|
}
|
|
1195
1193
|
|
|
1196
|
-
NOINLINE(static) void emit_duplicate_key_warning(JSON_ParserState *state, VALUE duplicate_key)
|
|
1197
|
-
{
|
|
1198
|
-
VALUE message = rb_sprintf(
|
|
1199
|
-
"detected duplicate key %"PRIsVALUE" in JSON object. This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`",
|
|
1200
|
-
rb_inspect(duplicate_key)
|
|
1201
|
-
);
|
|
1202
|
-
|
|
1203
|
-
emit_parse_warning(RSTRING_PTR(message), state);
|
|
1204
|
-
RB_GC_GUARD(message);
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
1194
|
NORETURN(static) void raise_duplicate_key_error(JSON_ParserState *state, VALUE duplicate_key)
|
|
1208
1195
|
{
|
|
1209
1196
|
VALUE message = rb_sprintf(
|
|
@@ -1224,23 +1211,9 @@ NORETURN(static) void raise_duplicate_key_error(JSON_ParserState *state, VALUE d
|
|
|
1224
1211
|
|
|
1225
1212
|
NOINLINE(static) void json_on_duplicate_key(JSON_ParserState *state, JSON_ParserConfig *config, size_t count, const VALUE *pairs)
|
|
1226
1213
|
{
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
return;
|
|
1230
|
-
|
|
1231
|
-
case JSON_DEPRECATED:
|
|
1232
|
-
// Only emit the first few deprecations to avoid spamming.
|
|
1233
|
-
if (state->emitted_deprecations < MAX_DEPRECATIONS) {
|
|
1234
|
-
state->emitted_deprecations++;
|
|
1235
|
-
emit_duplicate_key_warning(state, json_find_duplicated_key(count, pairs));
|
|
1236
|
-
}
|
|
1237
|
-
return;
|
|
1238
|
-
|
|
1239
|
-
case JSON_RAISE:
|
|
1240
|
-
raise_duplicate_key_error(state, json_find_duplicated_key(count, pairs));
|
|
1241
|
-
return;
|
|
1214
|
+
if (!config->allow_duplicate_key) {
|
|
1215
|
+
raise_duplicate_key_error(state, json_find_duplicated_key(count, pairs));
|
|
1242
1216
|
}
|
|
1243
|
-
UNREACHABLE;
|
|
1244
1217
|
}
|
|
1245
1218
|
|
|
1246
1219
|
static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfig *config, size_t count)
|
|
@@ -2006,13 +1979,13 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
|
|
|
2006
1979
|
if (key == sym_max_nesting) { config->max_nesting = RTEST(val) ? FIX2INT(val) : 0; }
|
|
2007
1980
|
else if (key == sym_allow_nan) { config->allow_nan = RTEST(val); }
|
|
2008
1981
|
else if (key == sym_allow_trailing_comma) { config->allow_trailing_comma = RTEST(val); }
|
|
2009
|
-
else if (key == sym_allow_comments) { config->
|
|
1982
|
+
else if (key == sym_allow_comments) { config->allow_comments = RTEST(val); }
|
|
2010
1983
|
else if (key == sym_allow_control_characters) { config->allow_control_characters = RTEST(val); }
|
|
2011
1984
|
else if (key == sym_allow_invalid_escape) { config->allow_invalid_escape = RTEST(val); }
|
|
2012
1985
|
else if (key == sym_symbolize_names) { config->symbolize_names = RTEST(val); }
|
|
2013
1986
|
else if (key == sym_freeze) { config->freeze = RTEST(val); }
|
|
2014
1987
|
else if (key == sym_on_load) { parser_config_wb_write(self, &config->on_load_proc, RTEST(val) ? val : Qfalse); }
|
|
2015
|
-
else if (key == sym_allow_duplicate_key) { config->
|
|
1988
|
+
else if (key == sym_allow_duplicate_key) { config->allow_duplicate_key = RTEST(val); }
|
|
2016
1989
|
else if (key == sym_decimal_class) {
|
|
2017
1990
|
if (RTEST(val)) {
|
|
2018
1991
|
if (rb_respond_to(val, i_try_convert)) {
|
|
@@ -2042,7 +2015,7 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
|
|
|
2042
2015
|
}
|
|
2043
2016
|
}
|
|
2044
2017
|
}
|
|
2045
|
-
else
|
|
2018
|
+
else {
|
|
2046
2019
|
if (!args->unknown_keywords) {
|
|
2047
2020
|
args->unknown_keywords = rb_obj_hide(rb_ary_new());
|
|
2048
2021
|
}
|
|
@@ -2070,15 +2043,7 @@ static void parser_config_init(JSON_ParserConfig *config, VALUE opts, VALUE self
|
|
|
2070
2043
|
// the provided keys than to check all possible keys.
|
|
2071
2044
|
rb_hash_foreach(opts, parser_config_init_i, (VALUE)&args);
|
|
2072
2045
|
|
|
2073
|
-
|
|
2074
|
-
if (RARRAY_LEN(args.unknown_keywords) == 1) {
|
|
2075
|
-
rb_raise(rb_eArgError, "unknown keyword: %" PRIsVALUE, RARRAY_AREF(args.unknown_keywords, 0));
|
|
2076
|
-
}
|
|
2077
|
-
else {
|
|
2078
|
-
VALUE keywords = rb_ary_join(args.unknown_keywords, rb_utf8_str_new_cstr(", "));
|
|
2079
|
-
rb_raise(rb_eArgError, "unknown keywords: %" PRIsVALUE, keywords);
|
|
2080
|
-
}
|
|
2081
|
-
}
|
|
2046
|
+
raise_argument_error_on_unknown_keywords(args.unknown_keywords);
|
|
2082
2047
|
}
|
|
2083
2048
|
|
|
2084
2049
|
/*
|
|
@@ -2566,6 +2531,7 @@ static VALUE cResumableParser_parse(VALUE self)
|
|
|
2566
2531
|
if (eos(&parser->state)) {
|
|
2567
2532
|
json_str_clear(parser->buffer);
|
|
2568
2533
|
parser->buffer = Qfalse;
|
|
2534
|
+
parser->state.start = parser->state.cursor = parser->state.end = 0;
|
|
2569
2535
|
}
|
|
2570
2536
|
parser->in_use = false;
|
|
2571
2537
|
|
|
@@ -2639,7 +2605,6 @@ static VALUE cResumableParser_clear(VALUE self)
|
|
|
2639
2605
|
parser->state.name_cache.length = 0;
|
|
2640
2606
|
parser->state.current_nesting = 0;
|
|
2641
2607
|
parser->state.in_array = 1;
|
|
2642
|
-
parser->state.emitted_deprecations = 0;
|
|
2643
2608
|
parser->state.start = parser->state.cursor = parser->state.end = NULL;
|
|
2644
2609
|
return self;
|
|
2645
2610
|
}
|