mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
mruby-regexp: disable first-byte skip when pattern can match empty
first_set_walk returned TRUE when it reached RE_MATCH via epsilon
transitions, but that's exactly the case where the optimization is
wrong: an empty-matchable pattern can start matching at any position,
including bytes that aren't in the computed first-byte set. The
skip-ahead loop in pike_vm then advanced past valid empty-match
positions, producing a match at the wrong offset (e.g. /a?/.match("b")
reported the empty match at index 1 instead of 0).
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -823,12 +823,18 @@ first_set_walk(const re_inst *code, uint32_t code_len,
|
||||
case RE_ANY: case RE_ANY_NL:
|
||||
return FALSE; /* any byte possible */
|
||||
case RE_MATCH:
|
||||
return TRUE; /* empty match; first_bytes still valid for other branches */
|
||||
/* Reaching MATCH via epsilon transitions means the regex can match
|
||||
zero characters at any position. Skipping bytes that aren't in the
|
||||
first-byte set would skip past valid empty-match positions, so the
|
||||
optimization isn't safe -- bail out and accept any starting byte. */
|
||||
return FALSE;
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
/* Walked off the end without hitting MATCH or a consuming op. Treat as
|
||||
empty-matchable, same as RE_MATCH. */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static mrb_bool
|
||||
|
||||
@@ -478,3 +478,17 @@ assert("Regexp - consecutive optional quantifiers (#6853)") do
|
||||
assert_equal [""], /a*b*/.match("").to_a
|
||||
assert_equal [""], /a?b?c?d?/.match("").to_a
|
||||
end
|
||||
|
||||
assert("Regexp - empty-matchable patterns find earliest match position") do
|
||||
# When a regex can match zero characters via epsilon transitions, the
|
||||
# first-byte skip-ahead optimization is unsafe: skipping past bytes
|
||||
# that aren't in the first-byte set would also skip past valid
|
||||
# empty-match positions.
|
||||
md = /a?/.match("b")
|
||||
assert_equal "", md[0]
|
||||
assert_equal 0, md.begin(0)
|
||||
|
||||
md = /a?b?/.match("c")
|
||||
assert_equal "", md[0]
|
||||
assert_equal 0, md.begin(0)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user