oj 3.17.1 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a7515f34ac9c85f3e60f14c886bf3701f41fd077bdc518c71c6125e6ca7913db
4
- data.tar.gz: f4a5cab57b8fc728e7ec4aac4dbc672d26b69f83f6aa31c98c6d6237892f6bdc
3
+ metadata.gz: 8bbb833f16ca15e4591772c707b059e1db37eee8aaf02240cd2f628bf1876f4c
4
+ data.tar.gz: 8020f6e49607b38fb120a5a6ea4eea9e5ba3e73f35b3556b7defb0202e93e015
5
5
  SHA512:
6
- metadata.gz: 0aa115483a049bb1a9b406d3d9fa1dda4dcb47cf50236301f5ea9acca4c21b20118a027462e384d60c0cb287be92a80b163dc924f58a93a0f5998d0f2e577f4c
7
- data.tar.gz: 2f31c241a2283f353bf503eb54d89921643ec5fdc0ecb8166c31d68b5deb79fb26f68ea412155cc17dc5734bce9a85451bd798c600365428d5f389eabdc41346
6
+ metadata.gz: df072519e6d25cc38a04328022741db7bfe3d39a1545337aa6828982eaf233673ee2e04bb697ffb037aa25e0521258881ea96204d8a5d46b0a93b80dc002e94e
7
+ data.tar.gz: d0683f49f90c751beeb45b2d1ffb39238fd7c0c42e8c2970ed76eeb8ab2f382221c3ef7cdbc7d619ea29c50b7b3bf29559a8ae1e3ec34aa9b56e13a8fb02eac5
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 3.17.4 - 2026-07-14
4
+
5
+ - Fixed issue in fast.c where rescuing from a panic did not reset the where counter.
6
+
7
+ - Multiple fixes thanks to @Watson1978.
8
+
9
+ ## 3.17.3 - 2026-06-04
10
+
11
+ - Fixed issue in intern.c and fast.c.
12
+
13
+ ## 3.17.2 - 2026-05-27
14
+
15
+ - Fixed multiple issues related to extreme sizes.
16
+
3
17
  ## 3.17.1 - 2026-05-15
4
18
 
5
19
  - Fixed "quoted string not terminated" error.
