oj 3.16.3 → 3.17.4

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.
Files changed (134) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +99 -0
  3. data/README.md +0 -16
  4. data/ext/oj/compat.c +3 -3
  5. data/ext/oj/custom.c +17 -11
  6. data/ext/oj/dump.c +619 -88
  7. data/ext/oj/dump.h +9 -2
  8. data/ext/oj/dump_compat.c +16 -9
  9. data/ext/oj/dump_leaf.c +1 -1
  10. data/ext/oj/dump_object.c +52 -27
  11. data/ext/oj/dump_strict.c +20 -14
  12. data/ext/oj/extconf.rb +11 -9
  13. data/ext/oj/fast.c +90 -65
  14. data/ext/oj/intern.c +1 -1
  15. data/ext/oj/mimic_json.c +14 -8
  16. data/ext/oj/object.c +18 -12
  17. data/ext/oj/oj.c +426 -11
  18. data/ext/oj/oj.h +60 -52
  19. data/ext/oj/parse.c +252 -48
  20. data/ext/oj/parse.h +0 -1
  21. data/ext/oj/parser.c +179 -47
  22. data/ext/oj/parser.h +4 -2
  23. data/ext/oj/rails.c +57 -36
  24. data/ext/oj/reader.c +17 -5
  25. data/ext/oj/rxclass.c +17 -1
  26. data/ext/oj/rxclass.h +2 -1
  27. data/ext/oj/safe.c +230 -0
  28. data/ext/oj/safe.h +79 -0
  29. data/ext/oj/saj.c +55 -20
  30. data/ext/oj/scp.c +3 -6
  31. data/ext/oj/simd.h +219 -0
  32. data/ext/oj/sparse.c +15 -3
  33. data/ext/oj/stream_writer.c +19 -14
  34. data/ext/oj/strict.c +2 -4
  35. data/ext/oj/string_writer.c +26 -14
  36. data/ext/oj/usual.c +37 -35
  37. data/ext/oj/wab.c +5 -4
  38. data/lib/oj/mimic.rb +1 -5
  39. data/lib/oj/schandler.rb +5 -4
  40. data/lib/oj/version.rb +1 -1
  41. data/pages/Encoding.md +1 -1
  42. metadata +22 -100
  43. data/test/_test_active.rb +0 -75
  44. data/test/_test_active_mimic.rb +0 -95
  45. data/test/_test_mimic_rails.rb +0 -123
  46. data/test/activerecord/result_test.rb +0 -31
  47. data/test/activesupport6/abstract_unit.rb +0 -44
  48. data/test/activesupport6/decoding_test.rb +0 -133
  49. data/test/activesupport6/encoding_test.rb +0 -507
  50. data/test/activesupport6/encoding_test_cases.rb +0 -98
  51. data/test/activesupport6/test_common.rb +0 -17
  52. data/test/activesupport6/test_helper.rb +0 -163
  53. data/test/activesupport6/time_zone_test_helpers.rb +0 -39
  54. data/test/activesupport7/abstract_unit.rb +0 -49
  55. data/test/activesupport7/decoding_test.rb +0 -125
  56. data/test/activesupport7/encoding_test.rb +0 -486
  57. data/test/activesupport7/encoding_test_cases.rb +0 -104
  58. data/test/activesupport7/time_zone_test_helpers.rb +0 -47
  59. data/test/files.rb +0 -29
  60. data/test/foo.rb +0 -14
  61. data/test/helper.rb +0 -39
  62. data/test/isolated/shared.rb +0 -309
  63. data/test/isolated/test_mimic_after.rb +0 -13
  64. data/test/isolated/test_mimic_alone.rb +0 -12
  65. data/test/isolated/test_mimic_as_json.rb +0 -45
  66. data/test/isolated/test_mimic_before.rb +0 -13
  67. data/test/isolated/test_mimic_define.rb +0 -28
  68. data/test/isolated/test_mimic_rails_after.rb +0 -22
  69. data/test/isolated/test_mimic_rails_before.rb +0 -21
  70. data/test/isolated/test_mimic_redefine.rb +0 -15
  71. data/test/json_gem/json_addition_test.rb +0 -216
  72. data/test/json_gem/json_common_interface_test.rb +0 -155
  73. data/test/json_gem/json_encoding_test.rb +0 -107
  74. data/test/json_gem/json_ext_parser_test.rb +0 -21
  75. data/test/json_gem/json_fixtures_test.rb +0 -36
  76. data/test/json_gem/json_generator_test.rb +0 -413
  77. data/test/json_gem/json_generic_object_test.rb +0 -90
  78. data/test/json_gem/json_parser_test.rb +0 -477
  79. data/test/json_gem/json_string_matching_test.rb +0 -42
  80. data/test/json_gem/test_helper.rb +0 -30
  81. data/test/mem.rb +0 -34
  82. data/test/perf.rb +0 -102
  83. data/test/perf_compat.rb +0 -128
  84. data/test/perf_dump.rb +0 -50
  85. data/test/perf_fast.rb +0 -162
  86. data/test/perf_file.rb +0 -62
  87. data/test/perf_object.rb +0 -134
  88. data/test/perf_once.rb +0 -59
  89. data/test/perf_parser.rb +0 -183
  90. data/test/perf_saj.rb +0 -101
  91. data/test/perf_scp.rb +0 -140
  92. data/test/perf_simple.rb +0 -289
  93. data/test/perf_strict.rb +0 -137
  94. data/test/perf_wab.rb +0 -129
  95. data/test/prec.rb +0 -23
  96. data/test/sample/change.rb +0 -13
  97. data/test/sample/dir.rb +0 -18
  98. data/test/sample/doc.rb +0 -35
  99. data/test/sample/file.rb +0 -47
  100. data/test/sample/group.rb +0 -15
  101. data/test/sample/hasprops.rb +0 -15
  102. data/test/sample/layer.rb +0 -11
  103. data/test/sample/line.rb +0 -20
  104. data/test/sample/oval.rb +0 -10
  105. data/test/sample/rect.rb +0 -9
  106. data/test/sample/shape.rb +0 -34
  107. data/test/sample/text.rb +0 -19
  108. data/test/sample.rb +0 -54
  109. data/test/sample_json.rb +0 -37
  110. data/test/test_compat.rb +0 -567
  111. data/test/test_custom.rb +0 -555
  112. data/test/test_debian.rb +0 -50
  113. data/test/test_fast.rb +0 -526
  114. data/test/test_file.rb +0 -250
  115. data/test/test_gc.rb +0 -60
  116. data/test/test_generate.rb +0 -21
  117. data/test/test_hash.rb +0 -39
  118. data/test/test_integer_range.rb +0 -72
  119. data/test/test_null.rb +0 -376
  120. data/test/test_object.rb +0 -1030
  121. data/test/test_parser.rb +0 -11
  122. data/test/test_parser_debug.rb +0 -27
  123. data/test/test_parser_saj.rb +0 -337
  124. data/test/test_parser_usual.rb +0 -227
  125. data/test/test_rails.rb +0 -35
  126. data/test/test_saj.rb +0 -188
  127. data/test/test_scp.rb +0 -431
  128. data/test/test_strict.rb +0 -441
  129. data/test/test_various.rb +0 -801
  130. data/test/test_wab.rb +0 -311
  131. data/test/test_writer.rb +0 -380
  132. data/test/tests.rb +0 -33
  133. data/test/tests_mimic.rb +0 -23
  134. data/test/tests_mimic_addition.rb +0 -16
