patch 9.0.2050: Vim9: crash with deferred function call and exception

Commit: 
https://github.com/vim/vim/commit/c59c1e0d88651a71ece7366e418f1253abbe2a28
Author: Yegappan Lakshmanan <yegap...@yahoo.com>
Date:   Thu Oct 19 10:52:34 2023 +0200

    patch 9.0.2050: Vim9: crash with deferred function call and exception
    
    Problem:  Vim9: crash with deferred function call and exception
    Solution: Save and restore exception state
    
    Crash when a deferred function is called after an exception and another
    exception is thrown
    
    closes: #13376
    closes: #13377
    
    Signed-off-by: Christian Brabandt <c...@256bit.org>
    Co-authored-by: Yegappan Lakshmanan <yegap...@yahoo.com>

diff --git a/src/ex_eval.c b/src/ex_eval.c
index 68dc6d78c..e319dee0f 100644
--- a/src/ex_eval.c
+++ b/src/ex_eval.c
@@ -747,6 +747,43 @@ finish_exception(except_T *excp)
     discard_exception(excp, TRUE);
 }
 
+/*
+ * Save the current exception state in "estate"
+ */
+    void
+exception_state_save(exception_state_T *estate)
+{
+    estate->estate_current_exception = current_exception;
+    estate->estate_did_throw = did_throw;
+    estate->estate_need_rethrow = need_rethrow;
+    estate->estate_trylevel = trylevel;
+}
+
+/*
+ * Restore the current exception state from "estate"
+ */
+    void
+exception_state_restore(exception_state_T *estate)
+{
+    if (current_exception == NULL)
+       current_exception = estate->estate_current_exception;
+    did_throw |= estate->estate_did_throw;
+    need_rethrow |= estate->estate_need_rethrow;
+    trylevel |= estate->estate_trylevel;
+}
+
+/*
+ * Clear the current exception state
+ */
+    void
+exception_state_clear(void)
+{
+    current_exception = NULL;
+    did_throw = FALSE;
+    need_rethrow = FALSE;
+    trylevel = 0;
+}
+
 /*
  * Flags specifying the message displayed by report_pending.
  */
diff --git a/src/proto/ex_eval.pro b/src/proto/ex_eval.pro
index a3be429b1..979d6fb8f 100644
--- a/src/proto/ex_eval.pro
+++ b/src/proto/ex_eval.pro
@@ -11,6 +11,9 @@ char *get_exception_string(void *value, except_type_T type, 
char_u *cmdname, int
 int throw_exception(void *value, except_type_T type, char_u *cmdname);
 void discard_current_exception(void);
 void catch_exception(except_T *excp);
+void exception_state_save(exception_state_T *estate);
+void exception_state_restore(exception_state_T *estate);
+void exception_state_clear(void);
 void report_make_pending(int pending, void *value);
 int cmd_is_name_only(char_u *arg);
 void ex_eval(exarg_T *eap);
diff --git a/src/structs.h b/src/structs.h
index f7f3b2ec5..a1a94b0eb 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1088,6 +1088,19 @@ struct cleanup_stuff
     except_T *exception;       // exception value
 };
 
+/*
+ * Exception state that is saved and restored when calling timer callback
+ * functions and deferred functions.
+ */
+typedef struct exception_state_S exception_state_T;
+struct exception_state_S
+{
+    except_T   *estate_current_exception;
+    int                estate_did_throw;
+    int                estate_need_rethrow;
+    int                estate_trylevel;
+};
+
 #ifdef FEAT_SYN_HL
 // struct passed to in_id_list()
 struct sp_syn
diff --git a/src/testdir/test_user_func.vim b/src/testdir/test_user_func.vim
index 8fc82c4e3..8c3f33dd6 100644
--- a/src/testdir/test_user_func.vim
+++ b/src/testdir/test_user_func.vim
@@ -873,11 +873,21 @@ endfunc
 " Test for calling a deferred function after an exception
 func Test_defer_after_exception()
   let g:callTrace = []
+  func Bar()
+    let g:callTrace += [1]
+    throw 'InnerException'
+  endfunc
+
   func Defer()
-    let g:callTrace += ['a']
-    let g:callTrace += ['b']
-    let g:callTrace += ['c']
-    let g:callTrace += ['d']
+    let g:callTrace += [2]
+    let g:callTrace += [3]
+    try
+      call Bar()
+    catch /InnerException/
+      let g:callTrace += [4]
+    endtry
+    let g:callTrace += [5]
+    let g:callTrace += [6]
   endfunc
 
   func Foo()
@@ -888,9 +898,9 @@ func Test_defer_after_exception()
   try
     call Foo()
   catch /TestException/
-    let g:callTrace += ['e']
+    let g:callTrace += [7]
   endtry
-  call assert_equal(['a', 'b', 'c', 'd', 'e'], g:callTrace)
+  call assert_equal([2, 3, 1, 4, 5, 6, 7], g:callTrace)
 
   delfunc Defer
   delfunc Foo
diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim
index f8280c6d2..75a358e85 100644
--- a/src/testdir/test_vim9_script.vim
+++ b/src/testdir/test_vim9_script.vim
@@ -4691,12 +4691,22 @@ def Test_defer_after_exception()
   var lines =<< trim END
     vim9script
 
