json 2.19.1 → 2.20.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: 04f1d3824b8119f86068d5c61701115a38fd9b0b37e59fb868baca59cb49fe6c
4
- data.tar.gz: f9267f5bf2c12bd5762878b31873052b85022354da9dc347d06f05fa0dd2f5e6
3
+ metadata.gz: 24e3bc40d587f5a4c001b5f4c7da30170bdf06f5401f2aa1928469c8fb3860e2
4
+ data.tar.gz: b668789b5f3a56d7db311eb4ec9d10b6d236debab23016fabd1682316d50ae50
5
5
  SHA512:
6
- metadata.gz: 03777fe540a81fa223ca8990d4f9128d97ad57e795fda888ad8a8914208ed5950b1c49d90488a58827d252245d624953750142578b13eacfb82843fd17fb409e
7
- data.tar.gz: 747625c3e441fe66d20710b4029b0b650e54e60313700d3c849b306c1ce2fac7a94dc9471962ccccea0a64031499adae59270149b4f70ed64795f269939fb6c5
6
+ metadata.gz: aaa62a65724e3caa24797ce93ed3ac710a0186a1fd2db990a20fddb10eb99c71d92a967d0bed9d5086cb5b203c5dda0793faa461fda1e5c6963c62da5b4db3dc
7
+ data.tar.gz: fc44e571dcb662a35a36165d7f7afbb94b11ab8d5660ab1a65905961b7548260152afbb3286fd233223ea5435b6bb62135a3b67b30bd26ca0cd8114e6921c980
data/CHANGES.md CHANGED
@@ -2,6 +2,54 @@
2
2
 
3
3
  ### Unreleased
4
4
 
5
+ ### 2026-06-23 (2.20.0)
6
+
7
+ * Both C and Java parsers are no longer recursive, so parsing very deep documents with `max_nesting: false` will no longer
8
+ result in `SystemStackError stack level too deep` errors.
9
+ * The `:max_nesting` option still defaults to `100`.
10
+ * Optimized floating point number parsing further by replacing the ryu algorithm by a port of Eisel-Lemire Fast Float.
11
+ * Added `JSON::ResumableParser` to parse streams of JSON documents. Not yet available on JRuby.
12
+ * Deprecate default support of JavaScript comments in the parser and add `allow_comments: true` parsing option.
13
+ * Integrate with Ruby 4.1 `ruby_sized_xfree`.
14
+
15
+ ### 2026-06-11 (2.19.9)
16
+
17
+ * Fix buffer overflow that could lead to a crash when writing JSON directly into an IO
18
+ with `JSON.generate(object, io)`. [CVE-2026-54696].
19
+
20
+ ### 2026-06-03 (2.19.8)
21
+
22
+ * Fix 1-byte buffer overread on EOS errors.
23
+ * Handle invalid types passed as `max_nesting` option.
24
+
25
+ ### 2026-05-28 (2.19.7)
26
+
27
+ * Fix some more edge cases with out of range floats.
28
+ * Ensure the string provided to `JSON.parse` can't be mutated during parsing.
29
+ * Add missing write barriers in `State#dup`.
30
+ * Further validate generator `depth` config.
31
+
32
+ ### 2026-05-28 (2.19.6)
33
+
34
+ * Cleanly handle overly large `depth` generator argument.
35
+ * Add missing write barrier in `ParserConfig`.
36
+
37
+ ### 2026-05-04 (2.19.5)
38
+
39
+ * Cap the parser to emit a maximum of 5 deprecation warnings per document. Emitting more is not helpful.
40
+
41
+ ### 2026-04-19 (2.19.4)
42
+
43
+ * Fix parsing of out of range floats (very large exponents that lead to either `0.0` or `Inf`).
44
+
45
+ ### 2026-03-25 (2.19.3)
46
+
47
+ * Fix handling of unescaped control characters preceeded by a backslash.
48
+
49
+ ### 2026-03-18 (2.19.2)
50
+
51
+ * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
52
+
5
53
  ### 2026-03-08 (2.19.1)
6
54
 
7
55
  * Fix a compiler dependent GC bug introduced in `2.18.0`.
@@ -20,6 +68,10 @@
20
68
 
21
69
  * Add `:allow_control_characters` parser options, to allow JSON strings containing unescaped ASCII control characters (e.g. newlines).
22
70
 
71
+ ### 2026-03-18 (2.17.1.2) - Security Backport
72
+
73
+ * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
74
+
23
75
  ### 2025-12-04 (2.17.1)
24
76
 
