Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions mrbgems/mruby-regexp/src/re_exec.c
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,14 @@ add_thread(pike_state *s, re_threadlist *list,
continue;

case RE_SAVE:
/* Slot 1 is the end of group 0, so this is where the whole match
closes. It may not close inside a character, the same rule the
seeding loop applies to where a match opens. Killing the thread
rather than the attempt lets a longer branch match instead. */
if (inst.offset == 1 && !s->binary && sp < s->str_end &&
mrb_re_utf8_interior_p(s->str, sp, s->str_end)) {
return;
}
if (!s->match_only) {
CAP(s, cap_slot)[inst.offset] = (int)(sp - s->str);
}
Expand Down Expand Up @@ -546,6 +554,13 @@ bt_match(const mrb_regexp_pattern *pat, const char *str, const char *str_end,
case RE_SAVE:
{
int slot = inst.offset;
/* End of group 0: the whole match may not close inside a character
(see the Pike VM case). Failing here backtracks into the other
branches, so a longer one can still match. */
if (slot == 1 && !binary && sp < str_end &&
mrb_re_utf8_interior_p(str, sp, str_end)) {
return FALSE;
}
if (slot < ncap) {
int old = captures[slot];
captures[slot] = (int)(sp - str);
Expand Down Expand Up @@ -714,6 +729,12 @@ literal_exec(const mrb_regexp_pattern *pat,
continue;
}
if (plen == 1 || memcmp(found + 1, pat->prefix + 1, plen - 1) == 0) {
if (!binary && found + plen < str_end &&
mrb_re_utf8_interior_p(str, found + plen, str_end)) {
sp = found + 1; /* ends inside a character, same rule as the end of
group 0 in the other engines */
continue;
}
/* match found */
if (captures && captures_size >= 2) {
captures[0] = (int)(found - str);
Expand Down
33 changes: 33 additions & 0 deletions mrbgems/mruby-regexp/test/regexp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,39 @@
assert_equal 2, ("x" + "\xb5").match(/.?[µ]/)[0].bytesize
end

assert("Regexp - a match does not end inside a character") do
# A pattern is compiled byte by byte and RE_CHAR consumes one byte, so a
# pattern holding a byte that reaches no character ends its match in the
# middle of one. "ĵ" is C4 B5, and a pattern of the single byte C4 used to
# match its lead byte alone and hand back half a character.
j = "ĵ"
assert_nil j.match(Regexp.new("\xc4")) # literal fast path
assert_nil ("x" + j).match(Regexp.new("\xc4"))
assert_nil j.match(Regexp.new("\xc4+")) # pike VM
assert_nil j.match(Regexp.new("(\xc4)\\1?")) # backtracking engine
assert_equal j.bytes, j.gsub(Regexp.new("\xc4"), "!").bytes
# A branch that does end on a boundary still matches, greedy or not.
assert_equal 2, j.match(Regexp.new("\xc4."))[0].bytesize
assert_equal 2, j.match(Regexp.new("\xc4(?:\xb5)?"))[0].bytesize
assert_equal 2, j.match(Regexp.new("\xc4(?:\xb5)??"))[0].bytesize
assert_equal 2, j.match(Regexp.new("(\xc4\xb5)\\1?"))[0].bytesize
assert_equal 0, j.match(Regexp.new("\xc4*"))[0].bytesize
# A lookaround ends at a position without consuming it, so it is not the
# end of the match and keeps its own answer.
assert_equal 2, j.match(Regexp.new("(?=\xc4)\xc4\xb5"))[0].bytesize
# A byte no lead byte reaches is a boundary, so a byte pattern still works.
b = "\x81"
assert_equal 1, (b + b).match(Regexp.new(b))[0].bytesize
assert_equal 2, (b + b).match(Regexp.new(b + "+"))[0].bytesize
assert_equal 1, ("a" + b).match(Regexp.new(b))[0].bytesize
# Read as binary every position is a boundary, so nothing changes there.
if Object.const_defined?(:Encoding)
bin = j.dup.force_encoding("ASCII-8BIT")
assert_equal 1, bin.match(Regexp.new("\xc4"))[0].bytesize
assert_equal 2, bin.match(Regexp.new("\xc4."))[0].bytesize
end
end

assert("Regexp - multibyte (UTF-8) match extraction") do
# Capture offsets are recorded in bytes; substring extraction must honor
# them as byte ranges so multibyte matches are not corrupted.
Expand Down
Loading