json 2.18.1 → 3.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb2890db4c527125d27bc7c21fc64d3ac532ffbec8080f89a678daf48c36e09e
4
- data.tar.gz: c4b37d085d05d3c43df97b3c24898dc6be61c76ba64c749b5a8a86bf4fc1198d
3
+ metadata.gz: b4a457db7bd0eba155736808c796e81741e7ea72438768e183a7b7a795e8de4e
4
+ data.tar.gz: 61bb013e78224c5c91f620d3097986735411befd5139f33eaecb2c3c510ababe
5
5
  SHA512:
6
- metadata.gz: fb55ef5a0aa6961ef0fe3bb30f398834820357045ad27a8fdb7e53eaba3af7c4d356ef26c0e73b7a87d2d9d51e500eae7193d7d1ae3aa1058c7973bcc462674b
7
- data.tar.gz: bfb499789bbcee7f5f8d67e32ded664dc62c632ae39fe80bf4bff3d6aec16eee3730a7c4883216a393326f2ab33e94e5f9c58da4c5b31627347108c36c2b211c
6
+ metadata.gz: 6e0b4b4a8160235dcef28c7924ecda754a7ae792a714e6b5fbc5baa810fd358129c4340dd1056d31df823bf139f2b42bccb9c2d400c7282079d3dcf3b2f8c2c8
7
+ data.tar.gz: e67782447e1934dcada56b848982e6bb15708997b9eca220f7b631470734e0b14b2b227ac82612b03b8eb6b3d54b7686d4627a6d551d05b8252da40203f07087
data/CHANGES.md CHANGED
@@ -2,6 +2,125 @@
2
2
 
3
3
  ### Unreleased
4
4
 
