patch 9.2.0254: w_locked can be bypassed when setting recursively

Commit: 
https://github.com/vim/vim/commit/7cb43f286e55853cf21b9d8870a430390c1cc8f1
Author: Sean Dewar <[email protected]>
Date:   Thu Mar 26 20:21:46 2026 +0000

    patch 9.2.0254: w_locked can be bypassed when setting recursively
    
    Problem:  w_locked can be bypassed when recursively set if not restored
              to its prior value.
    Solution: Rather than save/restore everywhere, just make it a count,
              like other locks (Sean Dewar)
    
    Requires the previous commit, otherwise b_nwindows will be wrong in
    tests, which causes a bunch of weird failures.
    
    closes: #19728
    
    Signed-off-by: Sean Dewar <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/src/arglist.c b/src/arglist.c
index 455d89713..6fccf69c2 100644
--- a/src/arglist.c
+++ b/src/arglist.c
@@ -200,7 +200,7 @@ alist_add(
     if (check_arglist_locked() == FAIL)
        return;
     arglist_locked = TRUE;
-    wp->w_locked = TRUE;
+    ++wp->w_locked;
 
 #ifdef BACKSLASH_IN_FILENAME
     slash_adjust(fname);
@@ -212,7 +212,7 @@ alist_add(
     ++al->al_ga.ga_len;
 
     arglist_locked = FALSE;
-    wp->w_locked = FALSE;
+    --wp->w_locked;
 }
 
 #if defined(BACKSLASH_IN_FILENAME)
@@ -373,7 +373,7 @@ alist_add_list(
            mch_memmove(&(ARGLIST[after + count]), &(ARGLIST[after]),
                                       (ARGCOUNT - after) * sizeof(aentry_T));
        arglist_locked = TRUE;
-       wp->w_locked = TRUE;
+       ++wp->w_locked;
        for (i = 0; i < count; ++i)
        {
            int flags = BLN_LISTED | (will_edit ? BLN_CURBUF : 0);
@@ -382,7 +382,7 @@ alist_add_list(
            ARGLIST[after + i].ae_fnum = buflist_add(files[i], flags);
        }
        arglist_locked = FALSE;
-       wp->w_locked = FALSE;
+       --wp->w_locked;
        ALIST(wp)->al_ga.ga_len += count;
        if (old_argcount > 0 && wp->w_arg_idx >= after)
            wp->w_arg_idx += count;
diff --git a/src/buffer.c b/src/buffer.c
index b624abb8a..0f119e762 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -80,15 +80,14 @@ static garray_T buf_reuse = GA_EMPTY;       // file numbers 
to recycle
     static void
 trigger_undo_ftplugin(buf_T *buf, win_T *win)
 {
-    int win_was_locked = win->w_locked;
     window_layout_lock();
-    buf->b_locked++;
-    win->w_locked = TRUE;
+    ++buf->b_locked;
+    ++win->w_locked;
     // b:undo_ftplugin may be set, undo it
     do_cmdline_cmd((char_u*)"if exists('b:undo_ftplugin') | :legacy :exe \
            b:undo_ftplugin | endif");
-    buf->b_locked--;
-    win->w_locked = win_was_locked;
+    --buf->b_locked;
+    --win->w_locked;
     window_layout_unlock();
 }
 
diff --git a/src/ex_cmds.c b/src/ex_cmds.c
index 7dfb7176e..d4d57d0a7 100644
--- a/src/ex_cmds.c
+++ b/src/ex_cmds.c
@@ -3082,7 +3082,7 @@ do_ecmd(
 
                // Set the w_locked flag to avoid that autocommands close the
                // window.  And set b_locked for the same reason.
-               the_curwin->w_locked = TRUE;
+               ++the_curwin->w_locked;
                ++buf->b_locked;
 
                if (curbuf == old_curbuf.br_buf)
@@ -3097,7 +3097,7 @@ do_ecmd(
 
                // Autocommands may have closed the window.
                if (win_valid(the_curwin))
-                   the_curwin->w_locked = FALSE;
+                   --the_curwin->w_locked;
                --buf->b_locked;
 
 #ifdef FEAT_EVAL
diff --git a/src/terminal.c b/src/terminal.c
index 734282c82..e9259947d 100644
--- a/src/terminal.c
+++ b/src/terminal.c
@@ -3736,10 +3736,10 @@ term_after_channel_closed(term_T *term)
                if (is_aucmd_win(wp))
                    do_set_w_locked = TRUE;
                if (do_set_w_locked)
-                   wp->w_locked = TRUE;
+                   ++wp->w_locked;
                do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
                if (do_set_w_locked)
-                   wp->w_locked = FALSE;
+                   --wp->w_locked;
                aucmd_restbuf(&aco);
            }
 #ifdef FEAT_PROP_POPUP
diff --git a/src/testdir/test_window_cmd.vim b/src/testdir/test_window_cmd.vim
index 6ead70a1a..aa631b290 100644
--- a/src/testdir/test_window_cmd.vim
+++ b/src/testdir/test_window_cmd.vim
@@ -2496,4 +2496,29 @@ func 
Test_laststatus_vsplit_row_height_mixed_stlo_reversed()
   call StopVimInTerminal(buf)
 endfunc
 
+func Test_window_w_locked_bypass()
+  split Xfoo
+  let s:win = win_getid()
+
+  augroup TestBypass
+    " :quit fired this with w_locked set.  Shouldn't be able to unset w_locked
+    " and close s:win if we do other stuff that also sets it.
+    au WinLeave * ++once call assert_equal(s:win, win_getid())
+                      \| quit | call assert_notequal(0, win_id2win(s:win))
+                      \| args Xbar
+                      \| argadd Xbaz
+                      \| edit Xbaz-but-cooler
+                      \| quit | call assert_notequal(0, win_id2win(s:win))
+  augroup END
+  quit
+  call assert_equal(1, bufexists('Xbaz-but-cooler')) " check WinLeave ran
+
+  unlet! s:win
+  augroup TestBypass
+    au!
+  augroup END
+  %argd!
+  %bw!
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index 5d352a98e..a0872c895 100644
--- a/src/version.c
+++ b/src/version.c
@@ -734,6 +734,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    254,
 /**/
     253,
 /**/
diff --git a/src/window.c b/src/window.c
index 0301dd5a6..38bc4677e 100644
--- a/src/window.c
+++ b/src/window.c
@@ -2717,10 +2717,10 @@ win_close_buffer(win_T *win, int action, int 
abort_if_last)
        bufref_T    bufref;
 
        set_bufref(&bufref, curbuf);
-       win->w_locked = TRUE;
+       ++win->w_locked;
        close_buffer(win, win->w_buffer, action, abort_if_last, TRUE, TRUE);
        if (win_valid_any_tab(win))
-           win->w_locked = FALSE;
+           --win->w_locked;
        // Make sure curbuf is valid. It can become invalid if 'bufhidden' is
        // "wipe".
        if (!bufref_valid(&bufref))
@@ -2823,19 +2823,19 @@ win_close(win_T *win, int free_buf)
            other_buffer = TRUE;
            if (!win_valid(win))
                return FAIL;
-           win->w_locked = TRUE;
+           ++win->w_locked;
            apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, FALSE, curbuf);
            if (!win_valid(win))
                return FAIL;
-           win->w_locked = FALSE;
+           --win->w_locked;
            if (last_window())
                return FAIL;
        }
-       win->w_locked = TRUE;
+       ++win->w_locked;
        apply_autocmds(EVENT_WINLEAVE, NULL, NULL, FALSE, curbuf);
        if (!win_valid(win))
            return FAIL;
-       win->w_locked = FALSE;
+       --win->w_locked;
        if (last_window())
            return FAIL;
 #ifdef FEAT_EVAL

-- 
-- 
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/E1w5rLD-007PEn-Ms%40256bit.org.

Raspunde prin e-mail lui