re2js 2.7.0 → 2.8.0

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.
@@ -21,6 +21,12 @@
21
21
  * @author rsc@google.com (Russ Cox)
22
22
  */
23
23
  export class Matcher {
24
+ /**
25
+ * V8 and WebKit have historical hard limits on the number of arguments
26
+ * that can be passed to a function. We cap replacer arguments to prevent
27
+ * Call Stack Overflow (DoS) vulnerabilities on massive ASTs.
28
+ */
29
+ static MAX_REPLACER_ARGS: number;
24
30
  /**
25
31
  * Quotes '\' and '$' in {@code s}, so that the returned string could be used in
26
32
  * {@link #appendReplacement} as a literal replacement of {@code s}.
@@ -210,31 +216,51 @@ export class Matcher {
210
216
  * Returns the input with all matches replaced by {@code replacement}, interpreted as for
211
217
  * {@code appendReplacement}.
212
218
  *
213
- * @param {string} replacement - the replacement string
219
+ * @param {string|Function} replacement - the replacement string or a replacer function
214
220
  * @param {boolean} [javaMode=false] - activate java mode (different behaviour for capture groups and special characters)
215
221
  * @returns {string} the input string with the matches replaced
216
222
  * @throws IndexOutOfBoundsException if replacement refers to an invalid group and javaMode is true
217
223
  */
218
- replaceAll(replacement: string, javaMode?: boolean): string;
224
+ replaceAll(replacement: string | Function, javaMode?: boolean): string;
219
225
  /**
220
226
  * Returns the input with the first match replaced by {@code replacement}, interpreted as for
221
227
  * {@code appendReplacement}.
222
228
  *
223
- * @param {string} replacement - the replacement string
229
+ * @param {string|Function} replacement - the replacement string or a replacer function
224
230
  * @param {boolean} [javaMode=false] - activate java mode (different behaviour for capture groups and special characters)
225
231
  * @returns {string} the input string with the first match replaced
226
232
  * @throws IndexOutOfBoundsException if replacement refers to an invalid group and javaMode is true
227
233
  */
228
- replaceFirst(replacement: string, javaMode?: boolean): string;
234
+ replaceFirst(replacement: string | Function, javaMode?: boolean): string;
229
235
  /**
230
236
  * Helper: replaceAll/replaceFirst hybrid.
231
- * @param {string} replacement - the replacement string
237
+ * @param {string|Function} replacement - the replacement string or a replacer function
232
238
  * @param {boolean} [all=true] - replace all matches
233
239
  * @param {boolean} [javaMode=false] - activate java mode (different behaviour for capture groups and special characters)
234
240
  * @returns {string}
235
241
  * @private
236
242
  */
237
243
  private replace;
244
+ /**
245
+ * Evaluates a replacer function for the current match and appends the result,
246
+ * along with any un-matched preceding text, advancing the append position.
247
+ * @param {Function} replacer - the replacer function
248
+ * @param {boolean} hasNamedGroups - cached flag if pattern has named groups
249
+ * @param {string|Uint8Array|number[]} originalInput - the cached original input reference
250
+ * @returns {string} the evaluated string to append
251
+ * @private
252
+ */
253
+ private appendReplacementFunc;
254
+ /**
255
+ * Builds the argument array for the replacer function matching the standard
256
+ * JS String.prototype.replace(regex, replacer) signature.
257
+ * @param {number} matchStart - the start index of the match
258
+ * @param {boolean} hasNamedGroups - cached flag if pattern has named groups
259
+ * @param {string|Uint8Array|number[]} originalInput - the cached original input reference
260
+ * @returns {Array} array of arguments
261
+ * @private
262
+ */
263
+ private buildReplacerArgs;
238
264
  }
