oj 3.17.3 → 3.17.5
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 +75 -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 +2 -2
- data/ext/oj/custom.c +5 -3
- 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 +87 -44
- data/ext/oj/intern.c +6 -1
- data/ext/oj/object.c +26 -15
- data/ext/oj/odd.c +6 -2
- data/ext/oj/oj.c +224 -10
- data/ext/oj/oj.h +5 -1
- data/ext/oj/parse.c +17 -3
- data/ext/oj/parse.h +0 -1
- data/ext/oj/parser.c +120 -35
- data/ext/oj/rails.c +166 -40
- data/ext/oj/reader.c +18 -6
- data/ext/oj/resolve.c +11 -2
- data/ext/oj/rxclass.c +16 -0
- data/ext/oj/rxclass.h +1 -0
- data/ext/oj/safe.c +62 -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 +5 -1
- 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 +2 -0
- data/pages/Security.md +21 -7
- metadata +2 -2
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;
|
|
@@ -838,10 +892,10 @@ static void parse(ojParser p, const byte *json, bool more) {
|
|
|
838
892
|
case EXP_DIGIT:
|
|
839
893
|
p->map = exp_map;
|
|
840
894
|
for (; NUM_DIGIT == digit_map[*b]; b++) {
|
|
841
|
-
|
|
895
|
+
int x = p->num.exp * 10 + (*b - '0');
|
|
842
896
|
|
|
843
897
|
if (x <= MAX_EXP) {
|
|
844
|
-
p->num.exp = x;
|
|
898
|
+
p->num.exp = (int16_t)x;
|
|
845
899
|
} else {
|
|
846
900
|
big_change(p);
|
|
847
901
|
p->map = big_exp_map;
|
|
@@ -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 {
|
|
@@ -1229,7 +1282,8 @@ static int opt_cb(VALUE rkey, VALUE value, VALUE ptr) {
|
|
|
1229
1282
|
* Oj::Parser.new(:usual, cache_keys: true).
|
|
1230
1283
|
*/
|
|
1231
1284
|
static VALUE parser_new(int argc, VALUE *argv, VALUE self) {
|
|
1232
|
-
ojParser
|
|
1285
|
+
ojParser p = OJ_R_ALLOC(struct _ojParser);
|
|
1286
|
+
volatile VALUE pv;
|
|
1233
1287
|
|
|
1234
1288
|
#if HAVE_RB_EXT_RACTOR_SAFE
|
|
1235
1289
|
// This doesn't seem to do anything.
|
|
@@ -1239,6 +1293,10 @@ static VALUE parser_new(int argc, VALUE *argv, VALUE self) {
|
|
|
1239
1293
|
buf_init(&p->key);
|
|
1240
1294
|
buf_init(&p->buf);
|
|
1241
1295
|
p->map = value_map;
|
|
1296
|
+
// Until the parser is wrapped the GC can not free it, so the mode and the
|
|
1297
|
+
// options, both of which raise on a value they do not take, are handled
|
|
1298
|
+
// after the wrap.
|
|
1299
|
+
pv = TypedData_Wrap_Struct(parser_class, &oj_parser_type, p);
|
|
1242
1300
|
|
|
1243
1301
|
if (argc < 1) {
|
|
1244
1302
|
oj_set_parser_validator(p);
|
|
@@ -1279,7 +1337,7 @@ static VALUE parser_new(int argc, VALUE *argv, VALUE self) {
|
|
|
1279
1337
|
rb_hash_foreach(ropts, opt_cb, (VALUE)p);
|
|
1280
1338
|
}
|
|
1281
1339
|
}
|
|
1282
|
-
return
|
|
1340
|
+
return pv;
|
|
1283
1341
|
}
|
|
1284
1342
|
|
|
1285
1343
|
// Create a new parser without setting the delegate. The parser is
|
|
@@ -1419,7 +1477,7 @@ static VALUE parser_parse(VALUE self, VALUE json) {
|
|
|
1419
1477
|
|
|
1420
1478
|
parser_reset(p);
|
|
1421
1479
|
p->start(p);
|
|
1422
|
-
parse(p, ptr, false);
|
|
1480
|
+
parse(p, ptr, (size_t)RSTRING_LEN(json), false);
|
|
1423
1481
|
validate_document_end(p);
|
|
1424
1482
|
|
|
1425
1483
|
return p->result(p);
|
|
@@ -1440,7 +1498,7 @@ static VALUE load(VALUE self) {
|
|
|
1440
1498
|
while (true) {
|
|
1441
1499
|
rb_funcall(p->reader, oj_readpartial_id, 2, INT2NUM(16385), rbuf);
|
|
1442
1500
|
if (0 < RSTRING_LEN(rbuf)) {
|
|
1443
|
-
parse(p, (byte *)StringValuePtr(rbuf), true);
|
|
1501
|
+
parse(p, (byte *)StringValuePtr(rbuf), (size_t)RSTRING_LEN(rbuf), true);
|
|
1444
1502
|
}
|
|
1445
1503
|
if (Qtrue == rb_funcall(p->reader, oj_eofq_id, 0)) {
|
|
1446
1504
|
if (0 < p->depth) {
|
|
@@ -1471,6 +1529,39 @@ static VALUE parser_load(VALUE self, VALUE reader) {
|
|
|
1471
1529
|
return p->result(p);
|
|
1472
1530
|
}
|
|
1473
1531
|
|
|
1532
|
+
struct _fileParse {
|
|
1533
|
+
ojParser p;
|
|
1534
|
+
const char *path;
|
|
1535
|
+
int fd;
|
|
1536
|
+
};
|
|
1537
|
+
|
|
1538
|
+
static VALUE file_parse(VALUE x) {
|
|
1539
|
+
struct _fileParse *fp = (struct _fileParse *)x;
|
|
1540
|
+
byte buf[16385];
|
|
1541
|
+
size_t size = sizeof(buf) - 1;
|
|
1542
|
+
ssize_t rsize;
|
|
1543
|
+
|
|
1544
|
+
while (true) {
|
|
1545
|
+
if (0 > (rsize = read(fp->fd, buf, size))) {
|
|
1546
|
+
rb_raise(rb_eIOError, "error reading from %s", fp->path);
|
|
1547
|
+
}
|
|
1548
|
+
if (0 == rsize) {
|
|
1549
|
+
break;
|
|
1550
|
+
}
|
|
1551
|
+
buf[rsize] = '\0';
|
|
1552
|
+
parse(fp->p, buf, rsize, true);
|
|
1553
|
+
}
|
|
1554
|
+
validate_document_end(fp->p);
|
|
1555
|
+
|
|
1556
|
+
return fp->p->result(fp->p);
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
static VALUE file_close(VALUE x) {
|
|
1560
|
+
close(((struct _fileParse *)x)->fd);
|
|
1561
|
+
|
|
1562
|
+
return Qnil;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1474
1565
|
/* Document-method: file(filename)
|
|
1475
1566
|
* call-seq: file(filename)
|
|
1476
1567
|
*
|
|
@@ -1479,9 +1570,10 @@ static VALUE parser_load(VALUE self, VALUE reader) {
|
|
|
1479
1570
|
* Returns the result according to the delegate of the parser.
|
|
1480
1571
|
*/
|
|
1481
1572
|
static VALUE parser_file(VALUE self, VALUE filename) {
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1573
|
+
struct _fileParse fp;
|
|
1574
|
+
ojParser p;
|
|
1575
|
+
const char *path;
|
|
1576
|
+
int fd;
|
|
1485
1577
|
|
|
1486
1578
|
TypedData_Get_Struct(self, struct _ojParser, &oj_parser_type, p);
|
|
1487
1579
|
|
|
@@ -1500,26 +1592,15 @@ static VALUE parser_file(VALUE self, VALUE filename) {
|
|
|
1500
1592
|
// Use threaded version.
|
|
1501
1593
|
// TBD only if has pthreads
|
|
1502
1594
|
// TBD parse_large(p, fd);
|
|
1595
|
+
close(fd);
|
|
1503
1596
|
return p->result(p);
|
|
1504
1597
|
}
|
|
1505
1598
|
#endif
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1599
|
+
fp.p = p;
|
|
1600
|
+
fp.path = path;
|
|
1601
|
+
fp.fd = fd;
|
|
1509
1602
|
|
|
1510
|
-
|
|
1511
|
-
if (0 < (rsize = read(fd, buf, size))) {
|
|
1512
|
-
buf[rsize] = '\0';
|
|
1513
|
-
parse(p, buf, true);
|
|
1514
|
-
}
|
|
1515
|
-
if (rsize <= 0) {
|
|
1516
|
-
if (0 != rsize) {
|
|
1517
|
-
rb_raise(rb_eIOError, "error reading from %s", path);
|
|
1518
|
-
}
|
|
1519
|
-
break;
|
|
1520
|
-
}
|
|
1521
|
-
}
|
|
1522
|
-
return p->result(p);
|
|
1603
|
+
return rb_ensure(file_parse, (VALUE)&fp, file_close, (VALUE)&fp);
|
|
1523
1604
|
}
|
|
1524
1605
|
|
|
1525
1606
|
/* Document-method: just_one
|
|
@@ -1632,15 +1713,19 @@ static VALUE parser_safe(int argc, VALUE *argv, VALUE self) {
|
|
|
1632
1713
|
options = rb_hash_new();
|
|
1633
1714
|
}
|
|
1634
1715
|
|
|
1635
|
-
ojParser
|
|
1716
|
+
ojParser p = OJ_R_ALLOC(struct _ojParser);
|
|
1717
|
+
volatile VALUE pv;
|
|
1636
1718
|
|
|
1637
1719
|
memset(p, 0, sizeof(struct _ojParser));
|
|
1638
1720
|
buf_init(&p->key);
|
|
1639
1721
|
buf_init(&p->buf);
|
|
1640
1722
|
p->map = value_map;
|
|
1723
|
+
// The limits raise on a value that is not an Integer, so the parser has to
|
|
1724
|
+
// be wrapped before they are read.
|
|
1725
|
+
pv = TypedData_Wrap_Struct(parser_class, &oj_parser_type, p);
|
|
1641
1726
|
oj_set_parser_safe(p, options);
|
|
1642
1727
|
|
|
1643
|
-
return
|
|
1728
|
+
return pv;
|
|
1644
1729
|
}
|
|
1645
1730
|
|
|
1646
1731
|
/* Document-class: Oj::Parser
|
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,15 +754,35 @@ 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
|
-
|
|
757
|
+
e->ropts.len = 0;
|
|
758
|
+
e->ropts.alen = 0;
|
|
759
|
+
e->ropts.table = NULL;
|
|
760
|
+
e->own_ropts = false;
|
|
665
761
|
|
|
666
762
|
if (1 <= argc && Qnil != *argv) {
|
|
667
763
|
e->arg = *argv;
|
|
668
764
|
} else {
|
|
669
765
|
e->arg = rb_hash_new();
|
|
670
766
|
}
|
|
671
|
-
|
|
767
|
+
// An encoder given no options of its own reads oj_default_options at encode
|
|
768
|
+
// time rather than copying it here. ActiveSupport keeps one option-less
|
|
769
|
+
// encoder for the life of the process, so a copy taken now would freeze
|
|
770
|
+
// every later Oj.default_options and ActiveSupport::JSON::Encoding change
|
|
771
|
+
// out of it - including the time_precision that set_encoder itself writes
|
|
772
|
+
// after ActiveSupport has already built and cached that encoder.
|
|
773
|
+
e->own_opts = T_HASH == rb_type(e->arg) && 0 < RHASH_SIZE(e->arg);
|
|
774
|
+
if (e->own_opts) {
|
|
775
|
+
e->opts = oj_default_options;
|
|
776
|
+
// Detach from the match_string regexps owned by the defaults before the
|
|
777
|
+
// options are parsed so that a :match_string option builds a chain of
|
|
778
|
+
// its own that is freed with the encoder.
|
|
779
|
+
e->opts.str_rx.head = NULL;
|
|
780
|
+
e->opts.str_rx.tail = NULL;
|
|
781
|
+
oj_parse_options(e->arg, &e->opts);
|
|
782
|
+
oj_options_take_ownership(&e->opts);
|
|
783
|
+
} else {
|
|
784
|
+
memset(&e->opts, 0, sizeof(e->opts));
|
|
785
|
+
}
|
|
672
786
|
|
|
673
787
|
return TypedData_Wrap_Struct(encoder_class, &oj_encoder_type, e);
|
|
674
788
|
}
|
|
@@ -765,7 +879,7 @@ static VALUE encoder_optimize(int argc, VALUE *argv, VALUE self) {
|
|
|
765
879
|
Encoder e;
|
|
766
880
|
TypedData_Get_Struct(self, struct _encoder, &oj_encoder_type, e);
|
|
767
881
|
|
|
768
|
-
optimize(argc, argv,
|
|
882
|
+
optimize(argc, argv, encoder_own_ropts(e), true);
|
|
769
883
|
|
|
770
884
|
return Qnil;
|
|
771
885
|
}
|
|
@@ -822,7 +936,7 @@ static VALUE encoder_deoptimize(int argc, VALUE *argv, VALUE self) {
|
|
|
822
936
|
Encoder e;
|
|
823
937
|
TypedData_Get_Struct(self, struct _encoder, &oj_encoder_type, e);
|
|
824
938
|
|
|
825
|
-
optimize(argc, argv,
|
|
939
|
+
optimize(argc, argv, encoder_own_ropts(e), false);
|
|
826
940
|
|
|
827
941
|
return Qnil;
|
|
828
942
|
}
|
|
@@ -853,7 +967,7 @@ static VALUE encoder_optimized(VALUE self, VALUE clas) {
|
|
|
853
967
|
ROpt ro;
|
|
854
968
|
|
|
855
969
|
TypedData_Get_Struct(self, struct _encoder, &oj_encoder_type, e);
|
|
856
|
-
ro = oj_rails_get_opt(
|
|
970
|
+
ro = oj_rails_get_opt(encoder_ropts(e), clas);
|
|
857
971
|
|
|
858
972
|
if (NULL == ro) {
|
|
859
973
|
return Qfalse;
|
|
@@ -962,11 +1076,22 @@ static VALUE encoder_encode(VALUE self, VALUE obj) {
|
|
|
962
1076
|
TypedData_Get_Struct(self, struct _encoder, &oj_encoder_type, e);
|
|
963
1077
|
|
|
964
1078
|
if (Qnil != e->arg) {
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1079
|
+
// as_json is allowed to write into the options hash it is handed, and
|
|
1080
|
+
// the ActiveSupport suite has a test that does exactly that. Handing
|
|
1081
|
+
// out the encoder's own hash lets one such call poison every later
|
|
1082
|
+
// encode by the same encoder, which on ActiveSupport 8.1 is every
|
|
1083
|
+
// option-less to_json in the process because json_encoder= caches one
|
|
1084
|
+
// encoder and keeps it. Rails never exposes a hash it will reuse:
|
|
1085
|
+
// JSONGemEncoder#encode passes options.dup, and only when there are
|
|
1086
|
+
// options at all, so with none as_json writes into its own default.
|
|
1087
|
+
// The copy is not frozen the way Rails 8.0 and later freeze theirs,
|
|
1088
|
+
// because Oj hands as_json the hash even when it is empty and that is
|
|
1089
|
+
// exactly the case Rails leaves writable.
|
|
1090
|
+
VALUE argv[1] = {T_HASH == rb_type(e->arg) ? rb_hash_dup(e->arg) : e->arg};
|
|
1091
|
+
|
|
1092
|
+
return encode(obj, encoder_ropts(e), encoder_opts(e), 1, argv);
|
|
1093
|
+
}
|
|
1094
|
+
return encode(obj, encoder_ropts(e), encoder_opts(e), 0, NULL);
|
|
970
1095
|
}
|
|
971
1096
|
|
|
972
1097
|
/* Document-method: encode
|
|
@@ -1230,11 +1355,7 @@ static void dump_array(VALUE a, int depth, Out out, bool as_ok) {
|
|
|
1230
1355
|
if (0 == cnt) {
|
|
1231
1356
|
*out->cur++ = ']';
|
|
1232
1357
|
} else {
|
|
1233
|
-
|
|
1234
|
-
size = d2 * out->opts->dump_opts.indent_size + out->opts->dump_opts.array_size + 1;
|
|
1235
|
-
} else {
|
|
1236
|
-
size = d2 * out->indent + 2;
|
|
1237
|
-
}
|
|
1358
|
+
size = indent_len(out, d2, out->opts->dump_opts.array_size) + 1;
|
|
1238
1359
|
assure_size(out, size * cnt);
|
|
1239
1360
|
cnt--;
|
|
1240
1361
|
for (i = 0; i <= cnt; i++) {
|
|
@@ -1256,7 +1377,7 @@ static void dump_array(VALUE a, int depth, Out out, bool as_ok) {
|
|
|
1256
1377
|
*out->cur++ = ',';
|
|
1257
1378
|
}
|
|
1258
1379
|
}
|
|
1259
|
-
size = depth
|
|
1380
|
+
size = indent_len(out, depth, out->opts->dump_opts.array_size) + 1;
|
|
1260
1381
|
assure_size(out, size);
|
|
1261
1382
|
if (out->opts->dump_opts.use) {
|
|
1262
1383
|
if (0 < out->opts->dump_opts.array_size) {
|
|
@@ -1286,7 +1407,7 @@ static int hash_cb(VALUE key, VALUE value, VALUE ov) {
|
|
|
1286
1407
|
if (out->omit_nil && Qnil == value) {
|
|
1287
1408
|
return ST_CONTINUE;
|
|
1288
1409
|
}
|
|
1289
|
-
if (NULL != out->opts->dump_opts.only || NULL != out->opts->dump_opts.except) {
|
|
1410
|
+
if (!out->key_filter_off && (NULL != out->opts->dump_opts.only || NULL != out->opts->dump_opts.except)) {
|
|
1290
1411
|
if (oj_key_skip(key, out->opts->dump_opts.only, out->opts->dump_opts.except)) {
|
|
1291
1412
|
return ST_CONTINUE;
|
|
1292
1413
|
}
|
|
@@ -1384,10 +1505,15 @@ static void dump_hash(VALUE obj, int depth, Out out, bool as_ok) {
|
|
|
1384
1505
|
}
|
|
1385
1506
|
|
|
1386
1507
|
static void dump_obj(VALUE obj, int depth, Out out, bool as_ok) {
|
|
1387
|
-
VALUE
|
|
1508
|
+
VALUE clas;
|
|
1509
|
+
int saved_argc = out->argc;
|
|
1510
|
+
VALUE *saved_argv = out->argv;
|
|
1388
1511
|
|
|
1389
1512
|
if (oj_code_dump(oj_compat_codes, obj, depth, out)) {
|
|
1390
|
-
|
|
1513
|
+
// Same as in dump_as_string(): the value is complete, so the options
|
|
1514
|
+
// stay available for the next sibling.
|
|
1515
|
+
out->argc = saved_argc;
|
|
1516
|
+
out->argv = saved_argv;
|
|
1391
1517
|
return;
|
|
1392
1518
|
}
|
|
1393
1519
|
clas = rb_obj_class(obj);
|