5
+ ### 2026-09-07 (3.0.0)
6
+
7
+ * Add `JSON::ParserError#json_path` to locate parse errors in the document as a JSONPath-style string (e.g. `$.foo[0].bar`). For duplicate key errors it points at the duplicated key itself.
8
+ * Fix the parser to also reject lone trailing UTF-16 surrogates (`\uDCxx` with no leading partner), symmetric to the leading-surrogate case. The Java parser already rejected these; this closes the CRuby/JRuby parity gap.
9
+
10
+ ### 2026-08-11 (3.0.0.rc1)
11
+
12
+ With the removal of the insecure `create_additions` option, `JSON.load` and `JSON.dump` are
13
+ now safe to use. Them being unsafe by default caused multiple security vulnerabilites in the past.
14
+
15
+ If you did depend on `create_additions`, the recommended migration is to [implement a custom serializer using
16
+ `JSON::Coder`](https://byroot.github.io/ruby/json/2025/08/02/whats-wrong-with-the-json-gem-api.html#the-create_additions-option).
17
+
18
+ All the mutable default options, such as `JSON.load_default_options` have been removed.
19
+ They were preventing Ractor compatiblity, and causing bug in libraries using JSON expecting the default behavior.
20
+ `JSON` methods now always behave the same unless monkey patched.
21
+
22
+ All methods options are now either keyword arguments or checked like keyword arguments, meaning
23
+ unknown options such as typos raise `ArgumentError`.
24
+
25
+ Duplicated keys are now rejected by default.
26
+
27
+ JavaScript comments in documents are no longer supported by default.
28
+
29
+ Numerous rarely used aliases have been removed.
30
+
31
+ * `JSON.load` defaults are now safe to use.
32
+ * All unknown options will now cause an `ArgumentError` rather than to be ignored.
33
+ * The `allow_comments` parsing option now defaults to `false`.
34
+ * The `allow_duplicate_key` option now defaults to `false`, for both parsing and generating JSON.
35
+ * Removed the `limit` positional argument of `JSON.dump`.
36
+ * Removed the `escape_slash` alias of `script_safe`.
37
+ * Removed `Kernel#j` and `Kernel#jj`.
38
+ * Removed `JSON.load_default_options`.
39
+ * Removed `JSON.unsafe_load_default_options`.
40
+ * Removed `JSON.dump_default_options`.
41
+ * Removed `JSON::State#[]` and `JSON::State#[]=`.
42
+ * Removed `JSON.unparse`.
43
+ * Removed `JSON.fast_generate`.
44
+ * Removed `JSON.fast_unparse`.
45
+ * Removed `JSON.pretty_unparse`.
46
+ * Removed `JSON.restore`.
47
+ * Removed `JSON::PRETTY_STATE_PROTOTYPE`.
48
+ * Removed the insecure `create_additions` option.
49
+ * Removed `JSON::GenericObject`.
50
+
51
+ ### 2026-07-31 (2.21.2)
52
+
53
+ * Fix a use-after-free bug in `JSON::ResumableParser`. [GHSA-9hj4-r449-hfvc][CVE-2026-71847].
54
+
55
+ ### 2026-07-13 (2.21.1)
56
+
57
+ * Fix a compilation issue on Window and Microsoft Visual C++.
58
+
59
+ ### 2026-07-12 (2.21.0)
60
+
61
+ * `JSON.generate` now accept a `sort_keys` option, which takes either a boolean or a block.
62
+ * Added `#empty?` and `#partial_value?` methods on `JSON::ResumableParser`.
63
+ * Numerous correctness and performance fixes for `JSON::ResumableParser`.
64
+ * Avoid triggering Ruby's `float out of range` warning when parsing out of range numbers.
65
+ * Declare C types with Ruby 4.1 `RUBY_TYPED_THREAD_SAFE_FREE`.
66
+
67
+ ### 2026-06-23 (2.20.0)
68
+
69
+ * Both C and Java parsers are no longer recursive, so parsing very deep documents with `max_nesting: false` will no longer
70
+ result in `SystemStackError stack level too deep` errors.
71
+ * The `:max_nesting` option still defaults to `100`.
72
+ * Optimized floating point number parsing further by replacing the ryu algorithm by a port of Eisel-Lemire Fast Float.
73
+ * Added `JSON::ResumableParser` to parse streams of JSON documents. Not yet available on JRuby.
74
+ * Deprecate default support of JavaScript comments in the parser and add `allow_comments: true` parsing option.
75
+ * Integrate with Ruby 4.1 `ruby_sized_xfree`.
76
+
77
+ ### 2026-06-11 (2.19.9)
78
+
79
+ * Fix buffer overflow that could lead to a crash when writing JSON directly into an IO
80
+ with `JSON.generate(object, io)`. [CVE-2026-54696].
81
+
82
+ ### 2026-06-03 (2.19.8)
83
+
84
+ * Fix 1-byte buffer overread on EOS errors.
85
+ * Handle invalid types passed as `max_nesting` option.
86
+
87
+ ### 2026-05-28 (2.19.7)
88
+
89
+ * Fix some more edge cases with out of range floats.
90
+ * Ensure the string provided to `JSON.parse` can't be mutated during parsing.
91
+ * Add missing write barriers in `State#dup`.
92
+ * Further validate generator `depth` config.
93
+
94
+ ### 2026-05-28 (2.19.6)
95
+
96
+ * Cleanly handle overly large `depth` generator argument.
97
+ * Add missing write barrier in `ParserConfig`.
98
+
99
+ ### 2026-05-04 (2.19.5)
100
+
101
+ * Cap the parser to emit a maximum of 5 deprecation warnings per document. Emitting more is not helpful.
102
+
103
+ ### 2026-04-19 (2.19.4)
104
+
105
+ * Fix parsing of out of range floats (very large exponents that lead to either `0.0` or `Inf`).
106
+
107
+ ### 2026-03-25 (2.19.3)
108
+
109
+ * Fix handling of unescaped control characters preceeded by a backslash.
110
+
111
+ ### 2026-03-18 (2.19.2)
112
+
113
+ * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
114
+
115
+ ### 2026-03-08 (2.19.1)
116
+
117
+ * Fix a compiler dependent GC bug introduced in `2.18.0`.
118
+
119
+ ### 2026-03-06 (2.19.0)
120
+
121
+ * Fix `allow_blank` parsing option to no longer allow invalid types (e.g. `load([], allow_blank: true)` now raise a type error).
122
+ * Add `allow_invalid_escape` parsing option to ignore backslashes that aren't followed by one of the valid escape characters.
123
+
5
124
  ### 2026-02-03 (2.18.1)
6
125
 
7
126
  * Fix a potential crash in very specific circumstance if GC triggers during a call to `to_json`
@@ -11,6 +130,10 @@
11
130
 
12
131
  * Add `:allow_control_characters` parser options, to allow JSON strings containing unescaped ASCII control characters (e.g. newlines).
13
132
 
133
+ ### 2026-03-18 (2.17.1.2) - Security Backport
134
+
135
+ * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
136
+
14
137
  ### 2025-12-04 (2.17.1)
15
138
 
16
139
  * Fix a regression in parsing of unicode surogate pairs (`\uXX\uXX`) that could cause an invalid string to be returned.
@@ -37,6 +160,10 @@
37
160
  * Optimized numbers parsing using SWAR (thanks to Scott Myron).
38
161
  * Optimized parsing of pretty printed documents using SWAR (thanks to Scott Myron).
39
162
 
163
+ ### 2026-03-18 (2.15.2.1) - Security Backport
164
+
165
+ * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
166
+
40
167
  ### 2025-10-25 (2.15.2)
41
168
 
42
169
  * 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/ryu.h::
19
- This file is adapted from the Ryu algorithm by Ulf Adams https://github.com/ulfjack/ryu.
20
- It is dual-licensed under Apache License 2.0 OR Boost Software License 1.0.
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
@@ -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. To encode raw binary strings, that aren't UTF-8
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) or `fast_generate` (which doesn't do any of the security
53
- checks generate performs, e. g. nesting deepness checks).
49
+ verbosely and nicely).
54
50
 
