patch 9.2.0446: runtime(netrw): off-by-one bug in s:NetrwUnMarkFile()

Commit: 
https://github.com/vim/vim/commit/7ccc273a4cb6012e5afbdb94d0f2597bc01fe2fd
Author: J. Paulo Seibt <[email protected]>
Date:   Tue May 5 20:06:15 2026 +0000

    patch 9.2.0446: runtime(netrw): off-by-one bug in s:NetrwUnMarkFile()
    
    Problem:  off-by-one bug in s:NetrwUnMarkFile()
    Solution: Correctly loop through all buffers to unlet all variables
              (J. Paulo Seibt)
    
    When the function loops through buffers to clear s:netrwmarkfilelist_#
    and s:netrwmarkfilemtch_#, it skips the last one at bufnr('$'), messing
    up mark highlights and causing other functions that operate on those
    arrays (like delete or rename) to target stale marked files.
    
    The bufnr() help page says that bufnr("$") returns the highest buffer
    number of existing buffers, so while ibuf < bufnr("$") does not clear
    the last buffer-local arrays.
    
    To reproduce:
    
    Just opening a fresh Vim and running :Ex opens a netrw buffer at the
    highest number. Then, typing mu after marking some files triggers the
    mark highlight bug, and finally typing D would act like calling the
    delete function against the previous marked files, as the buffer-local
    arrays where not touched by s:NetrwUnMarkFile.
    
    closes: #20129
    
    Signed-off-by: J. Paulo Seibt <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/runtime/pack/dist/opt/netrw/autoload/netrw.vim 
b/runtime/pack/dist/opt/netrw/autoload/netrw.vim
index 538ab0377..90fd70472 100644
--- a/runtime/pack/dist/opt/netrw/autoload/netrw.vim
+++ b/runtime/pack/dist/opt/netrw/autoload/netrw.vim
@@ -1,33 +1,7 @@
 " Creator:    Charles E Campbell
 " Previous Maintainer: Luca Saccarola <[email protected]>
 " Maintainer: This runtime file is looking for a new maintainer.
-" Last Change:
-" 2025 Aug 07 by Vim Project (use correct "=~#" for netrw_stylesize option 
#17901)
-" 2025 Aug 07 by Vim Project (netrw#BrowseX() distinguishes remote files 
#17794)
-" 2025 Aug 22 by Vim Project netrw#Explore handle terminal correctly #18069
-" 2025 Sep 05 by Vim Project ensure netrw#fs#Dirname() returns trailing slash 
#18199
-" 2025 Sep 11 by Vim Project only keep cursor position in tree mode #18275
-" 2025 Sep 17 by Vim Project tighten the regex to handle remote compressed 
archives #18318
-" 2025 Sep 18 by Vim Project 'equalalways' not always respected #18358
-" 2025 Oct 01 by Vim Project fix navigate to parent folder #18464
-" 2025 Oct 26 by Vim Project fix parsing of remote user names #18611
-" 2025 Oct 27 by Vim Project align comment after #18611
-" 2025 Nov 01 by Vim Project fix NetrwChgPerm #18674
-" 2025 Nov 13 by Vim Project don't wipe unnamed buffers #18740
-" 2025 Nov 18 by Vim Project use UNC paths when using scp and Windows paths 
#18764
-" 2025 Nov 28 by Vim Project fix undefined variable in *NetrwMenu #18829
-" 2025 Dec 26 by Vim Project fix use of g:netrw_cygwin #19015
-" 2026 Jan 19 by Vim Project do not create swapfiles #18854
-" 2026 Feb 15 by Vim Project fix global variable initialization for MS-Windows 
#19287
-" 2026 Feb 21 by Vim Project better absolute path detection on MS-Windows 
#19477
-" 2026 Feb 27 by Vim Project Make the hostname validation more strict
-" 2026 Mar 01 by Vim Project include portnumber in hostname checking #19533
-" 2026 Apr 01 by Vim Project use fnameescape() with netrw#FileUrlEdit()
-" 2026 Apr 05 by Vim Project Fix netrw#RFC2396() #19913
-" 2026 Apr 15 by Vim Project Add missing escape()
-" 2026 Apr 19 by Vim Project expand ~ on Windows #20003
-" 2026 Apr 21 by Vim Project fix shell-injection via tempfile suffix (sftp://, 
file://)
-" 2026 Apr 21 by Vim Project drop unused g:netrw_tmpfile_escape
+" Last Change: 2026 May 05
 " Copyright:  Copyright (C) 2016 Charles E. Campbell {{{1
 "             Permission is hereby granted to use and distribute this code,
 "             with or without modifications, provided that this copyright
@@ -6348,7 +6322,7 @@ function s:NetrwUnMarkFile(islocal)
     endif
 
     let ibuf= 1
-    while ibuf < bufnr("$")
+    while ibuf <= bufnr("$")
         if exists("s:netrwmarkfilelist_".ibuf)
             unlet s:netrwmarkfilelist_{ibuf}
             unlet s:netrwmarkfilemtch_{ibuf}
diff --git a/src/testdir/test_plugin_netrw.vim 
b/src/testdir/test_plugin_netrw.vim
index 893e42436..b66bb4a72 100644
--- a/src/testdir/test_plugin_netrw.vim
+++ b/src/testdir/test_plugin_netrw.vim
@@ -138,6 +138,33 @@ function Test_NetrwValidateHostname(hostname) abort
     return s:NetrwValidateHostname(a:hostname)
 endfunction
 
+" Test unmarking all files via s:NetrwUnMarkFile()
+function Test_NetrwUnMarkFile()
+    " set up: init global and buffer-local mark lists
+    new
+
+    " initializing the global mark list, the loop handles the buffer-local
+    let s:netrwmarkfilelist = ['test_file']
+
+    " making sure every buffer has a mark list and match pattern
+    let ibuf = 1
+    while ibuf <= bufnr('$')
+        let s:netrwmarkfilelist_{ibuf} = ['test_mark']
+        let s:netrwmarkfilemtch_{ibuf} = 'test_mark'
+        let ibuf = ibuf + 1
+    endwhile
+
+    call s:NetrwUnMarkFile(1)
+
+    call assert_false(exists('s:netrwmarkfilelist'), 'Global list should be 
wiped')
+    call assert_false(exists('s:netrwmarkfilelist_' . bufnr('$')), 'Last 
buffer-local list should be wiped')
+    call assert_false(exists('s:netrwmarkfilemtch_' . bufnr('$')), 'Last 
buffer-local match str should be wiped')
+    call assert_false(exists('s:netrwmarkfilelist_1'), 'First buffer-local 
list should be wiped')
+    call assert_false(exists('s:netrwmarkfilemtch_1'), 'First buffer-local 
match str should be wiped')
+
+    " wipe out the test buffer
+    bw
+endfunction
 " }}}
 END
 
@@ -645,4 +672,8 @@ func Test_netrw_Home_tilde()
   bw!
 endfunc
 
+func Test_netrw_unmark_all()
+  call Test_NetrwUnMarkFile()
+endfunc
+
 " vim:ts=8 sts=2 sw=2 et
diff --git a/src/version.c b/src/version.c
index 4d3f68d05..2f0af9ba1 100644
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    446,
 /**/
     445,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1wKMAZ-007aUw-Bs%40256bit.org.

Raspunde prin e-mail lui