visaljs 0.0.8 → 0.0.9

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.
Files changed (39) hide show
  1. package/README.md +6 -3
  2. package/THIRD_PARTY_LICENSES +1 -1
  3. package/cli.js +55 -4
  4. package/node/prelude/_http_agent.js +5 -0
  5. package/node/prelude/_http_server.js +7 -0
  6. package/node/prelude/dns.js +7 -1
  7. package/node/prelude/events.js +7 -3
  8. package/node/prelude/http.js +685 -279
  9. package/node/prelude/https.js +14 -3
  10. package/node/prelude/internal/events_extras.js +2 -5
  11. package/node/prelude/internal/features.js +17 -0
  12. package/node/prelude/internal/http_agent.js +88 -2
  13. package/node/prelude/internal/http_symbols.js +3 -0
  14. package/node/prelude/internal/net_bound.js +123 -0
  15. package/node/prelude/internal/net_onread.js +73 -0
  16. package/node/prelude/internal/net_tos.js +54 -0
  17. package/node/prelude/internal/perf_observe.js +7 -1
  18. package/node/prelude/internal/tls/wrap.js +2 -1
  19. package/node/prelude/module.js +42 -18
  20. package/node/prelude/net.js +195 -48
  21. package/node/prelude/perf_hooks.js +4 -0
  22. package/node/src/host_http.c +151 -21
  23. package/node/src/host_net.c +75 -9
  24. package/package.json +1 -1
  25. package/prebuilt/win32-x64/boringssl-ce573858472b7fc9.a +0 -0
  26. package/prebuilt/win32-x64/brotli-20d86a61f30baeb3.a +0 -0
  27. package/prebuilt/win32-x64/uv-b11bae65d7fced82.a +0 -0
  28. package/prebuilt/win32-x64/zlib-4ce2317a4c3f5fb5.a +0 -0
  29. package/prebuilt/win32-x64/zstd-015315ac7c784c73.a +0 -0
  30. package/runtime/include/js_atoms.h +4 -0
  31. package/runtime/include/js_web.h +3 -0
  32. package/runtime/include/jscontext.h +8 -0
  33. package/runtime/src/atoms.c +37 -3
  34. package/runtime/src/events.c +19 -0
  35. package/runtime/src/gc.c +4 -4
  36. package/runtime/src/internal.h +4 -4
  37. package/runtime/src/regexp.c +569 -12
  38. package/runtime/src/shape.c +68 -1
  39. package/runtime/src/weak.c +95 -24
@@ -36,14 +36,19 @@ typedef struct RegExpObject {
36
36
  bool one_class; /* the pattern is one character class: `units` (see find_class) */
37
37
  bool negated; /* ...that matches every unit from U+0100 up */
38
38
  uint32_t units[8]; /* its units below U+0100, a bit each */
39
+ bool analyzed; /* `prefilter` is set (see analyze) */
40
+ struct Prefilter *prefilter; /* where a match can start, or NULL: malloc'd */
39
41
  } RegExpObject;
40
42
 
43
+ static void free_prefilter(RegExpObject *re);
44
+
41
45
  static void regexp_visit(JSCell *c, JSTracer *trc) { js_trace_value(trc, ((RegExpObject *)c)->source); }
42
46
 
43
47
  static void regexp_finalize(JSRuntime *rt, JSCell *c) {
44
48
  (void)rt;
45
49
  RegExpObject *re = (RegExpObject *)c;
46
50
  if (re->owned) free((void *)re->bytecode);
51
+ free_prefilter(re);
47
52
  }
48
53
 
49
54
  static const JSClass regexp_class = { "RegExp", sizeof(RegExpObject), NULL, NULL, NULL, NULL, regexp_visit, regexp_finalize, NULL };
@@ -236,6 +241,8 @@ static JSValue regexp_new(JSContext *ctx, JSValue proto) {
236
241
  re->bytecode = NULL;
237
242
  re->owned = false;
238
243
  re->one_class = false;
244
+ re->analyzed = false;
245
+ re->prefilter = NULL;
239
246
  js_define_data(ctx, &re->obj, JS_ATOM(lastIndex), JS_UNDEFINED, JS_PROP_WRITABLE);
240
247
  return js_object_value(&re->obj);
241
248
  }
@@ -371,8 +378,269 @@ static int scan_class(const RegExpObject *re, const uint8_t *buf, int shift, uin
371
378
  return 0;
372
379
  }
373
380
 