data/ext/oj/compat.c CHANGED
@@ -27,7 +27,7 @@ static void hash_set_cstr(ParseInfo pi, Val kval, const char *str, size_t len, c
27
27
  volatile VALUE rkey = oj_calc_hash_key(pi, kval);
28
28
 
29
29
  if (Yes == pi->options.create_ok && NULL != pi->options.str_rx.head) {
30
- VALUE clas = oj_rxclass_match(&pi->options.str_rx, str, (int)len);
30
+ VALUE clas = oj_rxclass_match(&pi->options.str_rx, str, len);
31
31
 
32
32
  if (Qnil != clas) {
33
33
  rstr = rb_funcall(clas, oj_json_create_id, 1, rstr);
@@ -84,7 +84,7 @@ static void add_cstr(ParseInfo pi, const char *str, size_t len, const char *orig
84
84
  volatile VALUE rstr = oj_cstr_to_value(str, len, (size_t)pi->options.cache_str);
85
85
 
86
86
  if (Yes == pi->options.create_ok && NULL != pi->options.str_rx.head) {
87
- VALUE clas = oj_rxclass_match(&pi->options.str_rx, str, (int)len);
87
+ VALUE clas = oj_rxclass_match(&pi->options.str_rx, str, len);
88
88
 
89
89
  if (Qnil != clas) {
90
90
  pi->stack.head->val = rb_funcall(clas, oj_json_create_id, 1, rstr);
@@ -155,7 +155,7 @@ static void array_append_cstr(ParseInfo pi, const char *str, size_t len, const c
155
155
  volatile VALUE rstr = oj_cstr_to_value(str, len, (size_t)pi->options.cache_str);
156
156
 
157
157
  if (Yes == pi->options.create_ok && NULL != pi->options.str_rx.head) {
158
- VALUE clas = oj_rxclass_match(&pi->options.str_rx, str, (int)len);
158
+ VALUE clas = oj_rxclass_match(&pi->options.str_rx, str, len);
159
159
 
160
160
  if (Qnil != clas) {
161
161
  rb_ary_push(stack_peek(&pi->stack)->val, rb_funcall(clas, oj_json_create_id, 1, rstr));
data/ext/oj/custom.c CHANGED
@@ -915,7 +915,7 @@ static void hash_set_cstr(ParseInfo pi, Val kval, const char *str, size_t len, c
915
915
  volatile VALUE rkey = oj_calc_hash_key(pi, kval);
916
916
 
917
917
  if (Yes == pi->options.create_ok && NULL != pi->options.str_rx.head) {
918
- VALUE clas = oj_rxclass_match(&pi->options.str_rx, str, (int)len);
918
+ VALUE clas = oj_rxclass_match(&pi->options.str_rx, str, len);
919
919
 
920
920
  if (Qnil != clas) {
921
921
  rstr = rb_funcall(clas, oj_json_create_id, 1, rstr);
@@ -1020,7 +1020,7 @@ static void array_append_cstr(ParseInfo pi, const char *str, size_t len, const c
1020
1020
  volatile VALUE rstr = rb_utf8_str_new(str, len);
1021
1021
 
1022
1022
  if (Yes == pi->options.create_ok && NULL != pi->options.str_rx.head) {
1023
- VALUE clas = oj_rxclass_match(&pi->options.str_rx, str, (int)len);
1023
+ VALUE clas = oj_rxclass_match(&pi->options.str_rx, str, len);
1024
1024
 
1025
1025
  if (Qnil != clas) {
1026
1026
  rb_ary_push(stack_peek(&pi->stack)->val, rb_funcall(clas, oj_json_create_id, 1, rstr));
data/ext/oj/dump_compat.c CHANGED
@@ -162,6 +162,7 @@ static void dump_array(VALUE a, int depth, Out out, bool as_ok) {
162
162
  }
163
163
  }
164
164
  } else {
165
+ assure_size(out, size);
165
166
  fill_indent(out, d2);
166
167
  }
167
168
  oj_dump_compat_val(RARRAY_AREF(a, i), d2, out, true);
data/ext/oj/dump_object.c CHANGED
@@ -10,6 +10,24 @@ static const char hex_chars[17] = "0123456789abcdef";
10
10
 
11
11
  static void dump_obj_attrs(VALUE obj, VALUE clas, slot_t id, int depth, Out out);
12
12
 
13
+ // An odd class is dumped as {"^O":class,...} and is rebuilt with a create
14
+ // function when loaded so there is no place to put a "^i" circular id on it.
15
+ // Registering it in the circular cache would consume an id that is never
16
+ // written and any later occurrence would then be dumped as a "^r" that refers
17
+ // to nothing. Instead dump odd classes in full each time they are encountered
18
+ // just like Time, Rational, and Complex are.
19
+ static void dump_circular_obj(VALUE obj, VALUE clas, int depth, Out out) {
20
+ if (NULL != oj_get_odd(clas)) {
21
+ dump_obj_attrs(obj, clas, 0, depth, out);
22
+ } else {
23
+ long id = oj_check_circular(obj, out);
24
+
25
+ if (0 <= id) {
26
+ dump_obj_attrs(obj, clas, id, depth, out);
27
+ }
28
+ }
29
+ }
30
+
13
31
  static void dump_time(VALUE obj, Out out) {
14
32
  switch (out->opts->time_format) {
15
33
  case RubyTime:
@@ -47,11 +65,7 @@ static void dump_data(VALUE obj, int depth, Out out, bool as_ok) {
47
65
  oj_dump_cstr(str, len, 0, 0, out);
48
66
  }
49
67
  } else {
50
- long id = oj_check_circular(obj, out);
51
-
52
- if (0 <= id) {
53
- dump_obj_attrs(obj, clas, id, depth, out);
54
- }
68
+ dump_circular_obj(obj, clas, depth, out);
55
69
  }
56
70
  }
57
71
  }
@@ -74,11 +88,7 @@ static void dump_obj(VALUE obj, int depth, Out out, bool as_ok) {
74
88
  oj_dump_raw(str, len, out);
75
89
  }
76
90
  } else {
77
- long id = oj_check_circular(obj, out);
78
-
79
- if (0 <= id) {
80
- dump_obj_attrs(obj, clas, id, depth, out);
81
- }
91
+ dump_circular_obj(obj, clas, depth, out);
82
92
  }
83
93
  }
84
94
 
data/ext/oj/fast.c CHANGED
@@ -80,7 +80,7 @@ static void each_leaf(Doc doc, VALUE self);
80
80
  static int move_step(Doc doc, const char *path, int loc);
81
81
  static Leaf get_doc_leaf(Doc doc, const char *path);
82
82
  static Leaf get_leaf(Leaf *stack, Leaf *lp, const char *path);
83
- static void each_value(Doc doc, Leaf leaf);
83
+ static void each_value(Doc doc, Leaf leaf, VALUE self);
84
84
 
85
85
  VALUE oj_doc_class = Qundef;
86
86
 
@@ -92,7 +92,14 @@ inline static void next_non_white(ParseInfo pi) {
92
92
  case '\f':
93
93
  case '\n':
94
94
  case '\r': break;
95
- case '/': skip_comment(pi); break;
95
+ case '/':
96
+ skip_comment(pi);
97
+ // A comment that is not terminated by a newline ends on the null
98
+ // terminator. Stop here so the loop does not step past it.
99
+ if ('\0' == *pi->s) {
100
+ return;
101
+ }
102
+ break;
96
103
  default: return;
97
104
  }
98
105
  }
@@ -210,10 +217,11 @@ static void skip_comment(ParseInfo pi) {
210
217
  if ('*' == *pi->s && '/' == *(pi->s + 1)) {
211
218
  pi->s++;
212
219
  return;
213
- } else if ('\0' == *pi->s) {
214
- raise_error("comment not terminated", pi->str, pi->s);
215
220
  }
216
221
  }
222
+ // The loop only ends on the null terminator so the comment was never
223
+ // closed.
224
+ raise_error("comment not terminated", pi->str, pi->s);
217
225
  } else if ('/' == *pi->s) {
218
226
  for (; 1; pi->s++) {
219
227
  switch (*pi->s) {
@@ -710,21 +718,23 @@ static void mark_doc(void *ptr) {
710
718
  }
711
719
  #ifdef HAVE_RB_GC_MARK_MOVABLE
712
720
  static void compact_leaf(Leaf leaf) {
713
- switch (leaf->value_type) {
714
- case COL_VAL:
715
- if (NULL != leaf->elements) {
716
- Leaf first = leaf->elements->next;
717
- Leaf e = first;
721
+ if (NULL != leaf) {
722
+ switch (leaf->value_type) {
723
+ case COL_VAL:
724
+ if (NULL != leaf->elements) {
725
+ Leaf first = leaf->elements->next;
726
+ Leaf e = first;
718
727
 
719
- do {
720
- compact_leaf(e);
721
- e = e->next;
722
- } while (e != first);
723
- }
724
- break;
725
- case RUBY_VAL: leaf->value = rb_gc_location(leaf->value); break;
728
+ do {
729
+ compact_leaf(e);
730
+ e = e->next;
731
+ } while (e != first);
732
+ }
733
+ break;
734
+ case RUBY_VAL: leaf->value = rb_gc_location(leaf->value); break;
726
735
 
727
- default: break;
736
+ default: break;
737
+ }
728
738
  }
729
739
  }
730
740
 
@@ -790,14 +800,11 @@ static VALUE parse_json(VALUE clas, char *json, bool given) {
790
800
  doc->self = self;
791
801
  result = rb_protect(protect_open_proc, (VALUE)&pi, &ex);
792
802
  if (given || 0 != ex) {
803
+ // The doc is detached from its wrapper so the GC will not free it.
804
+ // doc_free() releases both the doc and its json buffer (doc->json), so
805
+ // the caller must not free json separately.
793
806
  DATA_PTR(doc->self) = NULL;
794
- // TBD is this needed?
795
- /*
796
807
  doc_free(pi.doc);
797
- if (0 != ex) { // will jump so caller will not free
798
- OJ_R_FREE(json);
799
- }
800
- */
801
808
  } else {
802
809
  result = doc->self;
803
810
  }
@@ -946,6 +953,7 @@ static void each_leaf(Doc doc, VALUE self) {
946
953
 
947
954
  doc->where++;
948
955
  if (MAX_STACK <= doc->where - doc->where_path) {
956
+ doc->where--;
949
957
  rb_raise(rb_const_get_at(Oj, rb_intern("DepthError")), "Path too deep. Limit is %d levels.", MAX_STACK);
950
958
  }
951
959
  do {
@@ -957,6 +965,9 @@ static void each_leaf(Doc doc, VALUE self) {
957
965
  }
958
966
  } else {
959
967
  rb_yield(self);
968
+ if (NULL == DATA_PTR(self)) {
969
+ rb_raise(rb_eIOError, "Document closed.");
970
+ }
960
971
  }
961
972
  }
962
973
 
@@ -969,8 +980,9 @@ static int move_step(Doc doc, const char *path, int loc) {
969
980
  } else {
970
981
  Leaf leaf;
971
982
 
983
+ // A document with no root (empty or comment only JSON) has no leaf to
984
+ // step from so the path can not be located.
972
985
  if (0 == doc->where || 0 == (leaf = *doc->where)) {
973
- printf("*** Internal error at %s\n", path);
974
986
  return loc;
975
987
  }
976
988
  if ('.' == *path && '.' == *(path + 1)) {
@@ -1050,19 +1062,22 @@ static int move_step(Doc doc, const char *path, int loc) {
1050
1062
  return loc;
1051
1063
  }
1052
1064
 
1053
- static void each_value(Doc doc, Leaf leaf) {
1065
+ static void each_value(Doc doc, Leaf leaf, VALUE self) {
1054
1066
  if (COL_VAL == leaf->value_type) {
1055
1067
  if (0 != leaf->elements) {
1056
1068
  Leaf first = leaf->elements->next;
1057
1069
  Leaf e = first;
1058
1070
 
1059
1071
  do {
1060
- each_value(doc, e);
1072
+ each_value(doc, e, self);
1061
1073
  e = e->next;
1062
1074
  } while (e != first);
1063
1075
  }
1064
1076
  } else {
1065
1077
  rb_yield(leaf_value(doc, leaf));
1078
+ if (NULL == DATA_PTR(self)) {
1079
+ rb_raise(rb_eIOError, "Document closed.");
1080
+ }
1066
1081
  }
1067
1082
  }
1068
1083
 
@@ -1098,12 +1113,7 @@ static VALUE doc_open(VALUE clas, VALUE str) {
1098
1113
 
1099
1114
  memcpy(json, StringValuePtr(str), len);
1100
1115
  obj = parse_json(clas, json, given);
1101
- // TBD is this needed
1102
- /*
1103
- if (given) {
1104
- OJ_R_FREE(json);
1105
- }
1106
- */
1116
+ // json is owned by the doc and freed by doc_free(); do not free it here.
1107
1117
  return obj;
1108
1118
  }
1109
1119
 
@@ -1135,7 +1145,9 @@ static VALUE doc_open_file(VALUE clas, VALUE filename) {
1135
1145
  int given = rb_block_given_p();
1136
1146
 
1137
1147
  path = StringValuePtr(filename);
1138
- if (0 == (f = fopen(path, "r"))) {
1148
+ // Open in binary mode. On Windows a text mode read translates CRLF into LF
1149
+ // so fewer bytes are read than the file size reported by ftell().
1150
+ if (0 == (f = fopen(path, "rb"))) {
1139
1151
  rb_raise(rb_eIOError, "%s", strerror(errno));
1140
1152
  }
1141
1153
  fseek(f, 0, SEEK_END);
@@ -1153,12 +1165,7 @@ static VALUE doc_open_file(VALUE clas, VALUE filename) {
1153
1165
  fclose(f);
1154
1166
  json[len] = '\0';
1155
1167
  obj = parse_json(clas, json, given);
1156
- // TBD is this needed
1157
- /*
1158
- if (given) {
1159
- OJ_R_FREE(json);
1160
- }
1161
- */
1168
+ // json is owned by the doc and freed by doc_free(); do not free it here.
1162
1169
  return obj;
1163
1170
  }
1164
1171
 
@@ -1257,10 +1264,14 @@ static VALUE doc_path(VALUE self) {
1257
1264
  * #=> nil
1258
1265
  */
1259
1266
  static VALUE doc_local_key(VALUE self) {
1260
- Doc doc = self_doc(self);
1261
- Leaf leaf = *doc->where;
1262
- volatile VALUE key = Qnil;
1267
+ Doc doc = self_doc(self);
1268
+ Leaf leaf;
1269
+ volatile VALUE key = Qnil;
1263
1270
 
1271
+ if (NULL == doc->where || NULL == *doc->where) {
1272
+ return Qnil;
1273
+ }
1274
+ leaf = *doc->where;
1264
1275
  if (T_HASH == leaf->parent_type) {
1265
1276
  key = rb_utf8_str_new_cstr(leaf->key);
1266
1277
  } else if (T_ARRAY == leaf->parent_type) {
@@ -1415,6 +1426,9 @@ static VALUE doc_each_leaf(int argc, VALUE *argv, VALUE self) {
1415
1426
  return Qnil;
1416
1427
  }
1417
1428
  }
1429
+ if (NULL == doc->where || NULL == *doc->where) {
1430
+ return Qnil;
1431
+ }
1418
1432
  each_leaf(doc, self);
1419
1433
  if (0 < wlen) {
1420
1434
  memcpy(doc->where_path, save_path, sizeof(Leaf) * (wlen + 1));
@@ -1498,12 +1512,19 @@ static VALUE doc_each_child(int argc, VALUE *argv, VALUE self) {
1498
1512
  Leaf first = (*doc->where)->elements->next;
1499
1513
  Leaf e = first;
1500
1514
 
1515
+ if (MAX_STACK <= (doc->where + 1) - doc->where_path) {
1516
+ rb_raise(rb_const_get_at(Oj, rb_intern("DepthError")), "Path too deep. Limit is %d levels.", MAX_STACK);
1517
+ }
1501
1518
  doc->where++;
1502
1519
  do {
1503
1520
  *doc->where = e;
1504
1521
  rb_yield(self);
1522
+ if (NULL == DATA_PTR(self)) {
1523
+ rb_raise(rb_eIOError, "Document closed.");
1524
+ }
1505
1525
  e = e->next;
1506
1526
  } while (e != first);
1527
+ doc->where--;
1507
1528
  }
1508
1529
  if (0 < wlen) {
1509
1530
  memcpy(doc->where_path, save_path, sizeof(Leaf) * (wlen + 1));
@@ -1547,7 +1568,7 @@ static VALUE doc_each_value(int argc, VALUE *argv, VALUE self) {
1547
1568
  path = StringValuePtr(*argv);
1548
1569
  }
1549
1570
  if (0 != (leaf = get_doc_leaf(doc, path))) {
1550
- each_value(doc, leaf);
1571
+ each_value(doc, leaf, self);
1551
1572
  }
1552
1573
  }
1553
1574
  return Qnil;
data/ext/oj/intern.c CHANGED
@@ -69,7 +69,7 @@ static VALUE form_attr(const char *str, size_t len) {
69
69
  memcpy(b + 1, str, len);
70
70
  b[len + 1] = '\0';
71
71
  }
72
- id = rb_intern3(buf, len + 1, oj_utf8_encoding);
72
+ id = rb_intern3(b, len + 1, oj_utf8_encoding);
73
73
  OJ_R_FREE(b);
74
74
  return id;
75
75
  }
data/ext/oj/object.c CHANGED
@@ -319,20 +319,26 @@ static int hat_value(ParseInfo pi, Val parent, const char *key, size_t klen, vol
319
319
  e1 = *RARRAY_CONST_PTR(value);
320
320
  // check for anonymous Struct
321
321
  if (T_ARRAY == rb_type(e1)) {
322
- VALUE args[1024];
323
- volatile VALUE rstr;
322
+ // Build the member-name list in a GC-managed Ruby array. Using a
323
+ // fixed C buffer (VALUE args[1024]) here overflowed the stack when
324
+ // the JSON supplied more than 1024 member names.
325
+ volatile VALUE names = rb_ary_new2(RARRAY_LEN(e1));
324
326
  size_t i;
325
327
  size_t cnt = RARRAY_LEN(e1);
326
328
 
327
329
  for (i = 0; i < cnt; i++) {
328
- rstr = RARRAY_AREF(e1, i);
329
- args[i] = rb_funcall(rstr, oj_to_sym_id, 0);
330
+ rb_ary_push(names, rb_funcall(RARRAY_AREF(e1, i), oj_to_sym_id, 0));
330
331
  }
331
- sc = rb_funcall2(rb_cStruct, oj_new_id, (int)cnt, args);
332
+ sc = rb_apply(rb_cStruct, oj_new_id, names);
332
333
  } else {
333
334
  // If struct is not defined then we let this fail and raise an exception.
334
335
  sc = oj_name2struct(pi, *RARRAY_CONST_PTR(value), rb_eArgError);
335
336
  }
337
+ if (Qundef == sc || Qnil == sc) {
338
+ // oj_name2struct already recorded the error; do not pass an
339
+ // unresolved (Qundef) class on to rb_obj_alloc/rb_class_new_instance.
340
+ return 1;
341
+ }
336
342
  if (sc == rb_cRange) {
337
343
  parent->val = rb_class_new_instance((int)(len - 1), RARRAY_CONST_PTR(value) + 1, rb_cRange);
338
344
  } else {
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;
@@ -604,9 +606,52 @@ static VALUE get_def_opts(VALUE self) {
604
606
  * - *:trace* [_Boolean_] turn trace on or off.
605
607
  * - *:safe* [_Boolean_] turn safe mimic on or off.
606
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
+
607
643
  static VALUE set_def_opts(VALUE self, VALUE opts) {
608
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
+ }
609
653
  oj_parse_options(opts, &oj_default_options);
654
+ keep_default_options();
610
655
 
611
656
  return Qnil;
612
657
  }
@@ -766,7 +811,10 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
766
811
  case T_FIXNUM:
767
812
  copts->dump_opts.indent_size = 0;
768
813
  *copts->dump_opts.indent_str = '\0';
769
- 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);
770
818
  break;
771
819
  case T_STRING:
772
820
  if (sizeof(copts->dump_opts.indent_str) <= (len = RSTRING_LEN(v))) {
@@ -916,6 +964,9 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
916
964
 
917
965
  len = RSTRING_LEN(v);
918
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
+ }
919
970
  copts->create_id = OJ_R_ALLOC_N(char, len + 1);
920
971
  strcpy((char *)copts->create_id, str);
921
972
  copts->create_id_len = len;
@@ -1047,7 +1098,11 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
1047
1098
  copts->array_class = v;
1048
1099
  }
1049
1100
  } else if (ignore_sym == k) {
1050
- 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
+ }
1051
1106
  copts->ignore = NULL;
1052
1107
  if (Qnil != v) {
1053
1108
  size_t cnt;
@@ -1120,15 +1175,15 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
1120
1175
  strncpy(copts->float_fmt, RSTRING_PTR(v), (size_t)RSTRING_LEN(v));
1121
1176
  copts->float_fmt[RSTRING_LEN(v)] = '\0';
1122
1177
  } else if (only_sym == k) {
1123
- if (NULL != copts->dump_opts.only) {
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) {
1124
1181
  OJ_R_FREE((void *)copts->dump_opts.only);
1125
- copts->dump_opts.only = NULL;
1126
1182
  }
1127
1183
  copts->dump_opts.only = make_only_value(v);
1128
1184
  } else if (except_sym == k) {
1129
- if (NULL != copts->dump_opts.except) {
1185
+ if (&oj_default_options == copts && NULL != copts->dump_opts.except) {
1130
1186
  OJ_R_FREE((void *)copts->dump_opts.except);
1131
- copts->dump_opts.except = NULL;
1132
1187
  }
1133
1188
  copts->dump_opts.except = make_only_value(v);
1134
1189
  }
@@ -1148,6 +1203,120 @@ void oj_parse_options(VALUE ropts, Options copts) {
1148
1203
  return;
1149
1204
  }
1150
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
+
1151
1320
  static int match_string_cb(VALUE key, VALUE value, VALUE rx) {
1152
1321
  RxClass rc = (RxClass)rx;
1153
1322
 
@@ -1439,6 +1608,7 @@ static VALUE dump_ensure(VALUE a) {
1439
1608
  volatile struct dump_arg *arg = (void *)a;
1440
1609
 
1441
1610
  oj_out_free(arg->out);
1611
+ oj_free_call_options(arg->copts);
1442
1612
 
1443
1613
  return Qnil;
1444
1614
  }
@@ -1554,6 +1724,7 @@ static VALUE to_file(int argc, VALUE *argv, VALUE self) {
1554
1724
  oj_parse_options(argv[2], &copts);
1555
1725
  }
1556
1726
  oj_write_obj_to_file(argv[1], StringValuePtr(*argv), &copts);
1727
+ oj_free_call_options(&copts);
1557
1728
 
1558
1729
  return Qnil;
1559
1730
  }
@@ -1575,6 +1746,7 @@ static VALUE to_stream(int argc, VALUE *argv, VALUE self) {
1575
1746
  oj_parse_options(argv[2], &copts);
1576
1747
  }
1577
1748
  oj_write_obj_to_stream(argv[1], *argv, &copts);
1749
+ oj_free_call_options(&copts);
1578
1750
 
1579
1751
  return Qnil;
1580
1752
  }
data/ext/oj/oj.h CHANGED
@@ -256,6 +256,10 @@ extern VALUE oj_custom_parse_cstr(int argc, VALUE *argv, char *json, size_t len)
256
256
 
257
257
  extern bool oj_hash_has_key(VALUE hash, VALUE key);
258
258
  extern void oj_parse_options(VALUE ropts, Options copts);
259
+ extern void oj_free_call_options(Options copts);
260
+ extern void oj_options_take_ownership(Options copts);
261
+ extern void oj_options_release(Options copts);
262
+ extern void oj_options_mark(Options copts);
259
263
 
260
264
  extern void oj_dump_obj_to_json(VALUE obj, Options copts, Out out);
261
265
  extern void oj_dump_obj_to_json_using_params(VALUE obj, Options copts, Out out, int argc, VALUE *argv);
data/ext/oj/parse.c CHANGED
@@ -394,7 +394,7 @@ void oj_scanner_init(void) {
394
394
  static void read_escaped_str(ParseInfo pi, const char *start) {
395
395
  struct _buf buf;
396
396
  const char *s;
397
- int cnt = (int)(pi->cur - start);
397
+ size_t cnt = pi->cur - start;
398
398
  uint32_t code;
399
399
  Val parent = stack_peek(&pi->stack);
400
400
 
@@ -669,7 +669,7 @@ static void read_num(ParseInfo pi) {
669
669
  // A trailing . is not a valid decimal but if encountered allow it
670
670
  // except when mimicking the JSON gem or in strict mode.
671
671
  if (StrictMode == pi->options.mode || CompatMode == pi->options.mode) {
672
- int pos = (int)(pi->cur - ni.str);
672
+ size_t pos = pi->cur - ni.str;
673
673
 
674
674
  if (1 == pos || (2 == pos && ni.neg)) {
675
675
  oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number");
@@ -1306,9 +1306,16 @@ CLEANUP:
1306
1306
  OJ_R_FREE(json);
1307
1307
  }
1308
1308
  stack_cleanup(&pi->stack);
1309
- if (pi->str_rx.head != oj_default_options.str_rx.head) {
1310
- oj_rxclass_cleanup(&pi->str_rx);
1309
+ // The parsers match against pi->options.str_rx. A :match_string option
1310
+ // builds a chain of its own for this call, which has to be freed. Without
1311
+ // the option the member still aliases the chain owned by the defaults and
1312
+ // must be left alone.
1313
+ if (pi->options.str_rx.head != oj_default_options.str_rx.head) {
1314
+ oj_rxclass_cleanup(&pi->options.str_rx);
1315
+ pi->options.str_rx.head = NULL;
1316
+ pi->options.str_rx.tail = NULL;
1311
1317
  }
1318
+ oj_free_call_options(&pi->options);
1312
1319
  if (err_has(&pi->err)) {
1313
1320
  rb_set_errinfo(Qnil);
1314
1321
  if (Qnil != pi->err_class) {
data/ext/oj/parse.h CHANGED
@@ -49,7 +49,6 @@ typedef struct _parseInfo {
49
49
  VALUE handler;
50
50
  struct _valStack stack;
51
51
  CircArray circ_array;
52
- struct _rxClass str_rx;
53
52
  int expect_value;
54
53
  int max_depth; // just for the json gem
55
54
  VALUE proc;
data/ext/oj/parser.c CHANGED
@@ -5,6 +5,7 @@
5
5
  #include <fcntl.h>
6
6
 
7
7
  #include "oj.h"
8
+ #include "simd.h"
8
9
 
9
10
  #define DEBUG 0
10
11
 
@@ -594,9 +595,64 @@ static void big_change(ojParser p) {
594
595
  }
595
596
  }
596
597
 
597
- static void parse(ojParser p, const byte *json, bool more) {
598
+ // Scan forward over string content, returning the first byte that is not
599
+ // STR_OK in string_map. This is a pure drop-in for the scalar loop
600
+ //
601
+ // for (; STR_OK == string_map[*b]; b++) {}
602
+ //
603
+ // and must return the exact same stop position. The stop set (bytes whose
604
+ // string_map class is not 'R'/STR_OK) is:
605
+ //
606
+ // * control bytes 0x00..0x1F (class '.', also stops at the NUL terminator)
607
+ // * the quote 0x22 '"' (class 'z')
608
+ // * the backslash 0x5C '\' (class 'A')
609
+ // * every high byte 0x80..0xFF (UTF-8 lead/continuation, classes M/P/Q/'.')
610
+ //
611
+ // Note this differs from parse.c's string_scan_neon, which stops only on
612
+ // \0 \\ " -- parser.c hands multi-byte UTF-8 to its state machine, so the
613
+ // scanner must stop on the high bytes too. The predictor below is derived from
614
+ // string_map, not copied from parse.c.
615
+ //
616
+ // The NEON path only loads a full 16-byte vector when [b, b+16) stays within
617
+ // [., end), so it never reads past the string's allocation; the sub-16-byte
618
+ // tail (and the whole scan on non-NEON builds) uses the scalar loop, which
619
+ // stops naturally at the guaranteed NUL terminator.
620
+ static inline const byte *oj_scan_str_simd(const byte *b, const byte *end) {
621
+ #ifdef HAVE_SIMD_NEON
622
+ if (SIMD_NEON == SIMD_Impl) {
623
+ const uint8x16_t quote = vdupq_n_u8('"');
624
+ const uint8x16_t bslash = vdupq_n_u8('\\');
625
+ const uint8x16_t space = vdupq_n_u8(0x20);
626
+ const uint8x16_t high = vdupq_n_u8(0x80);
627
+
628
+ while (b + sizeof(uint8x16_t) <= end) {
629
+ const uint8x16_t chunk = vld1q_u8((const uint8_t *)b);
630
+ // special lane == 0xFF for any byte that stops the scan.
631
+ const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(chunk, quote), vceqq_u8(chunk, bslash)),
632
+ vorrq_u8(vcltq_u8(chunk, space), vcgeq_u8(chunk, high)));
633
+ // Reduce to a 64-bit mask with 4 bits per lane (same idiom as
634
+ // parse.c's string_scan_neon) and locate the first set lane.
635
+ const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(special), 4);
636
+ uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0);
637
+ if (0 != mask) {
638
+ mask &= 0x8888888888888888ull;
639
+ return b + (OJ_CTZ64(mask) >> 2);
640
+ }
641
+ b += sizeof(uint8x16_t);
642
+ }
643
+ }
644
+ #else
645
+ (void)end;
646
+ #endif
647
+ for (; STR_OK == string_map[*b]; b++) {
648
+ }
649
+ return b;
650
+ }
651
+
652
+ static void parse(ojParser p, const byte *json, size_t len, bool more) {
598
653
  const byte *start;
599
- const byte *b = json;
654
+ const byte *b = json;
655
+ const byte *end = json + len;
600
656
  int i;
601
657
 
602
658
  p->line = 1;
@@ -629,8 +685,7 @@ static void parse(ojParser p, const byte *json, bool more) {
629
685
  b++;
630
686
  p->key.tail = p->key.head;
631
687
  start = b;
632
- for (; STR_OK == string_map[*b]; b++) {
633
- }
688
+ b = oj_scan_str_simd(b, end);
634
689
  buf_append_string(&p->key, (const char *)start, b - start);
635
690
  if ('"' == *b) {
636
691
  p->map = colon_map;
@@ -654,8 +709,7 @@ static void parse(ojParser p, const byte *json, bool more) {
654
709
  b++;
655
710
  start = b;
656
711
  p->buf.tail = p->buf.head;
657
- for (; STR_OK == string_map[*b]; b++) {
658
- }
712
+ b = oj_scan_str_simd(b, end);
659
713
  buf_append_string(&p->buf, (const char *)start, b - start);
660
714
  if ('"' == *b) {
661
715
  p->cur = b - json;
@@ -674,6 +728,10 @@ static void parse(ojParser p, const byte *json, bool more) {
674
728
  p->cur = b - json;
675
729
  p->funcs[p->stack[p->depth]].open_object(p);
676
730
  p->depth++;
731
+ if ((int)sizeof(p->stack) <= p->depth) {
732
+ parse_error(p, "too deeply nested");
733
+ break;
734
+ }
677
735
  p->stack[p->depth] = OBJECT_FUN;
678
736
  p->map = key1_map;
679
737
  break;
@@ -696,6 +754,10 @@ static void parse(ojParser p, const byte *json, bool more) {
696
754
  p->cur = b - json;
697
755
  p->funcs[p->stack[p->depth]].open_array(p);
698
756
  p->depth++;
757
+ if ((int)sizeof(p->stack) <= p->depth) {
758
+ parse_error(p, "too deeply nested");
759
+ break;
760
+ }
699
761
  p->stack[p->depth] = ARRAY_FUN;
700
762
  p->map = value_map;
701
763
  break;
@@ -897,8 +959,7 @@ static void parse(ojParser p, const byte *json, bool more) {
897
959
  break;
898
960
  case STR_OK:
899
961
  start = b;
900
- for (; STR_OK == string_map[*b]; b++) {
901
- }
962
+ b = oj_scan_str_simd(b, end);
902
963
  if (':' == p->next_map[256]) {
903
964
  buf_append_string(&p->key, (const char *)start, b - start);
904
965
  } else {
@@ -1399,14 +1460,19 @@ static void validate_document_end(ojParser p) {
1399
1460
  */
1400
1461
  static VALUE parser_parse(VALUE self, VALUE json) {
1401
1462
  ojParser p;
1402
- const byte *ptr = (const byte *)StringValuePtr(json);
1463
+ int frozen = OBJ_FROZEN(json);
1464
+ const byte *ptr;
1465
+
1466
+ if (!frozen) {
1467
+ rb_str_freeze(json);
1468
+ }
1469
+ ptr = (const byte *)StringValuePtr(json);
1403
1470
 
1404
1471
  TypedData_Get_Struct(self, struct _ojParser, &oj_parser_type, p);
1405
1472
 
1406
1473
  parser_reset(p);
1407
1474
  p->start(p);
1408
- parse(p, ptr, false);
1409
-
1475
+ parse(p, ptr, (size_t)RSTRING_LEN(json), false);
1410
1476
  validate_document_end(p);
1411
1477
 
1412
1478
  return p->result(p);
@@ -1427,7 +1493,7 @@ static VALUE load(VALUE self) {
1427
1493
  while (true) {
1428
1494
  rb_funcall(p->reader, oj_readpartial_id, 2, INT2NUM(16385), rbuf);
1429
1495
  if (0 < RSTRING_LEN(rbuf)) {
1430
- parse(p, (byte *)StringValuePtr(rbuf), true);
1496
+ parse(p, (byte *)StringValuePtr(rbuf), (size_t)RSTRING_LEN(rbuf), true);
1431
1497
  }
1432
1498
  if (Qtrue == rb_funcall(p->reader, oj_eofq_id, 0)) {
1433
1499
  if (0 < p->depth) {
@@ -1497,7 +1563,7 @@ static VALUE parser_file(VALUE self, VALUE filename) {
1497
1563
  while (true) {
1498
1564
  if (0 < (rsize = read(fd, buf, size))) {
1499
1565
  buf[rsize] = '\0';
1500
- parse(p, buf, true);
1566
+ parse(p, buf, rsize, true);
1501
1567
  }
1502
1568
  if (rsize <= 0) {
1503
1569
  if (0 != rsize) {
data/ext/oj/rails.c CHANGED
@@ -623,6 +623,7 @@ static void encoder_free(void *ptr) {
623
623
  if (NULL != ptr) {
624
624
  Encoder e = (Encoder)ptr;
625
625
 
626
+ oj_options_release(&e->opts);
626
627
  if (NULL != e->ropts.table) {
627
628
  OJ_R_FREE(e->ropts.table);
628
629
  }
@@ -633,10 +634,19 @@ static void encoder_free(void *ptr) {
633
634
  static void encoder_mark(void *ptr) {
634
635
  if (NULL != ptr) {
635
636
  Encoder e = (Encoder)ptr;
637
+ int i;
636
638
 
639
+ oj_options_mark(&e->opts);
637
640
  if (Qnil != e->arg) {
638
641
  rb_gc_mark(e->arg);
639
642
  }
643
+ // The optimized classes in the encoder table are not reachable from
644
+ // anywhere else once optimize() is called on the encoder itself.
645
+ for (i = 0; i < e->ropts.len; i++) {
646
+ if (Qnil != e->ropts.table[i].clas) {
647
+ rb_gc_mark(e->ropts.table[i].clas);
648
+ }
649
+ }
640
650
  }
641
651
  }
642
652
 
@@ -661,6 +671,11 @@ static VALUE encoder_new(int argc, VALUE *argv, VALUE self) {
661
671
  Encoder e = OJ_R_ALLOC(struct _encoder);
662
672
 
663
673
  e->opts = oj_default_options;
674
+ // Detach from the match_string regexps owned by the defaults before the
675
+ // options are parsed so that a :match_string option builds a chain of its
676
+ // own that is freed with the encoder.
677
+ e->opts.str_rx.head = NULL;
678
+ e->opts.str_rx.tail = NULL;
664
679
  copy_opts(&ropts, &e->ropts);
665
680
 
666
681
  if (1 <= argc && Qnil != *argv) {
@@ -669,6 +684,7 @@ static VALUE encoder_new(int argc, VALUE *argv, VALUE self) {
669
684
  e->arg = rb_hash_new();
670
685
  }
671
686
  oj_parse_options(e->arg, &e->opts);
687
+ oj_options_take_ownership(&e->opts);
672
688
 
673
689
  return TypedData_Wrap_Struct(encoder_class, &oj_encoder_type, e);
674
690
  }
data/ext/oj/reader.c CHANGED
@@ -164,8 +164,14 @@ static VALUE partial_io_cb(VALUE rbuf) {
164
164
  }
165
165
  str = StringValuePtr(rstr);
166
166
  cnt = RSTRING_LEN(rstr);
167
- strcpy(reader->tail, str);
168
- reader->read_end = reader->tail + cnt;
167
+ if (cnt > (size_t)(reader->end - reader->tail)) {
168
+ // A misbehaving IO returned more than the requested number of bytes.
169
+ // Copying it in with strcpy() would overflow the reader buffer.
170
+ rb_raise(rb_eIOError, "read returned more than the requested number of bytes");
171
+ }
172
+ memcpy(reader->tail, str, cnt);
173
+ reader->tail[cnt] = '\0';
174
+ reader->read_end = reader->tail + cnt;
169
175
 
170
176
  return Qtrue;
171
177
  }
@@ -184,8 +190,14 @@ static VALUE io_cb(VALUE rbuf) {
184
190
  }
185
191
  str = StringValuePtr(rstr);
186
192
  cnt = RSTRING_LEN(rstr);
187
- strcpy(reader->tail, str);
188
- reader->read_end = reader->tail + cnt;
193
+ if (cnt > (size_t)(reader->end - reader->tail)) {
194
+ // A misbehaving IO returned more than the requested number of bytes.
195
+ // Copying it in with strcpy() would overflow the reader buffer.
196
+ rb_raise(rb_eIOError, "read returned more than the requested number of bytes");
197
+ }
198
+ memcpy(reader->tail, str, cnt);
199
+ reader->tail[cnt] = '\0';
200
+ reader->read_end = reader->tail + cnt;
189
201
 
190
202
  return Qtrue;
191
203
  }
data/ext/oj/rxclass.c CHANGED
@@ -96,7 +96,7 @@ int oj_rxclass_append(RxClass rc, const char *expr, VALUE clas) {
96
96
  }
97
97
 
98
98
  VALUE
99
- oj_rxclass_match(RxClass rc, const char *str, int len) {
99
+ oj_rxclass_match(RxClass rc, const char *str, size_t len) {
100
100
  RxC rxc;
101
101
  char buf[4096];
102
102
 
@@ -142,3 +142,19 @@ void oj_rxclass_copy(RxClass src, RxClass dest) {
142
142
  }
143
143
  }
144
144
  }
145
+
146
+ // Add the Ruby objects in the chain to an array. The regexps and the classes
147
+ // can be referenced from nowhere else, as with a default match_string option,
148
+ // and are collected unless something holds onto them.
149
+ void oj_rxclass_keep(RxClass rc, VALUE keep) {
150
+ RxC rxc;
151
+
152
+ for (rxc = rc->head; NULL != rxc; rxc = rxc->next) {
153
+ if (Qnil != rxc->rrx) {
154
+ rb_ary_push(keep, rxc->rrx);
155
+ }
156
+ if (Qnil != rxc->clas) {
157
+ rb_ary_push(keep, rxc->clas);
158
+ }
159
+ }
160
+ }
data/ext/oj/rxclass.h CHANGED
@@ -19,8 +19,9 @@ typedef struct _rxClass {
19
19
  extern void oj_rxclass_init(RxClass rc);
20
20
  extern void oj_rxclass_cleanup(RxClass rc);
21
21
  extern int oj_rxclass_append(RxClass rc, const char *expr, VALUE clas);
22
- extern VALUE oj_rxclass_match(RxClass rc, const char *str, int len);
22
+ extern VALUE oj_rxclass_match(RxClass rc, const char *str, size_t len);
23
23
  extern void oj_rxclass_copy(RxClass src, RxClass dest);
24
24
  extern void oj_rxclass_rappend(RxClass rc, VALUE rx, VALUE clas);
25
+ extern void oj_rxclass_keep(RxClass rc, VALUE keep);
25
26
 
26
27
  #endif /* OJ_RXCLASS_H */
data/ext/oj/safe.c CHANGED
@@ -1,3 +1,4 @@
1
+
1
2
  #include "safe.h"
2
3
 
3
4
  static VALUE max_hash_size_sym, max_array_size_sym, max_depth_sym, max_total_elements_sym, max_hash_size_error_class,
data/ext/oj/saj.c CHANGED
@@ -82,7 +82,14 @@ inline static void next_non_white(ParseInfo pi) {
82
82
  case '\f':
83
83
  case '\n':
84
84
  case '\r': break;
85
- case '/': skip_comment(pi); break;
85
+ case '/':
86
+ skip_comment(pi);
87
+ /* A comment that is not terminated by a newline ends on the null
88
+ * terminator. Stop here so the loop does not step past it. */
89
+ if ('\0' == *pi->s) {
90
+ return;
91
+ }
92
+ break;
86
93
  default: return;
87
94
  }
88
95
  }
@@ -118,14 +125,15 @@ static void skip_comment(ParseInfo pi) {
118
125
  if ('*' == *pi->s && '/' == *(pi->s + 1)) {
119
126
  pi->s++;
120
127
  return;
121
- } else if ('\0' == *pi->s) {
122
- if (pi->has_error) {
123
- call_error("comment not terminated", pi, __FILE__, __LINE__);
124
- } else {
125
- raise_error("comment not terminated", pi->str, pi->s);
126
- }
127
128
  }
128
129
  }
130
+ /* The loop only ends on the null terminator so the comment was never
131
+ * closed. */
132
+ if (pi->has_error) {
133
+ call_error("comment not terminated", pi, __FILE__, __LINE__);
134
+ } else {
135
+ raise_error("comment not terminated", pi->str, pi->s);
136
+ }
129
137
  } else if ('/' == *pi->s) {
130
138
  for (; 1; pi->s++) {
131
139
  switch (*pi->s) {
@@ -606,6 +614,19 @@ static void saj_parse(VALUE handler, char *json) {
606
614
  }
607
615
  }
608
616
 
617
+ struct _sajArgs {
618
+ VALUE handler;
619
+ char *json;
620
+ };
621
+
622
+ static VALUE protect_saj_parse(VALUE x) {
623
+ struct _sajArgs *args = (struct _sajArgs *)x;
624
+
625
+ saj_parse(args->handler, args->json);
626
+
627
+ return Qnil;
628
+ }
629
+
609
630
  /* call-seq: saj_parse(handler, io)
610
631
  *
611
632
  * Parses an IO stream or file containing an JSON document. Raises an exception
@@ -670,8 +691,17 @@ oj_saj_parse(int argc, VALUE *argv, VALUE self) {
670
691
  rb_raise(rb_eArgError, "saj_parse() expected a String or IO Object.");
671
692
  }
672
693
  }
673
- saj_parse(*argv, json);
674
- OJ_R_FREE(json);
675
-
694
+ {
695
+ // saj_parse() raises on a malformed document so the json buffer has to
696
+ // be freed even when the parse does not return normally.
697
+ struct _sajArgs args = {*argv, json};
698
+ int ex = 0;
699
+
700
+ rb_protect(protect_saj_parse, (VALUE)&args, &ex);
701
+ OJ_R_FREE(json);
702
+ if (0 != ex) {
703
+ rb_jump_tag(ex);
704
+ }
705
+ }
676
706
  return Qnil;
677
707
  }
data/ext/oj/sparse.c CHANGED
@@ -907,6 +907,15 @@ CLEANUP:
907
907
  oj_circ_array_free(pi->circ_array);
908
908
  }
909
909
  stack_cleanup(&pi->stack);
910
+ // A :match_string option builds a chain of regexps for this call that has to
911
+ // be freed. Without the option the member aliases the chain owned by the
912
+ // defaults and must be left alone.
913
+ if (pi->options.str_rx.head != oj_default_options.str_rx.head) {
914
+ oj_rxclass_cleanup(&pi->options.str_rx);
915
+ pi->options.str_rx.head = NULL;
916
+ pi->options.str_rx.tail = NULL;
917
+ }
918
+ oj_free_call_options(&pi->options);
910
919
  if (0 != fd) {
911
920
  #ifdef _WIN32
912
921
  rb_w32_close(fd);
@@ -16,15 +16,29 @@ static void stream_writer_free(void *ptr) {
16
16
  return;
17
17
  }
18
18
  sw = (StreamWriter)ptr;
19
+ oj_options_release(&sw->sw.opts);
19
20
  OJ_R_FREE(sw->sw.out.buf);
20
21
  OJ_R_FREE(sw->sw.types);
21
22
  OJ_R_FREE(ptr);
22
23
  }
23
24
 
25
+ static void stream_writer_mark(void *ptr) {
26
+ if (NULL != ptr) {
27
+ StreamWriter sw = (StreamWriter)ptr;
28
+
29
+ oj_options_mark(&sw->sw.opts);
30
+ // The writer can be the only reference to the stream as in
31
+ // Oj::StreamWriter.new(StringIO.new) so it must be marked.
32
+ if (Qnil != sw->stream) {
33
+ rb_gc_mark(sw->stream);
34
+ }
35
+ }
36
+ }
37
+
24
38
  static const rb_data_type_t oj_stream_writer_type = {
25
39
  "Oj/stream_writer",
26
40
  {
27
- NULL,
41
+ stream_writer_mark,
28
42
  stream_writer_free,
29
43
  NULL,
30
44
  },
@@ -116,6 +130,7 @@ static VALUE stream_writer_new(int argc, VALUE *argv, VALUE self) {
116
130
  oj_str_writer_init(&sw->sw, 4096);
117
131
  sw->flush_limit = 0;
118
132
  }
133
+ oj_options_take_ownership(&sw->sw.opts);
119
134
  sw->sw.out.indent = sw->sw.opts.indent;
120
135
  sw->stream = stream;
121
136
  sw->type = type;
@@ -42,12 +42,18 @@ static void maybe_comma(StrWriter sw) {
42
42
 
43
43
  // Used by stream writer also.
44
44
  void oj_str_writer_init(StrWriter sw, int buf_size) {
45
- sw->opts = oj_default_options;
46
- sw->depth = 0;
47
- sw->types = OJ_R_ALLOC_N(char, 256);
48
- sw->types_end = sw->types + 256;
49
- *sw->types = '\0';
50
- sw->keyWritten = 0;
45
+ sw->opts = oj_default_options;
46
+ // Detach from the match_string regexps owned by the defaults before the
47
+ // options are parsed. A :match_string option then builds a chain of its own
48
+ // that is freed with the writer instead of being appended to the chain the
49
+ // defaults are using.
50
+ sw->opts.str_rx.head = NULL;
51
+ sw->opts.str_rx.tail = NULL;
52
+ sw->depth = 0;
53
+ sw->types = OJ_R_ALLOC_N(char, 256);
54
+ sw->types_end = sw->types + 256;
55
+ *sw->types = '\0';
56
+ sw->keyWritten = 0;
51
57
 
52
58
  if (0 == buf_size) {
53
59
  buf_size = 4096;
@@ -234,15 +240,22 @@ static void string_writer_free(void *ptr) {
234
240
  sw = (StrWriter)ptr;
235
241
 
236
242
  oj_out_free(&sw->out);
243
+ oj_options_release(&sw->opts);
237
244
 
238
245
  OJ_R_FREE(sw->types);
239
246
  OJ_R_FREE(ptr);
240
247
  }
241
248
 
249
+ static void string_writer_mark(void *ptr) {
250
+ if (NULL != ptr) {
251
+ oj_options_mark(&((StrWriter)ptr)->opts);
252
+ }
253
+ }
254
+
242
255
  static const rb_data_type_t oj_string_writer_type = {
243
256
  "Oj/string_writer",
244
257
  {
245
- NULL,
258
+ string_writer_mark,
246
259
  string_writer_free,
247
260
  NULL,
248
261
  },
@@ -279,6 +292,7 @@ static VALUE str_writer_new(int argc, VALUE *argv, VALUE self) {
279
292
  if (1 == argc) {
280
293
  oj_parse_options(argv[0], &sw->opts);
281
294
  }
295
+ oj_options_take_ownership(&sw->opts);
282
296
  sw->out.argc = argc - 1;
283
297
  sw->out.argv = argv + 1;
284
298
  sw->out.indent = sw->opts.indent;
data/ext/oj/usual.c CHANGED
@@ -63,7 +63,7 @@ static VALUE form_attr(const char *str, size_t len) {
63
63
  memcpy(b + 1, str, len);
64
64
  b[len + 1] = '\0';
65
65
 
66
- id = rb_intern3(buf, len + 1, oj_utf8_encoding);
66
+ id = rb_intern3(b, len + 1, oj_utf8_encoding);
67
67
  OJ_R_FREE(b);
68
68
  return id;
69
69
  }
@@ -200,7 +200,10 @@ static void push_key(ojParser p) {
200
200
  d->ktail = d->khead + pos;
201
201
  d->kend = d->khead + cap;
202
202
  }
203
- d->ktail->len = klen;
203
+ if (32000 < klen) {
204
+ rb_raise(oj_json_parser_error_class, "Key too long. Keys are limited to 32,000 bytes.");
205
+ }
206
+ d->ktail->len = (int16_t)klen;
204
207
  if (klen < sizeof(d->ktail->buf)) {
205
208
  memcpy(d->ktail->buf, key, klen);
206
209
  d->ktail->buf[klen] = '\0';
@@ -608,12 +611,16 @@ static void dfree(ojParser p) {
608
611
  Usual d = (Usual)p->ctx;
609
612
 
610
613
  cache_free(d->str_cache);
614
+ d->str_cache = NULL;
611
615
  cache_free(d->attr_cache);
616
+ d->attr_cache = NULL;
612
617
  if (NULL != d->sym_cache) {
613
618
  cache_free(d->sym_cache);
619
+ d->sym_cache = NULL;
614
620
  }
615
621
  if (NULL != d->class_cache) {
616
622
  cache_free(d->class_cache);
623
+ d->class_cache = NULL;
617
624
  }
618
625
  OJ_R_FREE(d->vhead);
619
626
  OJ_R_FREE(d->chead);
@@ -640,6 +647,12 @@ static void mark(ojParser p) {
640
647
  if (NULL != d->class_cache) {
641
648
  cache_mark(d->class_cache);
642
649
  }
650
+ if (Qnil != d->hash_class) {
651
+ rb_gc_mark(d->hash_class);
652
+ }
653
+ if (Qnil != d->array_class) {
654
+ rb_gc_mark(d->array_class);
655
+ }
643
656
  for (vp = d->vhead; vp < d->vtail; vp++) {
644
657
  if (Qundef != *vp) {
645
658
  rb_gc_mark(*vp);
@@ -1050,10 +1063,10 @@ static VALUE opt_symbol_keys_set(ojParser p, VALUE value) {
1050
1063
  if (NULL != d->sym_cache) {
1051
1064
  cache_free(d->sym_cache);
1052
1065
  d->sym_cache = NULL;
1066
+ d->key_cache = NULL;
1053
1067
  }
1054
- if (!d->cache_keys) {
1055
- d->get_key = str_key;
1056
- }
1068
+ d->cache_keys = false;
1069
+ d->get_key = str_key;
1057
1070
  }
1058
1071
  return (NULL != d->sym_cache) ? Qtrue : Qfalse;
1059
1072
  }
data/lib/oj/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Oj
2
2
  # Current version of the module.
3
- VERSION = '3.17.1'
3
+ VERSION = '3.17.4'
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oj
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.17.1
4
+ version: 3.17.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Ohler
@@ -231,7 +231,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
231
231
  - !ruby/object:Gem::Version
232
232
  version: '0'
233
233
  requirements: []
234
- rubygems_version: 4.0.6
234
+ rubygems_version: 4.0.3
235
235
  specification_version: 4
236
236
  summary: A fast JSON parser and serializer.
237
237
  test_files: []