mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
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:
1 parent
15f8590f17
commit
ee94b5b723
2 files changed
+20
-5
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user