# HG changeset patch # User Christian Brabandt # Date 1455565504 -3600 # Node ID 6db4b1c863ecb2ccb021cee93abdef3268d753aa # Parent adb3150edaaf8026a9addcb182bc9d82ccfbce5d commit https://github.com/vim/vim/commit/3bece9fee9c02934d3e295b29d253e13d4ef26a7 Author: Bram Moolenaar Date: Mon Feb 15 20:39:46 2016 +0100 patch 7.4.1322 Problem: Crash when unletting the variable that holds the channel in a callback function. (Christian Robinson) Solution: Increase the reference count while invoking the callback. diff --git a/src/channel.c b/src/channel.c --- a/src/channel.c +++ b/src/channel.c @@ -1217,8 +1217,6 @@ channel_close(channel_T *channel) #endif channel->ch_close_cb = NULL; - vim_free(channel->ch_callback); - channel->ch_callback = NULL; channel_clear(channel); } @@ -1298,6 +1296,9 @@ channel_clear(channel_T *channel) free_tv(json_head->jq_next->jq_value); remove_json_node(json_head, json_head->jq_next); } + + vim_free(channel->ch_callback); + channel->ch_callback = NULL; } #if defined(EXITFREE) || defined(PROTO) @@ -1802,15 +1803,28 @@ channel_select_check(int ret_in, void *r int channel_parse_messages(void) { - channel_T *channel; - int ret = FALSE; + channel_T *channel = first_channel; + int ret = FALSE; + int r; - for (channel = first_channel; channel != NULL; channel = channel->ch_next) - while (may_invoke_callback(channel) == OK) + while (channel != NULL) + { + /* Increase the refcount, in case the handler causes the channel to be + * unreferenced or closed. */ + ++channel->ch_refcount; + r = may_invoke_callback(channel); + if (channel_unref(channel)) + /* channel was freed, start over */ + channel = first_channel; + + if (r == OK) { - channel = first_channel; /* start over */ + channel = first_channel; /* something was done, start over */ ret = TRUE; } + else + channel = channel->ch_next; + } return ret; } diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -7730,12 +7730,21 @@ failret: return OK; } -#ifdef FEAT_CHANNEL - static void +#if defined(FEAT_CHANNEL) || defined(PROTO) +/* + * Decrement the reference count on "channel" and free it when it goes down to + * zero. + * Returns TRUE when the channel was freed. + */ + int channel_unref(channel_T *channel) { if (channel != NULL && --channel->ch_refcount <= 0) + { channel_free(channel); + return TRUE; + } + return FALSE; } #endif diff --git a/src/proto/eval.pro b/src/proto/eval.pro --- a/src/proto/eval.pro +++ b/src/proto/eval.pro @@ -79,6 +79,7 @@ int dict_add_list(dict_T *d, char *key, dictitem_T *dict_find(dict_T *d, char_u *key, int len); char_u *get_dict_string(dict_T *d, char_u *key, int save); long get_dict_number(dict_T *d, char_u *key); +int channel_unref(channel_T *channel); int string2float(char_u *text, float_T *value); char_u *get_function_name(expand_T *xp, int idx); char_u *get_expr_name(expand_T *xp, int idx); diff --git a/src/testdir/test_channel.vim b/src/testdir/test_channel.vim --- a/src/testdir/test_channel.vim +++ b/src/testdir/test_channel.vim @@ -295,3 +295,21 @@ func Test_pipe() call job_stop(job) endtry endfunc + +let s:unletResponse = '' +func s:UnletHandler(handle, msg) + let s:unletResponse = a:msg + unlet s:channelfd +endfunc + +" Test that "unlet handle" in a handler doesn't crash Vim. +func s:unlet_handle(port) + let s:channelfd = ch_open('localhost:' . a:port, s:chopt) + call ch_sendexpr(s:channelfd, "test", function('s:UnletHandler')) + sleep 10m + call assert_equal('what?', s:unletResponse) +endfunc + +func Test_unlet_handle() + call s:run_server('s:unlet_handle') +endfunc diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -748,6 +748,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1322, +/**/ 1321, /**/ 1320,