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 +4 -4
- data/CHANGELOG.md +14 -0
- data/ext/oj/compat.c +3 -3
- data/ext/oj/custom.c +2 -2
- data/ext/oj/dump_compat.c +1 -0
- data/ext/oj/dump_object.c +20 -10
- data/ext/oj/fast.c +64 -43
- data/ext/oj/intern.c +1 -1
- data/ext/oj/object.c +11 -5
- data/ext/oj/oj.c +178 -6
- data/ext/oj/oj.h +4 -0
- data/ext/oj/parse.c +11 -4
- data/ext/oj/parse.h +0 -1
- data/ext/oj/parser.c +79 -13
- data/ext/oj/rails.c +16 -0
- data/ext/oj/reader.c +16 -4
- data/ext/oj/rxclass.c +17 -1
- data/ext/oj/rxclass.h +2 -1
- data/ext/oj/safe.c +1 -0
- data/ext/oj/saj.c +40 -10
- data/ext/oj/sparse.c +9 -0
- data/ext/oj/stream_writer.c +16 -1
- data/ext/oj/string_writer.c +21 -7
- data/ext/oj/usual.c +18 -5
- data/lib/oj/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8bbb833f16ca15e4591772c707b059e1db37eee8aaf02240cd2f628bf1876f4c
|
|
4
|
+
data.tar.gz: 8020f6e49607b38fb120a5a6ea4eea9e5ba3e73f35b3556b7defb0202e93e015
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
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
|
-
|
|
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
|
-
|
|
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 '/':
|
|
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
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
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
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
1261
|
-
Leaf leaf
|
|
1262
|
-
volatile VALUE key
|
|
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
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
|
-
|
|
323
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1310
|
-
|
|
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
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
168
|
-
|
|
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
|
-
|
|
188
|
-
|
|
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,
|
|
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,
|
|
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
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 '/':
|
|
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
|
-
|
|
674
|
-
|
|
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);
|
data/ext/oj/stream_writer.c
CHANGED
|
@@ -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
|
-
|
|
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;
|
data/ext/oj/string_writer.c
CHANGED
|
@@ -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
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
sw->
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
1055
|
-
|
|
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
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.
|
|
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.
|
|
234
|
+
rubygems_version: 4.0.3
|
|
235
235
|
specification_version: 4
|
|
236
236
|
summary: A fast JSON parser and serializer.
|
|
237
237
|
test_files: []
|