oj 3.17.3 → 3.17.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/ext/oj/dump_compat.c +1 -0
- data/ext/oj/dump_object.c +20 -10
- data/ext/oj/fast.c +47 -39
- data/ext/oj/object.c +11 -5
- data/ext/oj/oj.c +172 -5
- data/ext/oj/oj.h +4 -0
- data/ext/oj/parse.c +9 -2
- data/ext/oj/parse.h +0 -1
- data/ext/oj/parser.c +64 -11
- data/ext/oj/rails.c +16 -0
- data/ext/oj/reader.c +16 -4
- data/ext/oj/rxclass.c +16 -0
- data/ext/oj/rxclass.h +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/lib/oj/version.rb +1 -1
- metadata +1 -1
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
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
|
@@ -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 {
|
|
@@ -972,8 +980,9 @@ static int move_step(Doc doc, const char *path, int loc) {
|
|
|
972
980
|
} else {
|
|
973
981
|
Leaf leaf;
|
|
974
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.
|
|
975
985
|
if (0 == doc->where || 0 == (leaf = *doc->where)) {
|
|
976
|
-
printf("*** Internal error at %s\n", path);
|
|
977
986
|
return loc;
|
|
978
987
|
}
|
|
979
988
|
if ('.' == *path && '.' == *(path + 1)) {
|
|
@@ -1104,12 +1113,7 @@ static VALUE doc_open(VALUE clas, VALUE str) {
|
|
|
1104
1113
|
|
|
1105
1114
|
memcpy(json, StringValuePtr(str), len);
|
|
1106
1115
|
obj = parse_json(clas, json, given);
|
|
1107
|
-
//
|
|
1108
|
-
/*
|
|
1109
|
-
if (given) {
|
|
1110
|
-
OJ_R_FREE(json);
|
|
1111
|
-
}
|
|
1112
|
-
*/
|
|
1116
|
+
// json is owned by the doc and freed by doc_free(); do not free it here.
|
|
1113
1117
|
return obj;
|
|
1114
1118
|
}
|
|
1115
1119
|
|
|
@@ -1141,7 +1145,9 @@ static VALUE doc_open_file(VALUE clas, VALUE filename) {
|
|
|
1141
1145
|
int given = rb_block_given_p();
|
|
1142
1146
|
|
|
1143
1147
|
path = StringValuePtr(filename);
|
|
1144
|
-
|
|
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"))) {
|
|
1145
1151
|
rb_raise(rb_eIOError, "%s", strerror(errno));
|
|
1146
1152
|
}
|
|
1147
1153
|
fseek(f, 0, SEEK_END);
|
|
@@ -1159,12 +1165,7 @@ static VALUE doc_open_file(VALUE clas, VALUE filename) {
|
|
|
1159
1165
|
fclose(f);
|
|
1160
1166
|
json[len] = '\0';
|
|
1161
1167
|
obj = parse_json(clas, json, given);
|
|
1162
|
-
//
|
|
1163
|
-
/*
|
|
1164
|
-
if (given) {
|
|
1165
|
-
OJ_R_FREE(json);
|
|
1166
|
-
}
|
|
1167
|
-
*/
|
|
1168
|
+
// json is owned by the doc and freed by doc_free(); do not free it here.
|
|
1168
1169
|
return obj;
|
|
1169
1170
|
}
|
|
1170
1171
|
|
|
@@ -1263,10 +1264,14 @@ static VALUE doc_path(VALUE self) {
|
|
|
1263
1264
|
* #=> nil
|
|
1264
1265
|
*/
|
|
1265
1266
|
static VALUE doc_local_key(VALUE self) {
|
|
1266
|
-
Doc doc
|
|
1267
|
-
Leaf leaf
|
|
1268
|
-
volatile VALUE key
|
|
1267
|
+
Doc doc = self_doc(self);
|
|
1268
|
+
Leaf leaf;
|
|
1269
|
+
volatile VALUE key = Qnil;
|
|
1269
1270
|
|
|
1271
|
+
if (NULL == doc->where || NULL == *doc->where) {
|
|
1272
|
+
return Qnil;
|
|
1273
|
+
}
|
|
1274
|
+
leaf = *doc->where;
|
|
1270
1275
|
if (T_HASH == leaf->parent_type) {
|
|
1271
1276
|
key = rb_utf8_str_new_cstr(leaf->key);
|
|
1272
1277
|
} else if (T_ARRAY == leaf->parent_type) {
|
|
@@ -1421,6 +1426,9 @@ static VALUE doc_each_leaf(int argc, VALUE *argv, VALUE self) {
|
|
|
1421
1426
|
return Qnil;
|
|
1422
1427
|
}
|
|
1423
1428
|
}
|
|
1429
|
+
if (NULL == doc->where || NULL == *doc->where) {
|
|
1430
|
+
return Qnil;
|
|
1431
|
+
}
|
|
1424
1432
|
each_leaf(doc, self);
|
|
1425
1433
|
if (0 < wlen) {
|
|
1426
1434
|
memcpy(doc->where_path, save_path, sizeof(Leaf) * (wlen + 1));
|
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
|
@@ -606,9 +606,52 @@ static VALUE get_def_opts(VALUE self) {
|
|
|
606
606
|
* - *:trace* [_Boolean_] turn trace on or off.
|
|
607
607
|
* - *:safe* [_Boolean_] turn safe mimic on or off.
|
|
608
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
|
+
|
|
609
643
|
static VALUE set_def_opts(VALUE self, VALUE opts) {
|
|
610
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
|
+
}
|
|
611
653
|
oj_parse_options(opts, &oj_default_options);
|
|
654
|
+
keep_default_options();
|
|
612
655
|
|
|
613
656
|
return Qnil;
|
|
614
657
|
}
|
|
@@ -921,6 +964,9 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
921
964
|
|
|
922
965
|
len = RSTRING_LEN(v);
|
|
923
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
|
+
}
|
|
924
970
|
copts->create_id = OJ_R_ALLOC_N(char, len + 1);
|
|
925
971
|
strcpy((char *)copts->create_id, str);
|
|
926
972
|
copts->create_id_len = len;
|
|
@@ -1052,7 +1098,11 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
1052
1098
|
copts->array_class = v;
|
|
1053
1099
|
}
|
|
1054
1100
|
} else if (ignore_sym == k) {
|
|
1055
|
-
|
|
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
|
+
}
|
|
1056
1106
|
copts->ignore = NULL;
|
|
1057
1107
|
if (Qnil != v) {
|
|
1058
1108
|
size_t cnt;
|
|
@@ -1125,15 +1175,15 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
1125
1175
|
strncpy(copts->float_fmt, RSTRING_PTR(v), (size_t)RSTRING_LEN(v));
|
|
1126
1176
|
copts->float_fmt[RSTRING_LEN(v)] = '\0';
|
|
1127
1177
|
} else if (only_sym == k) {
|
|
1128
|
-
|
|
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) {
|
|
1129
1181
|
OJ_R_FREE((void *)copts->dump_opts.only);
|
|
1130
|
-
copts->dump_opts.only = NULL;
|
|
1131
1182
|
}
|
|
1132
1183
|
copts->dump_opts.only = make_only_value(v);
|
|
1133
1184
|
} else if (except_sym == k) {
|
|
1134
|
-
if (NULL != copts->dump_opts.except) {
|
|
1185
|
+
if (&oj_default_options == copts && NULL != copts->dump_opts.except) {
|
|
1135
1186
|
OJ_R_FREE((void *)copts->dump_opts.except);
|
|
1136
|
-
copts->dump_opts.except = NULL;
|
|
1137
1187
|
}
|
|
1138
1188
|
copts->dump_opts.except = make_only_value(v);
|
|
1139
1189
|
}
|
|
@@ -1153,6 +1203,120 @@ void oj_parse_options(VALUE ropts, Options copts) {
|
|
|
1153
1203
|
return;
|
|
1154
1204
|
}
|
|
1155
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
|
+
|
|
1156
1320
|
static int match_string_cb(VALUE key, VALUE value, VALUE rx) {
|
|
1157
1321
|
RxClass rc = (RxClass)rx;
|
|
1158
1322
|
|
|
@@ -1444,6 +1608,7 @@ static VALUE dump_ensure(VALUE a) {
|
|
|
1444
1608
|
volatile struct dump_arg *arg = (void *)a;
|
|
1445
1609
|
|
|
1446
1610
|
oj_out_free(arg->out);
|
|
1611
|
+
oj_free_call_options(arg->copts);
|
|
1447
1612
|
|
|
1448
1613
|
return Qnil;
|
|
1449
1614
|
}
|
|
@@ -1559,6 +1724,7 @@ static VALUE to_file(int argc, VALUE *argv, VALUE self) {
|
|
|
1559
1724
|
oj_parse_options(argv[2], &copts);
|
|
1560
1725
|
}
|
|
1561
1726
|
oj_write_obj_to_file(argv[1], StringValuePtr(*argv), &copts);
|
|
1727
|
+
oj_free_call_options(&copts);
|
|
1562
1728
|
|
|
1563
1729
|
return Qnil;
|
|
1564
1730
|
}
|
|
@@ -1580,6 +1746,7 @@ static VALUE to_stream(int argc, VALUE *argv, VALUE self) {
|
|
|
1580
1746
|
oj_parse_options(argv[2], &copts);
|
|
1581
1747
|
}
|
|
1582
1748
|
oj_write_obj_to_stream(argv[1], *argv, &copts);
|
|
1749
|
+
oj_free_call_options(&copts);
|
|
1583
1750
|
|
|
1584
1751
|
return Qnil;
|
|
1585
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
|
@@ -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;
|
|
@@ -905,8 +959,7 @@ static void parse(ojParser p, const byte *json, bool more) {
|
|
|
905
959
|
break;
|
|
906
960
|
case STR_OK:
|
|
907
961
|
start = b;
|
|
908
|
-
|
|
909
|
-
}
|
|
962
|
+
b = oj_scan_str_simd(b, end);
|
|
910
963
|
if (':' == p->next_map[256]) {
|
|
911
964
|
buf_append_string(&p->key, (const char *)start, b - start);
|
|
912
965
|
} else {
|
|
@@ -1419,7 +1472,7 @@ static VALUE parser_parse(VALUE self, VALUE json) {
|
|
|
1419
1472
|
|
|
1420
1473
|
parser_reset(p);
|
|
1421
1474
|
p->start(p);
|
|
1422
|
-
parse(p, ptr, false);
|
|
1475
|
+
parse(p, ptr, (size_t)RSTRING_LEN(json), false);
|
|
1423
1476
|
validate_document_end(p);
|
|
1424
1477
|
|
|
1425
1478
|
return p->result(p);
|
|
@@ -1440,7 +1493,7 @@ static VALUE load(VALUE self) {
|
|
|
1440
1493
|
while (true) {
|
|
1441
1494
|
rb_funcall(p->reader, oj_readpartial_id, 2, INT2NUM(16385), rbuf);
|
|
1442
1495
|
if (0 < RSTRING_LEN(rbuf)) {
|
|
1443
|
-
parse(p, (byte *)StringValuePtr(rbuf), true);
|
|
1496
|
+
parse(p, (byte *)StringValuePtr(rbuf), (size_t)RSTRING_LEN(rbuf), true);
|
|
1444
1497
|
}
|
|
1445
1498
|
if (Qtrue == rb_funcall(p->reader, oj_eofq_id, 0)) {
|
|
1446
1499
|
if (0 < p->depth) {
|
|
@@ -1510,7 +1563,7 @@ static VALUE parser_file(VALUE self, VALUE filename) {
|
|
|
1510
1563
|
while (true) {
|
|
1511
1564
|
if (0 < (rsize = read(fd, buf, size))) {
|
|
1512
1565
|
buf[rsize] = '\0';
|
|
1513
|
-
parse(p, buf, true);
|
|
1566
|
+
parse(p, buf, rsize, true);
|
|
1514
1567
|
}
|
|
1515
1568
|
if (rsize <= 0) {
|
|
1516
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
|
@@ -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
|
@@ -22,5 +22,6 @@ extern int oj_rxclass_append(RxClass rc, const char *expr, VALUE clas);
|
|
|
22
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/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/lib/oj/version.rb
CHANGED