]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0825: regexp: submatch in a look-behind is empty with the NFA engine v9.2.0825
authorHirohito Higashi <h.east.727@gmail.com>
Wed, 22 Jul 2026 08:49:26 +0000 (08:49 +0000)
committerChristian Brabandt <cb@256bit.org>
Wed, 22 Jul 2026 08:49:26 +0000 (08:49 +0000)
Problem:  With the NFA engine a sub-expression inside a variable width
          look-behind, e.g. "\v(.)@<=", is empty for the first match on
          every line except the first one.  The old engine is correct
          (Mukundan)
Solution: The look-behind is retried from the previous line, because the
          width of "." is over-estimated.  While scanning that line the
          start state is added at the end of the line, where it gets the
          position of the line break as its start position, even though
          the match actually starts on the next line.  Use the position
          of the start of the next line in that case (Hirohito Higashi).

fixes:  #20802
closes: #20805

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Hirohito Higashi <h.east.727@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
src/regexp_nfa.c
src/testdir/test_regexp_utf8.vim
src/version.c

index f47d0c834467b01b14b8db7f14b9c2af4eb5b666..b224f9fd58b029fd2b53c45649a30ae4d050f768 100644 (file)
@@ -7269,7 +7269,32 @@ nfa_regmatch(
            }
            else
            {
-               if (addstate(nextlist, start, m, NULL, clen) == NULL)
+               char_u      *save_line = rex.line;
+               char_u      *save_input = rex.input;
+               linenr_T    save_lnum = rex.lnum;
+
+               // At the end of a line the match can only start on the next
+               // line, use that position instead of the line break.
+               if (REG_MULTI && clen == 0 && nfa_endp != NULL
+                                        && rex.lnum < nfa_endp->se_u.pos.lnum)
+               {
+                   char_u  *next_line = reg_getline(rex.lnum + 1);
+
+                   if (next_line != NULL)
+                   {
+                       rex.line = next_line;
+                       rex.input = rex.line;
+                       ++rex.lnum;
+                   }
+               }
+
+               r = addstate(nextlist, start, m, NULL, clen);
+
+               rex.line = save_line;
+               rex.input = save_input;
+               rex.lnum = save_lnum;
+
+               if (r == NULL)
                {
                    nfa_match = NFA_TOO_EXPENSIVE;
                    goto theend;
index d33c03c421032b1a79d79b4d4424cafffb38db8f..6dea55668a7a1c31a7ef0a9415d883475f89618e 100644 (file)
@@ -651,4 +651,29 @@ func Test_regex_collection_range_with_composing_crash()
   bwipe!
 endfunc
 
+" A submatch in a look-behind must not start on the previous line.  See
+" issue #20802.
+func Test_lookbehind_submatch_on_second_line()
+  new
+  for re in range(0, 2)
+    exe "set re=" .. re
+    call setline(1, ['testing', 'testing'])
+    %s/\v(.)@<=/[\1]/g
+    call assert_equal(['t[t]e[e]s[s]t[t]i[i]n[n]g',
+         \ 't[t]e[e]s[s]t[t]i[i]n[n]g'], getline(1, '$'), 're=' .. re)
+    %d _
+
+    " With "\_." the look-behind can match the line break, then the submatch
+    " does start on the previous line.
+    call setline(1, ['abc', 'def'])
+    %s/\v(\_.)@<=/[\1]/g
+    call assert_equal(['a[a]b[b]c[c]', '[]d[d]e[e]f[f]'],
+         \ getline(1, '$'), 're=' .. re)
+    %d _
+  endfor
+
+  set re&
+  bwipe!
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
index 80eae1f8841ee75f0e841f5c744336283903b4b9..4195d04c79172bf2faecb42f24c6afd5400ba7f1 100644 (file)
@@ -759,6 +759,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    825,
 /**/
     824,
 /**/