From ee94b5b72391410cd1277df4d9495ea1f475cd50 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 9 Aug 2026 13:48:48 +0900 Subject: [PATCH] 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 --- crates/sre_engine/src/engine.rs | 10 +++++----- crates/sre_engine/tests/tests.rs | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/crates/sre_engine/src/engine.rs b/crates/sre_engine/src/engine.rs index a110a75d6..690801e0d 100644 --- a/crates/sre_engine/src/engine.rs +++ b/crates/sre_engine/src/engine.rs @@ -998,12 +998,12 @@ fn search_info_literal( 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::(); - } else { - next_ctx.cursor = state.cursor; - } + next_ctx.cursor = state.cursor; if _match(req, state, next_ctx) { return true; diff --git a/crates/sre_engine/tests/tests.rs b/crates/sre_engine/tests/tests.rs index 53f5225d4..0a9f45c37 100644 --- a/crates/sre_engine/tests/tests.rs +++ b/crates/sre_engine/tests/tests.rs @@ -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); + } }