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.
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
- static void parse(ojParser p, const byte *json, bool more) {
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 = json;
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
- for (; STR_OK == string_map[*b]; b++) {
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
- for (; STR_OK == string_map[*b]; b++) {
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
- int16_t x = p->num.exp * 10 + (int16_t)(*b - '0');
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
- for (; STR_OK == string_map[*b]; b++) {
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 p = OJ_R_ALLOC(struct _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 TypedData_Wrap_Struct(parser_class, &oj_parser_type, p);
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
- const byte *ptr = (const byte *)StringValuePtr(json);
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
- ojParser p;
1470
- const char *path;
1471
- int fd;
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
- byte buf[16385];
1494
- size_t size = sizeof(buf) - 1;
1495
- size_t rsize;
1599
+ fp.p = p;
1600
+ fp.path = path;
1601
+ fp.fd = fd;
1496
1602
 
1497
- while (true) {
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 p = OJ_R_ALLOC(struct _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 TypedData_Wrap_Struct(parser_class, &oj_parser_type, p);
1728
+ return pv;
1631
1729
  }
1632
1730
 
1633
1731
  /* Document-class: Oj::Parser