oj 3.17.1 → 3.17.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +87 -0
- data/ext/oj/cache.c +17 -1
- data/ext/oj/cache.h +5 -0
- data/ext/oj/circarray.c +6 -2
- data/ext/oj/compat.c +5 -5
- data/ext/oj/custom.c +7 -5
- data/ext/oj/dump.c +27 -12
- data/ext/oj/dump.h +10 -0
- data/ext/oj/dump_compat.c +11 -4
- data/ext/oj/dump_object.c +20 -10
- data/ext/oj/extconf.rb +1 -0
- data/ext/oj/fast.c +104 -48
- data/ext/oj/intern.c +7 -2
- data/ext/oj/object.c +26 -15
- data/ext/oj/odd.c +6 -2
- data/ext/oj/oj.c +277 -26
- data/ext/oj/oj.h +6 -1
- data/ext/oj/parse.c +19 -5
- data/ext/oj/parse.h +0 -1
- data/ext/oj/parser.c +135 -37
- data/ext/oj/rails.c +180 -42
- data/ext/oj/reader.c +18 -6
- data/ext/oj/resolve.c +11 -2
- data/ext/oj/rxclass.c +17 -1
- data/ext/oj/rxclass.h +2 -1
- data/ext/oj/safe.c +63 -48
- data/ext/oj/safe.h +14 -2
- data/ext/oj/saj.c +46 -10
- data/ext/oj/sparse.c +16 -6
- data/ext/oj/stream_writer.c +16 -1
- data/ext/oj/string_writer.c +23 -7
- data/ext/oj/usual.c +23 -6
- data/ext/oj/util.c +27 -0
- data/ext/oj/util.h +2 -1
- data/ext/oj/val_stack.h +24 -5
- data/ext/oj/wab.c +14 -26
- data/lib/oj/version.rb +1 -1
- data/pages/Compatibility.md +1 -1
- data/pages/Custom.md +2 -3
- data/pages/InstallOptions.md +8 -6
- data/pages/Modes.md +1 -1
- data/pages/Options.md +5 -6
- data/pages/Parser.md +2 -2
- data/pages/Rails.md +6 -0
- data/pages/Security.md +21 -7
- metadata +2 -2
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;
|
|
@@ -297,7 +299,8 @@ static VALUE only_array_from_string(const char *str) {
|
|
|
297
299
|
* floats, 0 indicates use Ruby
|
|
298
300
|
* - *:float_format* [_String_] the C printf format string for printing floats.
|
|
299
301
|
* Default follows the float_precision and will be changed if float_precision is
|
|
300
|
-
* changed. The string can be no more than 6 bytes
|
|
302
|
+
* changed. The string can be no more than 6 bytes and can hold at most one
|
|
303
|
+
* directive, which must be one of aAeEfgG and take no argument of its own.
|
|
301
304
|
* - *:use_to_json* [_Boolean_|_nil_] call to_json() methods on dump, default is false
|
|
302
305
|
* - *:use_as_json* [_Boolean_|_nil_] call as_json() methods on dump, default is false
|
|
303
306
|
* - *:use_raw_json* [_Boolean_|_nil_] call raw_json() methods on dump, default is false
|
|
@@ -572,7 +575,8 @@ static VALUE get_def_opts(VALUE self) {
|
|
|
572
575
|
* when dumping the seconds portion of time.
|
|
573
576
|
* - *:float_format* [_String_] the C printf format string for printing floats.
|
|
574
577
|
* Default follows the float_precision and will be changed if float_precision
|
|
575
|
-
* is changed. The string can be no more than 6 bytes
|
|
578
|
+
* is changed. The string can be no more than 6 bytes and can hold at most one
|
|
579
|
+
* directive, which must be one of aAeEfgG and take no argument of its own.
|
|
576
580
|
* - *:float_precision* [_Fixnum_|_nil_] number of digits of precision when dumping floats, 0 indicates use Ruby.
|
|
577
581
|
* - *:use_to_json* [_Boolean_|_nil_] call to_json() methods on dump, default is false.
|
|
578
582
|
* - *:use_as_json* [_Boolean_|_nil_] call as_json() methods on dump, default is false.
|
|
@@ -604,9 +608,52 @@ static VALUE get_def_opts(VALUE self) {
|
|
|
604
608
|
* - *:trace* [_Boolean_] turn trace on or off.
|
|
605
609
|
* - *:safe* [_Boolean_] turn safe mimic on or off.
|
|
606
610
|
*/
|
|
611
|
+
static ID oj_default_options_keeper_id = 0;
|
|
612
|
+
|
|
613
|
+
// The default options are a plain C global so the Ruby objects in them, the
|
|
614
|
+
// classes of the hash_class, array_class, and ignore options and the regexps
|
|
615
|
+
// and the classes of the match_string option, can not be reached by the GC and
|
|
616
|
+
// are collected while the defaults still refer to them. Holding them in an
|
|
617
|
+
// array the GC does know about keeps them alive. The array is rebuilt whenever
|
|
618
|
+
// the defaults change and replaces the previous one so that only the objects
|
|
619
|
+
// the defaults are currently using are held.
|
|
620
|
+
static void keep_default_options(void) {
|
|
621
|
+
VALUE keep = rb_ary_new();
|
|
622
|
+
|
|
623
|
+
if (0 == oj_default_options_keeper_id) {
|
|
624
|
+
oj_default_options_keeper_id = rb_intern("@default_options_keeper");
|
|
625
|
+
}
|
|
626
|
+
if (Qnil != oj_default_options.hash_class) {
|
|
627
|
+
rb_ary_push(keep, oj_default_options.hash_class);
|
|
628
|
+
}
|
|
629
|
+
if (Qnil != oj_default_options.array_class) {
|
|
630
|
+
rb_ary_push(keep, oj_default_options.array_class);
|
|
631
|
+
}
|
|
632
|
+
if (NULL != oj_default_options.ignore) {
|
|
633
|
+
VALUE *vp;
|
|
634
|
+
|
|
635
|
+
for (vp = oj_default_options.ignore; Qnil != *vp; vp++) {
|
|
636
|
+
rb_ary_push(keep, *vp);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
oj_rxclass_keep(&oj_default_options.str_rx, keep);
|
|
640
|
+
// An instance variable of the Oj module is a reference the GC can follow on
|
|
641
|
+
// every engine, unlike a C global.
|
|
642
|
+
rb_ivar_set(Oj, oj_default_options_keeper_id, keep);
|
|
643
|
+
}
|
|
644
|
+
|
|
607
645
|
static VALUE set_def_opts(VALUE self, VALUE opts) {
|
|
608
646
|
Check_Type(opts, T_HASH);
|
|
647
|
+
// A :match_string option replaces the regexps instead of adding to them so
|
|
648
|
+
// the ones the defaults are holding have to be freed here, the only place
|
|
649
|
+
// that owns them. A per call options struct only aliases them.
|
|
650
|
+
if (Qnil != rb_hash_lookup(opts, match_string_sym)) {
|
|
651
|
+
oj_rxclass_cleanup(&oj_default_options.str_rx);
|
|
652
|
+
oj_default_options.str_rx.head = NULL;
|
|
653
|
+
oj_default_options.str_rx.tail = NULL;
|
|
654
|
+
}
|
|
609
655
|
oj_parse_options(opts, &oj_default_options);
|
|
656
|
+
keep_default_options();
|
|
610
657
|
|
|
611
658
|
return Qnil;
|
|
612
659
|
}
|
|
@@ -660,6 +707,46 @@ bool set_yesno_options(VALUE key, VALUE value, Options copts) {
|
|
|
660
707
|
return false;
|
|
661
708
|
}
|
|
662
709
|
|
|
710
|
+
inline static bool in_set(const char *set, char c) {
|
|
711
|
+
return '\0' != c && NULL != strchr(set, c);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// The format is used with one double and nothing else, so a directive that
|
|
715
|
+
// asks for anything more takes it from whatever is left in the argument
|
|
716
|
+
// registers. %s reads it as a pointer and %n writes through it.
|
|
717
|
+
static void validate_float_format(const char *str, size_t len) {
|
|
718
|
+
const char *s = str;
|
|
719
|
+
const char *end = str + len;
|
|
720
|
+
int cnt = 0;
|
|
721
|
+
|
|
722
|
+
for (; s < end; s++) {
|
|
723
|
+
if ('%' != *s) {
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
s++;
|
|
727
|
+
if (s < end && '%' == *s) {
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
if (1 < ++cnt) {
|
|
731
|
+
rb_raise(rb_eArgError, ":float_format must not have more than one directive.");
|
|
732
|
+
}
|
|
733
|
+
for (; s < end && in_set("-+ #0", *s); s++) {
|
|
734
|
+
}
|
|
735
|
+
for (; s < end && '0' <= *s && *s <= '9'; s++) {
|
|
736
|
+
}
|
|
737
|
+
if (s < end && '.' == *s) {
|
|
738
|
+
for (s++; s < end && '0' <= *s && *s <= '9'; s++) {
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
if (s < end && 'l' == *s) {
|
|
742
|
+
s++;
|
|
743
|
+
}
|
|
744
|
+
if (end <= s || !in_set("aAeEfgG", *s)) {
|
|
745
|
+
rb_raise(rb_eArgError, ":float_format directive must be one of aAeEfgG and take no argument of its own.");
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
663
750
|
static const char *make_only_value(VALUE v) {
|
|
664
751
|
switch (rb_type(v)) {
|
|
665
752
|
case RUBY_T_NIL:
|
|
@@ -749,12 +836,14 @@ static const char *make_only_value(VALUE v) {
|
|
|
749
836
|
return NULL;
|
|
750
837
|
}
|
|
751
838
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
839
|
+
// Apply one key and value to copts. Returns true if the key named an option Oj
|
|
840
|
+
// knows and false if it did not. An unrecognized key is still ignored rather
|
|
841
|
+
// than an error; the return only lets a caller tell the two cases apart.
|
|
842
|
+
static bool parse_one_option(VALUE k, VALUE v, Options copts) {
|
|
843
|
+
size_t len;
|
|
755
844
|
|
|
756
845
|
if (set_yesno_options(k, v, copts)) {
|
|
757
|
-
return
|
|
846
|
+
return true;
|
|
758
847
|
}
|
|
759
848
|
if (oj_indent_sym == k) {
|
|
760
849
|
switch (rb_type(v)) {
|
|
@@ -766,7 +855,10 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
766
855
|
case T_FIXNUM:
|
|
767
856
|
copts->dump_opts.indent_size = 0;
|
|
768
857
|
*copts->dump_opts.indent_str = '\0';
|
|
769
|
-
|
|
858
|
+
if (MAX_INDENT < FIX2INT(v)) {
|
|
859
|
+
rb_raise(rb_eArgError, "indent is limited to %d characters.", MAX_INDENT);
|
|
860
|
+
}
|
|
861
|
+
copts->indent = FIX2INT(v);
|
|
770
862
|
break;
|
|
771
863
|
case T_STRING:
|
|
772
864
|
if (sizeof(copts->dump_opts.indent_str) <= (len = RSTRING_LEN(v))) {
|
|
@@ -877,7 +969,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
877
969
|
}
|
|
878
970
|
} else if (bigdecimal_load_sym == k) {
|
|
879
971
|
if (Qnil == v) {
|
|
880
|
-
return
|
|
972
|
+
return true;
|
|
881
973
|
}
|
|
882
974
|
|
|
883
975
|
if (bigdecimal_sym == v || Qtrue == v) {
|
|
@@ -893,7 +985,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
893
985
|
}
|
|
894
986
|
} else if (compat_bigdecimal_sym == k) {
|
|
895
987
|
if (Qnil == v) {
|
|
896
|
-
return
|
|
988
|
+
return true;
|
|
897
989
|
}
|
|
898
990
|
copts->compat_bigdec = (Qtrue == v);
|
|
899
991
|
} else if (oj_decimal_class_sym == k) {
|
|
@@ -905,9 +997,13 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
905
997
|
rb_raise(rb_eArgError, ":decimal_class must be BigDecimal or Float.");
|
|
906
998
|
}
|
|
907
999
|
} else if (create_id_sym == k) {
|
|
1000
|
+
// A per call copy of the options aliases the buffer the default
|
|
1001
|
+
// options own, so only the defaults may free it.
|
|
1002
|
+
bool owned = (&oj_default_options == copts && NULL != copts->create_id && oj_json_class != copts->create_id);
|
|
1003
|
+
|
|
908
1004
|
if (Qnil == v) {
|
|
909
|
-
if (
|
|
910
|
-
OJ_R_FREE((char *)
|
|
1005
|
+
if (owned) {
|
|
1006
|
+
OJ_R_FREE((char *)copts->create_id);
|
|
911
1007
|
}
|
|
912
1008
|
copts->create_id = NULL;
|
|
913
1009
|
copts->create_id_len = 0;
|
|
@@ -915,7 +1011,10 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
915
1011
|
const char *str = StringValuePtr(v);
|
|
916
1012
|
|
|
917
1013
|
len = RSTRING_LEN(v);
|
|
918
|
-
if (len != copts->create_id_len || 0 != strcmp(copts->create_id, str)) {
|
|
1014
|
+
if (NULL == copts->create_id || len != copts->create_id_len || 0 != strcmp(copts->create_id, str)) {
|
|
1015
|
+
if (owned) {
|
|
1016
|
+
OJ_R_FREE((char *)copts->create_id);
|
|
1017
|
+
}
|
|
919
1018
|
copts->create_id = OJ_R_ALLOC_N(char, len + 1);
|
|
920
1019
|
strcpy((char *)copts->create_id, str);
|
|
921
1020
|
copts->create_id_len = len;
|
|
@@ -981,7 +1080,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
981
1080
|
}
|
|
982
1081
|
} else if (nan_sym == k) {
|
|
983
1082
|
if (Qnil == v) {
|
|
984
|
-
return
|
|
1083
|
+
return true;
|
|
985
1084
|
}
|
|
986
1085
|
if (null_sym == v) {
|
|
987
1086
|
copts->dump_opts.nan_dump = NullNan;
|
|
@@ -998,7 +1097,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
998
1097
|
}
|
|
999
1098
|
} else if (omit_nil_sym == k) {
|
|
1000
1099
|
if (Qnil == v) {
|
|
1001
|
-
return
|
|
1100
|
+
return true;
|
|
1002
1101
|
}
|
|
1003
1102
|
if (Qtrue == v) {
|
|
1004
1103
|
copts->dump_opts.omit_nil = true;
|
|
@@ -1009,7 +1108,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
1009
1108
|
}
|
|
1010
1109
|
} else if (omit_null_byte_sym == k) {
|
|
1011
1110
|
if (Qnil == v) {
|
|
1012
|
-
return
|
|
1111
|
+
return true;
|
|
1013
1112
|
}
|
|
1014
1113
|
if (Qtrue == v) {
|
|
1015
1114
|
copts->dump_opts.omit_null_byte = true;
|
|
@@ -1047,7 +1146,11 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
1047
1146
|
copts->array_class = v;
|
|
1048
1147
|
}
|
|
1049
1148
|
} else if (ignore_sym == k) {
|
|
1050
|
-
|
|
1149
|
+
// Only free when replacing the defaults; a per-call copy's ignore still
|
|
1150
|
+
// aliases the default-owned buffer, which oj_free_call_options() frees.
|
|
1151
|
+
if (&oj_default_options == copts) {
|
|
1152
|
+
OJ_R_FREE(copts->ignore);
|
|
1153
|
+
}
|
|
1051
1154
|
copts->ignore = NULL;
|
|
1052
1155
|
if (Qnil != v) {
|
|
1053
1156
|
size_t cnt;
|
|
@@ -1066,7 +1169,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
1066
1169
|
}
|
|
1067
1170
|
} else if (integer_range_sym == k) {
|
|
1068
1171
|
if (Qnil == v) {
|
|
1069
|
-
return
|
|
1172
|
+
return true;
|
|
1070
1173
|
}
|
|
1071
1174
|
if (rb_obj_class(v) == rb_cRange) {
|
|
1072
1175
|
VALUE min = rb_funcall(v, oj_begin_id, 0);
|
|
@@ -1097,7 +1200,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
1097
1200
|
}
|
|
1098
1201
|
} else if (symbol_keys_sym == k || oj_symbolize_names_sym == k) {
|
|
1099
1202
|
if (Qnil == v) {
|
|
1100
|
-
return
|
|
1203
|
+
return true;
|
|
1101
1204
|
}
|
|
1102
1205
|
copts->sym_key = (Qtrue == v) ? Yes : No;
|
|
1103
1206
|
|
|
@@ -1117,35 +1220,180 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
1117
1220
|
if (6 < RSTRING_LEN(v)) {
|
|
1118
1221
|
rb_raise(rb_eArgError, ":float_format must be 6 bytes or less.");
|
|
1119
1222
|
}
|
|
1223
|
+
validate_float_format(RSTRING_PTR(v), (size_t)RSTRING_LEN(v));
|
|
1120
1224
|
strncpy(copts->float_fmt, RSTRING_PTR(v), (size_t)RSTRING_LEN(v));
|
|
1121
1225
|
copts->float_fmt[RSTRING_LEN(v)] = '\0';
|
|
1122
1226
|
} else if (only_sym == k) {
|
|
1123
|
-
|
|
1227
|
+
// Only free when replacing the defaults; a per-call copy's pointer still
|
|
1228
|
+
// aliases the default-owned buffer, which oj_free_call_options() frees.
|
|
1229
|
+
if (&oj_default_options == copts && NULL != copts->dump_opts.only) {
|
|
1124
1230
|
OJ_R_FREE((void *)copts->dump_opts.only);
|
|
1125
|
-
copts->dump_opts.only = NULL;
|
|
1126
1231
|
}
|
|
1127
1232
|
copts->dump_opts.only = make_only_value(v);
|
|
1128
1233
|
} else if (except_sym == k) {
|
|
1129
|
-
if (NULL != copts->dump_opts.except) {
|
|
1234
|
+
if (&oj_default_options == copts && NULL != copts->dump_opts.except) {
|
|
1130
1235
|
OJ_R_FREE((void *)copts->dump_opts.except);
|
|
1131
|
-
copts->dump_opts.except = NULL;
|
|
1132
1236
|
}
|
|
1133
1237
|
copts->dump_opts.except = make_only_value(v);
|
|
1238
|
+
} else {
|
|
1239
|
+
return false;
|
|
1240
|
+
}
|
|
1241
|
+
return true;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
// Collects the return of parse_one_option() over every key in a hash.
|
|
1245
|
+
struct _optsParse {
|
|
1246
|
+
Options copts;
|
|
1247
|
+
bool consumed;
|
|
1248
|
+
};
|
|
1249
|
+
|
|
1250
|
+
static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
1251
|
+
struct _optsParse *op = (struct _optsParse *)opts;
|
|
1252
|
+
|
|
1253
|
+
if (parse_one_option(k, v, op->copts)) {
|
|
1254
|
+
op->consumed = true;
|
|
1134
1255
|
}
|
|
1135
1256
|
return ST_CONTINUE;
|
|
1136
1257
|
}
|
|
1137
1258
|
|
|
1138
|
-
|
|
1259
|
+
// Apply the options in ropts to copts and report whether any key was
|
|
1260
|
+
// recognized. A hash of nothing but unknown keys leaves copts untouched and
|
|
1261
|
+
// returns false.
|
|
1262
|
+
bool oj_parse_options_consumed(VALUE ropts, Options copts) {
|
|
1263
|
+
struct _optsParse op = {copts, false};
|
|
1264
|
+
|
|
1139
1265
|
if (T_HASH != rb_type(ropts)) {
|
|
1140
|
-
return;
|
|
1266
|
+
return false;
|
|
1267
|
+
}
|
|
1268
|
+
rb_hash_foreach(ropts, parse_options_cb, (VALUE)&op);
|
|
1269
|
+
if (Qnil != rb_hash_lookup(ropts, match_string_sym)) {
|
|
1270
|
+
op.consumed = true;
|
|
1141
1271
|
}
|
|
1142
|
-
rb_hash_foreach(ropts, parse_options_cb, (VALUE)copts);
|
|
1143
1272
|
oj_parse_opt_match_string(&copts->str_rx, ropts);
|
|
1144
1273
|
|
|
1145
1274
|
copts->dump_opts.use = (0 < copts->dump_opts.indent_size || 0 < copts->dump_opts.after_size ||
|
|
1146
1275
|
0 < copts->dump_opts.before_size || 0 < copts->dump_opts.hash_size ||
|
|
1147
1276
|
0 < copts->dump_opts.array_size);
|
|
1148
|
-
|
|
1277
|
+
|
|
1278
|
+
return op.consumed;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
void oj_parse_options(VALUE ropts, Options copts) {
|
|
1282
|
+
oj_parse_options_consumed(ropts, copts);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// Free the option buffers that oj_parse_options() allocated for a single call.
|
|
1286
|
+
// A per-call options struct is a copy of oj_default_options, so its create_id,
|
|
1287
|
+
// ignore, only, and except members initially alias the ones owned by the
|
|
1288
|
+
// defaults. Only free a member when it differs from the default, i.e. when it
|
|
1289
|
+
// was allocated for this call; the default-owned buffers must be left alone.
|
|
1290
|
+
void oj_free_call_options(Options copts) {
|
|
1291
|
+
if (NULL != copts->create_id && oj_default_options.create_id != copts->create_id) {
|
|
1292
|
+
OJ_R_FREE((char *)copts->create_id);
|
|
1293
|
+
copts->create_id = NULL;
|
|
1294
|
+
copts->create_id_len = 0;
|
|
1295
|
+
}
|
|
1296
|
+
if (NULL != copts->ignore && oj_default_options.ignore != copts->ignore) {
|
|
1297
|
+
OJ_R_FREE(copts->ignore);
|
|
1298
|
+
copts->ignore = NULL;
|
|
1299
|
+
}
|
|
1300
|
+
if (NULL != copts->dump_opts.only && oj_default_options.dump_opts.only != copts->dump_opts.only) {
|
|
1301
|
+
OJ_R_FREE((void *)copts->dump_opts.only);
|
|
1302
|
+
copts->dump_opts.only = NULL;
|
|
1303
|
+
}
|
|
1304
|
+
if (NULL != copts->dump_opts.except && oj_default_options.dump_opts.except != copts->dump_opts.except) {
|
|
1305
|
+
OJ_R_FREE((void *)copts->dump_opts.except);
|
|
1306
|
+
copts->dump_opts.except = NULL;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
static char *dup_option_str(const char *str, size_t len) {
|
|
1311
|
+
char *buf = OJ_R_ALLOC_N(char, len + 1);
|
|
1312
|
+
|
|
1313
|
+
memcpy(buf, str, len);
|
|
1314
|
+
buf[len] = '\0';
|
|
1315
|
+
|
|
1316
|
+
return buf;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
// Give an options struct private copies of the option buffers it still shares
|
|
1320
|
+
// with the defaults so that it owns all of them.
|
|
1321
|
+
//
|
|
1322
|
+
// An options struct that outlives the call it was created in can not rely on
|
|
1323
|
+
// the default-owned buffers it aliases: Oj.default_options= is free to replace
|
|
1324
|
+
// them at any time, which would leave the alias dangling. Call this once, in
|
|
1325
|
+
// the call that creates such an object and after oj_parse_options(), then free
|
|
1326
|
+
// the buffers with oj_options_release() when the object is collected.
|
|
1327
|
+
void oj_options_take_ownership(Options copts) {
|
|
1328
|
+
if (NULL != copts->create_id && oj_default_options.create_id == copts->create_id) {
|
|
1329
|
+
copts->create_id = dup_option_str(copts->create_id, copts->create_id_len);
|
|
1330
|
+
}
|
|
1331
|
+
if (NULL != copts->ignore && oj_default_options.ignore == copts->ignore) {
|
|
1332
|
+
VALUE *vp;
|
|
1333
|
+
VALUE *buf;
|
|
1334
|
+
size_t cnt = 0;
|
|
1335
|
+
|
|
1336
|
+
for (vp = copts->ignore; Qnil != *vp; vp++) {
|
|
1337
|
+
cnt++;
|
|
1338
|
+
}
|
|
1339
|
+
buf = OJ_R_ALLOC_N(VALUE, cnt + 1);
|
|
1340
|
+
memcpy(buf, copts->ignore, sizeof(VALUE) * (cnt + 1));
|
|
1341
|
+
copts->ignore = buf;
|
|
1342
|
+
}
|
|
1343
|
+
if (NULL != copts->dump_opts.only && oj_default_options.dump_opts.only == copts->dump_opts.only) {
|
|
1344
|
+
copts->dump_opts.only = dup_option_str(copts->dump_opts.only, strlen(copts->dump_opts.only));
|
|
1345
|
+
}
|
|
1346
|
+
if (NULL != copts->dump_opts.except && oj_default_options.dump_opts.except == copts->dump_opts.except) {
|
|
1347
|
+
copts->dump_opts.except = dup_option_str(copts->dump_opts.except, strlen(copts->dump_opts.except));
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// Free the option buffers of an options struct that owns all of them, i.e. one
|
|
1352
|
+
// that oj_options_take_ownership() was called on. Unlike oj_free_call_options()
|
|
1353
|
+
// this never looks at the defaults, so it is safe to call from a GC free
|
|
1354
|
+
// function, where the defaults may have been replaced since the object was
|
|
1355
|
+
// created.
|
|
1356
|
+
void oj_options_release(Options copts) {
|
|
1357
|
+
if (NULL != copts->create_id) {
|
|
1358
|
+
OJ_R_FREE((char *)copts->create_id);
|
|
1359
|
+
copts->create_id = NULL;
|
|
1360
|
+
copts->create_id_len = 0;
|
|
1361
|
+
}
|
|
1362
|
+
if (NULL != copts->ignore) {
|
|
1363
|
+
OJ_R_FREE(copts->ignore);
|
|
1364
|
+
copts->ignore = NULL;
|
|
1365
|
+
}
|
|
1366
|
+
if (NULL != copts->dump_opts.only) {
|
|
1367
|
+
OJ_R_FREE((void *)copts->dump_opts.only);
|
|
1368
|
+
copts->dump_opts.only = NULL;
|
|
1369
|
+
}
|
|
1370
|
+
if (NULL != copts->dump_opts.except) {
|
|
1371
|
+
OJ_R_FREE((void *)copts->dump_opts.except);
|
|
1372
|
+
copts->dump_opts.except = NULL;
|
|
1373
|
+
}
|
|
1374
|
+
// The str_rx chain is detached from the defaults when the owner is created
|
|
1375
|
+
// so the chain, if there is one, was built for this options struct alone.
|
|
1376
|
+
oj_rxclass_cleanup(&copts->str_rx);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
// Mark the Ruby objects held by an owned options struct. A long lived object
|
|
1380
|
+
// such as a writer or an encoder can be the only reference to the classes in
|
|
1381
|
+
// the options so without this they could be collected while the owner is still
|
|
1382
|
+
// alive leaving the options with dangling VALUEs.
|
|
1383
|
+
void oj_options_mark(Options copts) {
|
|
1384
|
+
if (Qnil != copts->hash_class) {
|
|
1385
|
+
rb_gc_mark(copts->hash_class);
|
|
1386
|
+
}
|
|
1387
|
+
if (Qnil != copts->array_class) {
|
|
1388
|
+
rb_gc_mark(copts->array_class);
|
|
1389
|
+
}
|
|
1390
|
+
if (NULL != copts->ignore) {
|
|
1391
|
+
VALUE *vp;
|
|
1392
|
+
|
|
1393
|
+
for (vp = copts->ignore; Qnil != *vp; vp++) {
|
|
1394
|
+
rb_gc_mark(*vp);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1149
1397
|
}
|
|
1150
1398
|
|
|
1151
1399
|
static int match_string_cb(VALUE key, VALUE value, VALUE rx) {
|
|
@@ -1439,6 +1687,7 @@ static VALUE dump_ensure(VALUE a) {
|
|
|
1439
1687
|
volatile struct dump_arg *arg = (void *)a;
|
|
1440
1688
|
|
|
1441
1689
|
oj_out_free(arg->out);
|
|
1690
|
+
oj_free_call_options(arg->copts);
|
|
1442
1691
|
|
|
1443
1692
|
return Qnil;
|
|
1444
1693
|
}
|
|
@@ -1554,6 +1803,7 @@ static VALUE to_file(int argc, VALUE *argv, VALUE self) {
|
|
|
1554
1803
|
oj_parse_options(argv[2], &copts);
|
|
1555
1804
|
}
|
|
1556
1805
|
oj_write_obj_to_file(argv[1], StringValuePtr(*argv), &copts);
|
|
1806
|
+
oj_free_call_options(&copts);
|
|
1557
1807
|
|
|
1558
1808
|
return Qnil;
|
|
1559
1809
|
}
|
|
@@ -1575,6 +1825,7 @@ static VALUE to_stream(int argc, VALUE *argv, VALUE self) {
|
|
|
1575
1825
|
oj_parse_options(argv[2], &copts);
|
|
1576
1826
|
}
|
|
1577
1827
|
oj_write_obj_to_stream(argv[1], *argv, &copts);
|
|
1828
|
+
oj_free_call_options(&copts);
|
|
1578
1829
|
|
|
1579
1830
|
return Qnil;
|
|
1580
1831
|
}
|
data/ext/oj/oj.h
CHANGED
|
@@ -197,6 +197,7 @@ typedef struct _out {
|
|
|
197
197
|
bool allocated;
|
|
198
198
|
bool omit_nil;
|
|
199
199
|
bool omit_null_byte;
|
|
200
|
+
bool key_filter_off; // rails: suppress the :only/:except key filter for an as_json result
|
|
200
201
|
int argc;
|
|
201
202
|
VALUE *argv;
|
|
202
203
|
ROptTable ropts;
|
|
@@ -256,6 +257,11 @@ extern VALUE oj_custom_parse_cstr(int argc, VALUE *argv, char *json, size_t len)
|
|
|
256
257
|
|
|
257
258
|
extern bool oj_hash_has_key(VALUE hash, VALUE key);
|
|
258
259
|
extern void oj_parse_options(VALUE ropts, Options copts);
|
|
260
|
+
extern bool oj_parse_options_consumed(VALUE ropts, Options copts);
|
|
261
|
+
extern void oj_free_call_options(Options copts);
|
|
262
|
+
extern void oj_options_take_ownership(Options copts);
|
|
263
|
+
extern void oj_options_release(Options copts);
|
|
264
|
+
extern void oj_options_mark(Options copts);
|
|
259
265
|
|
|
260
266
|
extern void oj_dump_obj_to_json(VALUE obj, Options copts, Out out);
|
|
261
267
|
extern void oj_dump_obj_to_json_using_params(VALUE obj, Options copts, Out out, int argc, VALUE *argv);
|
|
@@ -321,7 +327,6 @@ extern VALUE oj_max_nesting_sym;
|
|
|
321
327
|
extern VALUE oj_object_class_sym;
|
|
322
328
|
extern VALUE oj_object_nl_sym;
|
|
323
329
|
extern VALUE oj_quirks_mode_sym;
|
|
324
|
-
extern VALUE oj_skip_null_byte_sym;
|
|
325
330
|
extern VALUE oj_space_before_sym;
|
|
326
331
|
extern VALUE oj_space_sym;
|
|
327
332
|
extern VALUE oj_symbolize_names_sym;
|
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
|
|
|
@@ -414,6 +414,11 @@ static void read_escaped_str(ParseInfo pi, const char *start) {
|
|
|
414
414
|
|
|
415
415
|
if ('\\' == *s) {
|
|
416
416
|
s++;
|
|
417
|
+
if (pi->end <= s) {
|
|
418
|
+
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "quoted string not terminated");
|
|
419
|
+
buf_cleanup(&buf);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
417
422
|
switch (*s) {
|
|
418
423
|
case 'n': buf_append(&buf, '\n'); break;
|
|
419
424
|
case 'r': buf_append(&buf, '\r'); break;
|
|
@@ -669,7 +674,7 @@ static void read_num(ParseInfo pi) {
|
|
|
669
674
|
// A trailing . is not a valid decimal but if encountered allow it
|
|
670
675
|
// except when mimicking the JSON gem or in strict mode.
|
|
671
676
|
if (StrictMode == pi->options.mode || CompatMode == pi->options.mode) {
|
|
672
|
-
|
|
677
|
+
size_t pos = pi->cur - ni.str;
|
|
673
678
|
|
|
674
679
|
if (1 == pos || (2 == pos && ni.neg)) {
|
|
675
680
|
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number");
|
|
@@ -1096,7 +1101,9 @@ void oj_set_error_at(ParseInfo pi, VALUE err_clas, const char *file, int line, c
|
|
|
1096
1101
|
}
|
|
1097
1102
|
va_end(ap);
|
|
1098
1103
|
pi->err.clas = err_clas;
|
|
1099
|
-
|
|
1104
|
+
// The eight bytes below, plus the ')' and the terminator that the two bytes
|
|
1105
|
+
// held back from end are for.
|
|
1106
|
+
if (p + 8 <= end) {
|
|
1100
1107
|
*p++ = ' ';
|
|
1101
1108
|
*p++ = '(';
|
|
1102
1109
|
*p++ = 'a';
|
|
@@ -1306,9 +1313,16 @@ CLEANUP:
|
|
|
1306
1313
|
OJ_R_FREE(json);
|
|
1307
1314
|
}
|
|
1308
1315
|
stack_cleanup(&pi->stack);
|
|
1309
|
-
|
|
1310
|
-
|
|
1316
|
+
// The parsers match against pi->options.str_rx. A :match_string option
|
|
1317
|
+
// builds a chain of its own for this call, which has to be freed. Without
|
|
1318
|
+
// the option the member still aliases the chain owned by the defaults and
|
|
1319
|
+
// must be left alone.
|
|
1320
|
+
if (pi->options.str_rx.head != oj_default_options.str_rx.head) {
|
|
1321
|
+
oj_rxclass_cleanup(&pi->options.str_rx);
|
|
1322
|
+
pi->options.str_rx.head = NULL;
|
|
1323
|
+
pi->options.str_rx.tail = NULL;
|
|
1311
1324
|
}
|
|
1325
|
+
oj_free_call_options(&pi->options);
|
|
1312
1326
|
if (err_has(&pi->err)) {
|
|
1313
1327
|
rb_set_errinfo(Qnil);
|
|
1314
1328
|
if (Qnil != pi->err_class) {
|