oj 3.17.3 → 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/parse.c CHANGED
@@ -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;
@@ -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
- if (p + 3 < end) {
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
- if (pi->str_rx.head != oj_default_options.str_rx.head) {
1310
- oj_rxclass_cleanup(&pi->str_rx);
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) {
data/ext/oj/parse.h CHANGED
@@ -49,7 +49,6 @@ typedef struct _parseInfo {
49
49
  VALUE handler;
50
50
  struct _valStack stack;
51
51
  CircArray circ_array;
52
- struct _rxClass str_rx;
53
52
  int expect_value;
54
53
  int max_depth; // just for the json gem
55
54
  VALUE proc;
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;
@@ -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
- int16_t x = p->num.exp * 10 + (int16_t)(*b - '0');
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
- for (; STR_OK == string_map[*b]; b++) {
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 p = OJ_R_ALLOC(struct _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 TypedData_Wrap_Struct(parser_class, &oj_parser_type, p);
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
- ojParser p;
1483
- const char *path;
1484
- int fd;
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
- byte buf[16385];
1507
- size_t size = sizeof(buf) - 1;
1508
- size_t rsize;
1599
+ fp.p = p;
1600
+ fp.path = path;
1601
+ fp.fd = fd;
1509
1602
 
1510
- while (true) {
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 p = OJ_R_ALLOC(struct _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 TypedData_Wrap_Struct(parser_class, &oj_parser_type, p);
1728
+ return pv;
1644
1729
  }
1645
1730
 
1646
1731
  /* Document-class: Oj::Parser