239
265
  /**
240
266
  * A compiled representation of an RE2 regular expression
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.d.ts","sourceRoot":"","sources":["index.esm.js"],"names":[],"mappings":"AA22CA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH;IACE;;;;;;;OAOG;IACH,6BAJW,MAAM,aACN,OAAO,GACL,MAAM,CA2BlB;IACD;;;;OAIG;IACH,qBAHW,KAAK,SACL,MAAM,GAAC,MAAM,EAAE,GAAC,UAAU,EA6BpC;IAvBC;;;OAGG;IACH,cAFU,KAAK,CAEY;IAG3B,qBAAqB;IACrB,mBADW,MAAM,CACqC;IAEtD,uBAAuB;IACvB,QADW,MAAM,EAAE,CACH;IAChB,qCAAqC;IACrC,aADW,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CACC;IAClC,qBAAqB;IACrB,sBADW,MAAM,CACqC;IAUxD;;;OAGG;IACH,WAFa,KAAK,CAIjB;IAED;;;;OAIG;IACH,SAFa,OAAO,CAiBnB;IAbC,qBAAqB;IACrB,uCAAoD;IAEpD,qBAAqB;IACrB,8BAAkB;IAElB,8BAAqB;IAGrB,+BAAsB;IAEtB,+BAAmB;IAIrB;;;;OAIG;IACH,yBAHW,gBAAgB,GACd,OAAO,CASnB;IAHC,2CAAyB;IAK3B;;;;;OAKG;IACH,cAHW,MAAM,GAAC,MAAM,GACX,MAAM,CAYlB;IAED;;;;;OAKG;IACH,YAHW,MAAM,GAAC,MAAM,GACX,MAAM,CAYlB;IAED;;;;;;;;;OASG;IACH,eAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,cAHW,MAAM,GAAC,MAAM,GACX,MAAM,GAAC,IAAI,CAgBvB;IAED;;;;OAIG;IACH,kBAFa,MAAM,CAAC,MAAM,EAAE,MAAM,GAAC,IAAI,CAAC,CAWvC;IAED;;;;OAIG;IACH,cAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,kBAkBC;IAED;;;;;OAKG;IACH,WAFa,OAAO,CAInB;IAED;;;;;OAKG;IACH,aAFa,OAAO,CAInB;IAED;;;;;;;OAOG;IACH,aAJW,MAAM,GAAC,IAAI,GACT,OAAO,CA4BnB;IAED;;;;;;OAMG;IACH,iBAaC;IAED;;;;;OAKG;IACH,iBAJW,MAAM,OACN,MAAM,GACJ,MAAM,CAOlB;IAED;;;OAGG;IACH,eAFa,MAAM,CAIlB;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,0BAUC;IAED;;;;OAIG;IACH,sCAgEC;IAED;;;;OAIG;IACH,oCAuGC;IAED;;;;OAIG;IACH,cAFa,MAAM,CAIlB;IAED;;;;;;;;OAQG;IACH,wBALW,MAAM,aACN,OAAO,GACL,MAAM,CAKlB;IAED;;;;;;;;OAQG;IACH,0BALW,MAAM,aACN,OAAO,GACL,MAAM,CAKlB;IAED;;;;;;;OAOG;IACH,gBAWC;CACF;AAwtMD;;;;;;;;;GASG;AACH;IACE;;OAEG;IACH,gCAAuD;IACvD;;OAEG;IACH,sBAAmC;IACnC;;;OAGG;IACH,yBAAyC;IACzC;;OAEG;IACH,sCAAmE;IACnE;;OAEG;IACH,6BAAiD;IACjD;;OAEG;IACH,2BAA6C;IAE7C;;;;;;;;;;OAUG;IACH,kBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;;;;OASG;IACH,6BAJW,MAAM,aACN,OAAO,GACL,MAAM,CAIlB;IAED;;;;;;;;;;OAUG;IACH,6BAHW,MAAM,GAAC,MAAM,GACX,MAAM,CAIlB;IAED;;;;;OAKG;IACH,sBAJW,MAAM,UACN,MAAM,GACJ,KAAK,CA2BjB;IAED;;;;;;;OAOG;IACH,sBALW,MAAM,SACN,MAAM,GAAC,MAAM,EAAE,GAAC,UAAU,GACxB,OAAO,CAKnB;IAED;;;OAGG;IACH,wBAWC;IAED;;;;OAIG;IACH,qBAHW,MAAM,SACN,MAAM,EAOhB;IAHC,qBAA2B;IAE3B,mBAAuB;IAGzB;;;OAGG;IACH,cAEC;IAED;;;OAGG;IACH,SAFa,MAAM,CAIlB;IAED;;;OAGG;IACH,WAFa,MAAM,CAIlB;IACD,WAEC;IAED;;;;;OAKG;IACH,eAHW,MAAM,GAAC,MAAM,EAAE,GAAC,UAAU,GACxB,OAAO,CAInB;IAED;;;;;OAKG;IACH,eAHW,MAAM,GAAC,MAAM,EAAE,GAAC,UAAU,GACxB,OAAO,CAOnB;IAED;;;;;;;;OAQG;IACH,YAHW,MAAM,GAAC,MAAM,EAAE,GAAC,UAAU,GACxB,OAAO,CAUnB;IAED;;;;;;;;OAQG;IACH,iBAHW,MAAM,GAAC,MAAM,EAAE,GAAC,UAAU,GACxB,OAAO,CAKnB;IAED;;;;;;;;;;;;OAYG;IACH,aAJW,MAAM,UACN,MAAM,GACJ,MAAM,EAAE,CAgDpB;IAED;;;;;;OAMG;IACH,gBAHW,MAAM,GAAC,MAAM,EAAE,GAAC,UAAU,GACxB,gBAAgB,OAAO,CAiCnC;IAED;;;OAGG;IACH,YAFa,MAAM,CAIlB;IAED;;;;;;;;;OASG;IACH,eAFa,MAAM,CAIlB;IAED;;;;;OAKG;IACH,cAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,eAFa,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIlC;IAED;;;;OAIG;IACH,cAHW,GAAC,GACC,OAAO,CAUnB;CACF;AA9vOD;;GAEG;AACH;CAMC;AAxDD;IACE,8BAA8B;IAC9B,qBADY,MAAM,EAIjB;CACF;AA+DD;;GAEG;AACH;CAMC;AApBD;;GAEG;AACH;CAMC;AAaD;;GAEG;AACH;CAMC;AAjFD;;GAEG;AACH;IACE;;;OAGG;IACH,mBAHW,MAAM,UACN,MAAM,GAAC,IAAI,EAcrB;IAJC,qBAAqB;IACrB,OADW,MAAM,CACC;IAClB,0BAA0B;IAC1B,OADW,MAAM,GAAC,IAAI,CACJ;IAGpB;;;OAGG;IACH,kBAFa,MAAM,CAIlB;IAED;;;OAGG;IACH,cAFa,MAAM,GAAC,IAAI,CAIvB;CACF;AAqiND;IACE,qBAAqB;IACrB,mBADW,MAAM,CACuB;IACxC,qBAAqB;IACrB,qBADW,MAAM,CAC2B;IAC5C,qBAAqB;IACrB,oBADW,MAAM,CACyB;IAE1C;;;;OAIG;IACH,qBAHW,MAAM,UACN,MAAM,EAiBhB;IAdC,eAAoB;IACpB,gBAAoB;IAQpB,iBAAwB;IACxB,eAAiB;IACjB,kBAAgB;IAChB,gBAAe;IACf;;;;;;aAAoB;IAGtB;;;;;;OAMG;IACH,aAJW,MAAM,GACJ,MAAM,CAoBlB;IAED;;;;OAIG;IACH,WAFa,IAAI,CAahB;IAED;;;;OAIG;IACH,aAHW,MAAM,GAAC,MAAM,EAAE,GAAC,UAAU,GACxB,MAAM,EAAE,CAoBpB;CACF;AA4nBD,kHAUC;AA1qPD;;GAEG;AACH;IACE,qBAAkD;IAClD,oBAEC;IAED;;;OAGG;IACH,kBAFa,OAAO,CAInB;IAED;;;OAGG;IACH,mBAFa,OAAO,CAInB;CACF;AAm0GD;;GAEG;AACH;IAEI,YAAc;IACd,cAAc;IAGd,eAAe;IACf,gBAAkB;IAClB,cAAc;IAKhB,sBAEC;IAGD,kBAEC;IAID,uBAEC;IAID,sBAOC;IAKD,+BAWC;IAID,oBAoBC;IAeD,8BAYC;IACD,8BAYC;IACD;;;OAGG;IACH,YAFa,MAAM,CAelB;CACF;AArxDD;IACE,gCAA4B;IAC5B,uBASC;IARC,UAAgB;IAChB,0BAA2B;IAC3B,mBAAmB;IACnB,gBAAsB;IACtB,mBAAuB;IACvB,oBAAoB;IACpB,gBAAmB;IACnB,cAAc;IAIhB;;;;aAuCC;IAGD,wBAyDC;IACD,mBAyCC;IAGD,kDA+CC;IAGD,yDA+CC;IAGD,0DAwCC;CACF"}
1
+ {"version":3,"file":"index.esm.d.ts","sourceRoot":"","sources":["index.esm.js"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
  * re2js
3
3
  * RE2JS is the JavaScript port of RE2, a regular expression engine that provides linear time matching
4
4
  *
5
- * @version v2.7.0
5
+ * @version v2.8.0
6
6
  * @author Oleksii Vasyliev
7
7
  * @homepage https://github.com/le0pard/re2js#readme
8
8
  * @repository github:le0pard/re2js
@@ -1409,6 +1409,13 @@ class RE2JSInternalException extends RE2JSException {
1409
1409
  */
