json 2.19.3 → 2.19.8

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: 3182d9103a2ee3b673b923a4789e947a9cddb850f870f7fdb54d93f3bd1a5493
4
- data.tar.gz: 0ee85345c9e1c99223f9cc3e859e4a7295f40f88461ca0f47059cfcc4e80154c
3
+ metadata.gz: 5f07e2772537eccf97069c6a7d7290aceba9a18c6b635e8e822bc7b92137c678
4
+ data.tar.gz: cbb25a9d0e3434eba5d9fa2be9af5a69e1fe062c5113c461d854d129122adeed
5
5
  SHA512:
6
- metadata.gz: dedf64066bc4017b977330468b01613ce1d4a78c267b38519b7ef2705368e5d73187b64ca99777afaf900cc5f58523a722833e83c0ee011944bf15b34c33479e
7
- data.tar.gz: c187fa755e0a7bd2e2ddb74498062a8138ac4882630713e3372886b5648a22a453b1a388174038b2d4144a724e1d44d5b444b190e49e3e7eb2047d8b98eef0a8
6
+ metadata.gz: 2783e5483b100728ae73451008d3d5f880cc6e9dc663d773ed9dcdf27c23c3523c345f7ca70ae0e3687ab3b11262b380a7ecf455906d72b996f07eedbb1b14f8
7
+ data.tar.gz: 84ab5ff3feb2d961b4bd5d759d444b006e8ccaa6802465203ed04a8718db43b4473859fdd613c106b0cc53d4e96d1da8b4a16550361f455964a4a1a75ae3cb23
data/CHANGES.md CHANGED
@@ -2,13 +2,38 @@
2
2
 
3
3
  ### Unreleased
4
4
 
5
+ ### 2026-06-03 (2.19.8)
6
+
7
+ * Fix 1-byte buffer overread on EOS errors.
8
+ * Handle invalid types passed as `max_nesting` option.
9
+
10
+ ### 2026-05-28 (2.19.7)
11
+
12
+ * Fix some more edge cases with out of range floats.
13
+ * Ensure the string provided to `JSON.parse` can't be mutated during parsing.
14
+ * Add missing write barriers in `State#dup`.
15
+ * Further validate generator `depth` config.
16
+
17
+ ### 2026-05-28 (2.19.6)
18
+
19
+ * Cleanly handle overly large `depth` generator argument.
20
+ * Add missing write barrier in `ParserConfig`.
21
+
22
+ ### 2026-05-04 (2.19.5)
23
+
24
+ * Cap the parser to emit a maximum of 5 deprecation warnings per document. Emitting more is not helpful.
25
+
26
+ ### 2026-04-19 (2.19.4)
27
+
28
+ * Fix parsing of out of range floats (very large exponents that lead to either `0.0` or `Inf`).
29
+
5
30
  ### 2026-03-25 (2.19.3)
6
31
 
7
32
  * Fix handling of unescaped control characters preceeded by a backslash.
8
33
 
9
34
  ### 2026-03-18 (2.19.2)
10
35
 
