json 2.10.2 → 2.19.9

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: b909ed90787afa31835c9926529808ca3c6b161755e1f6126b7fe203231fa0c2
4
- data.tar.gz: efd6a873e98f9e4b2b8c078210f141dfde5f3773884bd5851f228c184ff94ff7
3
+ metadata.gz: 5e5fed66aaa650ac7aaf223c4d8e505a2d6ce3fe72599af4931356ae619f020b
4
+ data.tar.gz: 347004780c7a7568685502ade79f145371a49a213d348a65c99ea8fbffc6eee7
5
5
  SHA512:
6
- metadata.gz: bff39363d5d7fa2f209d5bf97a5728738058bc83600b732d95487e263a5a3901b1e18d5c42d7d2be019ac55ef069a50cedc1d9a8d22b11e6061f2eef747dc94d
7
- data.tar.gz: 693e480f8f2489e26c43eef56ad8977ab3f00fada03318a48b88ac55e2b82b15ffae92fd05dd47d86686fae39169b079f59a68fffc6efaf44ee3503a45060e30
6
+ metadata.gz: 0bffdb4cd21e4656a1402a00d95ae890dc75befff379aeef4aad575a7f715a0dbeb87cfef6fefa3cd5235c3302a29fabb510d5db67e527678023cd8a88c34b49
7
+ data.tar.gz: 7e08cfae0c5d404687644528ecfb9438c9ea254f43fd285d116ba1542a4ef78045463b18850324e91ba4d0c735e7d9ed4b583499aadafe7d0bab51dd54a8d086
data/CHANGES.md CHANGED
@@ -1,10 +1,207 @@
1
1
  # Changes
2
2
 
