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/parser.c
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
#include <fcntl.h>
|
|
6
6
|
|
|
7
7
|
#include "oj.h"
|
|
8
|
+
#include "simd.h"
|
|
8
9
|
|
|
9
10
|
#define DEBUG 0
|
|
10
11
|
|
|
@@ -594,9 +595,64 @@ static void big_change(ojParser p) {
|
|
|
594
595
|
}
|
|
595
596
|
}
|
|
596
597
|
|
|
597
|
-
|
|
598
|
+
// Scan forward over string content, returning the first byte that is not
|
|
599
|
+
// STR_OK in string_map. This is a pure drop-in for the scalar loop
|
|
600
|
+
//
|
|
601
|
+
// for (; STR_OK == string_map[*b]; b++) {}
|
|
602
|
+
//
|
|
603
|
+
// and must return the exact same stop position. The stop set (bytes whose
|
|
604
|
+
// string_map class is not 'R'/STR_OK) is:
|
|
605
|
+
//
|
|
606
|
+
// * control bytes 0x00..0x1F (class '.', also stops at the NUL terminator)
|
|
607
|
+
// * the quote 0x22 '"' (class 'z')
|
|
608
|
+
// * the backslash 0x5C '\' (class 'A')
|
|
609
|
+
// * every high byte 0x80..0xFF (UTF-8 lead/continuation, classes M/P/Q/'.')
|
|
610
|
+
//
|
|
611
|
+
// Note this differs from parse.c's string_scan_neon, which stops only on
|
|
612
|
+
// \0 \\ " -- parser.c hands multi-byte UTF-8 to its state machine, so the
|
|
613
|
+
// scanner must stop on the high bytes too. The predictor below is derived from
|
|
614
|
+
// string_map, not copied from parse.c.
|
|
615
|
+
//
|
|
616
|
+
// The NEON path only loads a full 16-byte vector when [b, b+16) stays within
|
|
617
|
+
// [., end), so it never reads past the string's allocation; the sub-16-byte
|
|
618
|
+
// tail (and the whole scan on non-NEON builds) uses the scalar loop, which
|
|
619
|
+
// stops naturally at the guaranteed NUL terminator.
|
|
620
|
+
static inline const byte *oj_scan_str_simd(const byte *b, const byte *end) {
|
|
621
|
+
#ifdef HAVE_SIMD_NEON
|
|
622
|
+
if (SIMD_NEON == SIMD_Impl) {
|
|
623
|
+
const uint8x16_t quote = vdupq_n_u8('"');
|
|
624
|
+
const uint8x16_t bslash = vdupq_n_u8('\\');
|
|
625
|
+
const uint8x16_t space = vdupq_n_u8(0x20);
|
|
626
|
+
const uint8x16_t high = vdupq_n_u8(0x80);
|
|
627
|
+
|
|
628
|
+
while (b + sizeof(uint8x16_t) <= end) {
|
|
629
|
+
const uint8x16_t chunk = vld1q_u8((const uint8_t *)b);
|
|
630
|
+
// special lane == 0xFF for any byte that stops the scan.
|
|
631
|
+
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(chunk, quote), vceqq_u8(chunk, bslash)),
|
|
632
|
+
vorrq_u8(vcltq_u8(chunk, space), vcgeq_u8(chunk, high)));
|
|
633
|
+
// Reduce to a 64-bit mask with 4 bits per lane (same idiom as
|
|
634
|
+
// parse.c's string_scan_neon) and locate the first set lane.
|
|
635
|
+
const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(special), 4);
|
|
636
|
+
uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0);
|
|
637
|
+
if (0 != mask) {
|
|
638
|
+
mask &= 0x8888888888888888ull;
|
|
639
|
+
return b + (OJ_CTZ64(mask) >> 2);
|
|
640
|
+
}
|
|
641
|
+
b += sizeof(uint8x16_t);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
#else
|
|
645
|
+
(void)end;
|
|
646
|
+
#endif
|
|
647
|
+
for (; STR_OK == string_map[*b]; b++) {
|
|
648
|
+
}
|
|
649
|
+
return b;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
static void parse(ojParser p, const byte *json, size_t len, bool more) {
|
|
598
653
|
const byte *start;
|
|
599
|
-
const byte *b
|
|
654
|
+
const byte *b = json;
|
|
655
|
+
const byte *end = json + len;
|
|
600
656
|
int i;
|
|
601
657
|
|
|
602
658
|
p->line = 1;
|
|
@@ -629,8 +685,7 @@ static void parse(ojParser p, const byte *json, bool more) {
|
|
|
629
685
|
b++;
|
|
630
686
|
p->key.tail = p->key.head;
|
|
631
687
|
start = b;
|
|
632
|
-
|
|
633
|
-
}
|
|
688
|
+
b = oj_scan_str_simd(b, end);
|
|
634
689
|
buf_append_string(&p->key, (const char *)start, b - start);
|
|
635
690
|
if ('"' == *b) {
|
|
636
691
|
p->map = colon_map;
|
|
@@ -654,8 +709,7 @@ static void parse(ojParser p, const byte *json, bool more) {
|
|
|
654
709
|
b++;
|
|
655
710
|
start = b;
|
|
656
711
|
p->buf.tail = p->buf.head;
|
|
657
|
-
|
|
658
|
-
}
|
|
712
|
+
b = oj_scan_str_simd(b, end);
|
|
659
713
|
buf_append_string(&p->buf, (const char *)start, b - start);
|
|
660
714
|
if ('"' == *b) {
|
|
661
715
|
p->cur = b - json;
|
|
@@ -674,6 +728,10 @@ static void parse(ojParser p, const byte *json, bool more) {
|
|
|
674
728
|
p->cur = b - json;
|
|
675
729
|
p->funcs[p->stack[p->depth]].open_object(p);
|
|
676
730
|
p->depth++;
|
|
731
|
+
if ((int)sizeof(p->stack) <= p->depth) {
|
|
732
|
+
parse_error(p, "too deeply nested");
|
|
733
|
+
break;
|
|
734
|
+
}
|
|
677
735
|
p->stack[p->depth] = OBJECT_FUN;
|
|
678
736
|
p->map = key1_map;
|
|
679
737
|
break;
|
|
@@ -696,6 +754,10 @@ static void parse(ojParser p, const byte *json, bool more) {
|
|
|
696
754
|
p->cur = b - json;
|
|
697
755
|
p->funcs[p->stack[p->depth]].open_array(p);
|
|
698
756
|
p->depth++;
|
|
757
|
+
if ((int)sizeof(p->stack) <= p->depth) {
|
|
758
|
+
parse_error(p, "too deeply nested");
|
|
759
|
+
break;
|
|
760
|
+
}
|
|
699
761
|
p->stack[p->depth] = ARRAY_FUN;
|
|
700
762
|
p->map = value_map;
|
|
701
763
|
break;
|
|
@@ -830,10 +892,10 @@ static void parse(ojParser p, const byte *json, bool more) {
|
|
|
830
892
|
case EXP_DIGIT:
|
|
831
893
|
p->map = exp_map;
|
|
832
894
|
for (; NUM_DIGIT == digit_map[*b]; b++) {
|
|
833
|
-
|
|
895
|
+
int x = p->num.exp * 10 + (*b - '0');
|
|
834
896
|
|
|
835
897
|
if (x <= MAX_EXP) {
|
|
836
|
-
p->num.exp = x;
|
|
898
|
+
p->num.exp = (int16_t)x;
|
|
837
899
|
} else {
|
|
838
900
|
big_change(p);
|
|
839
901
|
p->map = big_exp_map;
|
|
@@ -897,8 +959,7 @@ static void parse(ojParser p, const byte *json, bool more) {
|
|
|
897
959
|
break;
|
|
898
960
|
case STR_OK:
|
|
899
961
|
start = b;
|
|
900
|
-
|
|
901
|
-
}
|
|
962
|
+
b = oj_scan_str_simd(b, end);
|
|
902
963
|
if (':' == p->next_map[256]) {
|
|
903
964
|
buf_append_string(&p->key, (const char *)start, b - start);
|
|
904
965
|
} else {
|
|
@@ -1221,7 +1282,8 @@ static int opt_cb(VALUE rkey, VALUE value, VALUE ptr) {
|
|
|
1221
1282
|
* Oj::Parser.new(:usual, cache_keys: true).
|
|
1222
1283
|
*/
|
|
1223
1284
|
static VALUE parser_new(int argc, VALUE *argv, VALUE self) {
|
|
1224
|
-
ojParser
|
|
1285
|
+
ojParser p = OJ_R_ALLOC(struct _ojParser);
|
|
1286
|
+
volatile VALUE pv;
|
|
1225
1287
|
|
|
1226
1288
|
#if HAVE_RB_EXT_RACTOR_SAFE
|
|
1227
1289
|
// This doesn't seem to do anything.
|
|
@@ -1231,6 +1293,10 @@ static VALUE parser_new(int argc, VALUE *argv, VALUE self) {
|
|
|
1231
1293
|
buf_init(&p->key);
|
|
1232
1294
|
buf_init(&p->buf);
|
|
1233
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);
|
|
1234
1300
|
|
|
1235
1301
|
if (argc < 1) {
|
|
1236
1302
|
oj_set_parser_validator(p);
|
|
@@ -1271,7 +1337,7 @@ static VALUE parser_new(int argc, VALUE *argv, VALUE self) {
|
|
|
1271
1337
|
rb_hash_foreach(ropts, opt_cb, (VALUE)p);
|
|
1272
1338
|
}
|
|
1273
1339
|
}
|
|
1274
|
-
return
|
|
1340
|
+
return pv;
|
|
1275
1341
|
}
|
|
1276
1342
|
|
|
1277
1343
|
// Create a new parser without setting the delegate. The parser is
|
|
@@ -1399,14 +1465,19 @@ static void validate_document_end(ojParser p) {
|
|
|
1399
1465
|
*/
|
|
1400
1466
|
static VALUE parser_parse(VALUE self, VALUE json) {
|
|
1401
1467
|
ojParser p;
|
|
1402
|
-
|
|
1468
|
+
int frozen = OBJ_FROZEN(json);
|
|
1469
|
+
const byte *ptr;
|
|
1470
|
+
|
|
1471
|
+
if (!frozen) {
|
|
1472
|
+
rb_str_freeze(json);
|
|
1473
|
+
}
|
|
1474
|
+
ptr = (const byte *)StringValuePtr(json);
|
|
1403
1475
|
|
|
1404
1476
|
TypedData_Get_Struct(self, struct _ojParser, &oj_parser_type, p);
|
|
1405
1477
|
|
|
1406
1478
|
parser_reset(p);
|
|
1407
1479
|
p->start(p);
|
|
1408
|
-
parse(p, ptr, false);
|
|
1409
|
-
|
|
1480
|
+
parse(p, ptr, (size_t)RSTRING_LEN(json), false);
|
|
1410
1481
|
validate_document_end(p);
|
|
1411
1482
|
|
|
1412
1483
|
return p->result(p);
|
|
@@ -1427,7 +1498,7 @@ static VALUE load(VALUE self) {
|
|
|
1427
1498
|
while (true) {
|
|
1428
1499
|
rb_funcall(p->reader, oj_readpartial_id, 2, INT2NUM(16385), rbuf);
|
|
1429
1500
|
if (0 < RSTRING_LEN(rbuf)) {
|
|
1430
|
-
parse(p, (byte *)StringValuePtr(rbuf), true);
|
|
1501
|
+
parse(p, (byte *)StringValuePtr(rbuf), (size_t)RSTRING_LEN(rbuf), true);
|
|
1431
1502
|
}
|
|
1432
1503
|
if (Qtrue == rb_funcall(p->reader, oj_eofq_id, 0)) {
|
|
1433
1504
|
if (0 < p->depth) {
|
|
@@ -1458,6 +1529,39 @@ static VALUE parser_load(VALUE self, VALUE reader) {
|
|
|
1458
1529
|
return p->result(p);
|
|
1459
1530
|
}
|
|
1460
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
|
+
|
|
1461
1565
|
/* Document-method: file(filename)
|
|
1462
1566
|
* call-seq: file(filename)
|
|
1463
1567
|
*
|
|
@@ -1466,9 +1570,10 @@ static VALUE parser_load(VALUE self, VALUE reader) {
|
|
|
1466
1570
|
* Returns the result according to the delegate of the parser.
|
|
1467
1571
|
*/
|
|
1468
1572
|
static VALUE parser_file(VALUE self, VALUE filename) {
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1573
|
+
struct _fileParse fp;
|
|
1574
|
+
ojParser p;
|
|
1575
|
+
const char *path;
|
|
1576
|
+
int fd;
|
|
1472
1577
|
|
|
1473
1578
|
TypedData_Get_Struct(self, struct _ojParser, &oj_parser_type, p);
|
|
1474
1579
|
|
|
@@ -1487,26 +1592,15 @@ static VALUE parser_file(VALUE self, VALUE filename) {
|
|
|
1487
1592
|
// Use threaded version.
|
|
1488
1593
|
// TBD only if has pthreads
|
|
1489
1594
|
// TBD parse_large(p, fd);
|
|
1595
|
+
close(fd);
|
|
1490
1596
|
return p->result(p);
|
|
1491
1597
|
}
|
|
1492
1598
|
#endif
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1599
|
+
fp.p = p;
|
|
1600
|
+
fp.path = path;
|
|
1601
|
+
fp.fd = fd;
|
|
1496
1602
|
|
|
1497
|
-
|
|
1498
|
-
if (0 < (rsize = read(fd, buf, size))) {
|
|
1499
|
-
buf[rsize] = '\0';
|
|
1500
|
-
parse(p, buf, true);
|
|
1501
|
-
}
|
|
1502
|
-
if (rsize <= 0) {
|
|
1503
|
-
if (0 != rsize) {
|
|
1504
|
-
rb_raise(rb_eIOError, "error reading from %s", path);
|
|
1505
|
-
}
|
|
1506
|
-
break;
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
return p->result(p);
|
|
1603
|
+
return rb_ensure(file_parse, (VALUE)&fp, file_close, (VALUE)&fp);
|
|
1510
1604
|
}
|
|
1511
1605
|
|
|
1512
1606
|
/* Document-method: just_one
|
|
@@ -1619,15 +1713,19 @@ static VALUE parser_safe(int argc, VALUE *argv, VALUE self) {
|
|
|
1619
1713
|
options = rb_hash_new();
|
|
1620
1714
|
}
|
|
1621
1715
|
|
|
1622
|
-
ojParser
|
|
1716
|
+
ojParser p = OJ_R_ALLOC(struct _ojParser);
|
|
1717
|
+
volatile VALUE pv;
|
|
1623
1718
|
|
|
1624
1719
|
memset(p, 0, sizeof(struct _ojParser));
|
|
1625
1720
|
buf_init(&p->key);
|
|
1626
1721
|
buf_init(&p->buf);
|
|
1627
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);
|
|
1628
1726
|
oj_set_parser_safe(p, options);
|
|
1629
1727
|
|
|
1630
|
-
return
|
|
1728
|
+
return pv;
|
|
1631
1729
|
}
|
|
1632
1730
|
|
|
1633
1731
|
/* Document-class: Oj::Parser
|