json 2.15.1 → 2.16.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: 981ed5b9d1d88ec48021ed8ab7757ab595dbb2bb5a16e9b3595dc4aae5c1f193
4
- data.tar.gz: 1fc86464fa02dead5539e47dc37c3dcb3d068707ee8d1e32c574893e1bb702de
3
+ metadata.gz: d8bd00bbc63367659eb90ee7e3f4f1394fecdf713fc724f7908c6a2c7b79bbf7
4
+ data.tar.gz: efa1d81f687277247649de777f25be01e56ad8dfac1bdd9ce61111e10231185e
5
5
  SHA512:
6
- metadata.gz: c06d2e27a811c7b2368ed364f1fd72c0423922094ed29ec0de07a75db7aa53d3f2662aa80220f6d094fe39400603d2919b97308010fcc5c0efab86307089848b
7
- data.tar.gz: c032759c67a721b378518dd3c555e4e1c3f1ca34ff00f1bb21a2f136b626399efdd9c5a701aa018c3c9d5da050d0c110ee98e29853d4949db125f524fad1fdae
6
+ metadata.gz: cb8b505c08b88115a87aa0b6ab8b7f34d6e0611af7d37277cef243131990db6cb77ca2508045104588607d54bd58b0c2e67ce0728bd72c4c32d2d77a56b9762e
7
+ data.tar.gz: 8dec0d5ba99f33e68049328f060a600da2fe0545caf47c44c2e812a8316cbe64a99152f365be8654cb4af45493de3bed97baa61a0b2a71ae71a7466d0ee11e8c
data/CHANGES.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  ### Unreleased
4
4
 
5
+ ### 2025-11-07 (2.16.0)
6
+
7
+ * Deprecate `JSON::State#[]` and `JSON::State#[]=`. Consider using `JSON::Coder` instead.
8
+ * `JSON::Coder` now also yields to the block when encountering strings with invalid encoding.
9
+ * Fix GeneratorError messages to be UTF-8 encoded.
10
+ * Fix memory leak when `Exception` is raised, or `throw` is used during JSON generation.
11
+ * Optimized floating point number parsing by integrating the ryu algorithm (thanks to Josef Šimánek).
12
+ * Optimized numbers parsing using SWAR (thanks to Scott Myron).
13
+ * Optimized parsing of pretty printed documents using SWAR (thanks to Scott Myron).
14
+
15
+ ### 2025-10-25 (2.15.2)
16
+
17
+ * Fix `JSON::Coder` to have one dedicated depth counter per invocation.
18
+ After encountering a circular reference in `JSON::Coder#dump`, any further `#dump` call would raise `JSON::NestingError`.
19
+
5
20
  ### 2025-10-07 (2.15.1)
6
21
 
7
22
  * Fix incorrect escaping in the JRuby extension when encoding shared strings.
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
@@ -113,7 +113,23 @@ 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}`.
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
+ ```
117
133
 
118
134
  ## Combining JSON fragments
119
135
 
@@ -1,55 +1,9 @@
1
1
  #ifndef _FBUFFER_H_
2
2
  #define _FBUFFER_H_
3
3
 
4
- #include "ruby.h"
5
- #include "ruby/encoding.h"
4
+ #include "../json.h"
6
5
  #include "../vendor/jeaiii-ltoa.h"
7
6
 
