sre_engine: resume the tail match at prefix_skip, not past the whole prefix (#8473)

`search_info_literal`'s len>1 arm reset the tail-match cursor by advancing one
character past the matched prefix. That position equals the INFO block's
prefix_skip boundary only when the prefix ends where the skip does; for a
pattern like `ab(cd)`, `_get_literal_prefix` reports prefix_len=4 with
prefix_skip=2, so the tail resumed two characters too far.

`state.cursor` already holds `req.start + skip`, which is what
`sre_lib.h` SRE(search) computes as `ptr - (prefix_len - prefix_skip - 1)`,
and which the len==1 arm above already uses. Take it unconditionally.

Searching "xabcdcd" for `ab(cd)` returned span (1, 7) instead of (1, 5);
over a 600-case corpus stamped with CPython's own answers, 94 cases disagreed
before this change and none after. The wrong resume also dropped matches
outright, not only widened them.

Assisted-by: Claude
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2026-08-09 13:48:48 +09:00
1 parent 15f8590f17
commit ee94b5b723
2 files changed
+20 -5

No files matched your search

+5 -5
View File
@@ -998,12 +998,12 @@ fn search_info_literal<const LITERAL: bool, S: StrDrive>(
return true;
}
// `state.cursor` is `req.start + skip`, the position the
// tail match resumes from; advancing past the prefix
// instead would resume at `req.start + len` and only
// agree when the prefix ends at the skip boundary.
let mut next_ctx = ctx;
if skip != 0 {
next_ctx.advance_char::<S>();
} else {
next_ctx.cursor = state.cursor;
}
next_ctx.cursor = state.cursor;
if _match(req, state, next_ctx) {
return true;
+15
View File
@@ -252,4 +252,19 @@ mod tests {
#[rustfmt::skip] let p = Pattern { pattern: "\u{e0}+", code: &[14, 4, 0, 1, 4294967295, 24, 6, 1, 4294967295, 16, 224, 1, 1] };
// END GENERATED
}
#[test]
fn search_literal_prefix_longer_than_skip() {
// The INFO block carries prefix_len=4 and prefix_skip=2, so the tail
// match has to resume at the skip boundary rather than past the whole
// prefix.
// pattern p = re.compile('ab(cd)')
// START GENERATED by generate_tests.py
#[rustfmt::skip] let p = Pattern { pattern: "ab(cd)", code: &[14, 14, 1, 4, 4, 4, 2, 97, 98, 99, 100, 0, 0, 0, 0, 16, 97, 16, 98, 17, 0, 16, 99, 16, 100, 17, 1, 1] };
// END GENERATED
let (req, mut state) = p.state("xabcdcd");
assert!(state.search(req));
assert_eq!(state.start, 1);
assert_eq!(state.cursor.position, 5);
}
}