25
77
  * Fix a regression in parsing of unicode surogate pairs (`\uXX\uXX`) that could cause an invalid string to be returned.
@@ -46,6 +98,10 @@
46
98
  * Optimized numbers parsing using SWAR (thanks to Scott Myron).
47
99
  * Optimized parsing of pretty printed documents using SWAR (thanks to Scott Myron).
48
100
 
101
+ ### 2026-03-18 (2.15.2.1) - Security Backport
102
+
103
+ * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
104
+
49
105
  ### 2025-10-25 (2.15.2)
50
106
 
51
107
  * 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
@@ -249,6 +249,17 @@ There are also the methods `Kernel#j` for generate, and `Kernel#jj` for
249
249
  `pretty_generate` output to the console, that work analogous to Core Ruby's `p` and
250
250
  the `pp` library's `pp` methods.
251
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
+
252
263
  ## Development
253
264
 
254
265
  ### Prerequisites
@@ -295,5 +306,3 @@ The latest version of this library can be downloaded at
295
306
  Online Documentation should be located at
296
307
 
297
308
  * https://www.rubydoc.info/gems/json
298
-
299
- [Ragel]: http://www.colm.net/open-source/ragel/
@@ -37,13 +37,17 @@ 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, size_t initial_length, char *stack_buffer, size_t 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;
@@ -64,7 +68,7 @@ static inline void fbuffer_consumed(FBuffer *fb, size_t 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,45 +83,40 @@ static void fbuffer_flush(FBuffer *fb)
79
83
  fbuffer_clear(fb);
80
84
  }
81
85
 
82
- static void fbuffer_realloc(FBuffer *fb, size_t 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
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
- size_t 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
122
  static inline void fbuffer_inc_capa(FBuffer *fb, size_t requested)
@@ -131,6 +130,15 @@ static inline void fbuffer_inc_capa(FBuffer *fb, size_t requested)
131
130
  }
132
131
  }
133
132
 
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
+
134
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);
@@ -175,7 +183,7 @@ static void fbuffer_append_str_repeat(FBuffer *fb, VALUE str, size_t repeat)
175
183
  size_t len;
176
184
  RSTRING_GETMEM(str, ptr, len);
177
185
 
178
- fbuffer_inc_capa(fb, repeat * len);
186
+ fbuffer_inc_capa(fb, fbuffer_size_mul_or_raise(repeat, len));
179
187
  while (repeat) {
180
188
  #if JSON_DEBUG
181
189
  fb->requested = len;
@@ -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
 
@@ -722,27 +722,24 @@ static void State_compact(void *ptr)
722
722
  state->as_json = rb_gc_location(state->as_json);
723
723
  }
724
724
 
725
- static void State_free(void *ptr)
726
- {
727
- JSON_Generator_State *state = ptr;
728
- ruby_xfree(state);
729
- }
730
-
731
725
  static size_t State_memsize(const void *ptr)
732
726
  {
727
+ #ifdef HAVE_RUBY_TYPED_EMBEDDABLE
728
+ return 0;
729
+ #else
733
730
  return sizeof(JSON_Generator_State);
731
+ #endif
734
732
  }
735
733
 
736
734
  static const rb_data_type_t JSON_Generator_State_type = {
737
- "JSON/Generator/State",
738
- {
735
+ .wrap_struct_name = "JSON/Generator/State",
736
+ .function = {
739
737
  .dmark = State_mark,
740
- .dfree = State_free,
738
+ .dfree = RUBY_DEFAULT_FREE,
741
739
  .dsize = State_memsize,
742
740
  .dcompact = State_compact,
743
741
  },
744
- 0, 0,
745
- RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE,
742
+ .flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_EMBEDDABLE,
746
743
  };
747
744
 
748
745
  static void state_init(JSON_Generator_State *state)
@@ -826,9 +823,17 @@ static VALUE encode_json_string_rescue(VALUE str, VALUE exception)
826
823
  return Qundef;
827
824
  }
828
825
 
826
+ static inline int json_str_coderange(VALUE str) {
827
+ int coderange = RB_ENC_CODERANGE(str);
828
+ if (coderange == RUBY_ENC_CODERANGE_UNKNOWN) {
829
+ coderange = rb_enc_str_coderange(str);
830
+ }
831
+ return coderange;
832
+ }
833
+
829
834
  static inline bool valid_json_string_p(VALUE str)