55
51
  ## Casting non native types
56
52
 
@@ -85,7 +81,7 @@ Both of these behavior can be disabled using the `strict: true` option:
85
81
 
86
82
  ```ruby
87
83
  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)
84
+ JSON.generate(Position.new(1, 2), strict: true) # => Position not allowed in JSON (JSON::GeneratorError)
89
85
  ```
90
86
 
91
87
  ## JSON::Coder
@@ -117,13 +113,13 @@ It is also called for objects that do have a JSON equivalent, but are used as Ha
117
113
  as well as for strings that aren't valid UTF-8:
118
114
 
119
115
  ```ruby
120
- coder = JSON::Combining.new do |object, is_object_key|
116
+ coder = JSON::Coder.new do |object, is_object_key|
121
117
  case object
122
118
  when String
123
- if !string.valid_encoding? || string.encoding != Encoding::UTF_8
124
- Base64.encode64(string)
119
+ if !object.valid_encoding? || object.encoding != Encoding::UTF_8
120
+ Base64.encode64(object)
125
121
  else
126
- string
122
+ object
127
123
  end
128
124
  else
129
125
  object
@@ -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
- ## Round-tripping arbitrary types
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` (or `JSON.pretty_generate`) that creates a more readable
147
+ `JSON.pretty_generate` that creates a more readable
224
148
  output:
225
149
 
