From 674db4b01fc6b899c7f56a40b77fa40c32466f6c Mon Sep 17 00:00:00 2001 From: James McCoy Date: Thu, 12 Jan 2017 13:57:37 -0500 Subject: eval: Remove dictwatcher from watchers queue before freeing it This fixes a use-after-free noticed by ASAN which would occur when a dictwatcher was still active on a dictionary when the dictionary was freed. fun! MakeWatch() let d = {'foo': 'bar'} call dictwatcheradd(d, 'foo', function('...')) endfun Patch-by: oni-link Closes #5930 --- src/nvim/eval.c | 2 +- test/functional/ex_cmds/dict_notifications_spec.lua | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 1688a565c1..cdf60d9765 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6410,8 +6410,8 @@ static void dict_free_contents(dict_T *d) { while (!QUEUE_EMPTY(&d->watchers)) { QUEUE *w = QUEUE_HEAD(&d->watchers); DictWatcher *watcher = dictwatcher_node_data(w); - dictwatcher_free(watcher); QUEUE_REMOVE(w); + dictwatcher_free(watcher); } hash_clear(&d->dv_hashtab); diff --git a/test/functional/ex_cmds/dict_notifications_spec.lua b/test/functional/ex_cmds/dict_notifications_spec.lua index e6f7609016..5e89986c0f 100644 --- a/test/functional/ex_cmds/dict_notifications_spec.lua +++ b/test/functional/ex_cmds/dict_notifications_spec.lua @@ -3,6 +3,7 @@ local clear, nvim, source = helpers.clear, helpers.nvim, helpers.source local eq, next_msg = helpers.eq, helpers.next_message local exc_exec = helpers.exc_exec local command = helpers.command +local eval = helpers.eval describe('dictionary change notifications', function() @@ -255,5 +256,21 @@ describe('dictionary change notifications', function() eq({'notification', '2b', {'key', {old = 'v2', new = 'value'}}}, next_msg()) end) + + it('does not crash when freeing a watched dictionary', function() + source([[ + function! Watcher(dict, key, value) + echo a:key string(a:value) + endfunction + + function! MakeWatch() + let d = {'foo': 'bar'} + call dictwatcheradd(d, 'foo', function('Watcher')) + endfunction + ]]) + + command('call MakeWatch()') + eq(2, eval('1+1')) -- Still alive? + end) end) end) -- cgit