Skip to content

Commit

Permalink
patch 9.0.2050: Vim9: crash with deferred function call and exception
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
yegappan authored and chrisbra committed Oct 19, 2023
1 parent d7b616d commit c59c1e0
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 50 deletions.
37 changes: 37 additions & 0 deletions src/ex_eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
3 changes: 3 additions & 0 deletions src/proto/ex_eval.pro
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
13 changes: 13 additions & 0 deletions src/structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 16 additions & 6 deletions src/testdir/test_user_func.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand Down
24 changes: 17 additions & 7 deletions src/testdir/test_vim9_script.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand Down
19 changes: 6 additions & 13 deletions src/time.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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;
Expand Down
19 changes: 7 additions & 12 deletions src/userfunc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
19 changes: 7 additions & 12 deletions src/vim9execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit c59c1e0

Please sign in to comment.