11
- * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`.
36
+ * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
12
37
 
13
38
  ### 2026-03-08 (2.19.1)
14
39
 
@@ -28,6 +53,10 @@
28
53
 
29
54
  * Add `:allow_control_characters` parser options, to allow JSON strings containing unescaped ASCII control characters (e.g. newlines).
30
55
 
56
+ ### 2026-03-18 (2.17.1.2) - Security Backport
57
+
58
+ * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
59
+
31
60
  ### 2025-12-04 (2.17.1)
32
61
 
33
62
  * Fix a regression in parsing of unicode surogate pairs (`\uXX\uXX`) that could cause an invalid string to be returned.
@@ -54,6 +83,10 @@
54
83
  * Optimized numbers parsing using SWAR (thanks to Scott Myron).
55
84
  * Optimized parsing of pretty printed documents using SWAR (thanks to Scott Myron).
56
85
 
86
+ ### 2026-03-18 (2.15.2.1) - Security Backport
87
+
88
+ * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
89
+
57
90
  ### 2025-10-25 (2.15.2)
58
91
 
59
92
  * Fix `JSON::Coder` to have one dedicated depth counter per invocation.
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
@@ -131,6 +131,15 @@ static inline void fbuffer_inc_capa(FBuffer *fb, size_t requested)
131
131
  }
132
132
  }
133
133
 
134
+ static inline size_t fbuffer_size_mul_or_raise(size_t a, size_t b)
135
+ {
136
+ size_t result = a * b;
137
+ if (RB_UNLIKELY(a != 0 && (result / a) != b)) {
138
+ rb_raise(rb_eArgError, "Buffer overflow, the resulting document is too large to be generated");
139
+ }
140
+ return result;
141
+ }
142
+
134
143
  static inline void fbuffer_append_reserved(FBuffer *fb, const char *newstr, size_t len)
135
144
  {
136
145
  MEMCPY(fb->ptr + fb->len, newstr, char, len);
@@ -175,7 +184,7 @@ static void fbuffer_append_str_repeat(FBuffer *fb, VALUE str, size_t repeat)
175
184
  size_t len;
176
185
  RSTRING_GETMEM(str, ptr, len);
177
186
 
178
- fbuffer_inc_capa(fb, repeat * len);
187
+ fbuffer_inc_capa(fb, fbuffer_size_mul_or_raise(repeat, len));
179
188
  while (repeat) {
180
189
  #if JSON_DEBUG
181
190
  fb->requested = len;
@@ -5,6 +5,8 @@ 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
+
8
10
  $defs << "-DJSON_GENERATOR"
9
11
  $defs << "-DJSON_DEBUG" if ENV.fetch("JSON_DEBUG", "0") != "0"
10
12
 
@@ -722,27 +722,20 @@ 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
  {
733
727
  return sizeof(JSON_Generator_State);
734
728
  }
735
729
 
736
730
  static const rb_data_type_t JSON_Generator_State_type = {
737
- "JSON/Generator/State",
738
- {
731
+ .wrap_struct_name = "JSON/Generator/State",
732
+ .function = {
739
733
  .dmark = State_mark,
740
- .dfree = State_free,
734
+ .dfree = RUBY_DEFAULT_FREE,
741
735
  .dsize = State_memsize,
742
736
  .dcompact = State_compact,
743
737
  },
744
- 0, 0,
745
- RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE,
738
+ .flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_EMBEDDABLE,
746
739
  };
747
740
 
748
741
  static void state_init(JSON_Generator_State *state)
@@ -826,9 +819,17 @@ static VALUE encode_json_string_rescue(VALUE str, VALUE exception)
826
819
  return Qundef;
827
820
  }
828
821
 
822
+ static inline int json_str_coderange(VALUE str) {
823
+ int coderange = RB_ENC_CODERANGE(str);
824
+ if (coderange == RUBY_ENC_CODERANGE_UNKNOWN) {
825
+ coderange = rb_enc_str_coderange(str);
826
+ }
827
+ return coderange;
828
+ }
829
+
829
830
  static inline bool valid_json_string_p(VALUE str)
830
831
  {
831
- int coderange = rb_enc_str_coderange(str);
832
+ int coderange = json_str_coderange(str);
832
833
 
833
834
  if (RB_LIKELY(coderange == ENC_CODERANGE_7BIT)) {
834
835
  return true;
@@ -841,12 +842,8 @@ static inline bool valid_json_string_p(VALUE str)
841
842
  return false;
842
843
  }
843
844
 
844
- static inline VALUE ensure_valid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key)
845
+ NOINLINE(static) VALUE convert_invalid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key)
845
846
  {
846
- if (RB_LIKELY(valid_json_string_p(str))) {
847
- return str;
848
- }
849
-
850
847
  if (!as_json_called && data->state->strict && RTEST(data->state->as_json)) {
851
848
  VALUE coerced_str = json_call_as_json(data->state, str, Qfalse);
852
849
  if (coerced_str != str) {
@@ -882,6 +879,16 @@ static inline VALUE ensure_valid_encoding(struct generate_json_data *data, VALUE
882
879
  return rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str);
883
880
  }
884
881
 
882
+ ALWAYS_INLINE(static) VALUE ensure_valid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key)
883
+ {
884
+ if (RB_LIKELY(valid_json_string_p(str))) {
885
+ return str;
886
+ }
887
+ else {
888
+ return convert_invalid_encoding(data, str, as_json_called, is_key);
889
+ }
890
+ }
891
+
885
892
  static void raw_generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
886
893
  {
887
894
  fbuffer_append_char(buffer, '"');
@@ -900,7 +907,7 @@ static void raw_generate_json_string(FBuffer *buffer, struct generate_json_data
900
907
  search.chunk_end = NULL;
901
908
  #endif /* HAVE_SIMD */
902
909
 
903
- switch (rb_enc_str_coderange(obj)) {
910
+ switch (json_str_coderange(obj)) {
904
911
  case ENC_CODERANGE_7BIT:
905
912
  case ENC_CODERANGE_VALID:
906
913
  if (RB_UNLIKELY(data->state->ascii_only)) {
@@ -1360,12 +1367,14 @@ static VALUE cState_init_copy(VALUE obj, VALUE orig)
1360
1367
  if (!objState) rb_raise(rb_eArgError, "unallocated JSON::State");
1361
1368
 
1362
1369
  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;
1370
+
1371
+ RB_OBJ_WRITTEN(obj, Qundef, objState->indent);
1372
+ RB_OBJ_WRITTEN(obj, Qundef, objState->space);
1373
+ RB_OBJ_WRITTEN(obj, Qundef, objState->space_before);
1374
+ RB_OBJ_WRITTEN(obj, Qundef, objState->object_nl);
1375
+ RB_OBJ_WRITTEN(obj, Qundef, objState->array_nl);
1376
+ RB_OBJ_WRITTEN(obj, Qundef, objState->as_json);
1377
+
1369
1378
  return obj;
1370
1379
  }
1371
1380
 
@@ -1572,7 +1581,7 @@ static VALUE cState_max_nesting(VALUE self)
1572
1581
 
1573
1582
  static long long_config(VALUE num)
1574
1583
  {
1575
- return RTEST(num) ? FIX2LONG(num) : 0;
1584
+ return RTEST(num) ? NUM2LONG(num) : 0;
1576
1585
  }
1577
1586
 
1578
1587
  // depth must never be negative; reject early with a clear error.
@@ -1583,6 +1592,9 @@ static long depth_config(VALUE num)
1583
1592
  if (RB_UNLIKELY(d < 0)) {
1584
1593
  rb_raise(rb_eArgError, "depth must be >= 0 (got %ld)", d);
1585
1594
  }
1595
+ if (RB_UNLIKELY(d > INT_MAX)) {
1596
+ rb_raise(rb_eArgError, "depth is too large (got %ld)", d);
1597
+ }
1586
1598
  return d;
1587
1599
  }
1588
1600
 
data/ext/json/ext/json.h CHANGED
@@ -54,6 +54,17 @@ typedef unsigned char _Bool;
54
54
  # define RUBY_TYPED_FROZEN_SHAREABLE 0
55
55
  #endif
56
56
 
57
+ #ifdef RUBY_TYPED_EMBEDDABLE
58
+ # define HAVE_RUBY_TYPED_EMBEDDABLE 1
59
+ #else
60
+ # ifdef HAVE_CONST_RUBY_TYPED_EMBEDDABLE
61
+ # define RUBY_TYPED_EMBEDDABLE RUBY_TYPED_EMBEDDABLE
62
+ # define HAVE_RUBY_TYPED_EMBEDDABLE 1
63
+ # else
64
+ # define RUBY_TYPED_EMBEDDABLE 0
65
+ # endif
66
+ #endif
67
+
57
68
  #ifndef NORETURN
58
69
  #if defined(__has_attribute) && __has_attribute(noreturn)
59
70
  #define NORETURN(x) __attribute__((noreturn)) x
@@ -7,6 +7,10 @@ have_func("rb_str_to_interned_str", "ruby.h") # RUBY_VERSION >= 3.0
7
7
  have_func("rb_hash_new_capa", "ruby.h") # RUBY_VERSION >= 3.2
8
8
  have_func("rb_hash_bulk_insert", "ruby.h") # Missing on TruffleRuby
9
9
 
10
+ if RUBY_ENGINE == "ruby"
11
+ have_const("RUBY_TYPED_EMBEDDABLE", "ruby.h") # RUBY_VERSION >= 3.3
12
+ end
13
+
10
14
  append_cflags("-std=c99")
11
15
 
12
16
  if enable_config('parser-use-simd', default=!ENV["JSON_DISABLE_SIMD"])
@@ -241,17 +241,27 @@ static void rvalue_stack_mark(void *ptr)
241
241
  {
242
242
  rvalue_stack *stack = (rvalue_stack *)ptr;
243
243
  long index;
244
- for (index = 0; index < stack->head; index++) {
245
- rb_gc_mark(stack->ptr[index]);
244
+ if (stack && stack->ptr) {
245
+ for (index = 0; index < stack->head; index++) {
246
+ rb_gc_mark(stack->ptr[index]);
247
+ }
246
248
  }
247
249
  }
248
250
 
251
+ static void rvalue_stack_free_buffer(rvalue_stack *stack)
252
+ {
253
+ ruby_xfree(stack->ptr);
254
+ stack->ptr = NULL;
255
+ }
256
+
249
257
  static void rvalue_stack_free(void *ptr)
250
258
  {
251
259
  rvalue_stack *stack = (rvalue_stack *)ptr;
252
260
  if (stack) {
253
- ruby_xfree(stack->ptr);
261
+ rvalue_stack_free_buffer(stack);
262
+ #ifndef HAVE_RUBY_TYPED_EMBEDDABLE
254
263
  ruby_xfree(stack);
264
+ #endif
255
265
  }
256
266
  }
257
267
 
@@ -262,14 +272,13 @@ static size_t rvalue_stack_memsize(const void *ptr)
262
272
  }
263
273
 
264
274
  static const rb_data_type_t JSON_Parser_rvalue_stack_type = {
265
- "JSON::Ext::Parser/rvalue_stack",
266
- {
275
+ .wrap_struct_name = "JSON::Ext::Parser/rvalue_stack",
276
+ .function = {
267
277
  .dmark = rvalue_stack_mark,
268
278
  .dfree = rvalue_stack_free,
269
279
  .dsize = rvalue_stack_memsize,
270
280
  },
271
- 0, 0,
272
- RUBY_TYPED_FREE_IMMEDIATELY,
281
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_EMBEDDABLE,
273
282
  };
274
283
 
275
284
  static rvalue_stack *rvalue_stack_spill(rvalue_stack *old_stack, VALUE *handle, rvalue_stack **stack_ref)
@@ -291,8 +300,12 @@ static void rvalue_stack_eagerly_release(VALUE handle)
291
300
  if (handle) {
292
301
  rvalue_stack *stack;
293
302
  TypedData_Get_Struct(handle, rvalue_stack, &JSON_Parser_rvalue_stack_type, stack);
294
- RTYPEDDATA_DATA(handle) = NULL;
303
+ #ifdef HAVE_RUBY_TYPED_EMBEDDABLE
304
+ rvalue_stack_free_buffer(stack);
305
+ #else
295
306
  rvalue_stack_free(stack);
307
+ RTYPEDDATA_DATA(handle) = NULL;
308
+ #endif
296
309
  }
297
310
  }
298
311
 
@@ -343,7 +356,7 @@ typedef struct JSON_ParserStruct {
343
356
  } JSON_ParserConfig;
344
357
 
345
358
  typedef struct JSON_ParserStateStruct {
346
- VALUE stack_handle;
359
+ VALUE *stack_handle;
347
360
  const char *start;
348
361
  const char *cursor;
349
362
  const char *end;
@@ -351,6 +364,7 @@ typedef struct JSON_ParserStateStruct {
351
364
  rvalue_cache name_cache;
352
365
  int in_array;
353
366
  int current_nesting;
367
+ unsigned int emitted_deprecations;
354
368
  } JSON_ParserState;
355
369
 
356
370
  static inline size_t rest(JSON_ParserState *state) {
@@ -371,6 +385,13 @@ static inline char peek(JSON_ParserState *state)
371
385
 
372
386
  static void cursor_position(JSON_ParserState *state, long *line_out, long *column_out)
373
387
  {
388
+ JSON_ASSERT(state->cursor <= state->end);
389
+
390
+ // Redundant but helpful for hardening
391
+ if (RB_UNLIKELY(state->cursor > state->end)) {
392
+ state->cursor = state->end;
393
+ }
394
+
374
395
  const char *cursor = state->cursor;
375
396
  long column = 0;
376
397
  long line = 1;
@@ -436,9 +457,8 @@ static VALUE build_parse_error_message(const char *format, JSON_ParserState *sta
436
457
  }
437
458
  }
438
459
 
439
- VALUE msg = rb_sprintf(format, ptr);
440
- VALUE message = rb_enc_sprintf(enc_utf8, "%s at line %ld column %ld", RSTRING_PTR(msg), line, column);
441
- RB_GC_GUARD(msg);
460
+ VALUE message = rb_enc_sprintf(enc_utf8, format, ptr);
461
+ rb_str_catf(message, " at line %ld column %ld", line, column);
442
462
  return message;
443
463
  }
444
464
 
@@ -843,7 +863,7 @@ NOINLINE(static) VALUE json_decode_large_float(const char *start, long len)
843
863
  /* Ruby JSON optimized float decoder using vendored Ryu algorithm
844
864
  * Accepts pre-extracted mantissa and exponent from first-pass validation
845
865
  */
846
- static inline VALUE json_decode_float(JSON_ParserConfig *config, uint64_t mantissa, int mantissa_digits, int32_t exponent, bool negative,
866
+ static inline VALUE json_decode_float(JSON_ParserConfig *config, uint64_t mantissa, int mantissa_digits, int64_t exponent, bool negative,
847
867
  const char *start, const char *end)
848
868
  {
849
869
  if (RB_UNLIKELY(config->decimal_class)) {
@@ -851,13 +871,21 @@ static inline VALUE json_decode_float(JSON_ParserConfig *config, uint64_t mantis
851
871
  return rb_funcallv(config->decimal_class, config->decimal_method_id, 1, &text);
852
872
  }
853
873
 
874
+ if (RB_UNLIKELY(exponent > INT32_MAX)) {
875
+ return negative ? CMinusInfinity : CInfinity;
876
+ }
877
+
878
+ if (RB_UNLIKELY(exponent < INT32_MIN)) {
879
+ return rb_float_new(negative ? -0.0 : 0.0);
880
+ }
881
+
854
882
  // Fall back to rb_cstr_to_dbl for potential subnormals (rare edge case)
855
883
  // Ryu has rounding issues with subnormals around 1e-310 (< 2.225e-308)
856
884
  if (RB_UNLIKELY(mantissa_digits > 17 || mantissa_digits + exponent < -307)) {
857
885
  return json_decode_large_float(start, end - start);
858
886
  }
859
887
 
860
- return DBL2NUM(ryu_s2d_from_parts(mantissa, mantissa_digits, exponent, negative));
888
+ return DBL2NUM(ryu_s2d_from_parts(mantissa, mantissa_digits, (int32_t)exponent, negative));
861
889
  }
862
890
 
863
891
  static inline VALUE json_decode_array(JSON_ParserState *state, JSON_ParserConfig *config, long count)
@@ -911,9 +939,6 @@ NORETURN(static) void raise_duplicate_key_error(JSON_ParserState *state, VALUE d
911
939
  cursor_position(state, &line, &column);
912
940
  rb_str_concat(message, build_parse_error_message("", state, line, column)) ;
913
941
  rb_exc_raise(parse_error_new(message, line, column));
914
-
915
- raise_parse_error(RSTRING_PTR(message), state);
916
- RB_GC_GUARD(message);
917
942
  }
918
943
 
919
944
  static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfig *config, size_t count)
@@ -928,7 +953,12 @@ static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfi
928
953
  case JSON_IGNORE:
929
954
  break;
930
955
  case JSON_DEPRECATED:
931
- emit_duplicate_key_warning(state, json_find_duplicated_key(count, pairs));
956
+ // Only emit the first few deprecations to avoid spamming.
957
+ if (state->emitted_deprecations < 5) {
958
+ emit_duplicate_key_warning(state, json_find_duplicated_key(count, pairs));
959
+ state->emitted_deprecations++;
960
+ }
961
+
932
962
  break;
933
963
  case JSON_RAISE:
934
964
  raise_duplicate_key_error(state, json_find_duplicated_key(count, pairs));
@@ -950,7 +980,7 @@ static inline VALUE json_push_value(JSON_ParserState *state, JSON_ParserConfig *
950
980
  if (RB_UNLIKELY(config->on_load_proc)) {
951
981
  value = rb_proc_call_with_block(config->on_load_proc, 1, &value, Qnil);
952
982
  }
953
- rvalue_stack_push(state->stack, value, &state->stack_handle, &state->stack);
983
+ rvalue_stack_push(state->stack, value, state->stack_handle, &state->stack);
954
984
  return value;
955
985
  }
956
986
 
@@ -999,6 +1029,13 @@ ALWAYS_INLINE(static) bool string_scan(JSON_ParserState *state)
999
1029
  }
1000
1030
  state->cursor++;
1001
1031
  }
1032
+
1033
+ // If the string ended with an unterminated escape sequence, we might
1034
+ // have gone past the end.
1035
+ if (RB_UNLIKELY(state->cursor > state->end)) {
1036
+ state->cursor = state->end;
1037
+ }
1038
+
1002
1039
  return false;
1003
1040
  }
1004
1041
 
@@ -1135,7 +1172,7 @@ static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig
1135
1172
  const char first_digit = *state->cursor;
1136
1173
 
1137
1174
  // Variables for Ryu optimization - extract digits during parsing
1138
- int32_t exponent = 0;
1175
+ int64_t exponent = 0;
1139
1176
  int decimal_point_pos = -1;
1140
1177
  uint64_t mantissa = 0;
1141
1178
 
@@ -1179,7 +1216,11 @@ static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig
1179
1216
  raise_parse_error_at("invalid number: %s", state, start);
1180
1217
  }
1181
1218
 
1182
- exponent = negative_exponent ? -((int32_t)abs_exponent) : ((int32_t)abs_exponent);
1219
+ if (RB_UNLIKELY(exponent_digits >= 20 || abs_exponent > (uint64_t)INT64_MAX)) {
1220
+ exponent = negative_exponent ? INT64_MIN : INT64_MAX;
1221
+ } else {
1222
+ exponent = negative_exponent ? -(int64_t)abs_exponent : (int64_t)abs_exponent;
1223
+ }
1183
1224
  }
1184
1225
 
1185
1226
  if (integer) {
@@ -1434,23 +1475,39 @@ static void json_ensure_eof(JSON_ParserState *state)
1434
1475
 
1435
1476
  static VALUE convert_encoding(VALUE source)
1436
1477
  {
1437
- int encindex = RB_ENCODING_GET(source);
1478
+ StringValue(source);
1479
+ int encindex = RB_ENCODING_GET(source);
1480
+
1481
+ if (RB_LIKELY(encindex == utf8_encindex)) {
1482
+ return source;
1483
+ }
1484
+
1485
+ if (encindex == binary_encindex) {
1486
+ // For historical reason, we silently reinterpret binary strings as UTF-8
1487
+ return rb_enc_associate_index(rb_str_dup(source), utf8_encindex);
1488
+ }
1438
1489
 
1439
- if (RB_LIKELY(encindex == utf8_encindex)) {
1490
+ source = rb_funcall(source, i_encode, 1, Encoding_UTF_8);
1491
+ StringValue(source);
1440
1492
  return source;
1441
- }
1493
+ }
1442
1494
 
1443
- if (encindex == binary_encindex) {
1444
- // For historical reason, we silently reinterpret binary strings as UTF-8
1445
- return rb_enc_associate_index(rb_str_dup(source), utf8_encindex);
1446
- }
1495
+ struct parser_config_init_args {
1496
+ JSON_ParserConfig *config;
1497
+ VALUE self;
1498
+ };
1447
1499
 
1448
- return rb_funcall(source, i_encode, 1, Encoding_UTF_8);
1500
+ static void parser_config_wb_write(VALUE self, VALUE *dest, VALUE val)
1501
+ {
1502
+ *dest = val;
1503
+ if (self) RB_OBJ_WRITTEN(self, Qundef, val);
1449
1504
  }
1450
1505
 
1451
1506
  static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
1452
1507
  {
1453
- JSON_ParserConfig *config = (JSON_ParserConfig *)data;
1508
+ struct parser_config_init_args *args = (struct parser_config_init_args *)data;
1509
+ JSON_ParserConfig *config = args->config;
1510
+ VALUE self = args->self;
1454
1511
 
1455
1512
  if (key == sym_max_nesting) { config->max_nesting = RTEST(val) ? FIX2INT(val) : 0; }
1456
1513
  else if (key == sym_allow_nan) { config->allow_nan = RTEST(val); }
@@ -1459,15 +1516,15 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
1459
1516
  else if (key == sym_allow_invalid_escape) { config->allow_invalid_escape = RTEST(val); }
1460
1517
  else if (key == sym_symbolize_names) { config->symbolize_names = RTEST(val); }
1461
1518
  else if (key == sym_freeze) { config->freeze = RTEST(val); }
1462
- else if (key == sym_on_load) { config->on_load_proc = RTEST(val) ? val : Qfalse; }
1519
+ else if (key == sym_on_load) { parser_config_wb_write(self, &config->on_load_proc, RTEST(val) ? val : Qfalse); }
1463
1520
  else if (key == sym_allow_duplicate_key) { config->on_duplicate_key = RTEST(val) ? JSON_IGNORE : JSON_RAISE; }
1464
1521
  else if (key == sym_decimal_class) {
1465
1522
  if (RTEST(val)) {
1466
1523
  if (rb_respond_to(val, i_try_convert)) {
1467
- config->decimal_class = val;
1524
+ parser_config_wb_write(self, &config->decimal_class, val);
1468
1525
  config->decimal_method_id = i_try_convert;
1469
1526
  } else if (rb_respond_to(val, i_new)) {
1470
- config->decimal_class = val;
1527
+ parser_config_wb_write(self, &config->decimal_class, val);
1471
1528
  config->decimal_method_id = i_new;
1472
1529
  } else if (RB_TYPE_P(val, T_CLASS)) {
1473
1530
  VALUE name = rb_class_name(val);
@@ -1476,7 +1533,7 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
1476
1533
  if (last_colon) {
1477
1534
  const char *mod_path_end = last_colon - 1;
1478
1535
  VALUE mod_path = rb_str_substr(name, 0, mod_path_end - name_cstr);
1479
- config->decimal_class = rb_path_to_class(mod_path);
1536
+ parser_config_wb_write(self, &config->decimal_class, rb_path_to_class(mod_path));
1480
1537
 
1481
1538
  const char *method_name_beg = last_colon + 1;
1482
1539
  long before_len = method_name_beg - name_cstr;
@@ -1484,7 +1541,7 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
1484
1541
  VALUE method_name = rb_str_substr(name, before_len, len);
1485
1542
  config->decimal_method_id = SYM2ID(rb_str_intern(method_name));
1486
1543
  } else {
1487
- config->decimal_class = rb_mKernel;
1544
+ parser_config_wb_write(self, &config->decimal_class, rb_mKernel);
1488
1545
  config->decimal_method_id = SYM2ID(rb_str_intern(name));
1489
1546
  }
1490
1547
  }
@@ -1494,16 +1551,21 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
1494
1551
  return ST_CONTINUE;
1495
1552
  }
1496
1553
 
1497
- static void parser_config_init(JSON_ParserConfig *config, VALUE opts)
1554
+ static void parser_config_init(JSON_ParserConfig *config, VALUE opts, VALUE self)
1498
1555
  {
1499
1556
  config->max_nesting = 100;
1500
1557
 
1558
+ struct parser_config_init_args args = {
1559
+ .config = config,
1560
+ .self = self,
1561
+ };
1562
+
1501
1563
  if (!NIL_P(opts)) {
1502
1564
  Check_Type(opts, T_HASH);
1503
1565
  if (RHASH_SIZE(opts) > 0) {
1504
1566
  // We assume in most cases few keys are set so it's faster to go over
1505
1567
  // the provided keys than to check all possible keys.
1506
- rb_hash_foreach(opts, parser_config_init_i, (VALUE)config);
1568
+ rb_hash_foreach(opts, parser_config_init_i, (VALUE)&args);
1507
1569
  }
1508
1570
 
1509
1571
  }
@@ -1537,17 +1599,21 @@ static VALUE cParserConfig_initialize(VALUE self, VALUE opts)
1537
1599
  rb_check_frozen(self);
1538
1600
  GET_PARSER_CONFIG;
1539
1601
 
1540
- parser_config_init(config, opts);
1541
-
1542
- RB_OBJ_WRITTEN(self, Qundef, config->decimal_class);
1602
+ parser_config_init(config, opts, self);
1543
1603
 
1544
1604
  return self;
1545
1605
  }
1546
1606
 
1547
- static VALUE cParser_parse(JSON_ParserConfig *config, VALUE Vsource)
1607
+ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE src)
1548
1608
  {
1549
- Vsource = convert_encoding(StringValue(Vsource));
1550
- StringValue(Vsource);
1609
+ VALUE Vsource = convert_encoding(src);
1610
+
1611
+ // Ensure the string isn't mutated under us.
1612
+ // The classic API to use is `rb_str_locktmp`, but then we'd
1613
+ // need to use `rb_protect` to make sure we always unlock.
1614
+ if (Vsource == src) {
1615
+ Vsource = rb_str_new_frozen(Vsource);
1616
+ }
1551
1617
 
1552
1618
  VALUE rvalue_stack_buffer[RVALUE_STACK_INITIAL_CAPA];
1553
1619
  rvalue_stack stack = {
@@ -1558,13 +1624,16 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE Vsource)
1558
1624
 
1559
1625
  long len;
1560
1626
  const char *start;
1627
+
1561
1628
  RSTRING_GETMEM(Vsource, start, len);
1562
1629
 
1630
+ VALUE stack_handle = 0;
1563
1631
  JSON_ParserState _state = {
1564
1632
  .start = start,
1565
1633
  .cursor = start,
1566
1634
  .end = start + len,
1567
1635
  .stack = &stack,
1636
+ .stack_handle = &stack_handle,
1568
1637
  };
1569
1638
  JSON_ParserState *state = &_state;
1570
1639
 
@@ -1572,8 +1641,9 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE Vsource)
1572
1641
 
1573
1642
  // This may be skipped in case of exception, but
1574
1643
  // it won't cause a leak.
1575
- rvalue_stack_eagerly_release(state->stack_handle);
1576
-
1644
+ rvalue_stack_eagerly_release(stack_handle);
1645
+ RB_GC_GUARD(stack_handle);
1646
+ RB_GC_GUARD(Vsource);
1577
1647
  json_ensure_eof(state);
1578
1648
 
1579
1649
  return result;
@@ -1594,12 +1664,9 @@ static VALUE cParserConfig_parse(VALUE self, VALUE Vsource)
1594
1664
 
1595
1665
  static VALUE cParser_m_parse(VALUE klass, VALUE Vsource, VALUE opts)
1596
1666
  {
1597
- Vsource = convert_encoding(StringValue(Vsource));
1598
- StringValue(Vsource);
1599
-
1600
1667
  JSON_ParserConfig _config = {0};
1601
1668
  JSON_ParserConfig *config = &_config;
1602
- parser_config_init(config, opts);
1669
+ parser_config_init(config, opts, false);
1603
1670
 
1604
1671
  return cParser_parse(config, Vsource);
1605
1672
  }
@@ -1611,26 +1678,19 @@ static void JSON_ParserConfig_mark(void *ptr)
1611
1678
  rb_gc_mark(config->decimal_class);
1612
1679
  }
1613
1680
 
1614
- static void JSON_ParserConfig_free(void *ptr)
1615
- {
1616
- JSON_ParserConfig *config = ptr;
1617
- ruby_xfree(config);
1618
- }
1619
-
1620
1681
  static size_t JSON_ParserConfig_memsize(const void *ptr)
1621
1682
  {
1622
1683
  return sizeof(JSON_ParserConfig);
1623
1684
  }
1624
1685
 
1625
1686
  static const rb_data_type_t JSON_ParserConfig_type = {
1626
- "JSON::Ext::Parser/ParserConfig",
1627
- {
1687
+ .wrap_struct_name = "JSON::Ext::Parser/ParserConfig",
1688
+ .function = {
1628
1689
  JSON_ParserConfig_mark,
1629
- JSON_ParserConfig_free,
1690
+ RUBY_DEFAULT_FREE,
1630
1691
  JSON_ParserConfig_memsize,
1631
1692
  },
1632
- 0, 0,
1633
- RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE,
1693
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_EMBEDDABLE,
1634
1694
  };
1635
1695
 
1636
1696
  static VALUE cJSON_parser_s_allocate(VALUE klass)
@@ -48,7 +48,7 @@ module JSON
48
48
  SCRIPT_SAFE_ESCAPE_PATTERN = /[\/"\\\x0-\x1f\u2028-\u2029]/
49
49
 
50
50
  def self.native_type?(value) # :nodoc:
51
- (false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value)
51
+ (false == value || true == value || nil == value || String === value || Symbol === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value)
52
52
  end
53
53
 
54
54
  def self.native_key?(key) # :nodoc:
@@ -307,6 +307,9 @@ module JSON
307
307
  if !opts.key?(:max_nesting) # defaults to 100
308
308
  @max_nesting = 100
309
309
  elsif opts[:max_nesting]
310
+ unless opts[:max_nesting].is_a?(Integer)
311
+ raise TypeError, ":max_nesting must be an Integer, got: #{opts[:max_nesting].class}"
312
+ end
310
313
  @max_nesting = opts[:max_nesting]
311
314
  else
312
315
  @max_nesting = 0
@@ -517,11 +520,11 @@ module JSON
517
520
 
518
521
  if empty?
519
522
  state.depth -= 1
520
- return '{}'
523
+ return +'{}'
521
524
  end
522
525
 
523
526
  delim = ",#{state.object_nl}"
524
- result = +"{#{state.object_nl}"
527
+ result = "{#{state.object_nl}"
525
528
  first = true
526
529
  key_type = nil
527
530
  indent = !state.object_nl.empty?
@@ -558,7 +561,7 @@ module JSON
558
561
  raise TypeError, "#{key.class}#to_s returns an instance of #{key_str.class}, expected a String"
559
562
  end
560
563
 
561
- result = +"#{result}#{key_json}#{state.space_before}:#{state.space}"
564
+ result = "#{result}#{key_json}#{state.space_before}:#{state.space}"
562
565
  if state.strict? && !Generator.native_type?(value)
563
566
  if state.as_json
564
567
  value = state.as_json.call(value, false)
@@ -609,7 +612,7 @@ module JSON
609
612
 
610
613
  if empty?
611
614
  state.depth -= 1
612
- return '[]'
615
+ return +'[]'
613
616
  end
614
617
 
615
618
  result = '['.dup
@@ -734,17 +737,17 @@ module JSON
734
737
 
735
738
  module TrueClass
736
739
  # Returns a JSON string for true: 'true'.
737
- def to_json(*) 'true' end
740
+ def to_json(*) +'true' end
738
741
  end
739
742
 
740
743
  module FalseClass
741
744
  # Returns a JSON string for false: 'false'.
742
- def to_json(*) 'false' end
745
+ def to_json(*) +'false' end
743
746
  end
744
747
 
745
748
  module NilClass
746
749
  # Returns a JSON string for nil: 'null'.
747
- def to_json(*) 'null' end
750
+ def to_json(*) +'null' end
748
751
  end
749
752
  end
750
753
  end
data/lib/json/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSON
4
- VERSION = '2.19.3'
4
+ VERSION = '2.19.8'
5
5
  end
data/lib/json.rb CHANGED
@@ -335,8 +335,8 @@ require 'json/common'
335
335
  # JSON.generate(JSON::MinusInfinity)
336
336
  #
337
337
  # Allow:
338
- # ruby = [Float::NaN, Float::Infinity, Float::MinusInfinity]
339
- # JSON.generate(ruby, allow_nan: true) # => '[NaN,Infinity,-Infinity]'
338
+ # ruby = [Float::NAN, Float::INFINITY, JSON::NaN, JSON::Infinity, JSON::MinusInfinity]
339
+ # JSON.generate(ruby, allow_nan: true) # => '[NaN,Infinity,NaN,Infinity,-Infinity]'
340
340
  #
341
341
  # ---
342
342
  #
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.19.3
4
+ version: 2.19.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank
@@ -84,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
84
84
  - !ruby/object:Gem::Version
85
85
  version: '0'
86
86
  requirements: []
87
- rubygems_version: 4.0.3
87
+ rubygems_version: 4.0.12
88
88
  specification_version: 4
89
89
  summary: JSON Implementation for Ruby
90
90
  test_files: []