json 2.15.2 → 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: b883349b3a8a8c1ccb003e74779a577e3a16e8c3d8541693fb477a78aeac3a68
4
- data.tar.gz: efb11cf9e69ece0ebb11d33aec23401b0c1ed1d7a39c351f82998ba82cbd47b8
3
+ metadata.gz: d8bd00bbc63367659eb90ee7e3f4f1394fecdf713fc724f7908c6a2c7b79bbf7
4
+ data.tar.gz: efa1d81f687277247649de777f25be01e56ad8dfac1bdd9ce61111e10231185e
5
5
  SHA512:
6
- metadata.gz: f78e23c6bd8b8dcddaaf051d6ae60253fd6c96e108395b475d825d9daf3fba4f754f280ae095f456ee67917dd991c8477caa86038f378f23d49e17744467cb7a
7
- data.tar.gz: 64946a58a223efb1e333ff15eba3776d44505cfb78ba0102350177b6351f4643107db5f37ddb65c41569b4d58b5147176c8043b182abd152c74c63351be958fd
6
+ metadata.gz: cb8b505c08b88115a87aa0b6ab8b7f34d6e0611af7d37277cef243131990db6cb77ca2508045104588607d54bd58b0c2e67ce0728bd72c4c32d2d77a56b9762e
7
+ data.tar.gz: 8dec0d5ba99f33e68049328f060a600da2fe0545caf47c44c2e812a8316cbe64a99152f365be8654cb4af45493de3bed97baa61a0b2a71ae71a7466d0ee11e8c
data/CHANGES.md CHANGED
@@ -2,6 +2,16 @@
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
+
5
15
  ### 2025-10-25 (2.15.2)
6
16
 
7
17
  * Fix `JSON::Coder` to have one dedicated depth counter per invocation.
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
  }
@@ -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:
@@ -1521,9 +1549,7 @@ static VALUE cState_generate_new(int argc, VALUE *argv, VALUE self)
1521
1549
  .obj = obj,
1522
1550
  .func = generate_json
1523
1551
  };
1524
- rb_rescue(generate_json_try, (VALUE)&data, generate_json_rescue, (VALUE)&data);
1525
-
1526
- return fbuffer_finalize(&buffer);
1552
+ return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data);
1527
1553
  }
1528
1554
 
1529
1555
  static VALUE cState_initialize(int argc, VALUE *argv, VALUE self)
@@ -2029,9 +2055,7 @@ static VALUE cState_m_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io)
2029
2055
  .obj = obj,
2030
2056
  .func = generate_json,
2031
2057
  };
2032
- rb_rescue(generate_json_try, (VALUE)&data, generate_json_rescue, (VALUE)&data);
2033
-
2034
- return fbuffer_finalize(&buffer);
2058
+ return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data);
2035
2059
  }
2036
2060
 
2037
2061
  /*
@@ -0,0 +1,92 @@
1
+ #ifndef _JSON_H_
2
+ #define _JSON_H_
3
+
4
+ #include "ruby.h"
5
+ #include "ruby/encoding.h"
6
+ #include <stdint.h>
7
+
8
+ #if defined(RUBY_DEBUG) && RUBY_DEBUG
9
+ # define JSON_ASSERT RUBY_ASSERT
10
+ #else
11
+ # ifdef JSON_DEBUG
12
+ # include <assert.h>
13
+ # define JSON_ASSERT(x) assert(x)
14
+ # else
15
+ # define JSON_ASSERT(x)
16
+ # endif
17
+ #endif
18
+
19
+ /* shims */
20
+
21
+ #if SIZEOF_UINT64_T == SIZEOF_LONG_LONG
22
+ # define INT64T2NUM(x) LL2NUM(x)
23
+ # define UINT64T2NUM(x) ULL2NUM(x)
24
+ #elif SIZEOF_UINT64_T == SIZEOF_LONG
25
+ # define INT64T2NUM(x) LONG2NUM(x)
26
+ # define UINT64T2NUM(x) ULONG2NUM(x)
27
+ #else
28
+ # error No uint64_t conversion
29
+ #endif
30
+
31
+ /* This is the fallback definition from Ruby 3.4 */
32
+ #ifndef RBIMPL_STDBOOL_H
33
+ #if defined(__cplusplus)
34
+ # if defined(HAVE_STDBOOL_H) && (__cplusplus >= 201103L)
35
+ # include <cstdbool>
36
+ # endif
37
+ #elif defined(HAVE_STDBOOL_H)
38
+ # include <stdbool.h>
39
+ #elif !defined(HAVE__BOOL)
40
+ typedef unsigned char _Bool;
41
+ # define bool _Bool
42
+ # define true ((_Bool)+1)
43
+ # define false ((_Bool)+0)
44
+ # define __bool_true_false_are_defined
45
+ #endif
46
+ #endif
47
+
48
+ #ifndef NORETURN
49
+ #define NORETURN(x) x
50
+ #endif
51
+
52
+ #ifndef NOINLINE
53
+ #if defined(__has_attribute) && __has_attribute(noinline)
54
+ #define NOINLINE(x) __attribute__((noinline)) x
55
+ #else
56
+ #define NOINLINE(x) x
57
+ #endif
58
+ #endif
59
+
60
+ #ifndef ALWAYS_INLINE
61
+ #if defined(__has_attribute) && __has_attribute(always_inline)
62
+ #define ALWAYS_INLINE(x) inline __attribute__((always_inline)) x
63
+ #else
64
+ #define ALWAYS_INLINE(x) inline x
65
+ #endif
66
+ #endif
67
+
68
+ #ifndef RB_UNLIKELY
69
+ #define RB_UNLIKELY(expr) expr
70
+ #endif
71
+
72
+ #ifndef RB_LIKELY
73
+ #define RB_LIKELY(expr) expr
74
+ #endif
75
+
76
+ #ifndef MAYBE_UNUSED
77
+ # define MAYBE_UNUSED(x) x
78
+ #endif
79
+
80
+ #ifdef RUBY_DEBUG
81
+ #ifndef JSON_DEBUG
82
+ #define JSON_DEBUG RUBY_DEBUG
83
+ #endif
84
+ #endif
85
+
86
+ #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ && INTPTR_MAX == INT64_MAX
87
+ #define JSON_CPU_LITTLE_ENDIAN_64BITS 1
88
+ #else
89
+ #define JSON_CPU_LITTLE_ENDIAN_64BITS 0
90
+ #endif
91
+
92
+ #endif // _JSON_H_
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
  require 'mkmf'
3
3
 
4
+ $defs << "-DJSON_DEBUG" if ENV["JSON_DEBUG"]
4
5
  have_func("rb_enc_interned_str", "ruby/encoding.h") # RUBY_VERSION >= 3.0
6
+ have_func("rb_str_to_interned_str", "ruby.h") # RUBY_VERSION >= 3.0
5
7
  have_func("rb_hash_new_capa", "ruby.h") # RUBY_VERSION >= 3.2
6
8
  have_func("rb_hash_bulk_insert", "ruby.h") # Missing on TruffleRuby
7
9
  have_func("strnlen", "string.h") # Missing on Solaris 10