830
835
  {
831
- int coderange = rb_enc_str_coderange(str);
836
+ int coderange = json_str_coderange(str);
832
837
 
833
838
  if (RB_LIKELY(coderange == ENC_CODERANGE_7BIT)) {
834
839
  return true;
@@ -841,12 +846,8 @@ static inline bool valid_json_string_p(VALUE str)
841
846
  return false;
842
847
  }
843
848
 
844
- static inline VALUE ensure_valid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key)
849
+ NOINLINE(static) VALUE convert_invalid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key)
845
850
  {
846
- if (RB_LIKELY(valid_json_string_p(str))) {
847
- return str;
848
- }
849
-
850
851
  if (!as_json_called && data->state->strict && RTEST(data->state->as_json)) {
851
852
  VALUE coerced_str = json_call_as_json(data->state, str, Qfalse);
852
853
  if (coerced_str != str) {
@@ -882,6 +883,16 @@ static inline VALUE ensure_valid_encoding(struct generate_json_data *data, VALUE
882
883
  return rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str);
883
884
  }
884
885
 
886
+ ALWAYS_INLINE(static) VALUE ensure_valid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key)
887
+ {
888
+ if (RB_LIKELY(valid_json_string_p(str))) {
889
+ return str;
890
+ }
891
+ else {
892
+ return convert_invalid_encoding(data, str, as_json_called, is_key);
893
+ }
894
+ }
895
+
885
896
  static void raw_generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
886
897
  {
887
898
  fbuffer_append_char(buffer, '"');
@@ -900,7 +911,7 @@ static void raw_generate_json_string(FBuffer *buffer, struct generate_json_data
900
911
  search.chunk_end = NULL;
901
912
  #endif /* HAVE_SIMD */
902
913
 
903
- switch (rb_enc_str_coderange(obj)) {
914
+ switch (json_str_coderange(obj)) {
904
915
  case ENC_CODERANGE_7BIT:
905
916
  case ENC_CODERANGE_VALID:
906
917
  if (RB_UNLIKELY(data->state->ascii_only)) {
@@ -1297,10 +1308,8 @@ static inline VALUE cState_partial_generate(VALUE self, VALUE obj, generator_fun
1297
1308
  GET_STATE(self);
1298
1309
 
1299
1310
  char stack_buffer[FBUFFER_STACK_SIZE];
1300
- FBuffer buffer = {
1301
- .io = RTEST(io) ? io : Qfalse,
1302
- };
1303
- fbuffer_stack_init(&buffer, state->buffer_initial_length, stack_buffer, FBUFFER_STACK_SIZE);
1311
+ FBuffer buffer = { 0 };
1312
+ fbuffer_init(&buffer, state->buffer_initial_length, io, stack_buffer, FBUFFER_STACK_SIZE);
1304
1313
 
1305
1314
  struct generate_json_data data = {
1306
1315
  .buffer = &buffer,
@@ -1360,12 +1369,14 @@ static VALUE cState_init_copy(VALUE obj, VALUE orig)
1360
1369
  if (!objState) rb_raise(rb_eArgError, "unallocated JSON::State");
1361
1370
 
1362
1371
  MEMCPY(objState, origState, JSON_Generator_State, 1);
1363
- objState->indent = origState->indent;
1364
- objState->space = origState->space;
1365
- objState->space_before = origState->space_before;
1366
- objState->object_nl = origState->object_nl;
1367
- objState->array_nl = origState->array_nl;
1368
- objState->as_json = origState->as_json;
1372
+
1373
+ RB_OBJ_WRITTEN(obj, Qundef, objState->indent);
1374
+ RB_OBJ_WRITTEN(obj, Qundef, objState->space);
1375
+ RB_OBJ_WRITTEN(obj, Qundef, objState->space_before);
1376
+ RB_OBJ_WRITTEN(obj, Qundef, objState->object_nl);
1377
+ RB_OBJ_WRITTEN(obj, Qundef, objState->array_nl);
1378
+ RB_OBJ_WRITTEN(obj, Qundef, objState->as_json);
1379
+
1369
1380
  return obj;
1370
1381
  }
1371
1382
 
@@ -1572,7 +1583,7 @@ static VALUE cState_max_nesting(VALUE self)
1572
1583
 
1573
1584
  static long long_config(VALUE num)
1574
1585
  {
1575
- return RTEST(num) ? FIX2LONG(num) : 0;
1586
+ return RTEST(num) ? NUM2LONG(num) : 0;
1576
1587
  }
1577
1588
 
1578
1589
  // depth must never be negative; reject early with a clear error.
@@ -1583,6 +1594,9 @@ static long depth_config(VALUE num)
1583
1594
  if (RB_UNLIKELY(d < 0)) {
1584
1595
  rb_raise(rb_eArgError, "depth must be >= 0 (got %ld)", d);
1585
1596
  }
1597
+ if (RB_UNLIKELY(d > INT_MAX)) {
1598
+ rb_raise(rb_eArgError, "depth is too large (got %ld)", d);
1599
+ }
1586
1600
  return d;
1587
1601
  }
1588
1602
 
@@ -1854,10 +1868,8 @@ static VALUE cState_m_do_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io,
1854
1868
  configure_state(&state, Qfalse, opts);
1855
1869
 
1856
1870
  char stack_buffer[FBUFFER_STACK_SIZE];
1857
- FBuffer buffer = {
1858
- .io = RTEST(io) ? io : Qfalse,
1859
- };
1860
- fbuffer_stack_init(&buffer, state.buffer_initial_length, stack_buffer, FBUFFER_STACK_SIZE);
1871
+ FBuffer buffer = { 0 };
1872
+ fbuffer_init(&buffer, state.buffer_initial_length, io, stack_buffer, FBUFFER_STACK_SIZE);
1861
1873
 
1862
1874
  struct generate_json_data data = {
1863
1875
  .buffer = &buffer,
data/ext/json/ext/json.h CHANGED
@@ -11,6 +11,9 @@
11
11
 
12
12
  #if defined(RUBY_DEBUG) && RUBY_DEBUG
13
13
  # define JSON_ASSERT RUBY_ASSERT
14
+ # ifndef JSON_DEBUG
15
+ # define JSON_DEBUG 1
16
+ # endif
14
17
  #else
15
18
  # ifdef JSON_DEBUG
16
19
  # include <assert.h>
@@ -20,8 +23,18 @@
20
23
  # endif
21
24
  #endif
22
25
 
26
+ #ifdef JSON_DEBUG
27
+ # define JSON_UNREACHABLE_RETURN(val) rb_bug("Unreachable")
28
+ #else
29
+ # define JSON_UNREACHABLE_RETURN UNREACHABLE_RETURN
30
+ #endif
31
+
23
32
  /* shims */
24
33
 
34
+ #ifndef UNDEF_P
35
+ #define UNDEF_P(val) (val == Qundef)
36
+ #endif
37
+
25
38
  #if SIZEOF_UINT64_T == SIZEOF_LONG_LONG
26
39
  # define INT64T2NUM(x) LL2NUM(x)
27
40
  # define UINT64T2NUM(x) ULL2NUM(x)
@@ -49,11 +62,40 @@ typedef unsigned char _Bool;
49
62
  #endif
50
63
  #endif
51
64
 
65
+ #ifndef HAVE_RUBY_XFREE_SIZED
66
+ static inline void ruby_xfree_sized(void *ptr, size_t oldsize)
67
+ {
68
+ ruby_xfree(ptr);
69
+ }
70
+
71
+ static inline void *ruby_xrealloc2_sized(void *ptr, size_t new_elems, size_t elem_size, size_t old_elems)
72
+ {
73
+ return ruby_xrealloc2(ptr, new_elems, elem_size);
74
+ }
75
+ #endif
76
+
77
+ # define JSON_SIZED_REALLOC_N(v, T, m, n) \
78
+ ((v) = (T *)ruby_xrealloc2_sized((void *)(v), (m), sizeof(T), (n)))
79
+
80
+ # define JSON_SIZED_FREE(v) ruby_xfree_sized((void *)(v), sizeof(*(v)))
81
+ # define JSON_SIZED_FREE_N(v, n) ruby_xfree_sized((void *)(v), sizeof(*(v)) * (n))
82
+
52
83
  #ifndef HAVE_RB_EXT_RACTOR_SAFE
53
84
  # undef RUBY_TYPED_FROZEN_SHAREABLE
54
85
  # define RUBY_TYPED_FROZEN_SHAREABLE 0
55
86
  #endif
56
87
 
88
+ #ifdef RUBY_TYPED_EMBEDDABLE
89
+ # define HAVE_RUBY_TYPED_EMBEDDABLE 1
90
+ #else
91
+ # ifdef HAVE_CONST_RUBY_TYPED_EMBEDDABLE
92
+ # define RUBY_TYPED_EMBEDDABLE RUBY_TYPED_EMBEDDABLE
93
+ # define HAVE_RUBY_TYPED_EMBEDDABLE 1
94
+ # else
95
+ # define RUBY_TYPED_EMBEDDABLE 0
96
+ # endif
97
+ #endif
98
+
57
99
  #ifndef NORETURN
58
100
  #if defined(__has_attribute) && __has_attribute(noreturn)
59
101
  #define NORETURN(x) __attribute__((noreturn)) x
@@ -102,4 +144,36 @@ typedef unsigned char _Bool;
102
144
  #define JSON_CPU_LITTLE_ENDIAN_64BITS 0
103
145
  #endif
104
146
 
147
+ #ifdef JSON_TRUFFLERUBY_RB_CATCH_BUG
148
+
149
+ #undef RB_BLOCK_CALL_FUNC_ARGLIST
150
+ #define RB_BLOCK_CALL_FUNC_ARGLIST(yielded_arg, func_args) VALUE func_args
151
+
152
+ NORETURN(static inline) void json_rb_throw_obj(VALUE tag, VALUE obj)
153
+ {
154
+ VALUE exc = rb_exc_new_str(rb_eException, rb_utf8_str_new_cstr("throw_workaround"));
155
+ rb_ivar_set(exc, rb_intern("@throw_tag"), tag);
156
+ rb_ivar_set(exc, rb_intern("@throw_obj"), obj);
157
+ rb_exc_raise(exc);
158
+ }
159
+ #define rb_throw_obj json_rb_throw_obj
160
+
161
+ static inline VALUE json_rb_catch_obj(VALUE tag, VALUE (*func)(VALUE args), VALUE func_args)
162
+ {
163
+ int status;
164
+ VALUE result = rb_protect(func, func_args, &status);
165
+ if (status) {
166
+ VALUE exc = rb_errinfo();
167
+ if (tag == rb_ivar_get(exc, rb_intern("@throw_tag"))) {
168
+ rb_set_errinfo(Qnil);
169
+ return rb_ivar_get(exc, rb_intern("@throw_obj"));
170
+ }
171
+ rb_jump_tag(status);
172
+ }
173
+ return result;
174
+ }
175
+ #define rb_catch_obj json_rb_catch_obj
176
+
177
+ #endif // JSON_TRUFFLERUBY_RB_CATCH_BUG
178
+
105
179
  #endif // _JSON_H_
@@ -2,10 +2,47 @@
2
2
  require 'mkmf'
3
3
 
4
4
  $defs << "-DJSON_DEBUG" if ENV.fetch("JSON_DEBUG", "0") != "0"
5
+
6
+ if RUBY_ENGINE == 'truffleruby' && RUBY_ENGINE_VERSION < '40.0'
7
+ # Ref: https://github.com/truffleruby/truffleruby/issues/4329
8
+ # Ref: https://github.com/truffleruby/truffleruby/pull/4333
9
+ $defs << "-DJSON_TRUFFLERUBY_RB_CATCH_BUG"
10
+ end
11
+
5
12
  have_func("rb_enc_interned_str", "ruby/encoding.h") # RUBY_VERSION >= 3.0
6
13
  have_func("rb_str_to_interned_str", "ruby.h") # RUBY_VERSION >= 3.0
7
14
  have_func("rb_hash_new_capa", "ruby.h") # RUBY_VERSION >= 3.2
8
15
  have_func("rb_hash_bulk_insert", "ruby.h") # Missing on TruffleRuby
16
+ have_func("ruby_xfree_sized", "ruby.h") # RUBY_VERSION >= 4.1
17
+
18
+ def have_builtin_func(name, check_expr, opt = "", &b)
19
+ checking_for checking_message(name.funcall_style, nil, opt) do
20
+ if try_compile(<<SRC, opt, &b)
21
+ int foo;
22
+ int main() { #{check_expr}; return 0; }
23
+ SRC
24
+ $defs.push(format("-DHAVE_BUILTIN_%s", name.tr_cpp))
25
+ true
26
+ else
27
+ false
28
+ end
29
+ end
30
+ end
31
+
32
+ have_builtin_func("__builtin_clzll", "__builtin_clzll(0)")
33
+
34
+ if have_header("x86intrin.h")
35
+ have_func("_lzcnt_u64", "x86intrin.h")
36
+ end
37
+
38
+ if have_header("intrin.h")
39
+ have_func("__lzcnt64", "intrin.h")
40
+ have_func("_BitScanReverse64", "intrin.h")
41
+ end
42
+
43
+ if RUBY_ENGINE == "ruby"
44
+ have_const("RUBY_TYPED_EMBEDDABLE", "ruby.h") # RUBY_VERSION >= 3.3
45
+ end
9
46
 
10
47
  append_cflags("-std=c99")
11
48