-    var callTrace: list<string> = []
+    var callTrace: list<number> = []
+    def Bar()
+      callTrace += [1]
+      throw 'InnerException'
+    enddef
+
     def Defer()
-      callTrace += ['a']
-      callTrace += ['b']
-      callTrace += ['c']
-      callTrace += ['d']
+      callTrace += [2]
+      callTrace += [3]
+      try
+        Bar()
+      catch /InnerException/
+        callTrace += [4]
+      endtry
+      callTrace += [5]
+      callTrace += [6]
     enddef
 
     def Foo()
@@ -4707,10 +4717,10 @@ def Test_defer_after_exception()
     try
       Foo()
     catch /TestException/
-      callTrace += ['e']
+      callTrace += [7]
     endtry
 
-    assert_equal(['a', 'b', 'c', 'd', 'e'], callTrace)
+    assert_equal([2, 3, 1, 4, 5, 6, 7], callTrace)
   END
   v9.CheckScriptSuccess(lines)
 enddef
diff --git a/src/time.c b/src/time.c
index 62b38b4bf..8725a8852 100644
--- a/src/time.c
+++ b/src/time.c
@@ -561,13 +561,12 @@ check_due_timer(void)
            int prev_uncaught_emsg = uncaught_emsg;
            int save_called_emsg = called_emsg;
            int save_must_redraw = must_redraw;
-           int save_trylevel = trylevel;
-           int save_did_throw = did_throw;
-           int save_need_rethrow = need_rethrow;
            int save_ex_pressedreturn = get_pressedreturn();
            int save_may_garbage_collect = may_garbage_collect;
-           except_T *save_current_exception = current_exception;
-           vimvars_save_T vvsave;
+           vimvars_save_T      vvsave;
+           exception_state_T   estate;
+
+           exception_state_save(&estate);
 
            // Create a scope for running the timer callback, ignoring most of
            // the current scope, such as being inside a try/catch.
@@ -576,11 +575,8 @@ check_due_timer(void)
            called_emsg = 0;
            did_emsg = FALSE;
            must_redraw = 0;
-           trylevel = 0;
-           did_throw = FALSE;
-           need_rethrow = FALSE;
-           current_exception = NULL;
            may_garbage_collect = FALSE;
+           exception_state_clear();
            save_vimvars(&vvsave);
 
            // Invoke the callback.
@@ -597,10 +593,7 @@ check_due_timer(void)
                ++timer->tr_emsg_count;
            did_emsg = save_did_emsg;
            called_emsg = save_called_emsg;
-           trylevel = save_trylevel;
-           did_throw = save_did_throw;
-           need_rethrow = save_need_rethrow;
-           current_exception = save_current_exception;
+           exception_state_restore(&estate);
            restore_vimvars(&vvsave);
            if (must_redraw != 0)
                need_update_screen = TRUE;
diff --git a/src/userfunc.c b/src/userfunc.c
index 092b3927b..5ef0f7d9c 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -6252,21 +6252,16 @@ handle_defer_one(funccall_T *funccal)
        dr->dr_name = NULL;
 
        // If the deferred function is called after an exception, then only the
-       // first statement in the function will be executed.  Save and restore
-       // the try/catch/throw exception state.
-       int save_trylevel = trylevel;
-       int save_did_throw = did_throw;
-       int save_need_rethrow = need_rethrow;
-
-       trylevel = 0;
-       did_throw = FALSE;
-       need_rethrow = FALSE;
+       // first statement in the function will be executed (because of the
+       // exception).  So save and restore the try/catch/throw exception
+       // state.
+       exception_state_T estate;
+       exception_state_save(&estate);
+       exception_state_clear();
 
        call_func(name, -1, &rettv, dr->dr_argcount, dr->dr_argvars, &funcexe);
 
-       trylevel = save_trylevel;
-       did_throw = save_did_throw;
-       need_rethrow = save_need_rethrow;
+       exception_state_restore(&estate);
 
        clear_tv(&rettv);
        vim_free(name);
diff --git a/src/vim9execute.c b/src/vim9execute.c
index dd3d263b5..a6bf4715a 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -1141,21 +1141,16 @@ invoke_defer_funcs(ectx_T *ectx)
        functv->vval.v_string = NULL;
 
        // If the deferred function is called after an exception, then only the
-       // first statement in the function will be executed.  Save and restore
-       // the try/catch/throw exception state.
-       int save_trylevel = trylevel;
-       int save_did_throw = did_throw;
-       int save_need_rethrow = need_rethrow;
-
-       trylevel = 0;
-       did_throw = FALSE;
-       need_rethrow = FALSE;
+       // first statement in the function will be executed (because of the
+       // exception).  So save and restore the try/catch/throw exception
+       // state.
+       exception_state_T estate;
+       exception_state_save(&estate);
+       exception_state_clear();
 
        (void)call_func(name, -1, &rettv, argcount, argvars, &funcexe);
 
-       trylevel = save_trylevel;
-       did_throw = save_did_throw;
-       need_rethrow = save_need_rethrow;
+       exception_state_restore(&estate);
 
        clear_tv(&rettv);
        vim_free(name);

-- 
-- 
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 vim_dev+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/E1qtOsx-00Cr10-St%40256bit.org.

Raspunde prin e-mail lui