data/ext/oj/oj.c CHANGED
@@ -18,6 +18,9 @@
18
18
  #include "odd.h"
19
19
  #include "parse.h"
20
20
  #include "rails.h"
21
+ #include "simd.h"
22
+
23
+ #define MAX_INDENT 16
21
24
 
22
25
  typedef struct _yesNoOpt {
23
26
  VALUE sym;
@@ -36,6 +39,7 @@ ID oj_as_json_id;
36
39
  ID oj_begin_id;
37
40
  ID oj_bigdecimal_id;
38
41
  ID oj_end_id;
42
+ ID oj_eofq_id;
39
43
  ID oj_exclude_end_id;
40
44
  ID oj_error_id;
41
45
  ID oj_file_id;
@@ -118,7 +122,9 @@ static VALUE create_id_sym;
118
122
  static VALUE custom_sym;
119
123
  static VALUE empty_string_sym;
120
124
  static VALUE escape_mode_sym;
125
+ static VALUE except_sym;
121
126
  static VALUE integer_range_sym;
127
+ static VALUE max_integer_digits_sym;
122
128
  static VALUE fast_sym;
123
129
  static VALUE float_prec_sym;
124
130
  static VALUE float_format_sym;
@@ -136,6 +142,7 @@ static VALUE null_sym;
136
142
  static VALUE object_sym;
137
143
  static VALUE omit_null_byte_sym;
138
144
  static VALUE omit_nil_sym;
145
+ static VALUE only_sym;
139
146
  static VALUE rails_sym;
140
147
  static VALUE raise_sym;
141
148
  static VALUE ruby_sym;
@@ -165,6 +172,8 @@ pthread_mutex_t oj_cache_mutex;
165
172
  VALUE oj_cache_mutex = Qnil;
166
173
  #endif
167
174
 
175
+ SIMD_Implementation SIMD_Impl = SIMD_NONE;
176
+
168
177
  extern void oj_parser_init();
169
178
 
170
179
  const char oj_json_class[] = "json_class";
@@ -200,6 +209,7 @@ struct _options oj_default_options = {
200
209
  0, // cache_str
201
210
  0, // int_range_min
202
211
  0, // int_range_max
212
+ 0, // max_integer_digits
203
213
  oj_json_class, // create_id
204
214
  10, // create_id_len
205
215
  9, // sec_prec
@@ -224,6 +234,8 @@ struct _options oj_default_options = {
224
234
  false, // omit_nil
225
235
  false, // omit_null_byte
226
236
  MAX_DEPTH, // max_depth
237
+ NULL, // only
238
+ NULL, // except
227
239
  },
228
240
  {
229
241
  // str_rx
@@ -234,6 +246,22 @@ struct _options oj_default_options = {
234
246
  NULL,
235
247
  };
236
248
 
249
+ static VALUE only_array_from_string(const char *str) {
250
+ volatile VALUE a = Qnil;
251
+
252
+ if (NULL != str && 2 < strlen(str)) {
253
+ str++;
254
+ char *cp;
255
+
256
+ a = rb_ary_new();
257
+ while (NULL != (cp = strchr(str, ':'))) {
258
+ rb_ary_push(a, rb_id2sym(rb_intern2(str, cp - str)));
259
+ str = cp + 1;
260
+ }
261
+ }
262
+ return a;
263
+ }
264
+
237
265
  /* Document-method: default_options()
238
266
  * call-seq: default_options()
239
267
  *
@@ -310,10 +338,17 @@ struct _options oj_default_options = {
310
338
  * - *:cache_str* [_Fixnum_] maximum string value length to cache (strings less
311
339
  * than this are cached)
312
340
  * - *:integer_range* [_Range_] Dump integers outside range as strings.
341
+ * - *:max_integer_digits* [_Fixnum_] Maximum number of decimal digits allowed in a
342
+ * parsed integer. When the limit is exceeded a parse error is raised. 0 (the
343
+ * default) disables the limit. Setting a reasonable limit is recommended when
344
+ * parsing untrusted input to mitigate CPU-DoS attacks. Only applies to the
345
+ * legacy parsers (Oj.load, Oj::Doc, JSON.parse mimic); Oj::Parser is unaffected.
313
346
  * - *:trace* [_true,_|_false_] Trace all load and dump calls, default is false
314
347
  * (trace is off)
315
348
  * - *:safe* [_true,_|_false_] Safe mimic breaks JSON mimic to be safer, default
316
349
  * is false (safe is off)
350
+ * - *:only* [_nil,_|_Array_] A list of the fields to encode. All others are skipped.
351
+ * - *:except* [_nil,_|_Array_] A list of the fields to not encode. All others are encoded.
317
352
  *
318
353
  * Return [_Hash_] all current option settings.
319
354
  */
@@ -422,6 +457,7 @@ static VALUE get_def_opts(VALUE self) {
422
457
  } else {
423
458
  rb_hash_aset(opts, integer_range_sym, Qnil);
424
459
  }
460
+ rb_hash_aset(opts, max_integer_digits_sym, LONG2NUM((long)oj_default_options.max_integer_digits));
425
461
  switch (oj_default_options.escape_mode) {
426
462
  case NLEsc: rb_hash_aset(opts, escape_mode_sym, newline_sym); break;
427
463
  case JSONEsc: rb_hash_aset(opts, escape_mode_sym, json_sym); break;
@@ -479,6 +515,9 @@ static VALUE get_def_opts(VALUE self) {
479
515
  rb_hash_aset(opts, oj_hash_class_sym, oj_default_options.hash_class);
480
516
  rb_hash_aset(opts, oj_array_class_sym, oj_default_options.array_class);
481
517
 
518
+ rb_hash_aset(opts, only_sym, only_array_from_string(oj_default_options.dump_opts.only));
519
+ rb_hash_aset(opts, except_sym, only_array_from_string(oj_default_options.dump_opts.except));
520
+
482
521
  if (NULL == oj_default_options.ignore) {
483
522
  rb_hash_aset(opts, ignore_sym, Qnil);
484
523
  } else {
@@ -562,12 +601,57 @@ static VALUE get_def_opts(VALUE self) {
562
601
  * - *:cache_keys* [_Boolean_] if true then hash keys are cached
563
602
  * - *:cache_str* [_Fixnum_] maximum string value length to cache (strings less than this are cached)
564
603
  * - *:integer_range* [_Range_] Dump integers outside range as strings.
604
+ * - *:max_integer_digits* [_Fixnum_] Maximum decimal digits in a parsed integer
605
+ * (0 = unlimited). Use to mitigate CPU-DoS via huge integer values in JSON.
565
606
  * - *:trace* [_Boolean_] turn trace on or off.
566
607
  * - *:safe* [_Boolean_] turn safe mimic on or off.
567
608
  */
609
+ static ID oj_default_options_keeper_id = 0;
610
+
611
+ // The default options are a plain C global so the Ruby objects in them, the
612
+ // classes of the hash_class, array_class, and ignore options and the regexps
613
+ // and the classes of the match_string option, can not be reached by the GC and
614
+ // are collected while the defaults still refer to them. Holding them in an
615
+ // array the GC does know about keeps them alive. The array is rebuilt whenever
616
+ // the defaults change and replaces the previous one so that only the objects
617
+ // the defaults are currently using are held.
618
+ static void keep_default_options(void) {
619
+ VALUE keep = rb_ary_new();
620
+
621
+ if (0 == oj_default_options_keeper_id) {
622
+ oj_default_options_keeper_id = rb_intern("@default_options_keeper");
623
+ }
624
+ if (Qnil != oj_default_options.hash_class) {
625
+ rb_ary_push(keep, oj_default_options.hash_class);
626
+ }
627
+ if (Qnil != oj_default_options.array_class) {
628
+ rb_ary_push(keep, oj_default_options.array_class);
629
+ }
630
+ if (NULL != oj_default_options.ignore) {
631
+ VALUE *vp;
632
+
633
+ for (vp = oj_default_options.ignore; Qnil != *vp; vp++) {
634
+ rb_ary_push(keep, *vp);
635
+ }
636
+ }
637
+ oj_rxclass_keep(&oj_default_options.str_rx, keep);
638
+ // An instance variable of the Oj module is a reference the GC can follow on
639
+ // every engine, unlike a C global.
640
+ rb_ivar_set(Oj, oj_default_options_keeper_id, keep);
641
+ }
642
+
568
643
  static VALUE set_def_opts(VALUE self, VALUE opts) {
569
644
  Check_Type(opts, T_HASH);
645
+ // A :match_string option replaces the regexps instead of adding to them so
646
+ // the ones the defaults are holding have to be freed here, the only place
647
+ // that owns them. A per call options struct only aliases them.
648
+ if (Qnil != rb_hash_lookup(opts, match_string_sym)) {
649
+ oj_rxclass_cleanup(&oj_default_options.str_rx);
650
+ oj_default_options.str_rx.head = NULL;
651
+ oj_default_options.str_rx.tail = NULL;
652
+ }
570
653
  oj_parse_options(opts, &oj_default_options);
654
+ keep_default_options();
571
655
 
572
656
  return Qnil;
573
657
  }
@@ -621,6 +705,95 @@ bool set_yesno_options(VALUE key, VALUE value, Options copts) {
621
705
  return false;
622
706
  }
623
707
 
708
+ static const char *make_only_value(VALUE v) {
709
+ switch (rb_type(v)) {
710
+ case RUBY_T_NIL:
711
+ case RUBY_T_NONE: return NULL;
712
+ case RUBY_T_ARRAY: {
713
+ long len = rb_array_len(v);
714
+ long i;
715
+ long size = 0;
716
+ char *buf;
717
+ char *bp;
718
+
719
+ for (i = 0; i < len; i++) {
720
+ VALUE x = rb_ary_entry(v, i);
721
+
722
+ switch (rb_type(x)) {
723
+ case RUBY_T_SYMBOL:
724
+ size += strlen(rb_id2name(rb_sym2id(x)));
725
+ size++;
726
+ break;
727
+ case RUBY_T_STRING:
728
+ size += strlen(StringValueCStr(x));
729
+ size++;
730
+ break;
731
+ default: rb_raise(rb_eArgError, ":only and :except must be nil, symbol, string, or array."); break;
732
+ }
733
+ }
734
+ if (0 == size) {
735
+ return NULL;
736
+ }
737
+ buf = OJ_R_ALLOC_N(char, size + 2);
738
+ bp = buf;
739
+ *bp++ = ':';
740
+ for (i = 0; i < len; i++) {
741
+ VALUE x = rb_ary_entry(v, i);
742
+ const char *str;
743
+
744
+ switch (rb_type(x)) {
745
+ case RUBY_T_SYMBOL:
746
+ str = rb_id2name(rb_sym2id(x));
747
+ size = strlen(str);
748
+ memcpy(bp, str, size);
749
+ bp += size;
750
+ *bp++ = ':';
751
+ break;
752
+ case RUBY_T_STRING:
753
+ str = StringValueCStr(x);
754
+ size = strlen(str);
755
+ memcpy(bp, str, size);
756
+ bp += size;
757
+ *bp++ = ':';
758
+ break;
759
+ default:
760
+ // ignore
761
+ break;
762
+ }
763
+ }
764
+ *bp = '\0';
765
+
766
+ return buf;
767
+ }
768
+ case RUBY_T_STRING: {
769
+ const char *str = StringValueCStr(v);
770
+ size_t size = strlen(str);
771
+ char *buf = OJ_R_ALLOC_N(char, size + 3);
772
+
773
+ buf[0] = ':';
774
+ strcpy(buf + 1, str);
775
+ buf[size + 1] = ':';
776
+ buf[size + 2] = '\0';
777
+
778
+ return buf;
779
+ }
780
+ case RUBY_T_SYMBOL: {
781
+ const char *str = rb_id2name(rb_sym2id(v));
782
+ size_t size = strlen(str);
783
+ char *buf = OJ_R_ALLOC_N(char, size + 3);
784
+
785
+ buf[0] = ':';
786
+ strcpy(buf + 1, str);
787
+ buf[size + 1] = ':';
788
+ buf[size + 2] = '\0';
789
+
790
+ return buf;
791
+ }
792
+ default: rb_raise(rb_eArgError, ":only and zzz :except must be nil, symbol, string, or array."); break;
793
+ }
794
+ return NULL;
795
+ }
796
+
624
797
  static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
625
798
  Options copts = (Options)opts;
626
799
  size_t len;
@@ -638,7 +811,10 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
638
811
  case T_FIXNUM:
639
812
  copts->dump_opts.indent_size = 0;
640
813
  *copts->dump_opts.indent_str = '\0';
641
- copts->indent = FIX2INT(v);
814
+ if (MAX_INDENT < FIX2INT(v)) {
815
+ rb_raise(rb_eArgError, "indent is limited to %d characters.", MAX_INDENT);
816
+ }
817
+ copts->indent = FIX2INT(v);
642
818
  break;
643
819
  case T_STRING:
644
820
  if (sizeof(copts->dump_opts.indent_str) <= (len = RSTRING_LEN(v))) {
@@ -788,6 +964,9 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
788
964
 
789
965
  len = RSTRING_LEN(v);
790
966
  if (len != copts->create_id_len || 0 != strcmp(copts->create_id, str)) {
967
+ if (&oj_default_options == copts && oj_json_class != copts->create_id) {
968
+ OJ_R_FREE((char *)copts->create_id);
969
+ }
791
970
  copts->create_id = OJ_R_ALLOC_N(char, len + 1);
792
971
  strcpy((char *)copts->create_id, str);
793
972
  copts->create_id_len = len;
@@ -919,15 +1098,19 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
919
1098
  copts->array_class = v;
920
1099
  }
921
1100
  } else if (ignore_sym == k) {
922
- OJ_R_FREE(copts->ignore);
1101
+ // Only free when replacing the defaults; a per-call copy's ignore still
1102
+ // aliases the default-owned buffer, which oj_free_call_options() frees.
1103
+ if (&oj_default_options == copts) {
1104
+ OJ_R_FREE(copts->ignore);
1105
+ }
923
1106
  copts->ignore = NULL;
924
1107
  if (Qnil != v) {
925
- int cnt;
1108
+ size_t cnt;
926
1109
 
927
1110
  rb_check_type(v, T_ARRAY);
928
- cnt = (int)RARRAY_LEN(v);
1111
+ cnt = RARRAY_LEN(v);
929
1112
  if (0 < cnt) {
930
- int i;
1113
+ size_t i;
931
1114
 
932
1115
  copts->ignore = OJ_R_ALLOC_N(VALUE, cnt + 1);
933
1116
  for (i = 0; i < cnt; i++) {
@@ -953,6 +1136,20 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
953
1136
  } else if (Qfalse != v) {
954
1137
  rb_raise(rb_eArgError, ":integer_range must be a range of Fixnum.");
955
1138
  }
1139
+ } else if (max_integer_digits_sym == k) {
1140
+ if (Qnil == v || Qfalse == v) {
1141
+ copts->max_integer_digits = 0;
1142
+ } else if (T_FIXNUM == rb_type(v)) {
1143
+ long n = FIX2LONG(v);
1144
+
1145
+ if (n < 0) {
1146
+ rb_raise(rb_eArgError, ":max_integer_digits must be >= 0.");
1147
+ }
1148
+
1149
+ copts->max_integer_digits = (size_t)n;
1150
+ } else {
1151
+ rb_raise(rb_eArgError, ":max_integer_digits must be a non-negative Integer.");
1152
+ }
956
1153
  } else if (symbol_keys_sym == k || oj_symbolize_names_sym == k) {
957
1154
  if (Qnil == v) {
958
1155
  return ST_CONTINUE;
@@ -972,11 +1169,23 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
972
1169
  }
973
1170
  } else if (float_format_sym == k) {
974
1171
  rb_check_type(v, T_STRING);
975
- if (6 < (int)RSTRING_LEN(v)) {
1172
+ if (6 < RSTRING_LEN(v)) {
976
1173
  rb_raise(rb_eArgError, ":float_format must be 6 bytes or less.");
977
1174
  }
978
1175
  strncpy(copts->float_fmt, RSTRING_PTR(v), (size_t)RSTRING_LEN(v));
979
1176
  copts->float_fmt[RSTRING_LEN(v)] = '\0';
1177
+ } else if (only_sym == k) {
1178
+ // Only free when replacing the defaults; a per-call copy's pointer still
1179
+ // aliases the default-owned buffer, which oj_free_call_options() frees.
1180
+ if (&oj_default_options == copts && NULL != copts->dump_opts.only) {
1181
+ OJ_R_FREE((void *)copts->dump_opts.only);
1182
+ }
1183
+ copts->dump_opts.only = make_only_value(v);
1184
+ } else if (except_sym == k) {
1185
+ if (&oj_default_options == copts && NULL != copts->dump_opts.except) {
1186
+ OJ_R_FREE((void *)copts->dump_opts.except);
1187
+ }
1188
+ copts->dump_opts.except = make_only_value(v);
980
1189
  }
981
1190
  return ST_CONTINUE;
982
1191
  }
@@ -994,6 +1203,120 @@ void oj_parse_options(VALUE ropts, Options copts) {
994
1203
  return;
995
1204
  }
996
1205
 
1206
+ // Free the option buffers that oj_parse_options() allocated for a single call.
1207
+ // A per-call options struct is a copy of oj_default_options, so its create_id,
1208
+ // ignore, only, and except members initially alias the ones owned by the
1209
+ // defaults. Only free a member when it differs from the default, i.e. when it
1210
+ // was allocated for this call; the default-owned buffers must be left alone.
1211
+ void oj_free_call_options(Options copts) {
1212
+ if (NULL != copts->create_id && oj_default_options.create_id != copts->create_id) {
1213
+ OJ_R_FREE((char *)copts->create_id);
1214
+ copts->create_id = NULL;
1215
+ copts->create_id_len = 0;
1216
+ }
1217
+ if (NULL != copts->ignore && oj_default_options.ignore != copts->ignore) {
1218
+ OJ_R_FREE(copts->ignore);
1219
+ copts->ignore = NULL;
1220
+ }
1221
+ if (NULL != copts->dump_opts.only && oj_default_options.dump_opts.only != copts->dump_opts.only) {
1222
+ OJ_R_FREE((void *)copts->dump_opts.only);
1223
+ copts->dump_opts.only = NULL;
1224
+ }
1225
+ if (NULL != copts->dump_opts.except && oj_default_options.dump_opts.except != copts->dump_opts.except) {
1226
+ OJ_R_FREE((void *)copts->dump_opts.except);
1227
+ copts->dump_opts.except = NULL;
1228
+ }
1229
+ }
1230
+
1231
+ static char *dup_option_str(const char *str, size_t len) {
1232
+ char *buf = OJ_R_ALLOC_N(char, len + 1);
1233
+
1234
+ memcpy(buf, str, len);
1235
+ buf[len] = '\0';
1236
+
1237
+ return buf;
1238
+ }
1239
+
1240
+ // Give an options struct private copies of the option buffers it still shares
1241
+ // with the defaults so that it owns all of them.
1242
+ //
1243
+ // An options struct that outlives the call it was created in can not rely on
1244
+ // the default-owned buffers it aliases: Oj.default_options= is free to replace
1245
+ // them at any time, which would leave the alias dangling. Call this once, in
1246
+ // the call that creates such an object and after oj_parse_options(), then free
1247
+ // the buffers with oj_options_release() when the object is collected.
1248
+ void oj_options_take_ownership(Options copts) {
1249
+ if (NULL != copts->create_id && oj_default_options.create_id == copts->create_id) {
1250
+ copts->create_id = dup_option_str(copts->create_id, copts->create_id_len);
1251
+ }
1252
+ if (NULL != copts->ignore && oj_default_options.ignore == copts->ignore) {
1253
+ VALUE *vp;
1254
+ VALUE *buf;
1255
+ size_t cnt = 0;
1256
+
1257
+ for (vp = copts->ignore; Qnil != *vp; vp++) {
1258
+ cnt++;
1259
+ }
1260
+ buf = OJ_R_ALLOC_N(VALUE, cnt + 1);
1261
+ memcpy(buf, copts->ignore, sizeof(VALUE) * (cnt + 1));
1262
+ copts->ignore = buf;
1263
+ }
1264
+ if (NULL != copts->dump_opts.only && oj_default_options.dump_opts.only == copts->dump_opts.only) {
1265
+ copts->dump_opts.only = dup_option_str(copts->dump_opts.only, strlen(copts->dump_opts.only));
1266
+ }
1267
+ if (NULL != copts->dump_opts.except && oj_default_options.dump_opts.except == copts->dump_opts.except) {
1268
+ copts->dump_opts.except = dup_option_str(copts->dump_opts.except, strlen(copts->dump_opts.except));
1269
+ }
1270
+ }
1271
+
1272
+ // Free the option buffers of an options struct that owns all of them, i.e. one
1273
+ // that oj_options_take_ownership() was called on. Unlike oj_free_call_options()
1274
+ // this never looks at the defaults, so it is safe to call from a GC free
1275
+ // function, where the defaults may have been replaced since the object was
1276
+ // created.
1277
+ void oj_options_release(Options copts) {
1278
+ if (NULL != copts->create_id) {
1279
+ OJ_R_FREE((char *)copts->create_id);
1280
+ copts->create_id = NULL;
1281
+ copts->create_id_len = 0;
1282
+ }
1283
+ if (NULL != copts->ignore) {
1284
+ OJ_R_FREE(copts->ignore);
1285
+ copts->ignore = NULL;
1286
+ }
1287
+ if (NULL != copts->dump_opts.only) {
1288
+ OJ_R_FREE((void *)copts->dump_opts.only);
1289
+ copts->dump_opts.only = NULL;
1290
+ }
1291
+ if (NULL != copts->dump_opts.except) {
1292
+ OJ_R_FREE((void *)copts->dump_opts.except);
1293
+ copts->dump_opts.except = NULL;
1294
+ }
1295
+ // The str_rx chain is detached from the defaults when the owner is created
1296
+ // so the chain, if there is one, was built for this options struct alone.
1297
+ oj_rxclass_cleanup(&copts->str_rx);
1298
+ }
1299
+
1300
+ // Mark the Ruby objects held by an owned options struct. A long lived object
1301
+ // such as a writer or an encoder can be the only reference to the classes in
1302
+ // the options so without this they could be collected while the owner is still
1303
+ // alive leaving the options with dangling VALUEs.
1304
+ void oj_options_mark(Options copts) {
1305
+ if (Qnil != copts->hash_class) {
1306
+ rb_gc_mark(copts->hash_class);
1307
+ }
1308
+ if (Qnil != copts->array_class) {
1309
+ rb_gc_mark(copts->array_class);
1310
+ }
1311
+ if (NULL != copts->ignore) {
1312
+ VALUE *vp;
1313
+
1314
+ for (vp = copts->ignore; Qnil != *vp; vp++) {
1315
+ rb_gc_mark(*vp);
1316
+ }
1317
+ }
1318
+ }
1319
+
997
1320
  static int match_string_cb(VALUE key, VALUE value, VALUE rx) {
998
1321
  RxClass rc = (RxClass)rx;
999
1322
 
@@ -1187,7 +1510,7 @@ static VALUE load_file(int argc, VALUE *argv, VALUE self) {
1187
1510
  OJ_FREE(wide_path);
1188
1511
  }
1189
1512
  #else
1190
- fd = open(path, O_RDONLY);
1513
+ fd = open(path, O_RDONLY);
1191
1514
  #endif
1192
1515
  if (0 == fd) {
1193
1516
  rb_raise(rb_eIOError, "%s", strerror(errno));
@@ -1276,8 +1599,7 @@ static VALUE dump_body(VALUE a) {
1276
1599
  if (0 == arg->out->buf) {
1277
1600
  rb_raise(rb_eNoMemError, "Not enough memory.");
1278
1601
  }
1279
- rstr = rb_str_new2(arg->out->buf);
1280
- rstr = oj_encode(rstr);
1602
+ rstr = rb_utf8_str_new_cstr(arg->out->buf);
1281
1603
 
1282
1604
  return rstr;
1283
1605
  }
@@ -1286,6 +1608,7 @@ static VALUE dump_ensure(VALUE a) {
1286
1608
  volatile struct dump_arg *arg = (void *)a;
1287
1609
 
1288
1610
  oj_out_free(arg->out);
1611
+ oj_free_call_options(arg->copts);
1289
1612
 
1290
1613
  return Qnil;
1291
1614
  }
@@ -1377,8 +1700,7 @@ static VALUE to_json(int argc, VALUE *argv, VALUE self) {
1377
1700
  if (0 == out.buf) {
1378
1701
  rb_raise(rb_eNoMemError, "Not enough memory.");
1379
1702
  }
1380
- rstr = rb_str_new2(out.buf);
1381
- rstr = oj_encode(rstr);
1703
+ rstr = rb_utf8_str_new_cstr(out.buf);
1382
1704
 
1383
1705
  oj_out_free(&out);
1384
1706
 
@@ -1402,6 +1724,7 @@ static VALUE to_file(int argc, VALUE *argv, VALUE self) {
1402
1724
  oj_parse_options(argv[2], &copts);
1403
1725
  }
1404
1726
  oj_write_obj_to_file(argv[1], StringValuePtr(*argv), &copts);
1727
+ oj_free_call_options(&copts);
1405
1728
 
1406
1729
  return Qnil;
1407
1730
  }
@@ -1423,6 +1746,7 @@ static VALUE to_stream(int argc, VALUE *argv, VALUE self) {
1423
1746
  oj_parse_options(argv[2], &copts);
1424
1747
  }
1425
1748
  oj_write_obj_to_stream(argv[1], *argv, &copts);
1749
+ oj_free_call_options(&copts);
1426
1750
 
1427
1751
  return Qnil;
1428
1752
  }
@@ -1780,6 +2104,78 @@ static VALUE mem_report(VALUE self) {
1780
2104
  *
1781
2105
  * - *:wab* specifically for WAB data exchange.
1782
2106
  */
2107
+
2108
+ // =============================================================================
2109
+ // Runtime SIMD CPU detection
2110
+ // Cross-platform support for Windows (MSVC), Linux, and macOS (GCC/Clang)
2111
+ // =============================================================================
2112
+ SIMD_Implementation oj_get_simd_implementation(void) {
2113
+ #ifdef HAVE_SIMD_X86
2114
+ // x86/x86_64 runtime detection
2115
+
2116
+ #if defined(_MSC_VER)
2117
+ // MSVC: Use __cpuid intrinsic
2118
+ int cpu_info[4];
2119
+ __cpuid(cpu_info, 1);
2120
+
2121
+ // Check for SSE4.2 (bit 20 of ECX)
2122
+ if (cpu_info[2] & (1 << 20)) {
2123
+ return SIMD_SSE42;
2124
+ }
2125
+ // Check for SSE2 (bit 26 of EDX)
2126
+ if (cpu_info[3] & (1 << 26)) {
2127
+ return SIMD_SSE2;
2128
+ }
2129
+
2130
+ #elif defined(__GNUC__) || defined(__clang__)
2131
+ // GCC/Clang: Use __builtin_cpu_supports if available
2132
+ #if defined(__has_builtin)
2133
+ #if __has_builtin(__builtin_cpu_supports)
2134
+ #define OJ_HAS_BUILTIN_CPU_SUPPORTS 1
2135
+ #endif
2136
+ #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))
2137
+ // GCC 4.8+ has __builtin_cpu_supports
2138
+ #define OJ_HAS_BUILTIN_CPU_SUPPORTS 1
2139
+ #endif
2140
+
2141
+ #ifdef OJ_HAS_BUILTIN_CPU_SUPPORTS
2142
+ #ifdef HAVE_SIMD_SSE4_2
2143
+ if (__builtin_cpu_supports("sse4.2")) {
2144
+ return SIMD_SSE42;
2145
+ }
2146
+ #endif
2147
+ #ifdef HAVE_SIMD_SSE2
2148
+ if (__builtin_cpu_supports("sse2")) {
2149
+ return SIMD_SSE2;
2150
+ }
2151
+ #endif
2152
+ #else
2153
+ // Fallback: Use CPUID instruction directly
2154
+ unsigned int eax, ebx, ecx, edx;
2155
+ if (__get_cpuid(1, &eax, &ebx, &ecx, &edx)) {
2156
+ // Check for SSE4.2 (bit 20 of ECX)
2157
+ if (ecx & (1 << 20)) {
2158
+ return SIMD_SSE42;
2159
+ }
2160
+ // Check for SSE2 (bit 26 of EDX)
2161
+ if (edx & (1 << 26)) {
2162
+ return SIMD_SSE2;
2163
+ }
2164
+ }
2165
+ #endif // OJ_HAS_BUILTIN_CPU_SUPPORTS
2166
+
2167
+ #endif // _MSC_VER vs GCC/Clang
2168
+
2169
+ #endif // HAVE_SIMD_X86
2170
+
2171
+ #ifdef HAVE_SIMD_NEON
2172
+ // ARM NEON is always available on ARM64 and detected at compile time
2173
+ return SIMD_NEON;
2174
+ #endif
2175
+
2176
+ return SIMD_NONE;
2177
+ }
2178
+
1783
2179
  void Init_oj(void) {
1784
2180
  int err = 0;
1785
2181
 
@@ -1849,6 +2245,7 @@ void Init_oj(void) {
1849
2245
  oj_begin_id = rb_intern("begin");
1850
2246
  oj_bigdecimal_id = rb_intern("BigDecimal");
1851
2247
  oj_end_id = rb_intern("end");
2248
+ oj_eofq_id = rb_intern("eof?");
1852
2249
  oj_error_id = rb_intern("error");
1853
2250
  oj_exclude_end_id = rb_intern("exclude_end?");
1854
2251
  oj_file_id = rb_intern("file?");
@@ -1952,6 +2349,8 @@ void Init_oj(void) {
1952
2349
  rb_gc_register_address(&escape_mode_sym);
1953
2350
  integer_range_sym = ID2SYM(rb_intern("integer_range"));
1954
2351
  rb_gc_register_address(&integer_range_sym);
2352
+ max_integer_digits_sym = ID2SYM(rb_intern("max_integer_digits"));
2353
+ rb_gc_register_address(&max_integer_digits_sym);
1955
2354
  fast_sym = ID2SYM(rb_intern("fast"));
1956
2355
  rb_gc_register_address(&fast_sym);
1957
2356
  float_format_sym = ID2SYM(rb_intern("float_format"));
@@ -2058,6 +2457,10 @@ void Init_oj(void) {
2058
2457
  rb_gc_register_address(&xmlschema_sym);
2059
2458
  xss_safe_sym = ID2SYM(rb_intern("xss_safe"));
2060
2459
  rb_gc_register_address(&xss_safe_sym);
2460
+ only_sym = ID2SYM(rb_intern("only"));
2461
+ rb_gc_register_address(&only_sym);
2462
+ except_sym = ID2SYM(rb_intern("except"));
2463
+ rb_gc_register_address(&except_sym);
2061
2464
 
2062
2465
  oj_slash_string = rb_str_new2("/");
2063
2466
  rb_gc_register_address(&oj_slash_string);
@@ -2079,6 +2482,18 @@ void Init_oj(void) {
2079
2482
  #endif
2080
2483
  oj_init_doc();
2081
2484
 
2485
+ SIMD_Impl = oj_get_simd_implementation();
2486
+
2082
2487
  oj_parser_init();
2083
2488
  oj_scanner_init();
2489
+
2490
+ #ifdef HAVE_SIMD_NEON
2491
+ initialize_neon();
2492
+ #endif /* HAVE_SIMD_NEON */
2493
+
2494
+ #ifdef HAVE_SIMD_SSE4_2
2495
+ if (SIMD_Impl == SIMD_SSE42) {
2496
+ initialize_sse42();
2497
+ }
2498
+ #endif /* HAVE_SIMD_SSE4_2 */
2084
2499
  }