3
+ ### Unreleased
4
+
5
+ ### 2026-06-11 (2.19.9)
6
+
7
+ * Fix buffer overflow that could lead to a crash when writing JSON directly into an IO
8
+ with `JSON.generate(object, io)`. [CVE-PENDING].
9
+
10
+ ### 2026-06-03 (2.19.8)
11
+
12
+ * Fix 1-byte buffer overread on EOS errors.
13
+ * Handle invalid types passed as `max_nesting` option.
14
+
15
+ ### 2026-05-28 (2.19.7)
16
+
17
+ * Fix some more edge cases with out of range floats.
18
+ * Ensure the string provided to `JSON.parse` can't be mutated during parsing.
19
+ * Add missing write barriers in `State#dup`.
20
+ * Further validate generator `depth` config.
21
+
22
+ ### 2026-05-28 (2.19.6)
23
+
24
+ * Cleanly handle overly large `depth` generator argument.
25
+ * Add missing write barrier in `ParserConfig`.
26
+
27
+ ### 2026-05-04 (2.19.5)
28
+
29
+ * Cap the parser to emit a maximum of 5 deprecation warnings per document. Emitting more is not helpful.
30
+
31
+ ### 2026-04-19 (2.19.4)
32
+
33
+ * Fix parsing of out of range floats (very large exponents that lead to either `0.0` or `Inf`).
34
+
35
+ ### 2026-03-25 (2.19.3)
36
+
37
+ * Fix handling of unescaped control characters preceeded by a backslash.
38
+
39
+ ### 2026-03-18 (2.19.2)
40
+
41
+ * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
42
+
43
+ ### 2026-03-08 (2.19.1)
44
+
45
+ * Fix a compiler dependent GC bug introduced in `2.18.0`.
46
+
47
+ ### 2026-03-06 (2.19.0)
48
+
49
+ * Fix `allow_blank` parsing option to no longer allow invalid types (e.g. `load([], allow_blank: true)` now raise a type error).
50
+ * Add `allow_invalid_escape` parsing option to ignore backslashes that aren't followed by one of the valid escape characters.
51
+
52
+ ### 2026-02-03 (2.18.1)
53
+
54
+ * Fix a potential crash in very specific circumstance if GC triggers during a call to `to_json`
55
+ without first invoking a user defined `#to_json` method.
56
+
57
+ ### 2025-12-11 (2.18.0)
58
+
59
+ * Add `:allow_control_characters` parser options, to allow JSON strings containing unescaped ASCII control characters (e.g. newlines).
60
+
61
+ ### 2026-03-18 (2.17.1.2) - Security Backport
62
+
63
+ * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
64
+
65
+ ### 2025-12-04 (2.17.1)
66
+
67
+ * Fix a regression in parsing of unicode surogate pairs (`\uXX\uXX`) that could cause an invalid string to be returned.
68
+
69
+ ### 2025-12-03 (2.17.0)
70
+
71
+ * Improve `JSON.load` and `JSON.unsafe_load` to allow passing options as second argument.
72
+ * Fix the parser to no longer ignore invalid escapes in strings.
73
+ Only `\"`, `\\`, `\b`, `\f`, `\n`, `\r`, `\t` and `\u` are valid JSON escapes.
74
+ * Fixed `JSON::Coder` to use the depth it was initialized with.
75
+ * On TruffleRuby, fix the generator to not call `to_json` on the return value of `as_json` for `Float::NAN`.
76
+ * Fixed handling of `state.depth`: when `to_json` changes `state.depth` but does not restore it, it is reset
77
+ automatically to its initial value.
78
+ In particular, when a `NestingError` is raised, `depth` is no longer equal to `max_nesting` after the call to
79
+ generate, and is reset to its initial value. Similarly when `to_json` raises an exception.
80
+
81
+ ### 2025-11-07 (2.16.0)
82
+
83
+ * Deprecate `JSON::State#[]` and `JSON::State#[]=`. Consider using `JSON::Coder` instead.
84
+ * `JSON::Coder` now also yields to the block when encountering strings with invalid encoding.
85
+ * Fix GeneratorError messages to be UTF-8 encoded.
86
+ * Fix memory leak when `Exception` is raised, or `throw` is used during JSON generation.
87
+ * Optimized floating point number parsing by integrating the ryu algorithm (thanks to Josef Šimánek).
88
+ * Optimized numbers parsing using SWAR (thanks to Scott Myron).
89
+ * Optimized parsing of pretty printed documents using SWAR (thanks to Scott Myron).
90
+
91
+ ### 2026-03-18 (2.15.2.1) - Security Backport
92
+
93
+ * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
94
+
95
+ ### 2025-10-25 (2.15.2)
96
+
97
+ * Fix `JSON::Coder` to have one dedicated depth counter per invocation.
98
+ After encountering a circular reference in `JSON::Coder#dump`, any further `#dump` call would raise `JSON::NestingError`.
99
+
100
+ ### 2025-10-07 (2.15.1)
101
+
102
+ * Fix incorrect escaping in the JRuby extension when encoding shared strings.
103
+
104
+ ### 2025-09-22 (2.15.0)
105
+
106
+ * `JSON::Coder` callback now receive a second argument to convey whether the object is a hash key.
107
+ * Tuned the floating point number generator to not use scientific notation as aggressively.
108
+
109
+ ### 2025-09-18 (2.14.1)
110
+
111
+ * Fix `IndexOutOfBoundsException` in the JRuby extension when encoding shared strings.
112
+
113
+ ### 2025-09-18 (2.14.0)
114
+
115
+ * Add new `allow_duplicate_key` generator options. By default a warning is now emitted when a duplicated key is encountered.
116
+ In `json 3.0` an error will be raised.
117
+ ```ruby
118
+ >> Warning[:deprecated] = true
119
+ >> puts JSON.generate({ foo: 1, "foo" => 2 })
120
+ (irb):2: warning: detected duplicate key "foo" in {foo: 1, "foo" => 2}.
121
+ This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`
122
+ {"foo":1,"foo":2}
123
+ >> JSON.generate({ foo: 1, "foo" => 2 }, allow_duplicate_key: false)
124
+ detected duplicate key "foo" in {foo: 1, "foo" => 2} (JSON::GeneratorError)
125
+ ```
126
+ * Fix `JSON.generate` `strict: true` mode to also restrict hash keys.
127
+ * Fix `JSON::Coder` to also invoke block for hash keys that aren't strings nor symbols.
128
+ * Fix `JSON.unsafe_load` usage with proc
129
+ * Fix the parser to more consistently reject invalid UTF-16 surogate pairs.
130
+ * Stop defining `String.json_create`, `String#to_json_raw`, `String#to_json_raw_object` when `json/add` isn't loaded.
131
+
132
+ ### 2025-07-28 (2.13.2)
133
+
134
+ * Improve duplicate key warning and errors to include the key name and point to the right caller.
135
+
136
+ ### 2025-07-24 (2.13.1)
137
+
138
+ * Fix support for older compilers without `__builtin_cpu_supports`.
139
+
140
+ ### 2025-07-17 (2.13.0)
141
+
142
+ * Add new `allow_duplicate_key` parsing options. By default a warning is now emitted when a duplicated key is encountered.
143
+ In `json 3.0` an error will be raised.
144
+ * Optimize parsing further using SIMD to scan strings.
145
+
146
+ ### 2025-05-23 (2.12.2)
147
+
148
+ * Fix compiler optimization level.
149
+
150
+ ### 2025-05-23 (2.12.1)
151
+
152
+ * Fix a potential crash in large negative floating point number generation.
153
+ * Fix for JSON.pretty_generate to use passed state object's generate instead of state class as the required parameters aren't available.
154
+
155
+ ### 2025-05-12 (2.12.0)
156
+
157
+ * Improve floating point generation to not use scientific notation as much.
158
+ * Include line and column in parser errors. Both in the message and as exception attributes.
159
+ * Handle non-string hash keys with broken `to_s` implementations.
160
+ * `JSON.generate` now uses SSE2 (x86) or NEON (arm64) instructions when available to escape strings.
161
+
162
+ ### 2025-04-25 (2.11.3)
163
+
164
+ * Fix a regression in `JSON.pretty_generate` that could cause indentation to be off once some `#to_json` has been called.
165
+
166
+ ### 2025-04-24 (2.11.2)
167
+
168
+ * Add back `JSON::PRETTY_STATE_PROTOTYPE`. This constant was private API but is used by popular gems like `multi_json`.
169
+ It now emits a deprecation warning.
170
+
171
+ ### 2025-04-24 (2.11.1)
172
+
173
+ * Add back `JSON.restore`, `JSON.unparse`, `JSON.fast_unparse` and `JSON.pretty_unparse`.
174
+ These were deprecated 16 years ago, but never emitted warnings, only undocumented, so are
175
+ still used by a few gems.
176
+
177
+ ### 2025-04-24 (2.11.0)
178
+
179
+ * Optimize Integer generation to be ~1.8x faster.
180
+ * Optimize Float generation to be ~10x faster.
181
+ * Fix `JSON.load` proc argument to substitute the parsed object with the return value.
182
+ This better match `Marshal.load` behavior.
183
+ * Deprecate `JSON.fast_generate` (it's not any faster, so pointless).
184
+ * Deprecate `JSON.load_default_options`.
185
+ * Deprecate `JSON.unsafe_load_default_options`.
186
+ * Deprecate `JSON.dump_default_options`.
187
+ * Deprecate `Kernel#j`
188
+ * Deprecate `Kernel#jj`
189
+ * Remove outdated `JSON.iconv`.
190
+ * Remove `Class#json_creatable?` monkey patch.
191
+ * Remove deprecated `JSON.restore` method.
192
+ * Remove deprecated `JSON.unparse` method.
193
+ * Remove deprecated `JSON.fast_unparse` method.
194
+ * Remove deprecated `JSON.pretty_unparse` method.
195
+ * Remove deprecated `JSON::UnparserError` constant.
196
+ * Remove outdated `JSON::MissingUnicodeSupport` constant.
197
+
3
198
  ### 2025-03-12 (2.10.2)
4
199
 
5
200
  * Fix a potential crash in the C extension parser.
6
- * Raise a ParserError on all incomplete unicode escape sequence. This was the behavior until `2.10.0` unadvertently changed it.
201
+ * Raise a ParserError on all incomplete unicode escape sequence. This was the behavior until `2.10.0` inadvertently changed it.
7
202
  * Ensure document snippets that are included in parser errors don't include truncated multibyte characters.
203
+ * Ensure parser error snippets are valid UTF-8.
204
+ * Fix `JSON::GeneratorError#detailed_message` on Ruby < 3.2
8
205
 
9
206
  ### 2025-02-10 (2.10.1)
10
207
 
@@ -32,7 +229,7 @@
32
229
 
33
230
  ### 2024-11-14 (2.8.2)
34
231
 
35
- * `JSON.load_file` explictly read the file as UTF-8.
232
+ * `JSON.load_file` explicitly read the file as UTF-8.
36
233
 
37
234
  ### 2024-11-06 (2.8.1)
38
235
 
@@ -40,7 +237,7 @@
40
237
 
41
238
  ### 2024-11-06 (2.8.0)
42
239
 
43
- * Emit a deprecation warning when `JSON.load` create custom types without the `create_additions` option being explictly enabled.
240
+ * Emit a deprecation warning when `JSON.load` create custom types without the `create_additions` option being explicitly enabled.
44
241
  * Prefer to use `JSON.unsafe_load(string)` or `JSON.load(string, create_additions: true)`.
45
242
  * Emit a deprecation warning when serializing valid UTF-8 strings encoded in `ASCII_8BIT` aka `BINARY`.
46
243
  * Bump required Ruby version to 2.7.
@@ -48,7 +245,7 @@
48
245
  pre-existing support for comments, make it suitable to parse `jsonc` documents.
49
246
  * Many performance improvements to `JSON.parse` and `JSON.load`, up to `1.7x` faster on real world documents.
50
247
  * Some minor performance improvements to `JSON.dump` and `JSON.generate`.
51
- * `JSON.pretty_generate` no longer include newline inside empty object and arrays.
248
+ * `JSON.pretty_generate` no longer includes newlines inside empty object and arrays.
52
249
 
53
250
  ### 2024-11-04 (2.7.6)
54
251
 
@@ -65,13 +262,13 @@
65
262
  * Workaround a bug in 3.4.8 and older https://github.com/rubygems/rubygems/pull/6490.
66
263
  This bug would cause some gems with native extension to fail during compilation.
67
264
  * Workaround different versions of `json` and `json_pure` being loaded (not officially supported).
68
- * Make `json_pure` Ractor compatible.
265
+ * Make `json_pure` Ractor compatible.
69
266
 
70
267
  ### 2024-10-24 (2.7.3)
71
268
 
72
269
  * Numerous performance optimizations in `JSON.generate` and `JSON.dump` (up to 2 times faster).
73
- * Limit the size of ParserError exception messages, only include up to 32 bytes of the unparseable source.
74
- * Fix json-pure's `Object#to_json` to accept non state arguments
270
+ * Limit the size of ParserError exception messages, only include up to 32 bytes of the unparsable source.
271
+ * Fix json-pure's `Object#to_json` to accept non-state arguments.
75
272
  * Fix multiline comment support in `json-pure`.
76
273
  * Fix `JSON.parse` to no longer mutate the argument encoding when passed an ASCII-8BIT string.
77
274
  * Fix `String#to_json` to raise on invalid encoding in `json-pure`.
@@ -216,6 +413,7 @@
216
413
  ## 2015-09-11 (2.0.0)
217
414
  * Now complies to newest JSON RFC 7159.
218
415
  * Implements compatibility to ruby 2.4 integer unification.
416
+ * Removed support for `quirks_mode` option.
219
417
  * Drops support for old rubies whose life has ended, that is rubies < 2.0.
220
418
  Also see https://www.ruby-lang.org/en/news/2014/07/01/eol-for-1-8-7-and-1-9-2/
221
419
  * There were still some mentions of dual GPL licensing in the source, but JSON
data/LEGAL CHANGED
@@ -6,3 +6,15 @@
6
6
  All the files in this distribution are covered under either the Ruby's
7
7
  license (see the file COPYING) or public-domain except some files
8
8
  mentioned below.
9
+
10
+ ext/json/ext/vendor/fpconv.h::
11
+ This file is adapted from https://github.com/night-shift/fpconv
12
+ It is licensed under Boost Software License 1.0.
13
+
14
+ ext/json/ext/vendor/jeaiii-ltoa.h::
15
+ This file is adapted from https://github.com/jeaiii/itoa
16
+ It is licensed under the MIT License
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.
data/README.md CHANGED
@@ -97,7 +97,7 @@ Instead it is recommended to use the newer `JSON::Coder` API:
97
97
 
98
98
  ```ruby
99
99
  module MyApp
100
- API_JSON_CODER = JSON::Coder.new do |object|
100
+ API_JSON_CODER = JSON::Coder.new do |object, is_object_key|
101
101
  case object
102
102
  when Time
103
103
  object.iso8601(3)
@@ -113,6 +113,24 @@ puts MyApp::API_JSON_CODER.dump(Time.now.utc) # => "2025-01-21T08:41:44.286Z"
113
113
  The provided block is called for all objects that don't have a native JSON equivalent, and
114
114
  must return a Ruby object that has a native JSON equivalent.
115
115
 
116
+ It is also called for objects that do have a JSON equivalent, but are used as Hash keys, for instance `{ 1 => 2}`,
117
+ as well as for strings that aren't valid UTF-8:
118
+
119
+ ```ruby
120
+ coder = JSON::Combining.new do |object, is_object_key|
121
+ case object
122
+ when String
123
+ if !string.valid_encoding? || string.encoding != Encoding::UTF_8
124
+ Base64.encode64(string)
125
+ else
126
+ string
127
+ end
128
+ else
129
+ object
130
+ end
131
+ end
132
+ ```
133
+
116
134
  ## Combining JSON fragments
117
135
 
118
136
  To combine JSON fragments into a bigger JSON document, you can use `JSON::Fragment`:
@@ -231,8 +249,32 @@ There are also the methods `Kernel#j` for generate, and `Kernel#jj` for
231
249
  `pretty_generate` output to the console, that work analogous to Core Ruby's `p` and
232
250
  the `pp` library's `pp` methods.
233
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
+
234
263
  ## Development
235
264
 
265
+ ### Prerequisites
266
+
267
+ 1. Clone the repository
268
+ 2. Install dependencies with `bundle install`
269
+
270
+ ### Testing
271
+
272
+ The full test suite can be run with:
273
+
274
+ ```bash
275
+ bundle exec rake test
276
+ ```
277
+
236
278
  ### Release
237
279
 
238
280
  Update the `lib/json/version.rb` file.
@@ -1,39 +1,8 @@
1
1
  #ifndef _FBUFFER_H_
2
2
  #define _FBUFFER_H_
3
3
 
4
- #include "ruby.h"
5
- #include "ruby/encoding.h"
6
-
7
- /* shims */
8
- /* This is the fallback definition from Ruby 3.4 */
9
-
10
- #ifndef RBIMPL_STDBOOL_H
11
- #if defined(__cplusplus)
12
- # if defined(HAVE_STDBOOL_H) && (__cplusplus >= 201103L)
13
- # include <cstdbool>
14
- # endif
15
- #elif defined(HAVE_STDBOOL_H)
16
- # include <stdbool.h>
17
- #elif !defined(HAVE__BOOL)
18
- typedef unsigned char _Bool;
19
- # define bool _Bool
20
- # define true ((_Bool)+1)
21
- # define false ((_Bool)+0)
22
- # define __bool_true_false_are_defined
23
- #endif
24
- #endif
25
-
26
- #ifndef RB_UNLIKELY
27
- #define RB_UNLIKELY(expr) expr
28
- #endif
29
-
30
- #ifndef RB_LIKELY
31
- #define RB_LIKELY(expr) expr
32
- #endif
33
-
34
- #ifndef MAYBE_UNUSED
35
- # define MAYBE_UNUSED(x) x
36
- #endif
4
+ #include "../json.h"
5
+ #include "../vendor/jeaiii-ltoa.h"
37
6
 
38
7
  enum fbuffer_type {
39
8
  FBUFFER_HEAP_ALLOCATED = 0,
@@ -42,9 +11,12 @@ enum fbuffer_type {
42
11
 
43
12
  typedef struct FBufferStruct {
44
13
  enum fbuffer_type type;
45
- unsigned long initial_length;
46
- unsigned long len;
47
- unsigned long capa;
14
+ size_t initial_length;
15
+ size_t len;
16
+ size_t capa;
17
+ #if JSON_DEBUG
18
+ size_t requested;
19
+ #endif
48
20
  char *ptr;
49
21
  VALUE io;
50
22
  } FBuffer;
@@ -60,19 +32,37 @@ typedef struct FBufferStruct {
60
32
 
61
33
  static void fbuffer_free(FBuffer *fb);
62
34
  static void fbuffer_clear(FBuffer *fb);
63
- 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);
64
36
  static void fbuffer_append_long(FBuffer *fb, long number);
65
37
  static inline void fbuffer_append_char(FBuffer *fb, char newchr);
66
38
  static VALUE fbuffer_finalize(FBuffer *fb);
67
39
 
68
- 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)
69
41
  {
70
- fb->initial_length = (initial_length > 0) ? initial_length : FBUFFER_INITIAL_LENGTH_DEFAULT;
71
- 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 {
72
47
  fb->type = FBUFFER_STACK_ALLOCATED;
73
48
  fb->ptr = stack_buffer;
74
49
  fb->capa = stack_buffer_size;
50
+ fb->initial_length = (initial_length > 0) ? initial_length : FBUFFER_INITIAL_LENGTH_DEFAULT;
51
+ }
52
+ #if JSON_DEBUG
53
+ fb->requested = 0;
54
+ #endif
55
+ }
56
+
57
+ static inline void fbuffer_consumed(FBuffer *fb, size_t consumed)
58
+ {
59
+ #if JSON_DEBUG
60
+ if (consumed > fb->requested) {
61
+ rb_bug("fbuffer: Out of bound write");
75
62
  }
63
+ fb->requested = 0;
64
+ #endif
65
+ fb->len += consumed;
76
66
  }
77
67
 
78
68
  static void fbuffer_free(FBuffer *fb)
@@ -93,113 +83,177 @@ static void fbuffer_flush(FBuffer *fb)
93
83
  fbuffer_clear(fb);
94
84
  }
95
85
 
96
- static void fbuffer_realloc(FBuffer *fb, unsigned long required)
86
+ static void fbuffer_realloc(FBuffer *fb, size_t new_capa)
97
87
  {
98
- if (required > fb->capa) {
88
+ if (new_capa > fb->capa) {
99
89
  if (fb->type == FBUFFER_STACK_ALLOCATED) {
100
90
  const char *old_buffer = fb->ptr;
101
- fb->ptr = ALLOC_N(char, required);
91
+ fb->ptr = ALLOC_N(char, new_capa);
102
92
  fb->type = FBUFFER_HEAP_ALLOCATED;
103
93
  MEMCPY(fb->ptr, old_buffer, char, fb->len);
104
94
  } else {
105
- REALLOC_N(fb->ptr, char, required);
95
+ REALLOC_N(fb->ptr, char, new_capa);
106
96
  }
107
- fb->capa = required;
97
+ fb->capa = new_capa;
108
98
  }
109
99
  }
110
100
 
111
- static void fbuffer_do_inc_capa(FBuffer *fb, unsigned long requested)
101
+ static void fbuffer_do_inc_capa(FBuffer *fb, size_t requested)
112
102
  {
113
103
  if (RB_UNLIKELY(fb->io)) {
114
- if (fb->capa < FBUFFER_IO_BUFFER_SIZE) {
115
- fbuffer_realloc(fb, FBUFFER_IO_BUFFER_SIZE);
116
- } else {
104
+ if (fb->capa != 0) {
117
105
  fbuffer_flush(fb);
118
- }
119
-
120
- if (RB_LIKELY(requested < fb->capa)) {
121
- return;
106
+ if (RB_LIKELY(requested < fb->capa)) {
107
+ return;
108
+ }
122
109
  }
123
110
  }
124
111
 
125
- unsigned long required;
112
+ size_t new_capa = fb->capa ? fb->capa : fb->initial_length;
113
+ size_t needed_capa = requested + fb->len;
126
114
 
127
- if (RB_UNLIKELY(!fb->ptr)) {
128
- fb->ptr = ALLOC_N(char, fb->initial_length);
129
- fb->capa = fb->initial_length;
115
+ while (new_capa < needed_capa) {
116
+ new_capa *= 2;
130
117
  }
131
118
 
132
- for (required = fb->capa; requested > required - fb->len; required <<= 1);
133
-
134
- fbuffer_realloc(fb, required);
119
+ fbuffer_realloc(fb, new_capa);
135
120
  }
136
121
 
137
- static inline void fbuffer_inc_capa(FBuffer *fb, unsigned long requested)
122
+ static inline void fbuffer_inc_capa(FBuffer *fb, size_t requested)
138
123
  {
124
+ #if JSON_DEBUG
125
+ fb->requested = requested;
126
+ #endif
127
+
139
128
  if (RB_UNLIKELY(requested > fb->capa - fb->len)) {
140
129
  fbuffer_do_inc_capa(fb, requested);
141
130
  }
142
131
  }
143
132
 
144
- static void fbuffer_append(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)
143
+ {
144
+ MEMCPY(fb->ptr + fb->len, newstr, char, len);
145
+ fbuffer_consumed(fb, len);
146
+ }
147
+
148
+ static inline void fbuffer_append(FBuffer *fb, const char *newstr, size_t len)
145
149
  {
146
150
  if (len > 0) {
147
151
  fbuffer_inc_capa(fb, len);
148
- MEMCPY(fb->ptr + fb->len, newstr, char, len);
149
- fb->len += len;
152
+ fbuffer_append_reserved(fb, newstr, len);
153
+ }
154
+ }
155
+
156
+ /* Appends a character into a buffer. The buffer needs to have sufficient capacity, via fbuffer_inc_capa(...). */
157
+ static inline void fbuffer_append_reserved_char(FBuffer *fb, char chr)
158
+ {
159
+ #if JSON_DEBUG
160
+ if (fb->requested < 1) {
161
+ rb_bug("fbuffer: unreserved write");
150
162
  }
163
+ fb->requested--;
164
+ #endif
165
+
166
+ fb->ptr[fb->len] = chr;
167
+ fb->len++;
151
168
  }
152
169
 
153
170
  static void fbuffer_append_str(FBuffer *fb, VALUE str)
154
171
  {
155
- const char *newstr = StringValuePtr(str);
156
- unsigned long len = RSTRING_LEN(str);
172
+ const char *ptr;
173
+ size_t len;
174
+ RSTRING_GETMEM(str, ptr, len);
157
175
 
176
+ fbuffer_append(fb, ptr, len);
158
177
  RB_GC_GUARD(str);
178
+ }
159
179
 
160
- fbuffer_append(fb, newstr, len);
180
+ static void fbuffer_append_str_repeat(FBuffer *fb, VALUE str, size_t repeat)
181
+ {
182
+ const char *ptr;
183
+ size_t len;
184
+ RSTRING_GETMEM(str, ptr, len);
185
+
186
+ fbuffer_inc_capa(fb, fbuffer_size_mul_or_raise(repeat, len));
187
+ while (repeat) {
188
+ #if JSON_DEBUG
189
+ fb->requested = len;
190
+ #endif
191
+ fbuffer_append_reserved(fb, ptr, len);
192
+ repeat--;
193
+ }
194
+ RB_GC_GUARD(str);
161
195
  }
162
196
 
163
197
  static inline void fbuffer_append_char(FBuffer *fb, char newchr)
164
198
  {
165
199
  fbuffer_inc_capa(fb, 1);
166
200
  *(fb->ptr + fb->len) = newchr;
167
- fb->len++;
201
+ fbuffer_consumed(fb, 1);
168
202
  }
169
203
 
170
- static long fltoa(long number, char *buf)
204
+ static inline char *fbuffer_cursor(FBuffer *fb)
171
205
  {
172
- static const char digits[] = "0123456789";
173
- long sign = number;
174
- char* tmp = buf;
206
+ return fb->ptr + fb->len;
207
+ }
175
208
 
176
- if (sign < 0) number = -number;
177
- do *tmp-- = digits[number % 10]; while (number /= 10);
178
- if (sign < 0) *tmp-- = '-';
179
- return buf - tmp;
209
+ static inline void fbuffer_advance_to(FBuffer *fb, char *end)
210
+ {
211
+ fbuffer_consumed(fb, (end - fb->ptr) - fb->len);
180
212
  }
181
213
 
182
- #define LONG_BUFFER_SIZE 20
214
+ /*
215
+ * Appends the decimal string representation of \a number into the buffer.
216
+ */
183
217
  static void fbuffer_append_long(FBuffer *fb, long number)
184
218
  {
185
- char buf[LONG_BUFFER_SIZE];
186
- char *buffer_end = buf + LONG_BUFFER_SIZE;
187
- long len = fltoa(number, buffer_end - 1);
188
- fbuffer_append(fb, buffer_end - len, len);
219
+ /*
220
+ * The jeaiii_ultoa() function produces digits left-to-right,
221
+ * allowing us to write directly into the buffer, but we don't know
222
+ * the number of resulting characters.
223
+ *
224
+ * We do know, however, that the `number` argument is always in the
225
+ * range 0xc000000000000000 to 0x3fffffffffffffff, or, in decimal,
226
+ * -4611686018427387904 to 4611686018427387903. The max number of chars
227
+ * generated is therefore 20 (including a potential sign character).
228
+ */
229
+
230
+ static const int MAX_CHARS_FOR_LONG = 20;
231
+
232
+ fbuffer_inc_capa(fb, MAX_CHARS_FOR_LONG);
233
+
234
+ if (number < 0) {
235
+ fbuffer_append_reserved_char(fb, '-');
236
+
237
+ /*
238
+ * Since number is always > LONG_MIN, `-number` will not overflow
239
+ * and is always the positive abs() value.
240
+ */
241
+ number = -number;
242
+ }
243
+
244
+ char *end = jeaiii_ultoa(fbuffer_cursor(fb), number);
245
+ fbuffer_advance_to(fb, end);
189
246
  }
190
247
 
191
248
  static VALUE fbuffer_finalize(FBuffer *fb)
192
249
  {
193
250
  if (fb->io) {
194
251
  fbuffer_flush(fb);
195
- fbuffer_free(fb);
196
252
  rb_io_flush(fb->io);
197
253
  return fb->io;
198
254
  } else {
199
- VALUE result = rb_utf8_str_new(FBUFFER_PTR(fb), FBUFFER_LEN(fb));
200
- fbuffer_free(fb);
201
- return result;
255
+ return rb_utf8_str_new(FBUFFER_PTR(fb), FBUFFER_LEN(fb));
202
256
  }
203
257
  }
204
258
 
205
- #endif
259
+ #endif // _FBUFFER_H_