226
150
  ```ruby
@@ -245,9 +169,16 @@ 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.
172
+ ## Security
173
+
174
+ When parsing or serializing untrusted input, parser and generator options should never be user controlled.
175
+
176
+ ```ruby
177
+ # Dangerous, DO NOT DO THIS.
178
+ JSON.generate(params[:data], params[:options])
179
+ ```
180
+
181
+ Security vulnerability reports relying on attacker controlled parsing or generator options will be handled as regular bug fixes.
251
182
 
252
183
  ## Development
253
184
 
@@ -295,5 +226,3 @@ The latest version of this library can be downloaded at
295
226
  Online Documentation should be located at
296
227
 
297
228
  * https://www.rubydoc.info/gems/json
298
-
299
- [Ragel]: http://www.colm.net/open-source/ragel/
@@ -11,11 +11,11 @@ enum fbuffer_type {
11
11
 
12
12
  typedef struct FBufferStruct {
13
13
  enum fbuffer_type type;
14
- unsigned long initial_length;
15
- unsigned long len;
16
- unsigned long capa;
14
+ size_t initial_length;
15
+ size_t len;
16
+ size_t capa;
17
17
  #if JSON_DEBUG
18
- unsigned long requested;
18
+ size_t requested;
19
19
  #endif
20
20
  char *ptr;
21
21
  VALUE io;
@@ -32,25 +32,29 @@ typedef struct FBufferStruct {
32
32
 
33
33
  static void fbuffer_free(FBuffer *fb);
34
34
  static void fbuffer_clear(FBuffer *fb);
35
- static void fbuffer_append(FBuffer *fb, const char *newstr, unsigned long len);
35
+ static void fbuffer_append(FBuffer *fb, const char *newstr, size_t len);
36
36
  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 fbuffer_stack_init(FBuffer *fb, unsigned long initial_length, char *stack_buffer, long stack_buffer_size)
40
+ static void fbuffer_init(FBuffer *fb, size_t initial_length, VALUE io, char *stack_buffer, size_t stack_buffer_size)
41
41
  {
42
- fb->initial_length = (initial_length > 0) ? initial_length : FBUFFER_INITIAL_LENGTH_DEFAULT;
43
- if (stack_buffer) {
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;
50
54
  #endif
51
55
  }
52
56
 
53
- static inline void fbuffer_consumed(FBuffer *fb, unsigned long consumed)
57
+ static inline void fbuffer_consumed(FBuffer *fb, size_t consumed)
54
58
  {
55
59
  #if JSON_DEBUG
56
60
  if (consumed > fb->requested) {
@@ -64,7 +68,7 @@ static inline void fbuffer_consumed(FBuffer *fb, unsigned long consumed)
64
68
  static void fbuffer_free(FBuffer *fb)
65
69
  {
66
70
  if (fb->ptr && fb->type == FBUFFER_HEAP_ALLOCATED) {
67
- ruby_xfree(fb->ptr);
71
+ JSON_SIZED_FREE_N(fb->ptr, fb->capa);
68
72
  }
69
73
  }
70
74
 
@@ -79,48 +83,43 @@ static void fbuffer_flush(FBuffer *fb)
79
83
  fbuffer_clear(fb);
80
84
  }
81
85
 
82
- static void fbuffer_realloc(FBuffer *fb, unsigned long required)
86
+ static void fbuffer_realloc(FBuffer *fb, size_t new_capa)
83
87
  {
84
- if (required > fb->capa) {
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, required);
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
- REALLOC_N(fb->ptr, char, required);
95
+ JSON_SIZED_REALLOC_N(fb->ptr, char, new_capa, fb->capa);
92
96
  }
93
- fb->capa = required;
97
+ fb->capa = new_capa;
94
98
  }
95
99
  }
96
100
 
97
- static void fbuffer_do_inc_capa(FBuffer *fb, unsigned long requested)
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 < FBUFFER_IO_BUFFER_SIZE) {
101
- fbuffer_realloc(fb, FBUFFER_IO_BUFFER_SIZE);
102
- } else {
104
+ if (fb->capa != 0) {
103
105
  fbuffer_flush(fb);
104
- }
105
-
106
- if (RB_LIKELY(requested < fb->capa)) {
107
- return;
106
+ if (RB_LIKELY(requested < fb->capa)) {
107
+ return;
108
+ }
108
109
  }
109
110
  }
110
111
 
111
- unsigned long required;
112
+ size_t new_capa = fb->capa ? fb->capa : fb->initial_length;
113
+ size_t needed_capa = requested + fb->len;
112
114
 
113
- if (RB_UNLIKELY(!fb->ptr)) {
114
- fb->ptr = ALLOC_N(char, fb->initial_length);
115
- fb->capa = fb->initial_length;
115
+ while (new_capa < needed_capa) {
116
+ new_capa *= 2;
116
117
  }
117
118
 
118
- for (required = fb->capa; requested > required - fb->len; required <<= 1);
119
-
120
- fbuffer_realloc(fb, required);
119
+ fbuffer_realloc(fb, new_capa);
121
120
  }
122
121
 
123
- static inline void fbuffer_inc_capa(FBuffer *fb, unsigned long requested)
122
+ static inline void fbuffer_inc_capa(FBuffer *fb, size_t requested)
124
123
  {
125
124
  #if JSON_DEBUG
126
125
  fb->requested = requested;
@@ -131,13 +130,22 @@ static inline void fbuffer_inc_capa(FBuffer *fb, unsigned long requested)
131
130
  }
132
131
  }
133
132
 
134
- static inline void fbuffer_append_reserved(FBuffer *fb, const char *newstr, unsigned long len)
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
+
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);
137
145
  fbuffer_consumed(fb, len);
138
146
  }
139
147
 
140
- static inline void fbuffer_append(FBuffer *fb, const char *newstr, unsigned long len)
148
+ static inline void fbuffer_append(FBuffer *fb, const char *newstr, size_t len)
141
149
  {
142
150
  if (len > 0) {
143
151
  fbuffer_inc_capa(fb, len);
@@ -162,19 +170,20 @@ static inline void fbuffer_append_reserved_char(FBuffer *fb, char chr)
162
170
  static void fbuffer_append_str(FBuffer *fb, VALUE str)
163
171
  {
164
172
  const char *ptr;
165
- unsigned long len;
173
+ size_t len;
166
174
  RSTRING_GETMEM(str, ptr, len);
167
175
 
168
176
  fbuffer_append(fb, ptr, len);
177
+ RB_GC_GUARD(str);
169
178
  }
170
179
 
171
180
  static void fbuffer_append_str_repeat(FBuffer *fb, VALUE str, size_t repeat)
172
181
  {
173
182
  const char *ptr;
174
- unsigned long len;
183
+ size_t len;
175
184
  RSTRING_GETMEM(str, ptr, len);
176
185
 
177
- fbuffer_inc_capa(fb, repeat * len);
186
+ fbuffer_inc_capa(fb, fbuffer_size_mul_or_raise(repeat, len));
178
187
  while (repeat) {
179
188
  #if JSON_DEBUG
180
189
  fb->requested = len;
@@ -182,6 +191,7 @@ static void fbuffer_append_str_repeat(FBuffer *fb, VALUE str, size_t repeat)
182
191
  fbuffer_append_reserved(fb, ptr, len);
183
192
  repeat--;
184
193
  }
194
+ RB_GC_GUARD(str);
185
195
  }
186
196
 
187
197
  static inline void fbuffer_append_char(FBuffer *fb, char newchr)
@@ -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