381
+ /* ---- Where a match can start ----------------------------------------------------------------
382
+ *
383
+ * A pattern that isn't anchored or sticky compiles to a loop that tries every position in turn
384
+ * (split_goto_first +6; any; goto -11; save_start 0; ...): each failed attempt costs a trip
385
+ * through the interpreter. When the first units of a match can only be a few (a literal,
386
+ * /foo|bar/, /[cgt]gggtaaa/i, /\s+/), analyze reads them off the bytecode: the units each of
387
+ * the first PREFIX positions can hold, over every alternative. The search then looks for a
388
+ * position where they all fit (memchr for one first unit) and runs a copy of the bytecode whose
389
+ * loop is a goto there: one attempt each. The sets may hold more than can match, never less. */
390
+
391
+ #define PREFIX 4
392
+
393
+ typedef struct {
394
+ uint32_t units[8]; /* below U+0100, a bit each */
395
+ bool high; /* and any unit from U+0100 up */
396
+ } UnitSet;
397
+
398
+ typedef struct Prefilter {
399
+ int depth; /* the positions known: 1..PREFIX */
400
+ int single; /* at[0]'s one unit below U+0100, without high, else -1 */
401
+ UnitSet at[PREFIX];
402
+ uint8_t *once; /* the bytecode without its loop: an attempt at the start index only */
403
+ } Prefilter;
404
+
405
+ /* lre_canonicalize of the units below U+0100, without and with u/v: char_i and range_i compare
406
+ * the canonical unit. */
407
+ static uint32_t canonical[2][256];
408
+
409
+ static void free_prefilter(RegExpObject *re) {
410
+ if (re->prefilter) free(re->prefilter->once);
411
+ free(re->prefilter);
412
+ re->prefilter = NULL;
413
+ re->analyzed = false;
414
+ }
415
+
416
+ /* The bytecode's size: header, code, then the group names (NUL-terminated, each with a trailer). */
417
+ static size_t bytecode_size(const uint8_t *bc) {
418
+ size_t size = 8 + (size_t)(bc[4] | bc[5] << 8 | bc[6] << 16 | (uint32_t)bc[7] << 24);
419
+ const char *names = lre_get_groupnames(bc);
420
+ if (names) {
421
+ for (int i = 1; i < lre_get_capture_count(bc); i++) names += strlen(names) + LRE_GROUP_NAME_TRAILER_LEN;
422
+ size = (size_t)((const uint8_t *)names - bc);
423
+ }
424
+ return size;
425
+ }
426
+
427
+ static void add_unit(UnitSet *u, uint32_t c) {
428
+ if (c < 0x100) u->units[c >> 5] |= 1u << (c & 31);
429
+ else u->high = true;
430
+ }
431
+
432
+ static bool has_unit_in(const UnitSet *u, uint32_t c) {
433
+ return c < 0x100 ? (u->units[c >> 5] >> (c & 31)) & 1 : u->high;
434
+ }
435
+
436
+ static uint32_t get_u16_le(const uint8_t *p) { return p[0] | (uint32_t)p[1] << 8; }
437
+ static uint32_t get_u32_le(const uint8_t *p) { return p[0] | (uint32_t)p[1] << 8 | (uint32_t)p[2] << 16 | (uint32_t)p[3] << 24; }
438
+
439
+ typedef struct {
440
+ const uint8_t *code;
441
+ uint32_t length;
442
+ bool unicode;
443
+ int limit; /* every path consumes at least this many units first (PREFIX at most) */
444
+ int steps;
445
+ UnitSet at[PREFIX];
446
+ } Walk;
447
+
448
+ /* A class op (range, range32 and their _i forms) at p: the units below U+0100 it takes. */
449
+ static void add_ranges(Walk *w, UnitSet *u, const uint8_t *p, bool wide, bool fold) {
450
+ uint32_t n = get_u16_le(p + 1), size = wide ? 8 : 4;
451
+ for (uint32_t c = 0; c < 0x100; c++) {
452
+ uint32_t k = fold ? canonical[w->unicode][c] : c;
453
+ for (uint32_t i = 0; i < n; i++) {
454
+ const uint8_t *r = p + 3 + i * size;
455
+ uint32_t lo = wide ? get_u32_le(r) : get_u16_le(r), hi = wide ? get_u32_le(r + 4) : get_u16_le(r + 2);
456
+ if (k >= lo && k <= hi) {
457
+ add_unit(u, c);
458
+ break;
459
+ }
460
+ }
461
+ }
462
+ const uint8_t *last = p + 3 + (n - 1) * size;
463
+ if (fold || wide || get_u16_le(last + 2) >= 0x100) u->high = true;
464
+ }
465
+
466
+ /* The units the paths from pc can take at positions d.. (until w->limit). */
467
+ static void walk(Walk *w, uint32_t pc, int d) {
468
+ while (d < w->limit) {
469
+ if (++w->steps > 1000 || pc >= w->length) {
470
+ w->limit = 0;
471
+ return;
472
+ }
473
+ const uint8_t *p = w->code + pc;
474
+ UnitSet *u = &w->at[d];
475
+ switch (p[0]) {
476
+ case JS_REOP_char:
477
+ case JS_REOP_char32: {
478
+ uint32_t c = p[0] == JS_REOP_char ? get_u16_le(p + 1) : get_u32_le(p + 1);
479
+ add_unit(u, c); /* from U+10000 up: a high surrogate first */
480
+ pc += p[0] == JS_REOP_char ? 3 : 5;
481
+ d++;
482
+ break;
483
+ }
484
+ case JS_REOP_char_i:
485
+ case JS_REOP_char32_i: {
486
+ uint32_t c = p[0] == JS_REOP_char_i ? get_u16_le(p + 1) : get_u32_le(p + 1);
487
+ for (uint32_t k = 0; k < 0x100; k++)
488
+ if (canonical[w->unicode][k] == c) add_unit(u, k);
489
+ u->high = true; /* units from U+0100 up may fold to it too */
490
+ pc += p[0] == JS_REOP_char_i ? 3 : 5;
491
+ d++;
492
+ break;
493
+ }
494
+ case JS_REOP_range:
495
+ case JS_REOP_range_i:
496
+ add_ranges(w, u, p, false, p[0] == JS_REOP_range_i);
497
+ pc += 3 + get_u16_le(p + 1) * 4;
498
+ d++;
499
+ break;
500
+ case JS_REOP_range32:
501
+ case JS_REOP_range32_i:
502
+ add_ranges(w, u, p, true, p[0] == JS_REOP_range32_i);
503
+ pc += 3 + get_u16_le(p + 1) * 8;
504
+ d++;
505
+ break;
506
+ case JS_REOP_space:
507
+ case JS_REOP_not_space:
508
+ for (uint32_t k = 0; k < 0x100; k++)
509
+ if (lre_is_space(k) == (p[0] == JS_REOP_space)) add_unit(u, k);
510
+ u->high = true;
511
+ pc++;
512
+ d++;
513
+ break;
514
+ case JS_REOP_dot:
515
+ for (uint32_t k = 0; k < 0x100; k++)
516
+ if (k != '\n' && k != '\r') add_unit(u, k);
517
+ u->high = true;
518
+ pc++;
519
+ d++;
520
+ break;
521
+ case JS_REOP_goto:
522
+ pc += 5 + get_u32_le(p + 1);
523
+ break;
524
+ case JS_REOP_split_goto_first:
525
+ case JS_REOP_split_next_first:
526
+ walk(w, pc + 5, d);
527
+ walk(w, pc + 5 + get_u32_le(p + 1), d);
528
+ return;
529
+ case JS_REOP_save_start:
530
+ case JS_REOP_save_end:
531
+ pc += 2;
532
+ break;
533
+ case JS_REOP_save_reset:
534
+ pc += 3;
535
+ break;
536
+ case JS_REOP_line_start:
537
+ case JS_REOP_line_start_m:
538
+ case JS_REOP_line_end:
539
+ case JS_REOP_line_end_m:
540
+ case JS_REOP_word_boundary:
541
+ case JS_REOP_word_boundary_i:
542
+ case JS_REOP_not_word_boundary:
543
+ case JS_REOP_not_word_boundary_i:
544
+ pc++; /* zero-width: the next op's unit is at the same position */
545
+ break;
546
+ default: /* any, match, loops, lookarounds, back references: anything from here on */
547
+ w->limit = d;
548
+ return;
549
+ }
550
+ }
551
+ }
552
+
553
+ /* The share of printable ASCII the sets let through, roughly: a filter that lets most
554
+ * positions through only adds a call per position. */
555
+ static double selectivity(const Prefilter *f) {
556
+ double share = 1;
557
+ for (int k = 0; k < f->depth; k++) {
558
+ int n = 0;
559
+ for (uint32_t c = 0x20; c < 0x7F; c++) n += has_unit_in(&f->at[k], c);
560
+ share *= n / 95.0;
561
+ }
562
+ return share;
563
+ }
564
+
565
+ /* The prefilter of re's bytecode, made at its first long search; NULL when it has none. */
566
+ static Prefilter *analyze(RegExpObject *re) {
567
+ re->analyzed = true;
568
+ const uint8_t *bc = re->bytecode;
569
+ uint32_t code_length = (uint32_t)(bytecode_size(bc) - JS_RE_HEADER_LEN);
570
+ const uint8_t *p = bc + JS_RE_HEADER_LEN;
571
+ /* the search loop, as js_regexp_anchor reads it (an anchored pattern has a goto instead) */
572
+ if (code_length < 13 || p[0] != JS_REOP_split_goto_first || p[5] != JS_REOP_any || p[6] != JS_REOP_goto || p[11] != JS_REOP_save_start)
573
+ return NULL;
574
+ Walk w = { .code = p, .length = code_length, .unicode = (lre_get_flags(bc) & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) != 0,
575
+ .limit = PREFIX };
576
+ walk(&w, 11, 0);
577
+ if (w.limit == 0) return NULL;
578
+ Prefilter *f = calloc(1, sizeof *f);
579
+ if (!f) js_fatal("out of memory analyzing a regular expression");
580
+ f->depth = w.limit;
581
+ memcpy(f->at, w.at, sizeof f->at);
582
+ if (selectivity(f) > 0.2) {
583
+ free(f);
584
+ return NULL;
585
+ }
586
+ f->single = -1;
587
+ if (!f->at[0].high) {
588
+ int n = 0;
589
+ for (int c = 0; c < 0x100; c++)
590
+ if (has_unit_in(&f->at[0], (uint32_t)c)) f->single = n++ ? -1 : c;
591
+ if (n != 1) f->single = -1;
592
+ }
593
+ size_t size = bytecode_size(bc);
594
+ f->once = malloc(size);
595
+ if (!f->once) js_fatal("out of memory analyzing a regular expression");
596
+ memcpy(f->once, bc, size);
597
+ f->once[JS_RE_HEADER_LEN] = JS_REOP_goto; /* same offset: straight to save_start */
598
+ return f;
599
+ }
600
+
601
+ /* The first position from `from` where the prefilter's units all fit, or UINT32_MAX. */
602
+ static uint32_t next_start(const Prefilter *f, const uint8_t *buf, int shift, uint32_t from, uint32_t length) {
603
+ if (length < (uint32_t)f->depth) return UINT32_MAX;
604
+ uint32_t last = length - (uint32_t)f->depth; /* a match starts no later */
605
+ for (uint32_t i = from; i <= last; i++) {
606
+ if (!shift && f->single >= 0) {
607
+ const uint8_t *hit = memchr(buf + i, f->single, last - i + 1);
608
+ if (!hit) return UINT32_MAX;
609
+ i = (uint32_t)(hit - buf);
610
+ }
611
+ int k = 0;
612
+ for (; k < f->depth; k++) {
613
+ uint32_t c = shift ? ((const uint16_t *)buf)[i + k] : buf[i + k];
614
+ if (!has_unit_in(&f->at[k], c)) break;
615
+ }
616
+ if (k == f->depth) return i;
617
+ }
618
+ return UINT32_MAX;
619
+ }
620
+
621
+ /* Runs re's matcher on the string from `from` (at most its length): 1 with capture[] set (from
622
+ * lre_exec's alloc count), 0 for no match, or LRE_RET_* < 0. */
623
+ static int run(JSContext *ctx, RegExpObject *re, JSStrView v, uint32_t from, uint8_t **capture) {
624
+ const uint8_t *buf = v.chars;
625
+ int shift = v.latin1 ? 0 : 1;
626
+ int flags = lre_get_flags(re->bytecode);
627
+ if (re->one_class) return scan_class(re, buf, shift, from, v.length, (flags & LRE_FLAG_STICKY) != 0, capture);
628
+ /* surrogate pairs: lre_exec steps over them as code points, the scan doesn't */
629
+ bool pairs = !v.latin1 && (flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS));
630
+ if (!re->analyzed && v.length - from >= 64 && !pairs) re->prefilter = analyze(re);
631
+ const Prefilter *f = re->prefilter;
632
+ if (!f || pairs) return lre_exec(capture, re->bytecode, buf, (int)from, (int)v.length, shift, ctx);
633
+ for (uint32_t i = from;; i++) {
634
+ i = next_start(f, buf, shift, i, v.length);
635
+ if (i == UINT32_MAX) return 0;
636
+ int rc = lre_exec(capture, f->once, buf, (int)i, (int)v.length, shift, ctx);
637
+ if (rc != 0) return rc;
638
+ }
639
+ }
640
+
374
641
  static void set_matcher(JSContext *ctx, RegExpObject *re, JSValue source, const uint8_t *bytecode, bool owned) {
375
642
  if (re->owned) free((void *)re->bytecode);
643
+ free_prefilter(re);
376
644
  js_store(ctx, &re->obj.cell, &re->source, source);
377
645
  re->bytecode = bytecode;
378
646
  re->owned = owned;
@@ -407,13 +675,7 @@ static void copy_matcher(JSContext *ctx, RegExpObject *to, const RegExpObject *f
407
675
  return;
408
676
  }
409
677
  const uint8_t *bc = from->bytecode;
410
- /* header, bytecode, then the group names (NUL-terminated, each with a trailer) */
411
- size_t size = 8 + (size_t)(bc[4] | bc[5] << 8 | bc[6] << 16 | (uint32_t)bc[7] << 24);
412
- const char *names = lre_get_groupnames(bc);
413
- if (names) {
414
- for (int i = 1; i < lre_get_capture_count(bc); i++) names += strlen(names) + LRE_GROUP_NAME_TRAILER_LEN;
415
- size = (size_t)((const uint8_t *)names - bc);
416
- }
678
+ size_t size = bytecode_size(bc);
417
679
  uint8_t *copy = malloc(size);
418
680
  if (!copy) js_fatal("out of memory copying a regular expression");
419
681
  memcpy(copy, bc, size);
@@ -570,6 +832,8 @@ FLAG_GETTER(regexp_unicode, LRE_FLAG_UNICODE, "unicode")
570
832
  FLAG_GETTER(regexp_unicode_sets, LRE_FLAG_UNICODE_SETS, "unicodeSets")
571
833
  FLAG_GETTER(regexp_sticky, LRE_FLAG_STICKY, "sticky")
572
834
 
835
+ static const char *const flag_names[] = { "hasIndices", "global", "ignoreCase", "multiline", "dotAll", "unicode", "unicodeSets", "sticky" };
836
+
573
837
  /* get RegExp.prototype.flags (§22.2.6.4): from the flag getters, observably. */
574
838
  BUILTIN(regexp_flags) {
575
839
  UNUSED_ARGS;
@@ -578,11 +842,10 @@ BUILTIN(regexp_flags) {
578
842
  js_describe_value(ctx, this_val, what, sizeof what);
579
843
  return js_throw_type_error(ctx, "RegExp.prototype.flags getter called on non-object %s", what);
580
844
  }
581
- static const char *const names[] = { "hasIndices", "global", "ignoreCase", "multiline", "dotAll", "unicode", "unicodeSets", "sticky" };
582
845
  char out[9];
583
846
  size_t n = 0;
584
847
  for (int i = 0; i < 8; i++) {
585
- JSValue v = js_get_value(ctx, this_val, js_atom_from_cstr(ctx, names[i]));
848
+ JSValue v = js_get_value(ctx, this_val, js_atom_from_cstr(ctx, flag_names[i]));
586
849
  if (js_is_exception(v)) return v;
587
850
  if (js_truthy(v)) out[n++] = flag_chars[i];
588
851
  }
@@ -674,9 +937,7 @@ static JSValue builtin_exec(JSContext *ctx, JSValue rx, JSValue s) {
674
937
  if (!capture) js_fatal("out of memory matching a regular expression");
675
938
  int shift = v.latin1 ? 0 : 1;
676
939
  const uint8_t *buf = v.chars;
677
- int rc = last_index > v.length ? 2
678
- : re->one_class ? scan_class(re, buf, shift, (uint32_t)last_index, v.length, (flags & LRE_FLAG_STICKY) != 0, capture)
679
- : lre_exec(capture, bc, buf, (int)last_index, (int)v.length, shift, ctx);
940
+ int rc = last_index > v.length ? 2 : run(ctx, re, v, (uint32_t)last_index, capture);
680
941
  JSValue result = JS_NULL;
681
942
  if (rc < 0) {
682
943
  result = throw_exec_failure(ctx, rc);
@@ -776,11 +1037,139 @@ BUILTIN(regexp_proto_exec) {
776
1037
  return js_is_exception(s) ? s : builtin_exec(ctx, this_val, s);
777
1038
  }
778
1039
 
1040
+ /* ---- Unmodified RegExps --------------------------------------------------------------------
1041
+ *
1042
+ * The methods below follow the spec step by step: Get(rx, "flags") through the flag getters,
1043
+ * RegExpExec through `exec`, lastIndex read and written as a property, a match array for every
1044
+ * match. For a RegExp as created, on a RegExp.prototype whose methods are the originals, none of
1045
+ * that is observable: test, @@match, @@replace, @@search and @@split then run the matcher
1046
+ * directly and make only what they return (V8 does the same). Checked at every call, after the
1047
+ * steps that can run code (ToString of the arguments). */
1048
+
1049
+ static const JSNativeFn flag_fns[] = { regexp_has_indices, regexp_global, regexp_ignore_case, regexp_multiline,
1050
+ regexp_dot_all, regexp_unicode, regexp_unicode_sets, regexp_sticky };
1051
+
1052
+ static bool is_builtin(JSValue f, JSNativeFn fn) { return js_is_function(f) && js_as_function(f)->entry == fn; }
1053
+
1054
+ /* o's own `key` is a data property holding the built-in fn, or (getter) an accessor whose getter is. */
1055
+ static bool own_builtin(JSObject *o, JSValue key, JSNativeFn fn, bool getter) {
1056
+ uint8_t attrs;
1057
+ int32_t slot = js_shape_find(o->shape, key, &attrs);
1058
+ if (slot < 0 || ((attrs & JS_PROP_ACCESSOR) != 0) != getter) return false;
1059
+ JSValue v = *js_object_slot(o, (uint32_t)slot);
1060
+ return is_builtin(getter ? ((JSAccessorPair *)js_as_cell(v))->get : v, fn);
1061
+ }
1062
+
1063
+ /* rx, when it's a RegExp whose one own property is lastIndex, writable and a number, on the
1064
+ * realm's RegExp.prototype with the original exec: RegExpExec is then RegExpBuiltinExec, and
1065
+ * lastIndex reads and writes as its slot. Else NULL. */
1066
+ static RegExpObject *unmodified(JSContext *ctx, JSValue rx) {
1067
+ RegExpObject *re = as_regexp(rx);
1068
+ if (!re) return NULL;
1069
+ JSShape *s = re->obj.shape;
1070
+ JSValue proto = ctx->intrinsics[JS_INTRINSIC_REGEXP_PROTOTYPE];
1071
+ if (s->prop_count != 1 || (s->flags & JS_SHAPE_DICTIONARY) || !js_same_bits(s->proto, proto) ||
1072
+ !js_same_bits(s->key, JS_ATOM(lastIndex)) || s->attrs != JS_PROP_WRITABLE || !js_is_number(*js_object_slot(&re->obj, 0)))
1073
+ return NULL;
1074
+ return own_builtin(js_as_object(proto), JS_ATOM(exec), regexp_proto_exec, false) ? re : NULL;
1075
+ }
1076
+
1077
+ /* Get(rx, "flags") on an unmodified RegExp runs the original getters only: its flags are its
1078
+ * bytecode's. */
1079
+ static bool flags_unmodified(JSContext *ctx) {
1080
+ JSObject *p = js_as_object(ctx->intrinsics[JS_INTRINSIC_REGEXP_PROTOTYPE]);
1081
+ if (!own_builtin(p, JS_ATOM(flags), regexp_flags, true)) return false;
1082
+ for (int i = 0; i < 8; i++)
1083
+ if (!own_builtin(p, js_atom_from_cstr(ctx, flag_names[i]), flag_fns[i], true)) return false;
1084
+ return true;
1085
+ }
1086
+
1087
+ static double slot_last_index(JSContext *ctx, RegExpObject *re) {
1088
+ double d;
1089
+ if (to_length(ctx, *js_object_slot(&re->obj, 0), &d) < 0) js_fatal("ToLength of a number");
1090
+ return d;
1091
+ }
1092
+
1093
+ static void store_last_index(JSContext *ctx, RegExpObject *re, double index) {
1094
+ js_store(ctx, &re->obj.cell, js_object_slot(&re->obj, 0), js_number(index));
1095
+ }
1096
+
1097
+ /* The index in v of a position lre_exec gave. */
1098
+ static uint32_t index_of(JSStrView v, const uint8_t *p) {
1099
+ return (uint32_t)((p - (const uint8_t *)v.chars) >> (v.latin1 ? 0 : 1));
1100
+ }
1101
+
1102
+ /* lre_exec's capture[] for bc: `small`, or malloc'd (free it when it isn't `small`). */
1103
+ static uint8_t **capture_buffer(const uint8_t *bc, uint8_t **small) {
1104
+ int n = lre_get_alloc_count(bc);
1105
+ if (n <= 32) return small;
1106
+ uint8_t **capture = malloc(sizeof *capture * (size_t)n);
1107
+ if (!capture) js_fatal("out of memory matching a regular expression");
1108
+ return capture;
1109
+ }
1110
+
1111
+ /* test(s) (§22.2.6.16) on an unmodified RegExp: RegExpBuiltinExec without the match array. */
1112
+ static JSValue test_unmodified(JSContext *ctx, RegExpObject *re, JSValue s) {
1113
+ int flags = lre_get_flags(re->bytecode);
1114
+ bool global_or_sticky = (flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) != 0;
1115
+ double from = global_or_sticky ? slot_last_index(ctx, re) : 0;
1116
+ JSStrView v = js_string_view(ctx, s);
1117
+ uint8_t *small[32], **capture = capture_buffer(re->bytecode, small);
1118
+ int rc = from > v.length ? 0 : run(ctx, re, v, (uint32_t)from, capture);
1119
+ if (rc > 0 && global_or_sticky) store_last_index(ctx, re, index_of(v, capture[1]));
1120
+ if (rc == 0 && global_or_sticky) store_last_index(ctx, re, 0);
1121
+ if (capture != small) free(capture);
1122
+ return rc < 0 ? throw_exec_failure(ctx, rc) : js_bool(rc > 0);
1123
+ }
1124
+
1125
+ /* @@match with the g flag on an unmodified RegExp: the matched strings, or null. */
1126
+ static JSValue match_global_unmodified(JSContext *ctx, RegExpObject *re, JSValue s) {
1127
+ bool unicode = (lre_get_flags(re->bytecode) & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) != 0;
1128
+ uint8_t *small[32], **capture = capture_buffer(re->bytecode, small);
1129
+ JSValue a = js_new_array(ctx, 0), result;
1130
+ double i = 0;
1131
+ for (;;) {
1132
+ JSStrView v = js_string_view(ctx, s);
1133
+ int rc = i > v.length ? 0 : run(ctx, re, v, (uint32_t)i, capture);
1134
+ if (rc < 0) {
1135
+ store_last_index(ctx, re, i);
1136
+ result = throw_exec_failure(ctx, rc);
1137
+ break;
1138
+ }
1139
+ if (rc == 0) {
1140
+ store_last_index(ctx, re, 0);
1141
+ result = js_array_length(js_as_object(a)) ? a : JS_NULL;
1142
+ break;
1143
+ }
1144
+ uint32_t start = index_of(v, capture[0]), end = index_of(v, capture[1]);
1145
+ JSValue m = js_substring(ctx, s, start, end);
1146
+ if (js_is_exception(m) || js_array_push(ctx, js_as_object(a), m) < 0) {
1147
+ result = JS_EXCEPTION;
1148
+ break;
1149
+ }
1150
+ i = end == start ? advance_index(ctx, s, end, unicode) : end;
1151
+ }
1152
+ if (capture != small) free(capture);
1153
+ return result;
1154
+ }
1155
+
1156
+ /* @@search on an unmodified RegExp: lastIndex ends as it was, so it's left alone. */
1157
+ static JSValue search_unmodified(JSContext *ctx, RegExpObject *re, JSValue s) {
1158
+ JSStrView v = js_string_view(ctx, s);
1159
+ uint8_t *small[32], **capture = capture_buffer(re->bytecode, small);
1160
+ int rc = run(ctx, re, v, 0, capture);
1161
+ double index = rc > 0 ? (double)index_of(v, capture[0]) : -1;
1162
+ if (capture != small) free(capture);
1163
+ return rc < 0 ? throw_exec_failure(ctx, rc) : js_number(index);
1164
+ }
1165
+
779
1166
  BUILTIN(regexp_test) {
780
1167
  UNUSED_ARGS;
781
1168
  if (!js_is_object(this_val)) return throw_incompatible(ctx, "test", this_val);
782
1169
  JSValue s = js_to_string(ctx, ARG(0));
783
1170
  if (js_is_exception(s)) return s;
1171
+ RegExpObject *re = unmodified(ctx, this_val);
1172
+ if (re) return test_unmodified(ctx, re, s);
784
1173
  JSValue r = regexp_exec(ctx, this_val, s);
785
1174
  return js_is_exception(r) ? r : js_bool(!js_is_null(r));
786
1175
  }
@@ -805,6 +1194,9 @@ BUILTIN(regexp_symbol_match) {
805
1194
  if (!js_is_object(rx)) return throw_incompatible(ctx, "@@match", rx);
806
1195
  JSValue s = js_to_string(ctx, ARG(0));
807
1196
  if (js_is_exception(s)) return s;
1197
+ RegExpObject *re = unmodified(ctx, rx);
1198
+ if (re && flags_unmodified(ctx))
1199
+ return lre_get_flags(re->bytecode) & LRE_FLAG_GLOBAL ? match_global_unmodified(ctx, re, s) : builtin_exec(ctx, rx, s);
808
1200
  JSValue flags = js_get_value(ctx, rx, JS_ATOM(flags));
809
1201
  JSValue f = js_is_exception(flags) ? flags : js_to_string(ctx, flags);
810
1202
  if (js_is_exception(f)) return f;
@@ -1006,6 +1398,83 @@ static int get_substitution(JSContext *ctx, Builder *b, JSValue matched, JSValue
1006
1398
  return 0;
1007
1399
  }
1008
1400
 
1401
+ /* @@replace on an unmodified RegExp without named groups, of at most 32 captures. Every match is
1402
+ * found before a replacement function first runs, as in the spec, which collects the match
1403
+ * arrays first: positions only here, the strings made as each replacement needs them. */
1404
+ static JSValue replace_unmodified(JSContext *ctx, RegExpObject *re, JSValue s, JSValue replace_value, bool functional) {
1405
+ const uint8_t *bc = re->bytecode;
1406
+ int flags = lre_get_flags(bc);
1407
+ uint32_t ncap = (uint32_t)lre_get_capture_count(bc);
1408
+ bool global = (flags & LRE_FLAG_GLOBAL) != 0, sticky = (flags & LRE_FLAG_STICKY) != 0;
1409
+ bool unicode = (flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) != 0;
1410
+ uint32_t *pos = NULL; /* each match's captures: start and end, UINT32_MAX when unset */
1411
+ size_t count = 0, cap = 0;
1412
+ uint8_t *small[32], **capture = capture_buffer(bc, small);
1413
+ double i = global || !sticky ? 0 : slot_last_index(ctx, re);
1414
+ int rc;
1415
+ for (;;) {
1416
+ JSStrView v = js_string_view(ctx, s);
1417
+ rc = i > v.length ? 0 : run(ctx, re, v, (uint32_t)i, capture);
1418
+ if (rc <= 0) break;
1419
+ if (count == cap) {
1420
+ cap = cap ? cap * 2 : 8;
1421
+ uint32_t *grown = realloc(pos, cap * 2 * ncap * sizeof *pos);
1422
+ if (!grown) js_fatal("out of memory replacing");
1423
+ pos = grown;
1424
+ }
1425
+ uint32_t *m = pos + count++ * 2 * ncap;
1426
+ for (uint32_t k = 0; k < ncap; k++) {
1427
+ bool set = capture[2 * k] && capture[2 * k + 1];
1428
+ m[2 * k] = set ? index_of(v, capture[2 * k]) : UINT32_MAX;
1429
+ m[2 * k + 1] = set ? index_of(v, capture[2 * k + 1]) : UINT32_MAX;
1430
+ }
1431
+ if (!global) break;
1432
+ i = m[1] == m[0] ? advance_index(ctx, s, m[1], unicode) : m[1];
1433
+ }
1434
+ if (capture != small) free(capture);
1435
+ if (global) store_last_index(ctx, re, rc < 0 ? i : 0);
1436
+ else if (sticky && rc >= 0) store_last_index(ctx, re, rc ? pos[1] : 0);
1437
+ if (rc < 0) {
1438
+ free(pos);
1439
+ return throw_exec_failure(ctx, rc);
1440
+ }
1441
+ bool dollar = !functional && has_unit(ctx, replace_value, '$');
1442
+ Builder b = { 0 };
1443
+ uint32_t next = 0;
1444
+ for (size_t j = 0; j < count; j++) {
1445
+ const uint32_t *m = pos + j * 2 * ncap;
1446
+ if (m[0] > next) put_view(&b, js_string_view(ctx, s), next, m[0]);
1447
+ if (!functional && !dollar) {
1448
+ put_string(&b, ctx, replace_value);
1449
+ } else {
1450
+ JSValue args[34]; /* the match, its captures, the position, the string */
1451
+ for (uint32_t k = 0; k < ncap; k++) {
1452
+ args[k] = m[2 * k] == UINT32_MAX ? JS_UNDEFINED : js_substring(ctx, s, m[2 * k], m[2 * k + 1]);
1453
+ if (js_is_exception(args[k])) goto fail;
1454
+ }
1455
+ if (functional) {
1456
+ args[ncap] = js_number(m[0]);
1457
+ args[ncap + 1] = s;
1458
+ JSValue r = js_call(ctx, replace_value, JS_UNDEFINED, ncap + 2, args);
1459
+ JSValue replacement = js_is_exception(r) ? r : js_to_string(ctx, r);
1460
+ if (js_is_exception(replacement)) goto fail;
1461
+ put_string(&b, ctx, replacement);
1462
+ } else if (get_substitution(ctx, &b, args[0], s, m[0], args + 1, ncap - 1, JS_UNDEFINED, replace_value) < 0) {
1463
+ goto fail;
1464
+ }
1465
+ }
1466
+ next = m[1];
1467
+ }
1468
+ uint32_t length = js_string_length(s);
1469
+ if (next < length) put_view(&b, js_string_view(ctx, s), next, length);
1470
+ free(pos);
1471
+ return finish(ctx, &b);
1472
+ fail:
1473
+ free(pos);
1474
+ free(b.units);
1475
+ return JS_EXCEPTION;
1476
+ }
1477
+
1009
1478
  /* RegExp.prototype[@@replace](string, replaceValue) (§22.2.6.11) */
1010
1479
  BUILTIN(regexp_symbol_replace) {
1011
1480
  UNUSED_ARGS;
@@ -1019,6 +1488,9 @@ BUILTIN(regexp_symbol_replace) {
1019
1488
  replace_value = js_to_string(ctx, replace_value);
1020
1489
  if (js_is_exception(replace_value)) return replace_value;
1021
1490
  }
1491
+ RegExpObject *re = unmodified(ctx, rx);
1492
+ if (re && lre_get_capture_count(re->bytecode) <= 32 && !lre_get_groupnames(re->bytecode) && flags_unmodified(ctx))
1493
+ return replace_unmodified(ctx, re, s, replace_value, functional);
1022
1494
  JSValue flags = js_get_value(ctx, rx, JS_ATOM(flags));
1023
1495
  JSValue f = js_is_exception(flags) ? flags : js_to_string(ctx, flags);
1024
1496
  if (js_is_exception(f)) return f;
@@ -1121,6 +1593,8 @@ BUILTIN(regexp_symbol_search) {
1121
1593
  if (!js_is_object(rx)) return throw_incompatible(ctx, "@@search", rx);
1122
1594
  JSValue s = js_to_string(ctx, ARG(0));
1123
1595
  if (js_is_exception(s)) return s;
1596
+ RegExpObject *re = unmodified(ctx, rx);
1597
+ if (re) return search_unmodified(ctx, re, s);
1124
1598
  JSValue previous = js_get_value(ctx, rx, JS_ATOM(lastIndex));
1125
1599
  if (js_is_exception(previous)) return previous;
1126
1600
  if (!js_same_value(ctx, previous, js_int32(0)) && set_last_index(ctx, rx, 0) < 0) return JS_EXCEPTION;
@@ -1133,6 +1607,80 @@ BUILTIN(regexp_symbol_search) {
1133
1607
  return js_get_value(ctx, result, JS_ATOM(index));
1134
1608
  }
1135
1609
 
1610
+ /* @@split's splitter is new %RegExp%(rx, flags + "y") without running code when rx is unmodified,
1611
+ * RegExp.prototype's constructor and @@match and RegExp's @@species are the originals. */
1612
+ static bool species_unmodified(JSContext *ctx) {
1613
+ JSObject *p = js_as_object(ctx->intrinsics[JS_INTRINSIC_REGEXP_PROTOTYPE]);
1614
+ JSValue c = ctx->intrinsics[JS_INTRINSIC_REGEXP];
1615
+ uint8_t attrs;
1616
+ int32_t slot = js_shape_find(p->shape, JS_ATOM(constructor), &attrs);
1617
+ return slot >= 0 && !(attrs & JS_PROP_ACCESSOR) && js_same_bits(*js_object_slot(p, (uint32_t)slot), c) &&
1618
+ own_builtin(p, JS_SYMBOL(match), regexp_symbol_match, false) && own_builtin(js_as_object(c), JS_SYMBOL(species), regexp_species, true);
1619
+ }
1620
+
1621
+ /* @@split on an unmodified RegExp without the y, u or v flags. The spec's splitter tries a
1622
+ * sticky match at each position q in turn; a search from q finds the first q that matches, with
1623
+ * the same match there, and no splitter is made. */
1624
+ static JSValue split_unmodified(JSContext *ctx, RegExpObject *re, JSValue s, uint32_t lim) {
1625
+ JSValue a = js_new_array(ctx, 0);
1626
+ if (lim == 0) return a;
1627
+ uint32_t ncap = (uint32_t)lre_get_capture_count(re->bytecode);
1628
+ uint8_t *small[32], **capture = capture_buffer(re->bytecode, small);
1629
+ uint32_t size = js_string_length(s), p = 0, q = 0;
1630
+ JSValue result = a;
1631
+ if (size == 0) {
1632
+ int rc = run(ctx, re, js_string_view(ctx, s), 0, capture);
1633
+ if (rc < 0) result = throw_exec_failure(ctx, rc);
1634
+ else if (rc == 0 && js_array_push(ctx, js_as_object(a), s) < 0) result = JS_EXCEPTION;
1635
+ goto out;
1636
+ }
1637
+ while (q < size) {
1638
+ JSStrView v = js_string_view(ctx, s);
1639
+ int rc = run(ctx, re, v, q, capture);
1640
+ if (rc < 0) {
1641
+ result = throw_exec_failure(ctx, rc);
1642
+ goto out;
1643
+ }
1644
+ if (rc == 0) break;
1645
+ uint32_t start = index_of(v, capture[0]), e = index_of(v, capture[1]);
1646
+ if (start >= size) break; /* the spec's loop stops before trying the end */
1647
+ if (e == p) { /* empty, where the last piece ended */
1648
+ q = start + 1;
1649
+ continue;
1650
+ }
1651
+ uint32_t bounds[64]; /* the captures, read before allocating */
1652
+ for (uint32_t k = 1; k < ncap && k < 33; k++) {
1653
+ bool set = capture[2 * k] && capture[2 * k + 1];
1654
+ bounds[2 * (k - 1)] = set ? index_of(v, capture[2 * k]) : UINT32_MAX;
1655
+ bounds[2 * (k - 1) + 1] = set ? index_of(v, capture[2 * k + 1]) : UINT32_MAX;
1656
+ }
1657
+ JSValue sub = js_substring(ctx, s, p, start);
1658
+ if (js_is_exception(sub) || js_array_push(ctx, js_as_object(a), sub) < 0) {
1659
+ result = JS_EXCEPTION;
1660
+ goto out;
1661
+ }
1662
+ if (js_array_length(js_as_object(a)) == lim) goto out;
1663
+ p = e;
1664
+ for (uint32_t k = 1; k < ncap; k++) {
1665
+ uint32_t lo = bounds[2 * (k - 1)], hi = bounds[2 * (k - 1) + 1];
1666
+ JSValue c = lo == UINT32_MAX ? JS_UNDEFINED : js_substring(ctx, s, lo, hi);
1667
+ if (js_is_exception(c) || js_array_push(ctx, js_as_object(a), c) < 0) {
1668
+ result = JS_EXCEPTION;
1669
+ goto out;
1670
+ }
1671
+ if (js_array_length(js_as_object(a)) == lim) goto out;
1672
+ }
1673
+ q = p;
1674
+ }
1675
+ {
1676
+ JSValue tail = js_substring(ctx, s, p < size ? p : size, size);
1677
+ if (js_is_exception(tail) || js_array_push(ctx, js_as_object(a), tail) < 0) result = JS_EXCEPTION;
1678
+ }
1679
+ out:
1680
+ if (capture != small) free(capture);
1681
+ return result;
1682
+ }
1683
+
1136
1684
  /* RegExp.prototype[@@split](string, limit) (§22.2.6.14) */
1137
1685
  BUILTIN(regexp_symbol_split) {
1138
1686
  UNUSED_ARGS;
@@ -1140,6 +1688,11 @@ BUILTIN(regexp_symbol_split) {
1140
1688
  if (!js_is_object(rx)) return throw_incompatible(ctx, "@@split", rx);
1141
1689
  JSValue s = js_to_string(ctx, ARG(0));
1142
1690
  if (js_is_exception(s)) return s;
1691
+ RegExpObject *re = unmodified(ctx, rx);
1692
+ JSValue limit = ARG(1);
1693
+ if (re && !(lre_get_flags(re->bytecode) & (LRE_FLAG_STICKY | LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) &&
1694
+ (js_is_undefined(limit) || js_is_number(limit)) && flags_unmodified(ctx) && species_unmodified(ctx))
1695
+ return split_unmodified(ctx, re, s, js_is_undefined(limit) ? UINT32_MAX : (uint32_t)js_double_to_int32(js_as_number(limit)));
1143
1696
  JSValue c = species_constructor(ctx, rx);
1144
1697
  if (js_is_exception(c)) return c;
1145
1698
  JSValue flags = js_get_value(ctx, rx, JS_ATOM(flags));
@@ -1253,6 +1806,10 @@ static const JSBuiltin regexp_string_iterator_proto[] = {
1253
1806
  };
1254
1807
 
1255
1808
  static void install_regexp(JSContext *ctx, JSObject *global) {
1809
+ for (uint32_t c = 0; c < 0x100; c++) { /* the same in every realm */
1810
+ canonical[0][c] = (uint32_t)lre_canonicalize(c, 0);
1811
+ canonical[1][c] = (uint32_t)lre_canonicalize(c, 1);
1812
+ }
1256
1813
  JSValue proto = js_new_object_proto(ctx, ctx->intrinsics[JS_INTRINSIC_OBJECT_PROTOTYPE], 8);
1257
1814
  ctx->intrinsics[JS_INTRINSIC_REGEXP_PROTOTYPE] = proto;
1258
1815
  JSValue key = js_atom_from_cstr(ctx, "RegExp");