]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.0.2050: Vim9: crash with deferred function call and exception v9.0.2050
authorYegappan Lakshmanan <yegappan@yahoo.com>
Thu, 19 Oct 2023 08:52:34 +0000 (10:52 +0200)
committerChristian Brabandt <cb@256bit.org>
Thu, 19 Oct 2023 08:52:34 +0000 (10:52 +0200)
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 <cb@256bit.org>
Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
src/ex_eval.c
src/proto/ex_eval.pro
src/structs.h
src/testdir/test_user_func.vim
src/testdir/test_vim9_script.vim
src/time.c
src/userfunc.c
src/vim9execute.c

index 68dc6d78ca6e917073abad074ff0c9cb490aa235..e319dee0f0a84a5dd1cc920f133ca7f09da57085 100644 (file)
@@ -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.
  */
index a3be429b1945bff2bc8e0209b78b29d524aae52c..979d6fb8f499c337600b4454d404bc11718c8940 100644 (file)
@@ -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);
index f7f3b2ec56a4f746b781f8d59f5295b6fd0c2af6..a1a94b0ebbfb640889c661b5d46923ccd9527167 100644 (file)
@@ -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
index 8fc82c4e38264976616d45d60dfc76bbde0bae55..8c3f33dd67b324d2f78ad4c295bdce70a32a4f23 100644 (file)
@@ -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
index f8280c6d289854f04469022b6513ba597405ac35..75a358e859fe745a41f72b799892b31b7ec4e708 100644 (file)
@@ -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
index 62b38b4bf9bee35b736c06de5d6569524a991cfa..8725a8852cab1d22e8b1ae8deb6a513afc758b4d 100644 (file)
@@ -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;
index 092b3927b5b7cfb8dd28cfe15a74835d56fb66f9..5ef0f7d9c981c69861a53248f77bf9ef8a42a1e3 100644 (file)
@@ -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);
index dd3d263b523bbf320df67c7924bf768b42b15f48..a6bf4715a88b44bc8b4371ecb87f30faac3b15f1 100644 (file)
@@ -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);