summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2016-02-15 20:39:46 +0100
committerBram Moolenaar <Bram@vim.org>2016-02-15 20:39:46 +0100
commit3bece9fee9c02934d3e295b29d253e13d4ef26a7 (patch)
tree716659cb27215968e8e7790ac674cc456068ad2f
parent71b0f7b5c083d32fd37fa825f5d829b6a6c1a09a (diff)
patch 7.4.1322v7.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.
-rw-r--r--src/channel.c28
-rw-r--r--src/eval.c13
-rw-r--r--src/proto/eval.pro1
-rw-r--r--src/testdir/test_channel.vim18
-rw-r--r--src/version.c2
5 files changed, 53 insertions, 9 deletions
diff --git a/src/channel.c b/src/channel.c
index a806c5681a..cad5a1bc1f 100644
--- 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 *rfds_in)
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
index a36f3dd51d..53d41d1d98 100644
--- 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
index d1f5c3cc8e..e63205e11c 100644
--- a/src/proto/eval.pro
+++ b/src/proto/eval.pro
@@ -79,6 +79,7 @@ int dict_add_list(dict_T *d, char *key, list_T *list);
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
index 0e87899f75..5b7812182f 100644
--- 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
index f8d838b44d..f5da6fcdfc 100644
--- 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,