1410
1410
 
1411
1411
  class Matcher {
1412
+ /**
1413
+ * V8 and WebKit have historical hard limits on the number of arguments
1414
+ * that can be passed to a function. We cap replacer arguments to prevent
1415
+ * Call Stack Overflow (DoS) vulnerabilities on massive ASTs.
1416
+ */
1417
+ static MAX_REPLACER_ARGS = 65535;
1418
+
1412
1419
  /**
1413
1420
  * Quotes '\' and '$' in {@code s}, so that the returned string could be used in
1414
1421
  * {@link #appendReplacement} as a literal replacement of {@code s}.
@@ -1705,16 +1712,14 @@ class Matcher {
1705
1712
  * @private
1706
1713
  */
1707
1714
  genMatch(startByte, anchor) {
1708
- const hasLookbehinds = this.patternInput.re2().prog.numLb > 0;
1709
- const ngroup = hasLookbehinds ? 1 + this.patternGroupCount : 1;
1710
- const res = this.patternInput.re2().matchMachineInput(this.matcherInput, startByte, this.matcherInputLength, anchor, ngroup);
1715
+ const res = this.patternInput.re2().matchMachineInput(this.matcherInput, startByte, this.matcherInputLength, anchor, 1);
1711
1716
  const ok = res[0];
1712
1717
  if (!ok) {
1713
1718
  return false;
1714
1719
  }
1715
1720
  this.groups = res[1];
1716
1721
  this.hasMatch = true;
1717
- this.hasGroups = hasLookbehinds || this.patternGroupCount === 0;
1722
+ this.hasGroups = this.patternGroupCount === 0;
1718
1723
  this.anchorFlag = anchor;
1719
1724
  return true;
1720
1725
  }
@@ -1967,7 +1972,7 @@ class Matcher {
1967
1972
  * Returns the input with all matches replaced by {@code replacement}, interpreted as for
1968
1973
  * {@code appendReplacement}.
1969
1974
  *
1970
- * @param {string} replacement - the replacement string
1975
+ * @param {string|Function} replacement - the replacement string or a replacer function
1971
1976
  * @param {boolean} [javaMode=false] - activate java mode (different behaviour for capture groups and special characters)
1972
1977
  * @returns {string} the input string with the matches replaced
1973
1978
  * @throws IndexOutOfBoundsException if replacement refers to an invalid group and javaMode is true
@@ -1980,7 +1985,7 @@ class Matcher {
1980
1985
  * Returns the input with the first match replaced by {@code replacement}, interpreted as for
1981
1986
  * {@code appendReplacement}.
1982
1987
  *
1983
- * @param {string} replacement - the replacement string
1988
+ * @param {string|Function} replacement - the replacement string or a replacer function
1984
1989
  * @param {boolean} [javaMode=false] - activate java mode (different behaviour for capture groups and special characters)
1985
1990
  * @returns {string} the input string with the first match replaced
1986
1991
  * @throws IndexOutOfBoundsException if replacement refers to an invalid group and javaMode is true
@@ -1991,7 +1996,7 @@ class Matcher {
1991
1996
 
1992
1997
  /**
1993
1998
  * Helper: replaceAll/replaceFirst hybrid.
1994
- * @param {string} replacement - the replacement string
1999
+ * @param {string|Function} replacement - the replacement string or a replacer function
1995
2000
  * @param {boolean} [all=true] - replace all matches
1996
2001
  * @param {boolean} [javaMode=false] - activate java mode (different behaviour for capture groups and special characters)
1997
2002
  * @returns {string}
@@ -2000,8 +2005,21 @@ class Matcher {
2000
2005
  replace(replacement, all = true, javaMode = false) {
2001
2006
  let res = '';
2002
2007
  this.reset();
2008
+ const isFunc = typeof replacement === 'function';
2009
+
2010
+ // Cache named groups check to avoid GC thrashing on every match
2011
+ const hasNamedGroups = Object.keys(this.namedGroups).length > 0;
2012
+ let originalInput = null;
2013
+ if (isFunc) {
2014
+ // Prevent V8 Call Stack Overflow (DoS vector) on massive capture group counts
2015
+ if (this.groupCount() >= Matcher.MAX_REPLACER_ARGS) {
2016
+ throw new RE2JSGroupException('Too many capture groups to safely invoke replacer function');
2017
+ }
2018
+ // Resolve the original input reference exactly once outside the hot loop
2019
+ originalInput = this.matcherInput.isUTF8Encoding() ? this.matcherInput.asBytes() : this.matcherInput.asCharSequence();
2020
+ }
2003
2021
  while (this.find()) {
2004
- res += this.appendReplacement(replacement, javaMode);
2022
+ res += isFunc ? this.appendReplacementFunc(replacement, hasNamedGroups, originalInput) : this.appendReplacement(replacement, javaMode);
2005
2023
  if (!all) {
2006
2024
  break;
2007
2025
  }
@@ -2009,6 +2027,66 @@ class Matcher {
2009
2027
  res += this.appendTail();
2010
2028
  return res;
2011
2029
  }
2030
+
2031
+ /**
2032
+ * Evaluates a replacer function for the current match and appends the result,
2033
+ * along with any un-matched preceding text, advancing the append position.
2034
+ * @param {Function} replacer - the replacer function
2035
+ * @param {boolean} hasNamedGroups - cached flag if pattern has named groups
2036
+ * @param {string|Uint8Array|number[]} originalInput - the cached original input reference
2037
+ * @returns {string} the evaluated string to append
2038
+ * @private
2039
+ */
2040
+ appendReplacementFunc(replacer, hasNamedGroups, originalInput) {
2041
+ let res = '';
2042
+ const s = this.start();
2043
+ const e = this.end();
2044
+ if (this.appendPos < s) {
2045
+ res += this.substring(this.appendPos, s);
2046
+ }
2047
+ this.appendPos = e;
2048
+ const args = this.buildReplacerArgs(s, hasNamedGroups, originalInput);
2049
+ res += String(replacer(...args));
2050
+ return res;
2051
+ }
2052
+
2053
+ /**
2054
+ * Builds the argument array for the replacer function matching the standard
2055
+ * JS String.prototype.replace(regex, replacer) signature.
2056
+ * @param {number} matchStart - the start index of the match
2057
+ * @param {boolean} hasNamedGroups - cached flag if pattern has named groups
2058
+ * @param {string|Uint8Array|number[]} originalInput - the cached original input reference
2059
+ * @returns {Array} array of arguments
2060
+ * @private
2061
+ */
2062
+ buildReplacerArgs(matchStart, hasNamedGroups, originalInput) {
2063
+ const args = [this.group(0)]; // match
2064
+
2065
+ const numGroups = this.groupCount();
2066
+ // Fast-path capture group extraction
2067
+ for (let i = 1; i <= numGroups; i++) {
2068
+ const start = this.start(i);
2069
+ if (start < 0) {
2070
+ args.push(void 0);
2071
+ } else {
2072
+ args.push(this.substring(start, this.end(i)));
2073
+ }
2074
+ }
2075
+ args.push(matchStart); // offset
2076
+ args.push(originalInput); // original string (cached)
2077
+
2078
+ // Append named groups object if pattern contains them
2079
+ if (hasNamedGroups) {
2080
+ const parsedGroups = this.getNamedGroups();
2081
+ for (const key in parsedGroups) {
2082
+ if (parsedGroups[key] === null) {
2083
+ parsedGroups[key] = void 0;
2084
+ }
2085
+ }
2086
+ args.push(parsedGroups);
2087
+ }
2088
+ return args;
2089
+ }
2012
2090
  }
2013
2091
 
2014
2092
  /**
@@ -2335,30 +2413,35 @@ class Machine {
2335
2413
  }
2336
2414
  this.matched = false;
2337
2415
  this.matchcap.fill(-1);
2416
+
2417
+ // Lookbehinds must scan from the beginning of the string to build their state table,
2418
+ // even if the main pattern search is requested to start mid-string.
2419
+ let currentPos = this.prog.numLb > 0 ? 0 : pos;
2420
+ let matchStartPos = pos;
2338
2421
  let runq = this.q0;
2339
2422
  let nextq = this.q1;
2340
- let r = input.step(pos);
2423
+ let r = input.step(currentPos);
2341
2424
  let rune = r >> 3;
2342
2425
  let width = r & 7;
2343
2426
  let rune1 = -1;
2344
2427
  let width1 = 0;
2345
2428
  if (r !== MachineInputBase.EOF()) {
2346
- r = input.step(pos + width);
2429
+ r = input.step(currentPos + width);
2347
2430
  rune1 = r >> 3;
2348
2431
  width1 = r & 7;
2349
2432
  }
2350
2433
  let flag;
2351
- if (pos === 0) {
2434
+ if (currentPos === 0) {
2352
2435
  flag = Utils.emptyOpContext(-1, rune);
2353
2436
  } else {
2354
- flag = input.context(pos);
2437
+ flag = input.context(currentPos);
2355
2438
  }
2356
2439
  while (true) {
2357
2440
  if (runq.isEmpty()) {
2358
- if ((startCond & Utils.EMPTY_BEGIN_TEXT) !== 0 && pos !== 0) {
2441
+ if ((startCond & Utils.EMPTY_BEGIN_TEXT) !== 0 && currentPos !== 0) {
2359
2442
  break;
2360
2443
  }
2361
- if ((anchor === RE2Flags.ANCHOR_START || anchor === RE2Flags.ANCHOR_BOTH) && pos !== 0) {
2444
+ if ((anchor === RE2Flags.ANCHOR_START || anchor === RE2Flags.ANCHOR_BOTH) && currentPos !== 0) {
2362
2445
  break;
2363
2446
  }
2364
2447
  if (this.matched) {
@@ -2368,43 +2451,50 @@ class Machine {
2368
2451
  // Fast-forwarding the string pointer will skip over the positions where
2369
2452
  // the parallel lookbehind automata need to be spawned.
2370
2453
  if (this.prog.numLb === 0 && !(this.re2.prefix.length === 0) && rune1 !== this.re2.prefixRune && input.canCheckPrefix()) {
2371
- const advance = input.index(this.re2, pos);
2454
+ const advance = input.index(this.re2, currentPos);
2372
2455
  if (advance < 0) {
2373
2456
  break;
2374
2457
  }
2375
- pos += advance;
2376
- r = input.step(pos);
2458
+ currentPos += advance;
2459
+ r = input.step(currentPos);
2377
2460
  rune = r >> 3;
2378
2461
  width = r & 7;
2379
- r = input.step(pos + width);
2462
+ r = input.step(currentPos + width);
2380
2463
  rune1 = r >> 3;
2381
2464
  width1 = r & 7;
2382
2465
  }
2383
2466
  }
2384
- if (!this.matched && (pos === 0 || anchor === RE2Flags.UNANCHORED)) {
2385
- if (this.ncap > 0) {
2386
- this.matchcap[0] = pos;
2387
- }
2388
- // Spawn Lookbehind threads BEFORE the main pattern
2467
+
2468
+ // Optimize lookbehind spawning. Because lookbehinds are prefixed with `.*` by the compiler,
2469
+ // they only need to be spawned exactly once at the beginning of the string (currentPos === 0).
2470
+ if (currentPos === 0 && this.prog.numLb > 0) {
2389
2471
  for (let i = 0; i < this.prog.lbStarts.length; i++) {
2390
- this.add(runq, this.prog.lbStarts[i], pos, this.matchcap, flag, null);
2472
+ this.add(runq, this.prog.lbStarts[i], currentPos, this.matchcap, flag, null);
2473
+ }
2474
+ }
2475
+ if (!this.matched && (currentPos === 0 || anchor === RE2Flags.UNANCHORED)) {
2476
+ // ONLY spawn the main pattern if we have reached the requested search start boundary
2477
+ if (currentPos >= matchStartPos) {
2478
+ if (this.ncap > 0) {
2479
+ this.matchcap[0] = currentPos;
2480
+ }
2481
+ this.add(runq, this.prog.start, currentPos, this.matchcap, flag, null);
2391
2482
  }
2392
- this.add(runq, this.prog.start, pos, this.matchcap, flag, null);
2393
2483
  }
2394
- const nextPos = pos + width;
2484
+ const nextPos = currentPos + width;
2395
2485
  flag = input.context(nextPos);
2396
- this.step(runq, nextq, pos, nextPos, rune, flag, anchor, pos === input.endPos());
2486
+ this.step(runq, nextq, currentPos, nextPos, rune, flag, anchor, currentPos === input.endPos());
2397
2487
  if (width === 0) {
2398
2488
  break;
2399
2489
  }
2400
2490
  if (this.ncap === 0 && this.matched) {
2401
2491
  break;
2402
2492
  }
2403
- pos += width;
2493
+ currentPos += width;
2404
2494
  rune = rune1;
2405
2495
  width = width1;
2406
2496
  if (rune !== -1) {
2407
- r = input.step(pos + width);
2497
+ r = input.step(currentPos + width);
2408
2498
  rune1 = r >> 3;
2409
2499
  width1 = r & 7;
2410
2500
  }
@@ -2421,35 +2511,46 @@ class Machine {
2421
2511
  if ((anchor === RE2Flags.ANCHOR_START || anchor === RE2Flags.ANCHOR_BOTH) && pos !== 0) {
2422
2512
  return [];
2423
2513
  }
2514
+
2515
+ // Lookbehinds must scan from the beginning of the string to build their state table,
2516
+ // even if the main pattern search is requested to start mid-string.
2517
+ let currentPos = this.prog.numLb > 0 ? 0 : pos;
2518
+ let matchStartPos = pos;
2424
2519
  let runq = this.q0;
2425
2520
  let nextq = this.q1;
2426
- let r = input.step(pos);
2521
+ let r = input.step(currentPos);
2427
2522
  let rune = r >> 3;
2428
2523
  let width = r & 7;
2429
2524
  let rune1 = -1;
2430
2525
  let width1 = 0;
2431
2526
  if (r !== MachineInputBase.EOF()) {
2432
- r = input.step(pos + width);
2527
+ r = input.step(currentPos + width);
2433
2528
  rune1 = r >> 3;
2434
2529
  width1 = r & 7;
2435
2530
  }
2436
- let flag = pos === 0 ? Utils.emptyOpContext(-1, rune) : input.context(pos);
2531
+ let flag = currentPos === 0 ? Utils.emptyOpContext(-1, rune) : input.context(currentPos);
2437
2532
  const matches = new Set();
2438
2533
  while (true) {
2439
2534
  if (runq.isEmpty()) {
2440
- if ((startCond & Utils.EMPTY_BEGIN_TEXT) !== 0 && pos !== 0) break;
2441
- if ((anchor === RE2Flags.ANCHOR_START || anchor === RE2Flags.ANCHOR_BOTH) && pos !== 0) {
2535
+ if ((startCond & Utils.EMPTY_BEGIN_TEXT) !== 0 && currentPos !== 0) break;
2536
+ if ((anchor === RE2Flags.ANCHOR_START || anchor === RE2Flags.ANCHOR_BOTH) && currentPos !== 0) {
2442
2537
  break;
2443
2538
  }
2444
2539
  }
2445
- if (pos === 0 || anchor === RE2Flags.UNANCHORED) {
2446
- // Spawn Lookbehind threads BEFORE the main pattern
2540
+
2541
+ // Optimize lookbehind spawning to exactly once at BOF
2542
+ if (currentPos === 0 && this.prog.numLb > 0) {
2447
2543
  for (let i = 0; i < this.prog.lbStarts.length; i++) {
2448
- this.add(runq, this.prog.lbStarts[i], pos, this.matchcap, flag, null);
2544
+ this.add(runq, this.prog.lbStarts[i], currentPos, this.matchcap, flag, null);
2449
2545
  }
2450
- this.add(runq, this.prog.start, pos, this.matchcap, flag, null);
2451
2546
  }
2452
- const nextPos = pos + width;
2547
+ if (currentPos === 0 || anchor === RE2Flags.UNANCHORED) {
2548
+ // ONLY spawn the main pattern if we have reached the requested search start boundary
2549
+ if (currentPos >= matchStartPos) {
2550
+ this.add(runq, this.prog.start, currentPos, this.matchcap, flag, null);
2551
+ }
2552
+ }
2553
+ const nextPos = currentPos + width;
2453
2554
  flag = input.context(nextPos);
2454
2555
  for (let j = 0; j < runq.size; j++) {
2455
2556
  let t = runq.denseThreads[j];
@@ -2458,7 +2559,7 @@ class Machine {
2458
2559
  let add = false;
2459
2560
  switch (i.op) {
2460
2561
  case Inst.MATCH:
2461
- if (anchor === RE2Flags.ANCHOR_BOTH && pos !== input.endPos()) break;
2562
+ if (anchor === RE2Flags.ANCHOR_BOTH && currentPos !== input.endPos()) break;
2462
2563
  matches.add(i.arg); // Record the matched Set ID
2463
2564
  break;
2464
2565
  case Inst.RUNE:
@@ -2486,11 +2587,11 @@ class Machine {
2486
2587
  }
2487
2588
  runq.clear();
2488
2589
  if (width === 0) break;
2489
- pos += width;
2590
+ currentPos += width;
2490
2591
  rune = rune1;
2491
2592
  width = width1;
2492
2593
  if (rune !== -1) {
2493
- r = input.step(pos + width);
2594
+ r = input.step(currentPos + width);
2494
2595
  rune1 = r >> 3;
2495
2596
  width1 = r & 7;
2496
2597
  }