From 4ff3a9b1e3ba45f9dbd0ea8c721f27d9315c4d93 Mon Sep 17 00:00:00 2001
From: LemonBoy <thatlemon@gmail.com>
Date: Tue, 9 Jul 2024 20:03:24 +0200
Subject: [PATCH] patch 9.1.0554: :bw leaves jumplist and tagstack data around

Problem:  :bw leaves jumplist and tagstack data around
          (Paul "Joey" Clark)
Solution: Wipe jumplist and tagstack references to the wiped buffer
          (LemonBoy)

As documented the :bwipeout command brutally deletes all the references
to the buffer, so let's make it delete all the entries in the jump list
and tag stack referring to the wiped-out buffer.

fixes: #8201
closes: #15185

Signed-off-by: LemonBoy <thatlemon@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
---
 runtime/doc/version9.txt      |  1 +
 runtime/doc/windows.txt       |  5 +++--
 src/buffer.c                  |  5 +++++
 src/mark.c                    | 34 ++++++++++++++++++++++++++++++++++
 src/proto/mark.pro            |  1 +
 src/proto/tag.pro             |  1 +
 src/tag.c                     |  3 +--
 src/testdir/test_jumplist.vim | 22 ++++++----------------
 src/testdir/test_tagjump.vim  | 17 +++++++++++++++++
 9 files changed, 69 insertions(+), 20 deletions(-)

diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index 9cf20300f..df56215ea 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -31701,6 +31701,7 @@ Other improvements				*new-other-9.1*
 
 Changed						*changed-9.1*
 -------
+- |:bwipe| also wipes jumplist and tagstack data
 
 Added						*added-9.1*
 -----
diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt
index e264e5117..d3e5f6785 100644
--- a/runtime/doc/windows.txt
+++ b/runtime/doc/windows.txt
@@ -1,4 +1,4 @@
-*windows.txt*   For Vim version 9.0.  Last change: 2022 Nov 27
+*windows.txt*   For Vim version 9.1.  Last change: 2024 Jul 09
 
 
 		  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -1225,7 +1225,8 @@ list of buffers. |unlisted-buffer|
 :bw[ipeout][!] N1 N2 ...
 		Like |:bdelete|, but really delete the buffer.  Everything
 		related to the buffer is lost.  All marks in this buffer
-		become invalid, option settings are lost, etc.  Don't use this
+		become invalid, option settings are lost, the jumplist and
+		tagstack data will be purged, etc.  Don't use this
 		unless you know what you are doing. Examples: >
 		    :.+,$bwipeout   " wipe out all buffers after the current
 				    " one
