oj 3.16.11 → 3.17.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +133 -0
- data/README.md +0 -16
- 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 +12 -5
- data/ext/oj/dump.c +433 -206
- data/ext/oj/dump.h +12 -0
- data/ext/oj/dump_compat.c +17 -5
- data/ext/oj/dump_object.c +25 -10
- data/ext/oj/dump_strict.c +13 -8
- data/ext/oj/extconf.rb +11 -7
- data/ext/oj/fast.c +123 -60
- data/ext/oj/intern.c +7 -2
- data/ext/oj/mimic_json.c +3 -0
- data/ext/oj/object.c +26 -15
- data/ext/oj/odd.c +6 -2
- data/ext/oj/oj.c +511 -22
- data/ext/oj/oj.h +61 -53
- data/ext/oj/parse.c +229 -30
- data/ext/oj/parse.h +0 -1
- data/ext/oj/parser.c +226 -65
- data/ext/oj/rails.c +197 -57
- 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 +244 -0
- data/ext/oj/safe.h +91 -0
- data/ext/oj/saj.c +56 -12
- data/ext/oj/simd.h +210 -1
- data/ext/oj/sparse.c +22 -9
- 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 +15 -27
- 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 +21 -19
data/ext/oj/rails.c
CHANGED
|
@@ -16,6 +16,10 @@ typedef struct _encoder {
|
|
|
16
16
|
struct _rOptTable ropts;
|
|
17
17
|
struct _options opts;
|
|
18
18
|
VALUE arg;
|
|
19
|
+
// Each is false while the matching member is unused and the global it
|
|
20
|
+
// shadows - ropts or oj_default_options - is read instead.
|
|
21
|
+
bool own_ropts;
|
|
22
|
+
bool own_opts;
|
|
19
23
|
} *Encoder;
|
|
20
24
|
|
|
21
25
|
bool oj_rails_hash_opt = false;
|
|
@@ -83,6 +87,32 @@ static ROptTable copy_opts(ROptTable src, ROptTable dest) {
|
|
|
83
87
|
return NULL;
|
|
84
88
|
}
|
|
85
89
|
|
|
90
|
+
// The table an encoder reads from. Until the encoder is asked to optimize or
|
|
91
|
+
// deoptimize a class of its own it has no table and follows the global one.
|
|
92
|
+
// NULL is how oj_rails_get_opt() is told to use the global table, so the NULL
|
|
93
|
+
// has to reach it - handing it &e->ropts with an empty table instead would
|
|
94
|
+
// quietly turn every optimization off for this encoder.
|
|
95
|
+
static ROptTable encoder_ropts(Encoder e) {
|
|
96
|
+
return e->own_ropts ? &e->ropts : NULL;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// The options an encoder encodes with. See encoder_new() for why an encoder
|
|
100
|
+
// built without options follows the defaults instead of holding a copy.
|
|
101
|
+
static Options encoder_opts(Encoder e) {
|
|
102
|
+
return e->own_opts ? &e->opts : &oj_default_options;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// The table an encoder writes to. Copying the global table is deferred to here
|
|
106
|
+
// because it is a 6KB allocation and ActiveSupport builds an encoder for every
|
|
107
|
+
// encode call, while almost none of them ever optimize a class of their own.
|
|
108
|
+
static ROptTable encoder_own_ropts(Encoder e) {
|
|
109
|
+
if (!e->own_ropts) {
|
|
110
|
+
copy_opts(&ropts, &e->ropts);
|
|
111
|
+
e->own_ropts = true;
|
|
112
|
+
}
|
|
113
|
+
return &e->ropts;
|
|
114
|
+
}
|
|
115
|
+
|
|
86
116
|
static int dump_attr_cb(ID key, VALUE value, VALUE ov) {
|
|
87
117
|
Out out = (Out)ov;
|
|
88
118
|
int depth = out->depth;
|
|
@@ -341,11 +371,16 @@ typedef struct _strLen {
|
|
|
341
371
|
} *StrLen;
|
|
342
372
|
|
|
343
373
|
static void dump_actioncontroller_parameters(VALUE obj, int depth, Out out, bool as_ok) {
|
|
374
|
+
int saved_argc = out->argc;
|
|
375
|
+
VALUE *saved_argv = out->argv;
|
|
376
|
+
|
|
344
377
|
if (0 == parameters_id) {
|
|
345
378
|
parameters_id = rb_intern("@parameters");
|
|
346
379
|
}
|
|
347
380
|
out->argc = 0;
|
|
348
381
|
dump_rails_val(rb_ivar_get(obj, parameters_id), depth, out, true);
|
|
382
|
+
out->argc = saved_argc;
|
|
383
|
+
out->argv = saved_argv;
|
|
349
384
|
}
|
|
350
385
|
|
|
351
386
|
static StrLen columns_array(VALUE rcols, int *ccnt) {
|
|
@@ -375,7 +410,7 @@ static void dump_row(VALUE row, StrLen cols, int ccnt, int depth, Out out) {
|
|
|
375
410
|
|
|
376
411
|
assure_size(out, 2);
|
|
377
412
|
*out->cur++ = '{';
|
|
378
|
-
size =
|
|
413
|
+
size = indent_len(out, d2, out->opts->dump_opts.array_size) + 3;
|
|
379
414
|
for (i = 0; i < ccnt; i++, cols++) {
|
|
380
415
|
assure_size(out, size);
|
|
381
416
|
if (out->opts->dump_opts.use) {
|
|
@@ -398,7 +433,7 @@ static void dump_row(VALUE row, StrLen cols, int ccnt, int depth, Out out) {
|
|
|
398
433
|
*out->cur++ = ',';
|
|
399
434
|
}
|
|
400
435
|
}
|
|
401
|
-
size = depth
|
|
436
|
+
size = indent_len(out, depth, out->opts->dump_opts.array_size) + 1;
|
|
402
437
|
assure_size(out, size);
|
|
403
438
|
if (out->opts->dump_opts.use) {
|
|
404
439
|
if (0 < out->opts->dump_opts.array_size) {
|
|
@@ -423,7 +458,9 @@ static ID columns_id = 0;
|
|
|
423
458
|
static void dump_activerecord_result(VALUE obj, int depth, Out out, bool as_ok) {
|
|
424
459
|
volatile VALUE rows;
|
|
425
460
|
StrLen cols;
|
|
426
|
-
int ccnt
|
|
461
|
+
int ccnt = 0;
|
|
462
|
+
int saved_argc = out->argc;
|
|
463
|
+
VALUE *saved_argv = out->argv;
|
|
427
464
|
size_t i;
|
|
428
465
|
size_t rcnt;
|
|
429
466
|
size_t size;
|
|
@@ -439,11 +476,7 @@ static void dump_activerecord_result(VALUE obj, int depth, Out out, bool as_ok)
|
|
|
439
476
|
rcnt = RARRAY_LEN(rows);
|
|
440
477
|
assure_size(out, 2);
|
|
441
478
|
*out->cur++ = '[';
|
|
442
|
-
|
|
443
|
-
size = d2 * out->opts->dump_opts.indent_size + out->opts->dump_opts.array_size + 1;
|
|
444
|
-
} else {
|
|
445
|
-
size = d2 * out->indent + 2;
|
|
446
|
-
}
|
|
479
|
+
size = indent_len(out, d2, out->opts->dump_opts.array_size) + 1;
|
|
447
480
|
assure_size(out, 2);
|
|
448
481
|
for (i = 0; i < rcnt; i++) {
|
|
449
482
|
assure_size(out, size);
|
|
@@ -466,7 +499,7 @@ static void dump_activerecord_result(VALUE obj, int depth, Out out, bool as_ok)
|
|
|
466
499
|
}
|
|
467
500
|
}
|
|
468
501
|
OJ_R_FREE(cols);
|
|
469
|
-
size = depth
|
|
502
|
+
size = indent_len(out, depth, out->opts->dump_opts.array_size) + 1;
|
|
470
503
|
assure_size(out, size);
|
|
471
504
|
if (out->opts->dump_opts.use) {
|
|
472
505
|
if (0 < out->opts->dump_opts.array_size) {
|
|
@@ -483,6 +516,8 @@ static void dump_activerecord_result(VALUE obj, int depth, Out out, bool as_ok)
|
|
|
483
516
|
fill_indent(out, depth);
|
|
484
517
|
}
|
|
485
518
|
*out->cur++ = ']';
|
|
519
|
+
out->argc = saved_argc;
|
|
520
|
+
out->argv = saved_argv;
|
|
486
521
|
}
|
|
487
522
|
|
|
488
523
|
typedef struct _namedFunc {
|
|
@@ -491,8 +526,16 @@ typedef struct _namedFunc {
|
|
|
491
526
|
} *NamedFunc;
|
|
492
527
|
|
|
493
528
|
static void dump_as_string(VALUE obj, int depth, Out out, bool as_ok) {
|
|
529
|
+
int saved_argc = out->argc;
|
|
530
|
+
VALUE *saved_argv = out->argv;
|
|
531
|
+
|
|
494
532
|
if (oj_code_dump(oj_compat_codes, obj, depth, out)) {
|
|
495
|
-
|
|
533
|
+
// oj_code_dump() writes the whole value and returns, so there is no
|
|
534
|
+
// subtree here to keep the options away from. Leave them for the next
|
|
535
|
+
// sibling. Restore instead of leaving them alone in case the encode
|
|
536
|
+
// consumed them.
|
|
537
|
+
out->argc = saved_argc;
|
|
538
|
+
out->argv = saved_argv;
|
|
496
539
|
return;
|
|
497
540
|
}
|
|
498
541
|
oj_dump_obj_to_s(obj, out);
|
|
@@ -500,31 +543,53 @@ static void dump_as_string(VALUE obj, int depth, Out out, bool as_ok) {
|
|
|
500
543
|
|
|
501
544
|
static void dump_as_json(VALUE obj, int depth, Out out, bool as_ok) {
|
|
502
545
|
volatile VALUE ja;
|
|
546
|
+
bool selected;
|
|
547
|
+
int saved_argc = out->argc;
|
|
548
|
+
VALUE *saved_argv = out->argv;
|
|
503
549
|
|
|
504
550
|
TRACE(out->opts->trace, "as_json", obj, depth + 1, TraceRubyIn);
|
|
505
551
|
// Some classes elect to not take an options argument so check the arity
|
|
506
552
|
// of as_json.
|
|
507
553
|
if (0 == rb_obj_method_arity(obj, oj_as_json_id)) {
|
|
508
|
-
ja
|
|
554
|
+
ja = rb_funcall(obj, oj_as_json_id, 0);
|
|
555
|
+
selected = false; // as_json got no options so it did not do any :only/:except selection
|
|
509
556
|
} else {
|
|
510
|
-
|
|
557
|
+
selected = (0 < out->argc); // Rails handed the :only/:except options to as_json
|
|
558
|
+
ja = rb_funcall2(obj, oj_as_json_id, out->argc, out->argv);
|
|
511
559
|
}
|
|
512
560
|
TRACE(out->opts->trace, "as_json", obj, depth + 1, TraceRubyOut);
|
|
513
561
|
|
|
514
562
|
out->argc = 0;
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
563
|
+
// When as_json received the options it has already applied the Rails
|
|
564
|
+
// :only/:except selection at the attribute level (and kept the
|
|
565
|
+
// include_root_in_json wrapper and any :methods keys), so the Oj dump level
|
|
566
|
+
// :only/:except key filter must not be applied to the value it returned - a
|
|
567
|
+
// second pass would drop the wrapper (#1020) and the :methods keys (#1008).
|
|
568
|
+
// Suppress the filter only for this subtree (save and restore) so sibling
|
|
569
|
+
// keys outside the as_json result are still filtered. This is a flag on the
|
|
570
|
+
// dump state, not a change to out->opts: a StringWriter owns its options
|
|
571
|
+
// struct, so clearing only/except there would leak the buffers and kill the
|
|
572
|
+
// filter for every later push_value.
|
|
573
|
+
{
|
|
574
|
+
bool key_filter_off = out->key_filter_off;
|
|
521
575
|
|
|
522
|
-
if (
|
|
523
|
-
|
|
576
|
+
if (selected) {
|
|
577
|
+
out->key_filter_off = true;
|
|
578
|
+
}
|
|
579
|
+
if (ja == obj || !as_ok) {
|
|
580
|
+
// Once as_json is called it should never be called again on the same
|
|
581
|
+
// object with as_ok.
|
|
582
|
+
dump_rails_val(ja, depth, out, false);
|
|
524
583
|
} else {
|
|
525
584
|
dump_rails_val(ja, depth, out, true);
|
|
526
585
|
}
|
|
586
|
+
out->key_filter_off = key_filter_off;
|
|
527
587
|
}
|
|
588
|
+
// The options were consumed for this value only. Restore them so that the
|
|
589
|
+
// next sibling in a container is handed them too, the way ActiveSupport's
|
|
590
|
+
// Array#as_json and Hash#as_json pass them to every element.
|
|
591
|
+
out->argc = saved_argc;
|
|
592
|
+
out->argv = saved_argv;
|
|
528
593
|
}
|
|
529
594
|
|
|
530
595
|
static void dump_regexp(VALUE obj, int depth, Out out, bool as_ok) {
|
|
@@ -551,11 +616,16 @@ static VALUE activerecord_base = Qundef;
|
|
|
551
616
|
static ID attributes_id = 0;
|
|
552
617
|
|
|
553
618
|
static void dump_activerecord(VALUE obj, int depth, Out out, bool as_ok) {
|
|
619
|
+
int saved_argc = out->argc;
|
|
620
|
+
VALUE *saved_argv = out->argv;
|
|
621
|
+
|
|
554
622
|
if (0 == attributes_id) {
|
|
555
623
|
attributes_id = rb_intern("@attributes");
|
|
556
624
|
}
|
|
557
625
|
out->argc = 0;
|
|
558
626
|
dump_rails_val(rb_ivar_get(obj, attributes_id), depth, out, true);
|
|
627
|
+
out->argc = saved_argc;
|
|
628
|
+
out->argv = saved_argv;
|
|
559
629
|
}
|
|
560
630
|
|
|
561
631
|
static ROpt create_opt(ROptTable rot, VALUE clas) {
|
|
@@ -591,6 +661,12 @@ static ROpt create_opt(ROptTable rot, VALUE clas) {
|
|
|
591
661
|
ro->clas = clas;
|
|
592
662
|
ro->on = true;
|
|
593
663
|
ro->dump = dump_obj_attrs;
|
|
664
|
+
if (&ropts == rot) {
|
|
665
|
+
// Nothing marks the global table, so a class that lives only in it has
|
|
666
|
+
// to be a root. Encoders used to hold a copy of the table and mark it,
|
|
667
|
+
// which covered this by accident for as long as one was alive.
|
|
668
|
+
rb_gc_register_mark_object(clas);
|
|
669
|
+
}
|
|
594
670
|
for (nf = dump_map; NULL != nf->name; nf++) {
|
|
595
671
|
if (0 == strcmp(nf->name, classname)) {
|
|
596
672
|
ro->dump = nf->func;
|
|
@@ -623,6 +699,9 @@ static void encoder_free(void *ptr) {
|
|
|
623
699
|
if (NULL != ptr) {
|
|
624
700
|
Encoder e = (Encoder)ptr;
|
|
625
701
|
|
|
702
|
+
if (e->own_opts) {
|
|
703
|
+
oj_options_release(&e->opts);
|
|
704
|
+
}
|
|
626
705
|
if (NULL != e->ropts.table) {
|
|
627
706
|
OJ_R_FREE(e->ropts.table);
|
|
628
707
|
}
|
|
@@ -633,10 +712,25 @@ static void encoder_free(void *ptr) {
|
|
|
633
712
|
static void encoder_mark(void *ptr) {
|
|
634
713
|
if (NULL != ptr) {
|
|
635
714
|
Encoder e = (Encoder)ptr;
|
|
715
|
+
int i;
|
|
636
716
|
|
|
717
|
+
if (e->own_opts) {
|
|
718
|
+
oj_options_mark(&e->opts);
|
|
719
|
+
}
|
|
637
720
|
if (Qnil != e->arg) {
|
|
638
721
|
rb_gc_mark(e->arg);
|
|
639
722
|
}
|
|
723
|
+
// The optimized classes in the encoder table are not reachable from
|
|
724
|
+
// anywhere else once optimize() is called on the encoder itself. An
|
|
725
|
+
// encoder that never optimized anything has no table to walk - the
|
|
726
|
+
// classes in the global table are roots registered by create_opt().
|
|
727
|
+
if (NULL != e->ropts.table) {
|
|
728
|
+
for (i = 0; i < e->ropts.len; i++) {
|
|
729
|
+
if (Qnil != e->ropts.table[i].clas) {
|
|
730
|
+
rb_gc_mark(e->ropts.table[i].clas);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
640
734
|
}
|
|
641
735
|
}
|
|
642
736
|
|
|
@@ -660,14 +754,44 @@ static const rb_data_type_t oj_encoder_type = {
|
|
|
660
754
|
static VALUE encoder_new(int argc, VALUE *argv, VALUE self) {
|
|
661
755
|
Encoder e = OJ_R_ALLOC(struct _encoder);
|
|
662
756
|
|
|
663
|
-
e->
|
|
664
|
-
e->
|
|
665
|
-
|
|
757
|
+
e->ropts.len = 0;
|
|
758
|
+
e->ropts.alen = 0;
|
|
759
|
+
e->ropts.table = NULL;
|
|
760
|
+
e->own_ropts = false;
|
|
666
761
|
|
|
667
762
|
if (1 <= argc && Qnil != *argv) {
|
|
668
|
-
oj_parse_options(*argv, &e->opts);
|
|
669
763
|
e->arg = *argv;
|
|
764
|
+
} else {
|
|
765
|
+
e->arg = rb_hash_new();
|
|
766
|
+
}
|
|
767
|
+
// An encoder given no options of its own reads oj_default_options at encode
|
|
768
|
+
// time rather than copying it here. ActiveSupport keeps such an encoder for
|
|
769
|
+
// the life of the process, so a copy taken now would freeze every later
|
|
770
|
+
// Oj.default_options and ActiveSupport::JSON::Encoding change out of it -
|
|
771
|
+
// including the time_precision that set_encoder itself writes after
|
|
772
|
+
// ActiveSupport has already built and cached that encoder.
|
|
773
|
+
//
|
|
774
|
+
// A hash counts as options only if at least one key names an option Oj
|
|
775
|
+
// knows. Non-empty is not enough: ActiveSupport 8.1 caches a second encoder
|
|
776
|
+
// built with {escape: false}, a key Oj has never had.
|
|
777
|
+
e->own_opts = T_HASH == rb_type(e->arg) && 0 < RHASH_SIZE(e->arg);
|
|
778
|
+
if (e->own_opts) {
|
|
779
|
+
e->opts = oj_default_options;
|
|
780
|
+
// Detach from the match_string regexps owned by the defaults before the
|
|
781
|
+
// options are parsed so that a :match_string option builds a chain of
|
|
782
|
+
// its own that is freed with the encoder.
|
|
783
|
+
e->opts.str_rx.head = NULL;
|
|
784
|
+
e->opts.str_rx.tail = NULL;
|
|
785
|
+
e->own_opts = oj_parse_options_consumed(e->arg, &e->opts);
|
|
786
|
+
}
|
|
787
|
+
if (e->own_opts) {
|
|
788
|
+
oj_options_take_ownership(&e->opts);
|
|
789
|
+
} else {
|
|
790
|
+
// Nothing to free: every option that allocates is reached through a
|
|
791
|
+
// recognized key, and ownership is not taken until after that check.
|
|
792
|
+
memset(&e->opts, 0, sizeof(e->opts));
|
|
670
793
|
}
|
|
794
|
+
|
|
671
795
|
return TypedData_Wrap_Struct(encoder_class, &oj_encoder_type, e);
|
|
672
796
|
}
|
|
673
797
|
|
|
@@ -763,7 +887,7 @@ static VALUE encoder_optimize(int argc, VALUE *argv, VALUE self) {
|
|
|
763
887
|
Encoder e;
|
|
764
888
|
TypedData_Get_Struct(self, struct _encoder, &oj_encoder_type, e);
|
|
765
889
|
|
|
766
|
-
optimize(argc, argv,
|
|
890
|
+
optimize(argc, argv, encoder_own_ropts(e), true);
|
|
767
891
|
|
|
768
892
|
return Qnil;
|
|
769
893
|
}
|
|
@@ -820,7 +944,7 @@ static VALUE encoder_deoptimize(int argc, VALUE *argv, VALUE self) {
|
|
|
820
944
|
Encoder e;
|
|
821
945
|
TypedData_Get_Struct(self, struct _encoder, &oj_encoder_type, e);
|
|
822
946
|
|
|
823
|
-
optimize(argc, argv,
|
|
947
|
+
optimize(argc, argv, encoder_own_ropts(e), false);
|
|
824
948
|
|
|
825
949
|
return Qnil;
|
|
826
950
|
}
|
|
@@ -851,7 +975,7 @@ static VALUE encoder_optimized(VALUE self, VALUE clas) {
|
|
|
851
975
|
ROpt ro;
|
|
852
976
|
|
|
853
977
|
TypedData_Get_Struct(self, struct _encoder, &oj_encoder_type, e);
|
|
854
|
-
ro = oj_rails_get_opt(
|
|
978
|
+
ro = oj_rails_get_opt(encoder_ropts(e), clas);
|
|
855
979
|
|
|
856
980
|
if (NULL == ro) {
|
|
857
981
|
return Qfalse;
|
|
@@ -918,7 +1042,7 @@ static VALUE encode(VALUE obj, ROptTable ropts, Options opts, int argc, VALUE *a
|
|
|
918
1042
|
if (Yes == copts.circular) {
|
|
919
1043
|
oj_cache8_new(&out.circ_cache);
|
|
920
1044
|
}
|
|
921
|
-
|
|
1045
|
+
|
|
922
1046
|
rb_protect(protect_dump, (VALUE)&oo, &line);
|
|
923
1047
|
|
|
924
1048
|
if (0 == line) {
|
|
@@ -960,11 +1084,22 @@ static VALUE encoder_encode(VALUE self, VALUE obj) {
|
|
|
960
1084
|
TypedData_Get_Struct(self, struct _encoder, &oj_encoder_type, e);
|
|
961
1085
|
|
|
962
1086
|
if (Qnil != e->arg) {
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1087
|
+
// as_json is allowed to write into the options hash it is handed, and
|
|
1088
|
+
// the ActiveSupport suite has a test that does exactly that. Handing
|
|
1089
|
+
// out the encoder's own hash lets one such call poison every later
|
|
1090
|
+
// encode by the same encoder, which on ActiveSupport 8.1 is every
|
|
1091
|
+
// option-less to_json in the process because json_encoder= caches one
|
|
1092
|
+
// encoder and keeps it. Rails never exposes a hash it will reuse:
|
|
1093
|
+
// JSONGemEncoder#encode passes options.dup, and only when there are
|
|
1094
|
+
// options at all, so with none as_json writes into its own default.
|
|
1095
|
+
// The copy is not frozen the way Rails 8.0 and later freeze theirs,
|
|
1096
|
+
// because Oj hands as_json the hash even when it is empty and that is
|
|
1097
|
+
// exactly the case Rails leaves writable.
|
|
1098
|
+
VALUE argv[1] = {T_HASH == rb_type(e->arg) ? rb_hash_dup(e->arg) : e->arg};
|
|
1099
|
+
|
|
1100
|
+
return encode(obj, encoder_ropts(e), encoder_opts(e), 1, argv);
|
|
1101
|
+
}
|
|
1102
|
+
return encode(obj, encoder_ropts(e), encoder_opts(e), 0, NULL);
|
|
968
1103
|
}
|
|
969
1104
|
|
|
970
1105
|
/* Document-method: encode
|
|
@@ -1049,8 +1184,6 @@ static VALUE rails_set_encoder(VALUE self) {
|
|
|
1049
1184
|
} else {
|
|
1050
1185
|
rb_raise(rb_eStandardError, "ActiveSupport not loaded.");
|
|
1051
1186
|
}
|
|
1052
|
-
rb_funcall(active, rb_intern("json_encoder="), 1, encoder_class);
|
|
1053
|
-
|
|
1054
1187
|
json = rb_const_get_at(active, rb_intern("JSON"));
|
|
1055
1188
|
encoding = rb_const_get_at(json, rb_intern("Encoding"));
|
|
1056
1189
|
|
|
@@ -1077,6 +1210,12 @@ static VALUE rails_set_encoder(VALUE self) {
|
|
|
1077
1210
|
rb_define_module_function(encoding, "time_precision=", rails_time_precision, 1);
|
|
1078
1211
|
rb_gv_set("$VERBOSE", verbose);
|
|
1079
1212
|
|
|
1213
|
+
// Last, because ActiveSupport builds and caches encoders inside
|
|
1214
|
+
// json_encoder= and they should see the settings above already in the
|
|
1215
|
+
// defaults. escape_html and xml_time are read ahead of it for the same
|
|
1216
|
+
// reason.
|
|
1217
|
+
rb_funcall(active, rb_intern("json_encoder="), 1, encoder_class);
|
|
1218
|
+
|
|
1080
1219
|
return Qnil;
|
|
1081
1220
|
}
|
|
1082
1221
|
|
|
@@ -1217,8 +1356,7 @@ static void dump_array(VALUE a, int depth, Out out, bool as_ok) {
|
|
|
1217
1356
|
return;
|
|
1218
1357
|
}
|
|
1219
1358
|
}
|
|
1220
|
-
|
|
1221
|
-
if (as_ok && 0 < out->argc && rb_respond_to(a, oj_as_json_id)) {
|
|
1359
|
+
if (!oj_rails_array_opt && as_ok && rb_respond_to(a, oj_as_json_id)) {
|
|
1222
1360
|
dump_as_json(a, depth, out, false);
|
|
1223
1361
|
return;
|
|
1224
1362
|
}
|
|
@@ -1229,11 +1367,7 @@ static void dump_array(VALUE a, int depth, Out out, bool as_ok) {
|
|
|
1229
1367
|
if (0 == cnt) {
|
|
1230
1368
|
*out->cur++ = ']';
|
|
1231
1369
|
} else {
|
|
1232
|
-
|
|
1233
|
-
size = d2 * out->opts->dump_opts.indent_size + out->opts->dump_opts.array_size + 1;
|
|
1234
|
-
} else {
|
|
1235
|
-
size = d2 * out->indent + 2;
|
|
1236
|
-
}
|
|
1370
|
+
size = indent_len(out, d2, out->opts->dump_opts.array_size) + 1;
|
|
1237
1371
|
assure_size(out, size * cnt);
|
|
1238
1372
|
cnt--;
|
|
1239
1373
|
for (i = 0; i <= cnt; i++) {
|
|
@@ -1255,7 +1389,7 @@ static void dump_array(VALUE a, int depth, Out out, bool as_ok) {
|
|
|
1255
1389
|
*out->cur++ = ',';
|
|
1256
1390
|
}
|
|
1257
1391
|
}
|
|
1258
|
-
size = depth
|
|
1392
|
+
size = indent_len(out, depth, out->opts->dump_opts.array_size) + 1;
|
|
1259
1393
|
assure_size(out, size);
|
|
1260
1394
|
if (out->opts->dump_opts.use) {
|
|
1261
1395
|
if (0 < out->opts->dump_opts.array_size) {
|
|
@@ -1285,18 +1419,19 @@ static int hash_cb(VALUE key, VALUE value, VALUE ov) {
|
|
|
1285
1419
|
if (out->omit_nil && Qnil == value) {
|
|
1286
1420
|
return ST_CONTINUE;
|
|
1287
1421
|
}
|
|
1288
|
-
if (
|
|
1289
|
-
|
|
1290
|
-
|
|
1422
|
+
if (!out->key_filter_off && (NULL != out->opts->dump_opts.only || NULL != out->opts->dump_opts.except)) {
|
|
1423
|
+
if (oj_key_skip(key, out->opts->dump_opts.only, out->opts->dump_opts.except)) {
|
|
1424
|
+
return ST_CONTINUE;
|
|
1425
|
+
}
|
|
1291
1426
|
}
|
|
1292
1427
|
if (!out->opts->dump_opts.use) {
|
|
1293
1428
|
size = depth * out->indent + 1;
|
|
1294
1429
|
assure_size(out, size);
|
|
1295
1430
|
fill_indent(out, depth);
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1431
|
+
switch (rtype) {
|
|
1432
|
+
case T_STRING: oj_dump_str(key, 0, out, false); break;
|
|
1433
|
+
case T_SYMBOL: oj_dump_sym(key, 0, out, false); break;
|
|
1434
|
+
default: oj_dump_str(oj_safe_string_convert(key), 0, out, false); break;
|
|
1300
1435
|
}
|
|
1301
1436
|
*out->cur++ = ':';
|
|
1302
1437
|
} else {
|
|
@@ -1311,10 +1446,10 @@ static int hash_cb(VALUE key, VALUE value, VALUE ov) {
|
|
|
1311
1446
|
APPEND_CHARS(out->cur, out->opts->dump_opts.indent_str, out->opts->dump_opts.indent_size);
|
|
1312
1447
|
}
|
|
1313
1448
|
}
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1449
|
+
switch (rtype) {
|
|
1450
|
+
case T_STRING: oj_dump_str(key, 0, out, false); break;
|
|
1451
|
+
case T_SYMBOL: oj_dump_sym(key, 0, out, false); break;
|
|
1452
|
+
default: oj_dump_str(oj_safe_string_convert(key), 0, out, false); break;
|
|
1318
1453
|
}
|
|
1319
1454
|
size = out->opts->dump_opts.before_size + out->opts->dump_opts.after_size + 2;
|
|
1320
1455
|
assure_size(out, size);
|
|
@@ -1343,7 +1478,7 @@ static void dump_hash(VALUE obj, int depth, Out out, bool as_ok) {
|
|
|
1343
1478
|
return;
|
|
1344
1479
|
}
|
|
1345
1480
|
}
|
|
1346
|
-
if (
|
|
1481
|
+
if (!oj_rails_hash_opt && as_ok && rb_respond_to(obj, oj_as_json_id)) {
|
|
1347
1482
|
dump_as_json(obj, depth, out, false);
|
|
1348
1483
|
return;
|
|
1349
1484
|
}
|
|
@@ -1382,10 +1517,15 @@ static void dump_hash(VALUE obj, int depth, Out out, bool as_ok) {
|
|
|
1382
1517
|
}
|
|
1383
1518
|
|
|
1384
1519
|
static void dump_obj(VALUE obj, int depth, Out out, bool as_ok) {
|
|
1385
|
-
VALUE
|
|
1520
|
+
VALUE clas;
|
|
1521
|
+
int saved_argc = out->argc;
|
|
1522
|
+
VALUE *saved_argv = out->argv;
|
|
1386
1523
|
|
|
1387
1524
|
if (oj_code_dump(oj_compat_codes, obj, depth, out)) {
|
|
1388
|
-
|
|
1525
|
+
// Same as in dump_as_string(): the value is complete, so the options
|
|
1526
|
+
// stay available for the next sibling.
|
|
1527
|
+
out->argc = saved_argc;
|
|
1528
|
+
out->argv = saved_argv;
|
|
1389
1529
|
return;
|
|
1390
1530
|
}
|
|
1391
1531
|
clas = rb_obj_class(obj);
|
data/ext/oj/reader.c
CHANGED
|
@@ -88,8 +88,8 @@ void oj_reader_init(Reader reader, VALUE io, int fd, bool to_s) {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
int oj_reader_read(Reader reader) {
|
|
91
|
-
int
|
|
92
|
-
|
|
91
|
+
int err;
|
|
92
|
+
ptrdiff_t shift = 0;
|
|
93
93
|
|
|
94
94
|
if (0 == reader->read_func) {
|
|
95
95
|
return -1;
|
|
@@ -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/resolve.c
CHANGED
|
@@ -19,6 +19,9 @@ inline static VALUE resolve_classname(VALUE mod, const char *classname, int auto
|
|
|
19
19
|
|
|
20
20
|
if (rb_const_defined_at(mod, ci)) {
|
|
21
21
|
clas = rb_const_get_at(mod, ci);
|
|
22
|
+
if (!RB_TYPE_P(clas, T_CLASS) && !RB_TYPE_P(clas, T_MODULE)) {
|
|
23
|
+
clas = Qundef;
|
|
24
|
+
}
|
|
22
25
|
} else if (auto_define) {
|
|
23
26
|
clas = rb_define_class_under(mod, classname, oj_bag_class);
|
|
24
27
|
} else {
|
|
@@ -32,7 +35,8 @@ static VALUE resolve_classpath(ParseInfo pi, const char *name, size_t len, int a
|
|
|
32
35
|
VALUE clas;
|
|
33
36
|
char *end = class_name + sizeof(class_name) - 1;
|
|
34
37
|
char *s;
|
|
35
|
-
const char *n
|
|
38
|
+
const char *n = name;
|
|
39
|
+
size_t nlen = len;
|
|
36
40
|
|
|
37
41
|
clas = rb_cObject;
|
|
38
42
|
for (s = class_name; 0 < len; n++, len--) {
|
|
@@ -55,7 +59,12 @@ static VALUE resolve_classpath(ParseInfo pi, const char *name, size_t len, int a
|
|
|
55
59
|
}
|
|
56
60
|
*s = '\0';
|
|
57
61
|
if (Qundef == (clas = resolve_classname(clas, class_name, auto_define))) {
|
|
58
|
-
|
|
62
|
+
if (sizeof(class_name) <= nlen) {
|
|
63
|
+
nlen = sizeof(class_name) - 1;
|
|
64
|
+
}
|
|
65
|
+
memcpy(class_name, name, nlen);
|
|
66
|
+
class_name[nlen] = '\0';
|
|
67
|
+
oj_set_error_at(pi, error_class, __FILE__, __LINE__, "class '%s' is not defined", class_name);
|
|
59
68
|
if (Qnil != error_class) {
|
|
60
69
|
pi->err_class = error_class;
|
|
61
70
|
}
|
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 */
|