patch 9.1.1470: use-after-free with popup callback on error

Problem:  use-after-free with popup callback on error
          (Brian Carbone, lifepillar)
Solution: check if the popup window is valid before accessing it

fixes: #17558
closes: #17565

Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/popupwin.c b/src/popupwin.c
index 199ffaf..536e1b6 100644
--- a/src/popupwin.c
+++ b/src/popupwin.c
@@ -2421,11 +2421,17 @@
 
 /*
  * Close popup "wp" and invoke any close callback for it.
+ * Careful: callback function might have freed the popup window already
  */
     static void
 popup_close_and_callback(win_T *wp, typval_T *arg)
 {
-    int id = wp->w_id;
+    int id;
+
+    if (!win_valid(wp))
+       return;
+
+    id = wp->w_id;
 
 #ifdef FEAT_TERMINAL
     if (wp == curwin && curbuf->b_term != NULL)
diff --git a/src/testdir/test_popupwin.vim b/src/testdir/test_popupwin.vim
index 3c8e5d7..f01b749 100644
--- a/src/testdir/test_popupwin.vim
+++ b/src/testdir/test_popupwin.vim
@@ -1121,6 +1121,7 @@
   call assert_fails('call win_execute(winid, "wincmd t")', 'E994:')
   call assert_fails('call win_execute(winid, "wincmd b")', 'E994:')
   call popup_clear()
+  bw filename
 endfunc
 
 func Test_popup_with_wrap()
@@ -4449,4 +4450,47 @@
   call StopVimInTerminal(buf)
 endfunc
 
+func Test_popupwin_callback_closes_popupwin()
+  " Test that the command line is properly cleared for overlong
+  " popup windows and using popup_hide()
+  CheckRunVimInTerminal
+
+  let lines =<< trim END
+    vim9script
+
+    def Filter(winid: number, keyCode: string): bool
+        popup_close(winid)
+        colorscheme missing
+        return true
+    enddef
+
+    def Popup(): number
+        return popup_create('', {
+          border:      [2, 2, 2, 2],
+          close:       'button',
+          filter:      Filter,
+        })
+    enddef
+    nnoremap gs <scriptcmd>Popup()<cr>
+  END
+  call writefile(lines, 'XtestPopup1_win', 'D')
+  let buf = RunVimInTerminal('-S XtestPopup1_win', #{rows: 10})
+  let i = 0
+  while i <= 10
+    call term_sendkeys(buf, "gs")
+    call term_wait(buf)
+    " this was causing a use-after-free
+    call term_sendkeys(buf, "q")
+    " clear the hit-enter prompt
+    call term_sendkeys(buf, "\<cr>")
+    call term_wait(buf)
+    let i += 1
+  endwhile
+  call term_sendkeys(buf, ":echo 'Done'\<cr>")
+  call WaitForAssert({-> assert_match('Done', term_getline(buf, 10))})
+
+  " clean up
+  call StopVimInTerminal(buf)
+endfunc
+
 " vim: shiftwidth=2 sts=2
diff --git a/src/version.c b/src/version.c
index 8b4f3e7..05e85ad 100644
--- a/src/version.c
+++ b/src/version.c
@@ -710,6 +710,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1470,
+/**/
     1469,
 /**/
     1468,