diff --git a/src/buffer.c b/src/buffer.c
index cbec9b980..28967342d 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -750,10 +750,15 @@ aucmd_abort:
      */
     if (wipe_buf)
     {
+	win_T	*wp;
+
 	// Do not wipe out the buffer if it is used in a window.
 	if (buf->b_nwindows > 0)
 	    return FALSE;
 
+	FOR_ALL_WINDOWS(wp)
+	    mark_forget_file(wp, buf->b_fnum);
+
 	if (action == DOBUF_WIPE_REUSE)
 	{
 	    // we can re-use this buffer number, store it
diff --git a/src/mark.c b/src/mark.c
index 22e3c6257..85f7b68e2 100644
--- a/src/mark.c
+++ b/src/mark.c
@@ -129,6 +129,40 @@ setmark_pos(int c, pos_T *pos, int fnum)
     return FAIL;
 }
 
+/*
+ * Delete every entry referring to file 'fnum' from both the jumplist and the
+ * tag stack.
+ */
+    void
+mark_forget_file(win_T *wp, int fnum)
+{
+    int		i;
+
+    for (i = 0; i < wp->w_jumplistlen; ++i)
+	if (wp->w_jumplist[i].fmark.fnum == fnum)
+	{
+	    vim_free(wp->w_jumplist[i].fname);
+	    mch_memmove(&wp->w_jumplist[i], &wp->w_jumplist[i + 1],
+		    (wp->w_jumplistlen - i - 1) * sizeof(xfmark_T));
+	    if (wp->w_jumplistidx > i)
+		--wp->w_jumplistidx;
+	    --wp->w_jumplistlen;
+	    --i;
+	}
+
+    for (i = 0; i < wp->w_tagstacklen; i++)
+	if (wp->w_tagstack[i].fmark.fnum == fnum)
+	{
+	    tagstack_clear_entry(&wp->w_tagstack[i]);
+	    mch_memmove(&wp->w_tagstack[i], &wp->w_tagstack[i + 1],
+		    (wp->w_tagstacklen - i - 1) * sizeof(taggy_T));
+	    if (wp->w_tagstackidx > i)
+		--wp->w_tagstackidx;
+	    --wp->w_tagstacklen;
+	    --i;
+	}
+}
+
 /*
  * Set the previous context mark to the current position and add it to the
  * jump list.
diff --git a/src/proto/mark.pro b/src/proto/mark.pro
index cc45f0d3c..d398c3677 100644
--- a/src/proto/mark.pro
+++ b/src/proto/mark.pro
@@ -28,4 +28,5 @@ void set_last_cursor(win_T *win);
 void free_all_marks(void);
 xfmark_T *get_namedfm(void);
 void f_getmarklist(typval_T *argvars, typval_T *rettv);
+void mark_forget_file(win_T *wp, int fnum);
 /* vim: set ft=c : */
diff --git a/src/proto/tag.pro b/src/proto/tag.pro
index 6de463e92..eec7c24ed 100644
--- a/src/proto/tag.pro
+++ b/src/proto/tag.pro
@@ -14,4 +14,5 @@ int expand_tags(int tagnames, char_u *pat, int *num_file, char_u ***file);
 int get_tags(list_T *list, char_u *pat, char_u *buf_fname);
 void get_tagstack(win_T *wp, dict_T *retdict);
 int set_tagstack(win_T *wp, dict_T *d, int action);
+void tagstack_clear_entry(taggy_T *item);
 /* vim: set ft=c : */
diff --git a/src/tag.c b/src/tag.c
index d406fdec1..57f9fe016 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -144,7 +144,6 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char_
 #if defined(FEAT_QUICKFIX) && defined(FEAT_EVAL)
 static int add_llist_tags(char_u *tag, int num_matches, char_u **matches);
 #endif
-static void tagstack_clear_entry(taggy_T *item);
 
 static char_u	*tagmatchname = NULL;	// name of last used tag
 
@@ -4225,7 +4224,7 @@ find_extra(char_u **pp)
 /*
  * Free a single entry in a tag stack
  */
-    static void
+    void
 tagstack_clear_entry(taggy_T *item)
 {
     VIM_CLEAR(item->tagname);
diff --git a/src/testdir/test_jumplist.vim b/src/testdir/test_jumplist.vim
index 8fbf39f55..0f43a8c6e 100644
--- a/src/testdir/test_jumplist.vim
+++ b/src/testdir/test_jumplist.vim
@@ -62,26 +62,16 @@ endfunc
 func Test_jumplist_invalid()
   new
   clearjumps
-  " put some randome text
-  put ='a'
-  let prev = bufnr('%')
+  " Put some random text and fill the jump list.
+  call setline(1, ['foo', 'bar', 'baz'])
+  normal G
+  normal gg
   setl nomodified bufhidden=wipe
   e XXJumpListBuffer
-  let bnr = bufnr('%')
-  " 1) empty jumplist
-  let expected = [[
-   \ {'lnum': 2, 'bufnr': prev, 'col': 0, 'coladd': 0}], 1]
-  call assert_equal(expected, getjumplist())
+  " The jump list is empty as the buffer was wiped out.
+  call assert_equal([[], 0], getjumplist())
   let jumps = execute(':jumps')
   call assert_equal('>', jumps[-1:])
-  " now jump back
-  exe ":norm! \<c-o>"
-  let expected = [[
-    \ {'lnum': 2, 'bufnr': prev, 'col': 0, 'coladd': 0},
-    \ {'lnum': 1, 'bufnr': bnr,  'col': 0, 'coladd': 0}], 0]
-  call assert_equal(expected, getjumplist())
-  let jumps = execute(':jumps')
-  call assert_match('>  0     2    0 -invalid-', jumps)
 endfunc
 
 " Test for '' mark in an empty buffer
diff --git a/src/testdir/test_tagjump.vim b/src/testdir/test_tagjump.vim
index 432906efb..71237855c 100644
--- a/src/testdir/test_tagjump.vim
+++ b/src/testdir/test_tagjump.vim
@@ -943,6 +943,23 @@ func Test_tag_stack()
   call settagstack(1, {'items' : []})
   call assert_fails('pop', 'E73:')
 
+  " References to wiped buffer are deleted.
+  for i in range(10, 20)
+    edit Xtest
+    exe "tag var" .. i
+  endfor
+  edit Xtest
+
+  let t = gettagstack()
+  call assert_equal(11, t.length)
+  call assert_equal(12, t.curidx)
+
+  bwipe!
+
+  let t = gettagstack()
+  call assert_equal(0, t.length)
+  call assert_equal(1, t.curidx)
+
   set tags&
   %bwipe
 endfunc
-- 
2.33.0

