oj 3.16.11 → 3.17.6

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.
data/ext/oj/oj.c CHANGED
@@ -20,6 +20,8 @@
20
20
  #include "rails.h"
21
21
  #include "simd.h"
22
22
 
23
+ #define MAX_INDENT 16
24
+
23
25
  typedef struct _yesNoOpt {
24
26
  VALUE sym;
25
27
  char *attr;
@@ -120,7 +122,9 @@ static VALUE create_id_sym;
120
122
  static VALUE custom_sym;
121
123
  static VALUE empty_string_sym;
122
124
  static VALUE escape_mode_sym;
125
+ static VALUE except_sym;
123
126
  static VALUE integer_range_sym;
127
+ static VALUE max_integer_digits_sym;
124
128
  static VALUE fast_sym;
125
129
  static VALUE float_prec_sym;
126
130
  static VALUE float_format_sym;
@@ -138,6 +142,7 @@ static VALUE null_sym;
138
142
  static VALUE object_sym;
139
143
  static VALUE omit_null_byte_sym;
140
144
  static VALUE omit_nil_sym;
145
+ static VALUE only_sym;
141
146
  static VALUE rails_sym;
142
147
  static VALUE raise_sym;
143
148
  static VALUE ruby_sym;
@@ -167,6 +172,8 @@ pthread_mutex_t oj_cache_mutex;
167
172
  VALUE oj_cache_mutex = Qnil;
168
173
  #endif
169
174
 
175
+ SIMD_Implementation SIMD_Impl = SIMD_NONE;
176
+
170
177
  extern void oj_parser_init();
171
178
 
172
179
  const char oj_json_class[] = "json_class";
@@ -202,6 +209,7 @@ struct _options oj_default_options = {
202
209
  0, // cache_str
203
210
  0, // int_range_min
204
211
  0, // int_range_max
212
+ 0, // max_integer_digits
205
213
  oj_json_class, // create_id
206
214
  10, // create_id_len
207
215
  9, // sec_prec
@@ -226,6 +234,8 @@ struct _options oj_default_options = {
226
234
  false, // omit_nil
227
235
  false, // omit_null_byte
228
236
  MAX_DEPTH, // max_depth
237
+ NULL, // only
238
+ NULL, // except
229
239
  },
230
240
  {
231
241
  // str_rx
@@ -236,6 +246,22 @@ struct _options oj_default_options = {
236
246
  NULL,
237
247
  };
238
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
+
239
265
  /* Document-method: default_options()
240
266
  * call-seq: default_options()
241
267
  *
@@ -273,7 +299,8 @@ struct _options oj_default_options = {
273
299
  * floats, 0 indicates use Ruby
274
300
  * - *:float_format* [_String_] the C printf format string for printing floats.
275
301
  * Default follows the float_precision and will be changed if float_precision is
276
- * changed. The string can be no more than 6 bytes.
302
+ * changed. The string can be no more than 6 bytes and can hold at most one
303
+ * directive, which must be one of aAeEfgG and take no argument of its own.
277
304
  * - *:use_to_json* [_Boolean_|_nil_] call to_json() methods on dump, default is false
278
305
  * - *:use_as_json* [_Boolean_|_nil_] call as_json() methods on dump, default is false
279
306
  * - *:use_raw_json* [_Boolean_|_nil_] call raw_json() methods on dump, default is false
@@ -312,10 +339,17 @@ struct _options oj_default_options = {
312
339
  * - *:cache_str* [_Fixnum_] maximum string value length to cache (strings less
313
340
  * than this are cached)
314
341
  * - *:integer_range* [_Range_] Dump integers outside range as strings.
342
+ * - *:max_integer_digits* [_Fixnum_] Maximum number of decimal digits allowed in a
343
+ * parsed integer. When the limit is exceeded a parse error is raised. 0 (the
344
+ * default) disables the limit. Setting a reasonable limit is recommended when
345
+ * parsing untrusted input to mitigate CPU-DoS attacks. Only applies to the
346
+ * legacy parsers (Oj.load, Oj::Doc, JSON.parse mimic); Oj::Parser is unaffected.
315
347
  * - *:trace* [_true,_|_false_] Trace all load and dump calls, default is false
316
348
  * (trace is off)
317
349
  * - *:safe* [_true,_|_false_] Safe mimic breaks JSON mimic to be safer, default
318
350
  * is false (safe is off)
351
+ * - *:only* [_nil,_|_Array_] A list of the fields to encode. All others are skipped.
352
+ * - *:except* [_nil,_|_Array_] A list of the fields to not encode. All others are encoded.
319
353
  *
320
354
  * Return [_Hash_] all current option settings.
321
355
  */
@@ -424,6 +458,7 @@ static VALUE get_def_opts(VALUE self) {
424
458
  } else {
425
459
  rb_hash_aset(opts, integer_range_sym, Qnil);
426
460
  }
461
+ rb_hash_aset(opts, max_integer_digits_sym, LONG2NUM((long)oj_default_options.max_integer_digits));
427
462
  switch (oj_default_options.escape_mode) {
428
463
  case NLEsc: rb_hash_aset(opts, escape_mode_sym, newline_sym); break;
429
464
  case JSONEsc: rb_hash_aset(opts, escape_mode_sym, json_sym); break;
@@ -481,6 +516,9 @@ static VALUE get_def_opts(VALUE self) {
481
516
  rb_hash_aset(opts, oj_hash_class_sym, oj_default_options.hash_class);
482
517
  rb_hash_aset(opts, oj_array_class_sym, oj_default_options.array_class);
483
518
 
519
+ rb_hash_aset(opts, only_sym, only_array_from_string(oj_default_options.dump_opts.only));
520
+ rb_hash_aset(opts, except_sym, only_array_from_string(oj_default_options.dump_opts.except));
521
+
484
522
  if (NULL == oj_default_options.ignore) {
485
523
  rb_hash_aset(opts, ignore_sym, Qnil);
486
524
  } else {
@@ -537,7 +575,8 @@ static VALUE get_def_opts(VALUE self) {
537
575
  * when dumping the seconds portion of time.
538
576
  * - *:float_format* [_String_] the C printf format string for printing floats.
539
577
  * Default follows the float_precision and will be changed if float_precision
540
- * is changed. The string can be no more than 6 bytes.
578
+ * is changed. The string can be no more than 6 bytes and can hold at most one
579
+ * directive, which must be one of aAeEfgG and take no argument of its own.
541
580
  * - *:float_precision* [_Fixnum_|_nil_] number of digits of precision when dumping floats, 0 indicates use Ruby.
542
581
  * - *:use_to_json* [_Boolean_|_nil_] call to_json() methods on dump, default is false.
543
582
  * - *:use_as_json* [_Boolean_|_nil_] call as_json() methods on dump, default is false.
@@ -564,12 +603,57 @@ static VALUE get_def_opts(VALUE self) {
564
603
  * - *:cache_keys* [_Boolean_] if true then hash keys are cached
565
604
  * - *:cache_str* [_Fixnum_] maximum string value length to cache (strings less than this are cached)
566
605
  * - *:integer_range* [_Range_] Dump integers outside range as strings.
606
+ * - *:max_integer_digits* [_Fixnum_] Maximum decimal digits in a parsed integer
607
+ * (0 = unlimited). Use to mitigate CPU-DoS via huge integer values in JSON.
567
608
  * - *:trace* [_Boolean_] turn trace on or off.
568
609
  * - *:safe* [_Boolean_] turn safe mimic on or off.
569
610
  */
611
+ static ID oj_default_options_keeper_id = 0;
612
+
613
+ // The default options are a plain C global so the Ruby objects in them, the
614
+ // classes of the hash_class, array_class, and ignore options and the regexps
615
+ // and the classes of the match_string option, can not be reached by the GC and
616
+ // are collected while the defaults still refer to them. Holding them in an
617
+ // array the GC does know about keeps them alive. The array is rebuilt whenever
618
+ // the defaults change and replaces the previous one so that only the objects
619
+ // the defaults are currently using are held.
620
+ static void keep_default_options(void) {
621
+ VALUE keep = rb_ary_new();
622
+
623
+ if (0 == oj_default_options_keeper_id) {
624
+ oj_default_options_keeper_id = rb_intern("@default_options_keeper");
625
+ }
626
+ if (Qnil != oj_default_options.hash_class) {
627
+ rb_ary_push(keep, oj_default_options.hash_class);
628
+ }
629
+ if (Qnil != oj_default_options.array_class) {
630
+ rb_ary_push(keep, oj_default_options.array_class);
631
+ }
632
+ if (NULL != oj_default_options.ignore) {
633
+ VALUE *vp;
634
+
635
+ for (vp = oj_default_options.ignore; Qnil != *vp; vp++) {
636
+ rb_ary_push(keep, *vp);
637
+ }
638
+ }
639
+ oj_rxclass_keep(&oj_default_options.str_rx, keep);
640
+ // An instance variable of the Oj module is a reference the GC can follow on
641
+ // every engine, unlike a C global.
642
+ rb_ivar_set(Oj, oj_default_options_keeper_id, keep);
643
+ }
644
+
570
645
  static VALUE set_def_opts(VALUE self, VALUE opts) {
571
646
  Check_Type(opts, T_HASH);
647
+ // A :match_string option replaces the regexps instead of adding to them so
648
+ // the ones the defaults are holding have to be freed here, the only place
649
+ // that owns them. A per call options struct only aliases them.
650
+ if (Qnil != rb_hash_lookup(opts, match_string_sym)) {
651
+ oj_rxclass_cleanup(&oj_default_options.str_rx);
652
+ oj_default_options.str_rx.head = NULL;
653
+ oj_default_options.str_rx.tail = NULL;
654
+ }
572
655
  oj_parse_options(opts, &oj_default_options);
656
+ keep_default_options();
573
657
 
574
658
  return Qnil;
575
659
  }
@@ -623,12 +707,143 @@ bool set_yesno_options(VALUE key, VALUE value, Options copts) {
623
707
  return false;
624
708
  }
625
709
 
626
- static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
627
- Options copts = (Options)opts;
628
- size_t len;
710
+ inline static bool in_set(const char *set, char c) {
711
+ return '\0' != c && NULL != strchr(set, c);
712
+ }
713
+
714
+ // The format is used with one double and nothing else, so a directive that
715
+ // asks for anything more takes it from whatever is left in the argument
716
+ // registers. %s reads it as a pointer and %n writes through it.
717
+ static void validate_float_format(const char *str, size_t len) {
718
+ const char *s = str;
719
+ const char *end = str + len;
720
+ int cnt = 0;
721
+
722
+ for (; s < end; s++) {
723
+ if ('%' != *s) {
724
+ continue;
725
+ }
726
+ s++;
727
+ if (s < end && '%' == *s) {
728
+ continue;
729
+ }
730
+ if (1 < ++cnt) {
731
+ rb_raise(rb_eArgError, ":float_format must not have more than one directive.");
732
+ }
733
+ for (; s < end && in_set("-+ #0", *s); s++) {
734
+ }
735
+ for (; s < end && '0' <= *s && *s <= '9'; s++) {
736
+ }
737
+ if (s < end && '.' == *s) {
738
+ for (s++; s < end && '0' <= *s && *s <= '9'; s++) {
739
+ }
740
+ }
741
+ if (s < end && 'l' == *s) {
742
+ s++;
743
+ }
744
+ if (end <= s || !in_set("aAeEfgG", *s)) {
745
+ rb_raise(rb_eArgError, ":float_format directive must be one of aAeEfgG and take no argument of its own.");
746
+ }
747
+ }
748
+ }
749
+
750
+ static const char *make_only_value(VALUE v) {
751
+ switch (rb_type(v)) {
752
+ case RUBY_T_NIL:
753
+ case RUBY_T_NONE: return NULL;
754
+ case RUBY_T_ARRAY: {
755
+ long len = rb_array_len(v);
756
+ long i;
757
+ long size = 0;
758
+ char *buf;
759
+ char *bp;
760
+
761
+ for (i = 0; i < len; i++) {
762
+ VALUE x = rb_ary_entry(v, i);
763
+
764
+ switch (rb_type(x)) {
765
+ case RUBY_T_SYMBOL:
766
+ size += strlen(rb_id2name(rb_sym2id(x)));
767
+ size++;
768
+ break;
769
+ case RUBY_T_STRING:
770
+ size += strlen(StringValueCStr(x));
771
+ size++;
772
+ break;
773
+ default: rb_raise(rb_eArgError, ":only and :except must be nil, symbol, string, or array."); break;
774
+ }
775
+ }
776
+ if (0 == size) {
777
+ return NULL;
778
+ }
779
+ buf = OJ_R_ALLOC_N(char, size + 2);
780
+ bp = buf;
781
+ *bp++ = ':';
782
+ for (i = 0; i < len; i++) {
783
+ VALUE x = rb_ary_entry(v, i);
784
+ const char *str;
785
+
786
+ switch (rb_type(x)) {
787
+ case RUBY_T_SYMBOL:
788
+ str = rb_id2name(rb_sym2id(x));
789
+ size = strlen(str);
790
+ memcpy(bp, str, size);
791
+ bp += size;
792
+ *bp++ = ':';
793
+ break;
794
+ case RUBY_T_STRING:
795
+ str = StringValueCStr(x);
796
+ size = strlen(str);
797
+ memcpy(bp, str, size);
798
+ bp += size;
799
+ *bp++ = ':';
800
+ break;
801
+ default:
802
+ // ignore
803
+ break;
804
+ }
805
+ }
806
+ *bp = '\0';
807
+
808
+ return buf;
809
+ }
810
+ case RUBY_T_STRING: {
811
+ const char *str = StringValueCStr(v);
812
+ size_t size = strlen(str);
813
+ char *buf = OJ_R_ALLOC_N(char, size + 3);
814
+
815
+ buf[0] = ':';
816
+ strcpy(buf + 1, str);
817
+ buf[size + 1] = ':';
818
+ buf[size + 2] = '\0';
819
+
820
+ return buf;
821
+ }
822
+ case RUBY_T_SYMBOL: {
823
+ const char *str = rb_id2name(rb_sym2id(v));
824
+ size_t size = strlen(str);
825
+ char *buf = OJ_R_ALLOC_N(char, size + 3);
826
+
827
+ buf[0] = ':';
828
+ strcpy(buf + 1, str);
829
+ buf[size + 1] = ':';
830
+ buf[size + 2] = '\0';
831
+
832
+ return buf;
833
+ }
834
+ default: rb_raise(rb_eArgError, ":only and zzz :except must be nil, symbol, string, or array."); break;
835
+ }
836
+ return NULL;
837
+ }
838
+
839
+ // Apply one key and value to copts. Returns true if the key named an option Oj
840
+ // knows and false if it did not. An unrecognized key is still ignored rather
841
+ // than an error; the return only lets a caller tell the two cases apart.
842
+ static bool parse_one_option(VALUE k, VALUE v, Options copts) {
843
+ size_t len;
629
844
 
630
845
  if (set_yesno_options(k, v, copts)) {
631
- return ST_CONTINUE;
846
+ return true;
632
847
  }
633
848
  if (oj_indent_sym == k) {
634
849
  switch (rb_type(v)) {
@@ -640,7 +855,10 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
640
855
  case T_FIXNUM:
641
856
  copts->dump_opts.indent_size = 0;
642
857
  *copts->dump_opts.indent_str = '\0';
643
- copts->indent = FIX2INT(v);
858
+ if (MAX_INDENT < FIX2INT(v)) {
859
+ rb_raise(rb_eArgError, "indent is limited to %d characters.", MAX_INDENT);
860
+ }
861
+ copts->indent = FIX2INT(v);
644
862
  break;
645
863
  case T_STRING:
646
864
  if (sizeof(copts->dump_opts.indent_str) <= (len = RSTRING_LEN(v))) {
@@ -751,7 +969,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
751
969
  }
752
970
  } else if (bigdecimal_load_sym == k) {
753
971
  if (Qnil == v) {
754
- return ST_CONTINUE;
972
+ return true;
755
973
  }
756
974
 
757
975
  if (bigdecimal_sym == v || Qtrue == v) {
@@ -767,7 +985,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
767
985
  }
768
986
  } else if (compat_bigdecimal_sym == k) {
769
987
  if (Qnil == v) {
770
- return ST_CONTINUE;
988
+ return true;
771
989
  }
772
990
  copts->compat_bigdec = (Qtrue == v);
773
991
  } else if (oj_decimal_class_sym == k) {
@@ -779,9 +997,13 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
779
997
  rb_raise(rb_eArgError, ":decimal_class must be BigDecimal or Float.");
780
998
  }
781
999
  } else if (create_id_sym == k) {
1000
+ // A per call copy of the options aliases the buffer the default
1001
+ // options own, so only the defaults may free it.
1002
+ bool owned = (&oj_default_options == copts && NULL != copts->create_id && oj_json_class != copts->create_id);
1003
+
782
1004
  if (Qnil == v) {
783
- if (oj_json_class != oj_default_options.create_id && NULL != copts->create_id) {
784
- OJ_R_FREE((char *)oj_default_options.create_id);
1005
+ if (owned) {
1006
+ OJ_R_FREE((char *)copts->create_id);
785
1007
  }
786
1008
  copts->create_id = NULL;
787
1009
  copts->create_id_len = 0;
@@ -789,7 +1011,10 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
789
1011
  const char *str = StringValuePtr(v);
790
1012
 
791
1013
  len = RSTRING_LEN(v);
792
- if (len != copts->create_id_len || 0 != strcmp(copts->create_id, str)) {
1014
+ if (NULL == copts->create_id || len != copts->create_id_len || 0 != strcmp(copts->create_id, str)) {
1015
+ if (owned) {
1016
+ OJ_R_FREE((char *)copts->create_id);
1017
+ }
793
1018
  copts->create_id = OJ_R_ALLOC_N(char, len + 1);
794
1019
  strcpy((char *)copts->create_id, str);
795
1020
  copts->create_id_len = len;
@@ -855,7 +1080,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
855
1080
  }
856
1081
  } else if (nan_sym == k) {
857
1082
  if (Qnil == v) {
858
- return ST_CONTINUE;
1083
+ return true;
859
1084
  }
860
1085
  if (null_sym == v) {
861
1086
  copts->dump_opts.nan_dump = NullNan;
@@ -872,7 +1097,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
872
1097
  }
873
1098
  } else if (omit_nil_sym == k) {
874
1099
  if (Qnil == v) {
875
- return ST_CONTINUE;
1100
+ return true;
876
1101
  }
877
1102
  if (Qtrue == v) {
878
1103
  copts->dump_opts.omit_nil = true;
@@ -883,7 +1108,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
883
1108
  }
884
1109
  } else if (omit_null_byte_sym == k) {
885
1110
  if (Qnil == v) {
886
- return ST_CONTINUE;
1111
+ return true;
887
1112
  }
888
1113
  if (Qtrue == v) {
889
1114
  copts->dump_opts.omit_null_byte = true;
@@ -921,7 +1146,11 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
921
1146
  copts->array_class = v;
922
1147
  }
923
1148
  } else if (ignore_sym == k) {
924
- OJ_R_FREE(copts->ignore);
1149
+ // Only free when replacing the defaults; a per-call copy's ignore still
1150
+ // aliases the default-owned buffer, which oj_free_call_options() frees.
1151
+ if (&oj_default_options == copts) {
1152
+ OJ_R_FREE(copts->ignore);
1153
+ }
925
1154
  copts->ignore = NULL;
926
1155
  if (Qnil != v) {
927
1156
  size_t cnt;
@@ -940,7 +1169,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
940
1169
  }
941
1170
  } else if (integer_range_sym == k) {
942
1171
  if (Qnil == v) {
943
- return ST_CONTINUE;
1172
+ return true;
944
1173
  }
945
1174
  if (rb_obj_class(v) == rb_cRange) {
946
1175
  VALUE min = rb_funcall(v, oj_begin_id, 0);
@@ -955,9 +1184,23 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
955
1184
  } else if (Qfalse != v) {
956
1185
  rb_raise(rb_eArgError, ":integer_range must be a range of Fixnum.");
957
1186
  }
1187
+ } else if (max_integer_digits_sym == k) {
1188
+ if (Qnil == v || Qfalse == v) {
1189
+ copts->max_integer_digits = 0;
1190
+ } else if (T_FIXNUM == rb_type(v)) {
1191
+ long n = FIX2LONG(v);
1192
+
1193
+ if (n < 0) {
1194
+ rb_raise(rb_eArgError, ":max_integer_digits must be >= 0.");
1195
+ }
1196
+
1197
+ copts->max_integer_digits = (size_t)n;
1198
+ } else {
1199
+ rb_raise(rb_eArgError, ":max_integer_digits must be a non-negative Integer.");
1200
+ }
958
1201
  } else if (symbol_keys_sym == k || oj_symbolize_names_sym == k) {
959
1202
  if (Qnil == v) {
960
- return ST_CONTINUE;
1203
+ return true;
961
1204
  }
962
1205
  copts->sym_key = (Qtrue == v) ? Yes : No;
963
1206
 
@@ -977,23 +1220,180 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
977
1220
  if (6 < RSTRING_LEN(v)) {
978
1221
  rb_raise(rb_eArgError, ":float_format must be 6 bytes or less.");
979
1222
  }
1223
+ validate_float_format(RSTRING_PTR(v), (size_t)RSTRING_LEN(v));
980
1224
  strncpy(copts->float_fmt, RSTRING_PTR(v), (size_t)RSTRING_LEN(v));
981
1225
  copts->float_fmt[RSTRING_LEN(v)] = '\0';
1226
+ } else if (only_sym == k) {
1227
+ // Only free when replacing the defaults; a per-call copy's pointer still
1228
+ // aliases the default-owned buffer, which oj_free_call_options() frees.
1229
+ if (&oj_default_options == copts && NULL != copts->dump_opts.only) {
1230
+ OJ_R_FREE((void *)copts->dump_opts.only);
1231
+ }
1232
+ copts->dump_opts.only = make_only_value(v);
1233
+ } else if (except_sym == k) {
1234
+ if (&oj_default_options == copts && NULL != copts->dump_opts.except) {
1235
+ OJ_R_FREE((void *)copts->dump_opts.except);
1236
+ }
1237
+ copts->dump_opts.except = make_only_value(v);
1238
+ } else {
1239
+ return false;
1240
+ }
1241
+ return true;
1242
+ }
1243
+
1244
+ // Collects the return of parse_one_option() over every key in a hash.
1245
+ struct _optsParse {
1246
+ Options copts;
1247
+ bool consumed;
1248
+ };
1249
+
1250
+ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
1251
+ struct _optsParse *op = (struct _optsParse *)opts;
1252
+
1253
+ if (parse_one_option(k, v, op->copts)) {
1254
+ op->consumed = true;
982
1255
  }
983
1256
  return ST_CONTINUE;
984
1257
  }
985
1258
 
986
- void oj_parse_options(VALUE ropts, Options copts) {
1259
+ // Apply the options in ropts to copts and report whether any key was
1260
+ // recognized. A hash of nothing but unknown keys leaves copts untouched and
1261
+ // returns false.
1262
+ bool oj_parse_options_consumed(VALUE ropts, Options copts) {
1263
+ struct _optsParse op = {copts, false};
1264
+
987
1265
  if (T_HASH != rb_type(ropts)) {
988
- return;
1266
+ return false;
1267
+ }
1268
+ rb_hash_foreach(ropts, parse_options_cb, (VALUE)&op);
1269
+ if (Qnil != rb_hash_lookup(ropts, match_string_sym)) {
1270
+ op.consumed = true;
989
1271
  }
990
- rb_hash_foreach(ropts, parse_options_cb, (VALUE)copts);
991
1272
  oj_parse_opt_match_string(&copts->str_rx, ropts);
992
1273
 
993
1274
  copts->dump_opts.use = (0 < copts->dump_opts.indent_size || 0 < copts->dump_opts.after_size ||
994
1275
  0 < copts->dump_opts.before_size || 0 < copts->dump_opts.hash_size ||
995
1276
  0 < copts->dump_opts.array_size);
996
- return;
1277
+
1278
+ return op.consumed;
1279
+ }
1280
+
1281
+ void oj_parse_options(VALUE ropts, Options copts) {
1282
+ oj_parse_options_consumed(ropts, copts);
1283
+ }
1284
+
1285
+ // Free the option buffers that oj_parse_options() allocated for a single call.
1286
+ // A per-call options struct is a copy of oj_default_options, so its create_id,
1287
+ // ignore, only, and except members initially alias the ones owned by the
1288
+ // defaults. Only free a member when it differs from the default, i.e. when it
1289
+ // was allocated for this call; the default-owned buffers must be left alone.
1290
+ void oj_free_call_options(Options copts) {
1291
+ if (NULL != copts->create_id && oj_default_options.create_id != copts->create_id) {
1292
+ OJ_R_FREE((char *)copts->create_id);
1293
+ copts->create_id = NULL;
1294
+ copts->create_id_len = 0;
1295
+ }
1296
+ if (NULL != copts->ignore && oj_default_options.ignore != copts->ignore) {
1297
+ OJ_R_FREE(copts->ignore);
1298
+ copts->ignore = NULL;
1299
+ }
1300
+ if (NULL != copts->dump_opts.only && oj_default_options.dump_opts.only != copts->dump_opts.only) {
1301
+ OJ_R_FREE((void *)copts->dump_opts.only);
1302
+ copts->dump_opts.only = NULL;
1303
+ }
1304
+ if (NULL != copts->dump_opts.except && oj_default_options.dump_opts.except != copts->dump_opts.except) {
1305
+ OJ_R_FREE((void *)copts->dump_opts.except);
1306
+ copts->dump_opts.except = NULL;
1307
+ }
1308
+ }
1309
+
1310
+ static char *dup_option_str(const char *str, size_t len) {
1311
+ char *buf = OJ_R_ALLOC_N(char, len + 1);
1312
+
1313
+ memcpy(buf, str, len);
1314
+ buf[len] = '\0';
1315
+
1316
+ return buf;
1317
+ }
1318
+
1319
+ // Give an options struct private copies of the option buffers it still shares
1320
+ // with the defaults so that it owns all of them.
1321
+ //
1322
+ // An options struct that outlives the call it was created in can not rely on
1323
+ // the default-owned buffers it aliases: Oj.default_options= is free to replace
1324
+ // them at any time, which would leave the alias dangling. Call this once, in
1325
+ // the call that creates such an object and after oj_parse_options(), then free
1326
+ // the buffers with oj_options_release() when the object is collected.
1327
+ void oj_options_take_ownership(Options copts) {
1328
+ if (NULL != copts->create_id && oj_default_options.create_id == copts->create_id) {
1329
+ copts->create_id = dup_option_str(copts->create_id, copts->create_id_len);
1330
+ }
1331
+ if (NULL != copts->ignore && oj_default_options.ignore == copts->ignore) {
1332
+ VALUE *vp;
1333
+ VALUE *buf;
1334
+ size_t cnt = 0;
1335
+
1336
+ for (vp = copts->ignore; Qnil != *vp; vp++) {
1337
+ cnt++;
1338
+ }
1339
+ buf = OJ_R_ALLOC_N(VALUE, cnt + 1);
1340
+ memcpy(buf, copts->ignore, sizeof(VALUE) * (cnt + 1));
1341
+ copts->ignore = buf;
1342
+ }
1343
+ if (NULL != copts->dump_opts.only && oj_default_options.dump_opts.only == copts->dump_opts.only) {
1344
+ copts->dump_opts.only = dup_option_str(copts->dump_opts.only, strlen(copts->dump_opts.only));
1345
+ }
1346
+ if (NULL != copts->dump_opts.except && oj_default_options.dump_opts.except == copts->dump_opts.except) {
1347
+ copts->dump_opts.except = dup_option_str(copts->dump_opts.except, strlen(copts->dump_opts.except));
1348
+ }
1349
+ }
1350
+
1351
+ // Free the option buffers of an options struct that owns all of them, i.e. one
1352
+ // that oj_options_take_ownership() was called on. Unlike oj_free_call_options()
1353
+ // this never looks at the defaults, so it is safe to call from a GC free
1354
+ // function, where the defaults may have been replaced since the object was
1355
+ // created.
1356
+ void oj_options_release(Options copts) {
1357
+ if (NULL != copts->create_id) {
1358
+ OJ_R_FREE((char *)copts->create_id);
1359
+ copts->create_id = NULL;
1360
+ copts->create_id_len = 0;
1361
+ }
1362
+ if (NULL != copts->ignore) {
1363
+ OJ_R_FREE(copts->ignore);
1364
+ copts->ignore = NULL;
1365
+ }
1366
+ if (NULL != copts->dump_opts.only) {
1367
+ OJ_R_FREE((void *)copts->dump_opts.only);
1368
+ copts->dump_opts.only = NULL;
1369
+ }
1370
+ if (NULL != copts->dump_opts.except) {
1371
+ OJ_R_FREE((void *)copts->dump_opts.except);
1372
+ copts->dump_opts.except = NULL;
1373
+ }
1374
+ // The str_rx chain is detached from the defaults when the owner is created
1375
+ // so the chain, if there is one, was built for this options struct alone.
1376
+ oj_rxclass_cleanup(&copts->str_rx);
1377
+ }
1378
+
1379
+ // Mark the Ruby objects held by an owned options struct. A long lived object
1380
+ // such as a writer or an encoder can be the only reference to the classes in
1381
+ // the options so without this they could be collected while the owner is still
1382
+ // alive leaving the options with dangling VALUEs.
1383
+ void oj_options_mark(Options copts) {
1384
+ if (Qnil != copts->hash_class) {
1385
+ rb_gc_mark(copts->hash_class);
1386
+ }
1387
+ if (Qnil != copts->array_class) {
1388
+ rb_gc_mark(copts->array_class);
1389
+ }
1390
+ if (NULL != copts->ignore) {
1391
+ VALUE *vp;
1392
+
1393
+ for (vp = copts->ignore; Qnil != *vp; vp++) {
1394
+ rb_gc_mark(*vp);
1395
+ }
1396
+ }
997
1397
  }
998
1398
 
999
1399
  static int match_string_cb(VALUE key, VALUE value, VALUE rx) {
@@ -1287,6 +1687,7 @@ static VALUE dump_ensure(VALUE a) {
1287
1687
  volatile struct dump_arg *arg = (void *)a;
1288
1688
 
1289
1689
  oj_out_free(arg->out);
1690
+ oj_free_call_options(arg->copts);
1290
1691
 
1291
1692
  return Qnil;
1292
1693
  }
@@ -1402,6 +1803,7 @@ static VALUE to_file(int argc, VALUE *argv, VALUE self) {
1402
1803
  oj_parse_options(argv[2], &copts);
1403
1804
  }
1404
1805
  oj_write_obj_to_file(argv[1], StringValuePtr(*argv), &copts);
1806
+ oj_free_call_options(&copts);
1405
1807
 
1406
1808
  return Qnil;
1407
1809
  }
@@ -1423,6 +1825,7 @@ static VALUE to_stream(int argc, VALUE *argv, VALUE self) {
1423
1825
  oj_parse_options(argv[2], &copts);
1424
1826
  }
1425
1827
  oj_write_obj_to_stream(argv[1], *argv, &copts);
1828
+ oj_free_call_options(&copts);
1426
1829
 
1427
1830
  return Qnil;
1428
1831
  }
@@ -1780,6 +2183,78 @@ static VALUE mem_report(VALUE self) {
1780
2183
  *
1781
2184
  * - *:wab* specifically for WAB data exchange.
1782
2185
  */
2186
+
2187
+ // =============================================================================
2188
+ // Runtime SIMD CPU detection
2189
+ // Cross-platform support for Windows (MSVC), Linux, and macOS (GCC/Clang)
2190
+ // =============================================================================
2191
+ SIMD_Implementation oj_get_simd_implementation(void) {
2192
+ #ifdef HAVE_SIMD_X86
2193
+ // x86/x86_64 runtime detection
2194
+
2195
+ #if defined(_MSC_VER)
2196
+ // MSVC: Use __cpuid intrinsic
2197
+ int cpu_info[4];
2198
+ __cpuid(cpu_info, 1);
2199
+
2200
+ // Check for SSE4.2 (bit 20 of ECX)
2201
+ if (cpu_info[2] & (1 << 20)) {
2202
+ return SIMD_SSE42;
2203
+ }
2204
+ // Check for SSE2 (bit 26 of EDX)
2205
+ if (cpu_info[3] & (1 << 26)) {
2206
+ return SIMD_SSE2;
2207
+ }
2208
+
2209
+ #elif defined(__GNUC__) || defined(__clang__)
2210
+ // GCC/Clang: Use __builtin_cpu_supports if available
2211
+ #if defined(__has_builtin)
2212
+ #if __has_builtin(__builtin_cpu_supports)
2213
+ #define OJ_HAS_BUILTIN_CPU_SUPPORTS 1
2214
+ #endif
2215
+ #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))
2216
+ // GCC 4.8+ has __builtin_cpu_supports
2217
+ #define OJ_HAS_BUILTIN_CPU_SUPPORTS 1
2218
+ #endif
2219
+
2220
+ #ifdef OJ_HAS_BUILTIN_CPU_SUPPORTS
2221
+ #ifdef HAVE_SIMD_SSE4_2
2222
+ if (__builtin_cpu_supports("sse4.2")) {
2223
+ return SIMD_SSE42;
2224
+ }
2225
+ #endif
2226
+ #ifdef HAVE_SIMD_SSE2
2227
+ if (__builtin_cpu_supports("sse2")) {
2228
+ return SIMD_SSE2;
2229
+ }
2230
+ #endif
2231
+ #else
2232
+ // Fallback: Use CPUID instruction directly
2233
+ unsigned int eax, ebx, ecx, edx;
2234
+ if (__get_cpuid(1, &eax, &ebx, &ecx, &edx)) {
2235
+ // Check for SSE4.2 (bit 20 of ECX)
2236
+ if (ecx & (1 << 20)) {
2237
+ return SIMD_SSE42;
2238
+ }
2239
+ // Check for SSE2 (bit 26 of EDX)
2240
+ if (edx & (1 << 26)) {
2241
+ return SIMD_SSE2;
2242
+ }
2243
+ }
2244
+ #endif // OJ_HAS_BUILTIN_CPU_SUPPORTS
2245
+
2246
+ #endif // _MSC_VER vs GCC/Clang
2247
+
2248
+ #endif // HAVE_SIMD_X86
2249
+
2250
+ #ifdef HAVE_SIMD_NEON
2251
+ // ARM NEON is always available on ARM64 and detected at compile time
2252
+ return SIMD_NEON;
2253
+ #endif
2254
+
2255
+ return SIMD_NONE;
2256
+ }
2257
+
1783
2258
  void Init_oj(void) {
1784
2259
  int err = 0;
1785
2260
 
@@ -1953,6 +2428,8 @@ void Init_oj(void) {
1953
2428
  rb_gc_register_address(&escape_mode_sym);
1954
2429
  integer_range_sym = ID2SYM(rb_intern("integer_range"));
1955
2430
  rb_gc_register_address(&integer_range_sym);
2431
+ max_integer_digits_sym = ID2SYM(rb_intern("max_integer_digits"));
2432
+ rb_gc_register_address(&max_integer_digits_sym);
1956
2433
  fast_sym = ID2SYM(rb_intern("fast"));
1957
2434
  rb_gc_register_address(&fast_sym);
1958
2435
  float_format_sym = ID2SYM(rb_intern("float_format"));
@@ -2059,6 +2536,10 @@ void Init_oj(void) {
2059
2536
  rb_gc_register_address(&xmlschema_sym);
2060
2537
  xss_safe_sym = ID2SYM(rb_intern("xss_safe"));
2061
2538
  rb_gc_register_address(&xss_safe_sym);
2539
+ only_sym = ID2SYM(rb_intern("only"));
2540
+ rb_gc_register_address(&only_sym);
2541
+ except_sym = ID2SYM(rb_intern("except"));
2542
+ rb_gc_register_address(&except_sym);
2062
2543
 
2063
2544
  oj_slash_string = rb_str_new2("/");
2064
2545
  rb_gc_register_address(&oj_slash_string);
@@ -2080,10 +2561,18 @@ void Init_oj(void) {
2080
2561
  #endif
2081
2562
  oj_init_doc();
2082
2563
 
2564
+ SIMD_Impl = oj_get_simd_implementation();
2565
+
2083
2566
  oj_parser_init();
2084
2567
  oj_scanner_init();
2085
2568
 
2086
2569
  #ifdef HAVE_SIMD_NEON
2087
2570
  initialize_neon();
2088
2571
  #endif /* HAVE_SIMD_NEON */
2572
+
2573
+ #ifdef HAVE_SIMD_SSE4_2
2574
+ if (SIMD_Impl == SIMD_SSE42) {
2575
+ initialize_sse42();
2576
+ }
2577
+ #endif /* HAVE_SIMD_SSE4_2 */
2089
2578
  }