8
- /* shims */
9
- /* This is the fallback definition from Ruby 3.4 */
10
-
11
- #ifndef RBIMPL_STDBOOL_H
12
- #if defined(__cplusplus)
13
- # if defined(HAVE_STDBOOL_H) && (__cplusplus >= 201103L)
14
- # include <cstdbool>
15
- # endif
16
- #elif defined(HAVE_STDBOOL_H)
17
- # include <stdbool.h>
18
- #elif !defined(HAVE__BOOL)
19
- typedef unsigned char _Bool;
20
- # define bool _Bool
21
- # define true ((_Bool)+1)
22
- # define false ((_Bool)+0)
23
- # define __bool_true_false_are_defined
24
- #endif
25
- #endif
26
-
27
- #ifndef NOINLINE
28
- #if defined(__has_attribute) && __has_attribute(noinline)
29
- #define NOINLINE() __attribute__((noinline))
30
- #else
31
- #define NOINLINE()
32
- #endif
33
- #endif
34
-
35
- #ifndef RB_UNLIKELY
36
- #define RB_UNLIKELY(expr) expr
37
- #endif
38
-
39
- #ifndef RB_LIKELY
40
- #define RB_LIKELY(expr) expr
41
- #endif
42
-
43
- #ifndef MAYBE_UNUSED
44
- # define MAYBE_UNUSED(x) x
45
- #endif
46
-
47
- #ifdef RUBY_DEBUG
48
- #ifndef JSON_DEBUG
49
- #define JSON_DEBUG RUBY_DEBUG
50
- #endif
51
- #endif
52
-
53
7
  enum fbuffer_type {
54
8
  FBUFFER_HEAP_ALLOCATED = 0,
55
9
  FBUFFER_STACK_ALLOCATED = 1,
@@ -283,14 +237,11 @@ static VALUE fbuffer_finalize(FBuffer *fb)
283
237
  {
284
238
  if (fb->io) {
285
239
  fbuffer_flush(fb);
286
- fbuffer_free(fb);
287
240
  rb_io_flush(fb->io);
288
241
  return fb->io;
289
242
  } else {
290
- VALUE result = rb_utf8_str_new(FBUFFER_PTR(fb), FBUFFER_LEN(fb));
291
- fbuffer_free(fb);
292
- return result;
243
+ return rb_utf8_str_new(FBUFFER_PTR(fb), FBUFFER_LEN(fb));
293
244
  }
294
245
  }
295
246
 
296
- #endif
247
+ #endif // _FBUFFER_H_
@@ -1,4 +1,4 @@
1
- #include "ruby.h"
1
+ #include "../json.h"
2
2
  #include "../fbuffer/fbuffer.h"
3
3
  #include "../vendor/fpconv.c"
4
4
 
@@ -36,10 +36,6 @@ typedef struct JSON_Generator_StateStruct {
36
36
  bool strict;
37
37
  } JSON_Generator_State;
38
38
 
39
- #ifndef RB_UNLIKELY
40
- #define RB_UNLIKELY(cond) (cond)
41
- #endif
42
-
43
39
  static VALUE mJSON, cState, cFragment, eGeneratorError, eNestingError, Encoding_UTF_8;
44
40
 
45
41
  static ID i_to_s, i_to_json, i_new, i_pack, i_unpack, i_create_id, i_extend, i_encode;
@@ -85,23 +81,18 @@ static void generate_json_fragment(FBuffer *buffer, struct generate_json_data *d
85
81
 
86
82
  static int usascii_encindex, utf8_encindex, binary_encindex;
87
83
 
88
- #ifdef RBIMPL_ATTR_NORETURN
89
- RBIMPL_ATTR_NORETURN()
90
- #endif
91
- static void raise_generator_error_str(VALUE invalid_object, VALUE str)
84
+ NORETURN(static void) raise_generator_error_str(VALUE invalid_object, VALUE str)
92
85
  {
86
+ rb_enc_associate_index(str, utf8_encindex);
93
87
  VALUE exc = rb_exc_new_str(eGeneratorError, str);
94
88
  rb_ivar_set(exc, rb_intern("@invalid_object"), invalid_object);
95
89
  rb_exc_raise(exc);
96
90
  }
97
91
 
98
- #ifdef RBIMPL_ATTR_NORETURN
99
- RBIMPL_ATTR_NORETURN()
100
- #endif
101
92
  #ifdef RBIMPL_ATTR_FORMAT
102
93
  RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 3)
103
94
  #endif
104
- static void raise_generator_error(VALUE invalid_object, const char *fmt, ...)
95
+ NORETURN(static void) raise_generator_error(VALUE invalid_object, const char *fmt, ...)
105
96
  {
106
97
  va_list args;
107
98
  va_start(args, fmt);
@@ -136,13 +127,7 @@ typedef struct _search_state {
136
127
  #endif /* HAVE_SIMD */
137
128
  } search_state;
138
129
 
139
- #if (defined(__GNUC__ ) || defined(__clang__))
140
- #define FORCE_INLINE __attribute__((always_inline))
141
- #else
142
- #define FORCE_INLINE
143
- #endif
144
-
145
- static inline FORCE_INLINE void search_flush(search_state *search)
130
+ static ALWAYS_INLINE() void search_flush(search_state *search)
146
131
  {
147
132
  // Do not remove this conditional without profiling, specifically escape-heavy text.
148
133
  // escape_UTF8_char_basic will advance search->ptr and search->cursor (effectively a search_flush).
@@ -185,7 +170,7 @@ static inline unsigned char search_escape_basic(search_state *search)
185
170
  return 0;
186
171
  }
187
172
 
188
- static inline FORCE_INLINE void escape_UTF8_char_basic(search_state *search)
173
+ static ALWAYS_INLINE() void escape_UTF8_char_basic(search_state *search)
189
174
  {
190
175
  const unsigned char ch = (unsigned char)*search->ptr;
191
176
  switch (ch) {
@@ -272,7 +257,7 @@ static inline void escape_UTF8_char(search_state *search, unsigned char ch_len)
272
257
 
273
258
  #ifdef HAVE_SIMD
274
259
 
275
- static inline FORCE_INLINE char *copy_remaining_bytes(search_state *search, unsigned long vec_len, unsigned long len)
260
+ static ALWAYS_INLINE() char *copy_remaining_bytes(search_state *search, unsigned long vec_len, unsigned long len)
276
261
  {
277
262
  // Flush the buffer so everything up until the last 'len' characters are unflushed.
278
263
  search_flush(search);
@@ -295,7 +280,7 @@ static inline FORCE_INLINE char *copy_remaining_bytes(search_state *search, unsi
295
280
 
296
281
  #ifdef HAVE_SIMD_NEON
297
282
 
298
- static inline FORCE_INLINE unsigned char neon_next_match(search_state *search)
283
+ static ALWAYS_INLINE() unsigned char neon_next_match(search_state *search)
299
284
  {
300
285
  uint64_t mask = search->matches_mask;
301
286
  uint32_t index = trailing_zeros64(mask) >> 2;
@@ -409,7 +394,7 @@ static inline unsigned char search_escape_basic_neon(search_state *search)
409
394
 
410
395
  #ifdef HAVE_SIMD_SSE2
411
396
 
412
- static inline FORCE_INLINE unsigned char sse2_next_match(search_state *search)
397
+ static ALWAYS_INLINE() unsigned char sse2_next_match(search_state *search)
413
398
  {
414
399
  int mask = search->matches_mask;
415
400
  int index = trailing_zeros(mask);
@@ -433,7 +418,7 @@ static inline FORCE_INLINE unsigned char sse2_next_match(search_state *search)
433
418
  #define TARGET_SSE2
434
419
  #endif
435
420
 
436
- static inline TARGET_SSE2 FORCE_INLINE unsigned char search_escape_basic_sse2(search_state *search)
421
+ static TARGET_SSE2 ALWAYS_INLINE() unsigned char search_escape_basic_sse2(search_state *search)
437
422
  {
438
423
  if (RB_UNLIKELY(search->has_matches)) {
439
424
  // There are more matches if search->matches_mask > 0.
@@ -995,13 +980,12 @@ static inline VALUE vstate_get(struct generate_json_data *data)
995
980
  return data->vstate;
996
981
  }
997
982
 
998
- struct hash_foreach_arg {
999
- VALUE hash;
1000
- struct generate_json_data *data;
1001
- int first_key_type;
1002
- bool first;
1003
- bool mixed_keys_encountered;
1004
- };
983
+ static VALUE
984
+ json_call_as_json(JSON_Generator_State *state, VALUE object, VALUE is_key)
985
+ {
986
+ VALUE proc_args[2] = {object, is_key};
987
+ return rb_proc_call_with_block(state->as_json, 2, proc_args, Qnil);
988
+ }
1005
989
 
1006
990
  static VALUE
1007
991
  convert_string_subclass(VALUE key)
@@ -1018,6 +1002,129 @@ convert_string_subclass(VALUE key)
1018
1002
  return key_to_s;
1019
1003
  }
1020
1004
 
1005
+ static bool enc_utf8_compatible_p(int enc_idx)
1006
+ {
1007
+ if (enc_idx == usascii_encindex) return true;
1008
+ if (enc_idx == utf8_encindex) return true;
1009
+ return false;
1010
+ }
1011
+
1012
+ static VALUE encode_json_string_try(VALUE str)
1013
+ {
1014
+ return rb_funcall(str, i_encode, 1, Encoding_UTF_8);
1015
+ }
1016
+
1017
+ static VALUE encode_json_string_rescue(VALUE str, VALUE exception)
1018
+ {
1019
+ raise_generator_error_str(str, rb_funcall(exception, rb_intern("message"), 0));
1020
+ return Qundef;
1021
+ }
1022
+
1023
+ static inline bool valid_json_string_p(VALUE str)
1024
+ {
1025
+ int coderange = rb_enc_str_coderange(str);
1026
+
1027
+ if (RB_LIKELY(coderange == ENC_CODERANGE_7BIT)) {
1028
+ return true;
1029
+ }
1030
+
1031
+ if (RB_LIKELY(coderange == ENC_CODERANGE_VALID)) {
1032
+ return enc_utf8_compatible_p(RB_ENCODING_GET_INLINED(str));
1033
+ }
1034
+
1035
+ return false;
1036
+ }
1037
+
1038
+ static inline VALUE ensure_valid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key)
1039
+ {
1040
+ if (RB_LIKELY(valid_json_string_p(str))) {
1041
+ return str;
1042
+ }
1043
+
1044
+ if (!as_json_called && data->state->strict && RTEST(data->state->as_json)) {
1045
+ VALUE coerced_str = json_call_as_json(data->state, str, Qfalse);
1046
+ if (coerced_str != str) {
1047
+ if (RB_TYPE_P(coerced_str, T_STRING)) {
1048
+ if (!valid_json_string_p(coerced_str)) {
1049
+ raise_generator_error(str, "source sequence is illegal/malformed utf-8");
1050
+ }
1051
+ } else {
1052
+ // as_json could return another type than T_STRING
1053
+ if (is_key) {
1054
+ raise_generator_error(coerced_str, "%"PRIsVALUE" not allowed as object key in JSON", CLASS_OF(coerced_str));
1055
+ }
1056
+ }
1057
+
1058
+ return coerced_str;
1059
+ }
1060
+ }
1061
+
1062
+ if (RB_ENCODING_GET_INLINED(str) == binary_encindex) {
1063
+ VALUE utf8_string = rb_enc_associate_index(rb_str_dup(str), utf8_encindex);
1064
+ switch (rb_enc_str_coderange(utf8_string)) {
1065
+ case ENC_CODERANGE_7BIT:
1066
+ return utf8_string;
1067
+ case ENC_CODERANGE_VALID:
1068
+ // For historical reason, we silently reinterpret binary strings as UTF-8 if it would work.
1069
+ // TODO: Raise in 3.0.0
1070
+ rb_warn("JSON.generate: UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0");
1071
+ return utf8_string;
1072
+ break;
1073
+ }
1074
+ }
1075
+
1076
+ return rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str);
1077
+ }
1078
+
1079
+ static void raw_generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
1080
+ {
1081
+ fbuffer_append_char(buffer, '"');
1082
+
1083
+ long len;
1084
+ search_state search;
1085
+ search.buffer = buffer;
1086
+ RSTRING_GETMEM(obj, search.ptr, len);
1087
+ search.cursor = search.ptr;
1088
+ search.end = search.ptr + len;
1089
+
1090
+ #ifdef HAVE_SIMD
1091
+ search.matches_mask = 0;
1092
+ search.has_matches = false;
1093
+ search.chunk_base = NULL;
1094
+ #endif /* HAVE_SIMD */
1095
+
1096
+ switch (rb_enc_str_coderange(obj)) {
1097
+ case ENC_CODERANGE_7BIT:
1098
+ case ENC_CODERANGE_VALID:
1099
+ if (RB_UNLIKELY(data->state->ascii_only)) {
1100
+ convert_UTF8_to_ASCII_only_JSON(&search, data->state->script_safe ? script_safe_escape_table : ascii_only_escape_table);
1101
+ } else if (RB_UNLIKELY(data->state->script_safe)) {
1102
+ convert_UTF8_to_script_safe_JSON(&search);
1103
+ } else {
1104
+ convert_UTF8_to_JSON(&search);
1105
+ }
1106
+ break;
1107
+ default:
1108
+ raise_generator_error(obj, "source sequence is illegal/malformed utf-8");
1109
+ break;
1110
+ }
1111
+ fbuffer_append_char(buffer, '"');
1112
+ }
1113
+
1114
+ static void generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
1115
+ {
1116
+ obj = ensure_valid_encoding(data, obj, false, false);
1117
+ raw_generate_json_string(buffer, data, obj);
1118
+ }
1119
+
1120
+ struct hash_foreach_arg {
1121
+ VALUE hash;
1122
+ struct generate_json_data *data;
1123
+ int first_key_type;
1124
+ bool first;
1125
+ bool mixed_keys_encountered;
1126
+ };
1127
+
1021
1128
  NOINLINE()
1022
1129
  static void
1023
1130
  json_inspect_hash_with_mixed_keys(struct hash_foreach_arg *arg)
@@ -1034,13 +1141,6 @@ json_inspect_hash_with_mixed_keys(struct hash_foreach_arg *arg)
1034
1141
  }
1035
1142
  }
1036
1143
 
1037
- static VALUE
1038
- json_call_as_json(JSON_Generator_State *state, VALUE object, VALUE is_key)
1039
- {
1040
- VALUE proc_args[2] = {object, is_key};
1041
- return rb_proc_call_with_block(state->as_json, 2, proc_args, Qnil);
1042
- }
1043
-
1044
1144
  static int
1045
1145
  json_object_i(VALUE key, VALUE val, VALUE _arg)
1046
1146
  {
@@ -1106,8 +1206,10 @@ json_object_i(VALUE key, VALUE val, VALUE _arg)
1106
1206
  break;
1107
1207
  }
1108
1208
 
1209
+ key_to_s = ensure_valid_encoding(data, key_to_s, as_json_called, true);
1210
+
1109
1211
  if (RB_LIKELY(RBASIC_CLASS(key_to_s) == rb_cString)) {
1110
- generate_json_string(buffer, data, key_to_s);
1212
+ raw_generate_json_string(buffer, data, key_to_s);
1111
1213
  } else {
1112
1214
  generate_json(buffer, data, key_to_s);
1113
1215
  }
@@ -1124,7 +1226,7 @@ static inline long increase_depth(struct generate_json_data *data)
1124
1226
  JSON_Generator_State *state = data->state;
1125
1227
  long depth = ++state->depth;
1126
1228
  if (RB_UNLIKELY(depth > state->max_nesting && state->max_nesting)) {
1127
- rb_raise(eNestingError, "nesting of %ld is too deep", --state->depth);
1229
+ rb_raise(eNestingError, "nesting of %ld is too deep. Did you try to serialize objects with circular references?", --state->depth);
1128
1230
  }
1129
1231
  return depth;
1130
1232
  }
@@ -1190,85 +1292,6 @@ static void generate_json_array(FBuffer *buffer, struct generate_json_data *data
1190
1292
  fbuffer_append_char(buffer, ']');
1191
1293
  }
1192
1294
 
1193
- static inline int enc_utf8_compatible_p(int enc_idx)
1194
- {
1195
- if (enc_idx == usascii_encindex) return 1;
1196
- if (enc_idx == utf8_encindex) return 1;
1197
- return 0;
1198
- }
1199
-
1200
- static VALUE encode_json_string_try(VALUE str)
1201
- {
1202
- return rb_funcall(str, i_encode, 1, Encoding_UTF_8);
1203
- }
1204
-
1205
- static VALUE encode_json_string_rescue(VALUE str, VALUE exception)
1206
- {
1207
- raise_generator_error_str(str, rb_funcall(exception, rb_intern("message"), 0));
1208
- return Qundef;
1209
- }
1210
-
1211
- static inline VALUE ensure_valid_encoding(VALUE str)
1212
- {
1213
- int encindex = RB_ENCODING_GET(str);
1214
- VALUE utf8_string;
1215
- if (RB_UNLIKELY(!enc_utf8_compatible_p(encindex))) {
1216
- if (encindex == binary_encindex) {
1217
- utf8_string = rb_enc_associate_index(rb_str_dup(str), utf8_encindex);
1218
- switch (rb_enc_str_coderange(utf8_string)) {
1219
- case ENC_CODERANGE_7BIT:
1220
- return utf8_string;
1221
- case ENC_CODERANGE_VALID:
1222
- // For historical reason, we silently reinterpret binary strings as UTF-8 if it would work.
1223
- // TODO: Raise in 3.0.0
1224
- rb_warn("JSON.generate: UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0");
1225
- return utf8_string;
1226
- break;
1227
- }
1228
- }
1229
-
1230
- str = rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str);
1231
- }
1232
- return str;
1233
- }
1234
-
1235
- static void generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
1236
- {
1237
- obj = ensure_valid_encoding(obj);
1238
-
1239
- fbuffer_append_char(buffer, '"');
1240
-
1241
- long len;
1242
- search_state search;
1243
- search.buffer = buffer;
1244
- RSTRING_GETMEM(obj, search.ptr, len);
1245
- search.cursor = search.ptr;
1246
- search.end = search.ptr + len;
1247
-
1248
- #ifdef HAVE_SIMD
1249
- search.matches_mask = 0;
1250
- search.has_matches = false;
1251
- search.chunk_base = NULL;
1252
- #endif /* HAVE_SIMD */
1253
-
1254
- switch (rb_enc_str_coderange(obj)) {
1255
- case ENC_CODERANGE_7BIT:
1256
- case ENC_CODERANGE_VALID:
1257
- if (RB_UNLIKELY(data->state->ascii_only)) {
1258
- convert_UTF8_to_ASCII_only_JSON(&search, data->state->script_safe ? script_safe_escape_table : ascii_only_escape_table);
1259
- } else if (RB_UNLIKELY(data->state->script_safe)) {
1260
- convert_UTF8_to_script_safe_JSON(&search);
1261
- } else {
1262
- convert_UTF8_to_JSON(&search);
1263
- }
1264
- break;
1265
- default:
1266
- raise_generator_error(obj, "source sequence is illegal/malformed utf-8");
1267
- break;
1268
- }
1269
- fbuffer_append_char(buffer, '"');
1270
- }
1271
-
1272
1295
  static void generate_json_fallback(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
1273
1296
  {
1274
1297
  VALUE tmp;
@@ -1407,7 +1430,16 @@ start:
1407
1430
  break;
1408
1431
  case T_STRING:
1409
1432
  if (klass != rb_cString) goto general;
1410
- generate_json_string(buffer, data, obj);
1433
+
1434
+ if (RB_LIKELY(valid_json_string_p(obj))) {
1435
+ raw_generate_json_string(buffer, data, obj);
1436
+ } else if (as_json_called) {
1437
+ raise_generator_error(obj, "source sequence is illegal/malformed utf-8");
1438
+ } else {
1439
+ obj = ensure_valid_encoding(data, obj, false, false);
1440
+ as_json_called = true;
1441
+ goto start;
1442
+ }
1411
1443
  break;
1412
1444
  case T_SYMBOL:
1413
1445
  generate_json_symbol(buffer, data, obj);
@@ -1443,16 +1475,14 @@ static VALUE generate_json_try(VALUE d)
1443
1475
 
1444
1476
  data->func(data->buffer, data, data->obj);
1445
1477
 
1446
- return Qnil;
1478
+ return fbuffer_finalize(data->buffer);
1447
1479
  }
1448
1480
 
1449
- static VALUE generate_json_rescue(VALUE d, VALUE exc)
1481
+ static VALUE generate_json_ensure(VALUE d)
1450
1482
  {
1451
1483
  struct generate_json_data *data = (struct generate_json_data *)d;
1452
1484
  fbuffer_free(data->buffer);
1453
1485
 
1454
- rb_exc_raise(exc);
1455
-
1456
1486
  return Qundef;
1457
1487
  }
1458
1488
 
@@ -1473,9 +1503,7 @@ static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func,
1473
1503
  .obj = obj,
1474
1504
  .func = func
1475
1505
  };
1476
- rb_rescue(generate_json_try, (VALUE)&data, generate_json_rescue, (VALUE)&data);
1477
-
1478
- return fbuffer_finalize(&buffer);
1506
+ return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data);
1479
1507
  }
