Skip to content

Commit f0f527d

Browse files
authored
fix: add UseAnchoredLiteral case to FindIndices* methods (#110)
FindIndices*, FindIndicesAt*, and findIndicesAtWithState were missing the UseAnchoredLiteral strategy case, causing patterns like ^/.*[\w-]+\.php$ to fall through to slow NFA path. Regression: 0.01ms -> 408ms (40,000x slower) Fix: 408ms -> 0.5ms (back to O(1) anchored literal matching) Fixes #107
1 parent 1480f40 commit f0f527d

File tree

2 files changed

+35
-0
lines changed

2 files changed

+35
-0
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414

1515
---
1616

17+
## [0.11.8] - 2026-02-01
18+
19+
### Fixed
20+
- **Critical regression in UseAnchoredLiteral strategy** (Issue #107)
21+
- `FindIndices*` and `findIndicesAtWithState` were missing `UseAnchoredLiteral` case
22+
- Pattern `^/.*[\w-]+\.php$` fell through to slow NFA path: 0.01ms → 408ms (40,000x slower)
23+
- Added `findIndicesAnchoredLiteral` and `findIndicesAnchoredLiteralAt` methods
24+
- Now correctly uses O(1) anchored literal matching: **408ms → 0.5ms**
25+
26+
---
27+
1728
## [0.11.7] - 2026-02-01
1829

1930
### Fixed

meta/find_indices.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ func (e *Engine) FindIndices(haystack []byte) (start, end int, found bool) {
4949
return e.findIndicesAhoCorasick(haystack)
5050
case UseMultilineReverseSuffix:
5151
return e.findIndicesMultilineReverseSuffix(haystack)
52+
case UseAnchoredLiteral:
53+
return e.findIndicesAnchoredLiteral(haystack)
5254
default:
5355
return e.findIndicesNFA(haystack)
5456
}
@@ -91,6 +93,8 @@ func (e *Engine) FindIndicesAt(haystack []byte, at int) (start, end int, found b
9193
return e.findIndicesAhoCorasickAt(haystack, at)
9294
case UseMultilineReverseSuffix:
9395
return e.findIndicesMultilineReverseSuffixAt(haystack, at)
96+
case UseAnchoredLiteral:
97+
return e.findIndicesAnchoredLiteralAt(haystack, at)
9498
default:
9599
return e.findIndicesNFAAt(haystack, at)
96100
}
@@ -452,6 +456,24 @@ func (e *Engine) findIndicesMultilineReverseSuffix(haystack []byte) (int, int, b
452456
return e.multilineReverseSuffixSearcher.FindIndicesAt(haystack, 0)
453457
}
454458

459+
// findIndicesAnchoredLiteral uses O(1) specialized matching for ^prefix.*suffix$ patterns.
460+
// For anchored patterns, match always spans [0, len(haystack)].
461+
func (e *Engine) findIndicesAnchoredLiteral(haystack []byte) (int, int, bool) {
462+
if MatchAnchoredLiteral(haystack, e.anchoredLiteralInfo) {
463+
return 0, len(haystack), true
464+
}
465+
return -1, -1, false
466+
}
467+
468+
// findIndicesAnchoredLiteralAt searches using anchored literal at position - zero alloc.
469+
// Anchored patterns can only match from position 0.
470+
func (e *Engine) findIndicesAnchoredLiteralAt(haystack []byte, at int) (int, int, bool) {
471+
if at > 0 {
472+
return -1, -1, false
473+
}
474+
return e.findIndicesAnchoredLiteral(haystack)
475+
}
476+
455477
// findIndicesMultilineReverseSuffixAt searches using multiline suffix optimization from position - zero alloc.
456478
func (e *Engine) findIndicesMultilineReverseSuffixAt(haystack []byte, at int) (int, int, bool) {
457479
if e.multilineReverseSuffixSearcher == nil {
@@ -824,6 +846,8 @@ func (e *Engine) findIndicesAtWithState(haystack []byte, at int, state *SearchSt
824846
return e.findIndicesAhoCorasickAt(haystack, at)
825847
case UseMultilineReverseSuffix:
826848
return e.findIndicesMultilineReverseSuffixAt(haystack, at)
849+
case UseAnchoredLiteral:
850+
return e.findIndicesAnchoredLiteralAt(haystack, at)
827851
default:
828852
return e.findIndicesNFAAtWithState(haystack, at, state)
829853
}

0 commit comments

Comments
 (0)