regex/exec.rs:61 does &str_data[search_start_byte..] when lastIndex > 0, so every zero-width assertion is evaluated against the slice rather than the real haystack. Wrong in both directions, and no m flag is needed:
const r = /^b/g; r.lastIndex = 1; r.exec("ab") // node: null perry: "b"
const l = /(?<=a)b/g; l.lastIndex = 1; l.exec("ab") // node: "b" perry: null
Under /m it is severe — while ((m = /^/gm.exec("one\r\ntwo"))) yields an empty match at every index.
Why this is worth doing next
It limits how much of #9408's fix reaches real code. #9427 made multiline ^/$ recognise every LineTerminator, but any exec-loop with a sticky/global regex still evaluates those anchors against a slice — so the correct anchor semantics are undone at the call site for exactly the iteration pattern that scans line by line. Together they're one user-visible behaviour; separately, each is half fixed.
The fix, already scoped
Use the positional search APIs on the full haystack instead of slicing:
regex::Regex::captures_at
fancy_regex::Regex::captures_from_pos
regress::Regex::find_from
with the sticky check becoming start() == search_start_byte, and OwnedExecMatch::from_*'s base offset becoming 0 (it currently compensates for the slice).
Found while fixing #9408/#9409; deliberately left out of #9427 as a third independent root cause, with the exclusion documented in that PR's fixture.
regex/exec.rs:61does&str_data[search_start_byte..]whenlastIndex > 0, so every zero-width assertion is evaluated against the slice rather than the real haystack. Wrong in both directions, and nomflag is needed:Under
/mit is severe —while ((m = /^/gm.exec("one\r\ntwo")))yields an empty match at every index.Why this is worth doing next
It limits how much of #9408's fix reaches real code. #9427 made multiline
^/$recognise every LineTerminator, but anyexec-loop with a sticky/global regex still evaluates those anchors against a slice — so the correct anchor semantics are undone at the call site for exactly the iteration pattern that scans line by line. Together they're one user-visible behaviour; separately, each is half fixed.The fix, already scoped
Use the positional search APIs on the full haystack instead of slicing:
regex::Regex::captures_atfancy_regex::Regex::captures_from_posregress::Regex::find_fromwith the sticky check becoming
start() == search_start_byte, andOwnedExecMatch::from_*'s base offset becoming 0 (it currently compensates for the slice).Found while fixing #9408/#9409; deliberately left out of #9427 as a third independent root cause, with the exclusion documented in that PR's fixture.