1480
1508
 
1481
1509
  /* call-seq:
@@ -1491,10 +1519,37 @@ static VALUE cState_generate(int argc, VALUE *argv, VALUE self)
1491
1519
  rb_check_arity(argc, 1, 2);
1492
1520
  VALUE obj = argv[0];
1493
1521
  VALUE io = argc > 1 ? argv[1] : Qnil;
1494
- VALUE result = cState_partial_generate(self, obj, generate_json, io);
1522
+ return cState_partial_generate(self, obj, generate_json, io);
1523
+ }
1524
+
1525
+ static VALUE cState_generate_new(int argc, VALUE *argv, VALUE self)
1526
+ {
1527
+ rb_check_arity(argc, 1, 2);
1528
+ VALUE obj = argv[0];
1529
+ VALUE io = argc > 1 ? argv[1] : Qnil;
1530
+
1495
1531
  GET_STATE(self);
1496
- (void)state;
1497
- return result;
1532
+
1533
+ JSON_Generator_State new_state;
1534
+ MEMCPY(&new_state, state, JSON_Generator_State, 1);
1535
+
1536
+ // FIXME: depth shouldn't be part of JSON_Generator_State, as that prevents it from being used concurrently.
1537
+ new_state.depth = 0;
1538
+
1539
+ char stack_buffer[FBUFFER_STACK_SIZE];
1540
+ FBuffer buffer = {
1541
+ .io = RTEST(io) ? io : Qfalse,
1542
+ };
1543
+ fbuffer_stack_init(&buffer, state->buffer_initial_length, stack_buffer, FBUFFER_STACK_SIZE);
1544
+
1545
+ struct generate_json_data data = {
1546
+ .buffer = &buffer,
1547
+ .vstate = Qfalse,
1548
+ .state = &new_state,
1549
+ .obj = obj,
1550
+ .func = generate_json
1551
+ };
1552
+ return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data);
1498
1553
  }
1499
1554
 
1500
1555
  static VALUE cState_initialize(int argc, VALUE *argv, VALUE self)
@@ -2000,9 +2055,7 @@ static VALUE cState_m_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io)
2000
2055
  .obj = obj,
2001
2056
  .func = generate_json,
2002
2057
  };
2003
- rb_rescue(generate_json_try, (VALUE)&data, generate_json_rescue, (VALUE)&data);
2004
-
2005
- return fbuffer_finalize(&buffer);
2058
+ return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data);
2006
2059
  }
2007
2060
 
2008
2061
  /*
@@ -2072,7 +2125,7 @@ void Init_generator(void)
2072
2125
  rb_define_method(cState, "buffer_initial_length", cState_buffer_initial_length, 0);
2073
2126
  rb_define_method(cState, "buffer_initial_length=", cState_buffer_initial_length_set, 1);
2074
2127
  rb_define_method(cState, "generate", cState_generate, -1);
2075
- rb_define_alias(cState, "generate_new", "generate"); // :nodoc:
2128
+ rb_define_method(cState, "generate_new", cState_generate_new, -1); // :nodoc:
2076
2129
 
2077
2130
  rb_define_private_method(cState, "allow_duplicate_key?", cState_allow_duplicate_key_p, 0);
2078
2131