mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 19:38:20 +00:00
Merge pull request #14259 from janlazo/fix-dictwatcherdel-crash
Fix dictwatcherdel crash
This commit is contained in:
@@ -5289,10 +5289,10 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
|
|||||||
|
|
||||||
QUEUE *w = NULL;
|
QUEUE *w = NULL;
|
||||||
DictWatcher *watcher = NULL;
|
DictWatcher *watcher = NULL;
|
||||||
QUEUE_FOREACH(w, &dd->watchers) {
|
QUEUE_FOREACH(w, &dd->watchers, {
|
||||||
watcher = tv_dict_watcher_node_data(w);
|
watcher = tv_dict_watcher_node_data(w);
|
||||||
set_ref_in_callback(&watcher->callback, copyID, ht_stack, list_stack);
|
set_ref_in_callback(&watcher->callback, copyID, ht_stack, list_stack);
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@@ -1109,6 +1109,7 @@ void tv_dict_watcher_add(dict_T *const dict, const char *const key_pattern,
|
|||||||
watcher->key_pattern_len = key_pattern_len;
|
watcher->key_pattern_len = key_pattern_len;
|
||||||
watcher->callback = callback;
|
watcher->callback = callback;
|
||||||
watcher->busy = false;
|
watcher->busy = false;
|
||||||
|
watcher->needs_free = false;
|
||||||
QUEUE_INSERT_TAIL(&dict->watchers, &watcher->node);
|
QUEUE_INSERT_TAIL(&dict->watchers, &watcher->node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1182,22 +1183,30 @@ bool tv_dict_watcher_remove(dict_T *const dict, const char *const key_pattern,
|
|||||||
QUEUE *w = NULL;
|
QUEUE *w = NULL;
|
||||||
DictWatcher *watcher = NULL;
|
DictWatcher *watcher = NULL;
|
||||||
bool matched = false;
|
bool matched = false;
|
||||||
QUEUE_FOREACH(w, &dict->watchers) {
|
bool queue_is_busy = false;
|
||||||
|
QUEUE_FOREACH(w, &dict->watchers, {
|
||||||
watcher = tv_dict_watcher_node_data(w);
|
watcher = tv_dict_watcher_node_data(w);
|
||||||
|
if (watcher->busy) {
|
||||||
|
queue_is_busy = true;
|
||||||
|
}
|
||||||
if (tv_callback_equal(&watcher->callback, &callback)
|
if (tv_callback_equal(&watcher->callback, &callback)
|
||||||
&& watcher->key_pattern_len == key_pattern_len
|
&& watcher->key_pattern_len == key_pattern_len
|
||||||
&& memcmp(watcher->key_pattern, key_pattern, key_pattern_len) == 0) {
|
&& memcmp(watcher->key_pattern, key_pattern, key_pattern_len) == 0) {
|
||||||
matched = true;
|
matched = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
if (!matched) {
|
if (!matched) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (queue_is_busy) {
|
||||||
|
watcher->needs_free = true;
|
||||||
|
} else {
|
||||||
QUEUE_REMOVE(w);
|
QUEUE_REMOVE(w);
|
||||||
tv_dict_watcher_free(watcher);
|
tv_dict_watcher_free(watcher);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1258,9 +1267,10 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key,
|
|||||||
|
|
||||||
typval_T rettv;
|
typval_T rettv;
|
||||||
|
|
||||||
|
bool any_needs_free = false;
|
||||||
dict->dv_refcount++;
|
dict->dv_refcount++;
|
||||||
QUEUE *w;
|
QUEUE *w;
|
||||||
QUEUE_FOREACH(w, &dict->watchers) {
|
QUEUE_FOREACH(w, &dict->watchers, {
|
||||||
DictWatcher *watcher = tv_dict_watcher_node_data(w);
|
DictWatcher *watcher = tv_dict_watcher_node_data(w);
|
||||||
if (!watcher->busy && tv_dict_watcher_matches(watcher, key)) {
|
if (!watcher->busy && tv_dict_watcher_matches(watcher, key)) {
|
||||||
rettv = TV_INITIAL_VALUE;
|
rettv = TV_INITIAL_VALUE;
|
||||||
@@ -1268,8 +1278,20 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key,
|
|||||||
callback_call(&watcher->callback, 3, argv, &rettv);
|
callback_call(&watcher->callback, 3, argv, &rettv);
|
||||||
watcher->busy = false;
|
watcher->busy = false;
|
||||||
tv_clear(&rettv);
|
tv_clear(&rettv);
|
||||||
|
if (watcher->needs_free) {
|
||||||
|
any_needs_free = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
if (any_needs_free) {
|
||||||
|
QUEUE_FOREACH(w, &dict->watchers, {
|
||||||
|
DictWatcher *watcher = tv_dict_watcher_node_data(w);
|
||||||
|
if (watcher->needs_free) {
|
||||||
|
QUEUE_REMOVE(w);
|
||||||
|
tv_dict_watcher_free(watcher);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
tv_dict_unref(dict);
|
tv_dict_unref(dict);
|
||||||
|
|
||||||
for (size_t i = 1; i < ARRAY_SIZE(argv); i++) {
|
for (size_t i = 1; i < ARRAY_SIZE(argv); i++) {
|
||||||
|
@@ -89,6 +89,7 @@ typedef struct dict_watcher {
|
|||||||
size_t key_pattern_len;
|
size_t key_pattern_len;
|
||||||
QUEUE node;
|
QUEUE node;
|
||||||
bool busy; // prevent recursion if the dict is changed in the callback
|
bool busy; // prevent recursion if the dict is changed in the callback
|
||||||
|
bool needs_free;
|
||||||
} DictWatcher;
|
} DictWatcher;
|
||||||
|
|
||||||
/// Bool variable values
|
/// Bool variable values
|
||||||
|
@@ -119,8 +119,8 @@ static MultiQueue *multiqueue_new(MultiQueue *parent, put_callback put_cb,
|
|||||||
void multiqueue_free(MultiQueue *this)
|
void multiqueue_free(MultiQueue *this)
|
||||||
{
|
{
|
||||||
assert(this);
|
assert(this);
|
||||||
while (!QUEUE_EMPTY(&this->headtail)) {
|
QUEUE *q;
|
||||||
QUEUE *q = QUEUE_HEAD(&this->headtail);
|
QUEUE_FOREACH(q, &this->headtail, {
|
||||||
MultiQueueItem *item = multiqueue_node_data(q);
|
MultiQueueItem *item = multiqueue_node_data(q);
|
||||||
if (this->parent) {
|
if (this->parent) {
|
||||||
QUEUE_REMOVE(&item->data.item.parent_item->node);
|
QUEUE_REMOVE(&item->data.item.parent_item->node);
|
||||||
@@ -128,7 +128,7 @@ void multiqueue_free(MultiQueue *this)
|
|||||||
}
|
}
|
||||||
QUEUE_REMOVE(q);
|
QUEUE_REMOVE(q);
|
||||||
xfree(item);
|
xfree(item);
|
||||||
}
|
})
|
||||||
|
|
||||||
xfree(this);
|
xfree(this);
|
||||||
}
|
}
|
||||||
|
@@ -33,11 +33,17 @@ typedef struct _queue {
|
|||||||
#define QUEUE_DATA(ptr, type, field) \
|
#define QUEUE_DATA(ptr, type, field) \
|
||||||
((type *)((char *)(ptr) - offsetof(type, field)))
|
((type *)((char *)(ptr) - offsetof(type, field)))
|
||||||
|
|
||||||
// Important note: mutating the list while QUEUE_FOREACH is
|
// Important note: the node currently being processed can be safely deleted.
|
||||||
// iterating over its elements results in undefined behavior.
|
// otherwise, mutating the list while QUEUE_FOREACH is iterating over its
|
||||||
#define QUEUE_FOREACH(q, h) \
|
// elements results in undefined behavior.
|
||||||
for ( /* NOLINT(readability/braces) */ \
|
#define QUEUE_FOREACH(q, h, code) \
|
||||||
(q) = (h)->next; (q) != (h); (q) = (q)->next)
|
(q) = (h)->next; \
|
||||||
|
while((q) != (h)) { \
|
||||||
|
QUEUE *next = q->next; \
|
||||||
|
code \
|
||||||
|
(q) = next; \
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ffi.cdef is unable to swallow `bool` in place of `int` here.
|
// ffi.cdef is unable to swallow `bool` in place of `int` here.
|
||||||
static inline int QUEUE_EMPTY(const QUEUE *const q)
|
static inline int QUEUE_EMPTY(const QUEUE *const q)
|
||||||
|
@@ -343,19 +343,17 @@ static int build_cmd_line(char **argv, wchar_t **cmd_line, bool is_cmdexe)
|
|||||||
utf8_cmd_line_len += argc;
|
utf8_cmd_line_len += argc;
|
||||||
char *utf8_cmd_line = xmalloc(utf8_cmd_line_len);
|
char *utf8_cmd_line = xmalloc(utf8_cmd_line_len);
|
||||||
*utf8_cmd_line = NUL;
|
*utf8_cmd_line = NUL;
|
||||||
while (1) {
|
QUEUE *q;
|
||||||
QUEUE *head = QUEUE_HEAD(&args_q);
|
QUEUE_FOREACH(q, &args_q, {
|
||||||
QUEUE_REMOVE(head);
|
ArgNode *arg_node = QUEUE_DATA(q, ArgNode, node);
|
||||||
ArgNode *arg_node = QUEUE_DATA(head, ArgNode, node);
|
|
||||||
xstrlcat(utf8_cmd_line, arg_node->arg, utf8_cmd_line_len);
|
xstrlcat(utf8_cmd_line, arg_node->arg, utf8_cmd_line_len);
|
||||||
xfree(arg_node->arg);
|
xfree(arg_node->arg);
|
||||||
xfree(arg_node);
|
xfree(arg_node);
|
||||||
if (QUEUE_EMPTY(&args_q)) {
|
QUEUE_REMOVE(q);
|
||||||
break;
|
if (!QUEUE_EMPTY(&args_q)) {
|
||||||
} else {
|
|
||||||
xstrlcat(utf8_cmd_line, " ", utf8_cmd_line_len);
|
xstrlcat(utf8_cmd_line, " ", utf8_cmd_line_len);
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
int result = utf8_to_utf16(utf8_cmd_line, -1, cmd_line);
|
int result = utf8_to_utf16(utf8_cmd_line, -1, cmd_line);
|
||||||
xfree(utf8_cmd_line);
|
xfree(utf8_cmd_line);
|
||||||
@@ -507,11 +505,11 @@ static int build_env_block(dict_T *denv, wchar_t **env_block)
|
|||||||
*env_block = xmalloc(sizeof(**env_block) * env_block_len);
|
*env_block = xmalloc(sizeof(**env_block) * env_block_len);
|
||||||
wchar_t *pos = *env_block;
|
wchar_t *pos = *env_block;
|
||||||
|
|
||||||
QUEUE_FOREACH(q, &env_q) {
|
QUEUE_FOREACH(q, &env_q, {
|
||||||
EnvNode *env_node = QUEUE_DATA(q, EnvNode, node);
|
EnvNode *env_node = QUEUE_DATA(q, EnvNode, node);
|
||||||
memcpy(pos, env_node->str, env_node->len * sizeof(*pos));
|
memcpy(pos, env_node->str, env_node->len * sizeof(*pos));
|
||||||
pos += env_node->len;
|
pos += env_node->len;
|
||||||
}
|
})
|
||||||
|
|
||||||
*pos = L'\0';
|
*pos = L'\0';
|
||||||
|
|
||||||
|
@@ -371,4 +371,79 @@ describe('VimL dictionary notifications', function()
|
|||||||
eq(1, eval('g:called'))
|
eq(1, eval('g:called'))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('does not crash when using dictwatcherdel in callback', function()
|
||||||
|
source([[
|
||||||
|
let g:d = {}
|
||||||
|
|
||||||
|
function! W1(...)
|
||||||
|
" Delete current and following watcher.
|
||||||
|
call dictwatcherdel(g:d, '*', function('W1'))
|
||||||
|
call dictwatcherdel(g:d, '*', function('W2'))
|
||||||
|
try
|
||||||
|
call dictwatcherdel({}, 'meh', function('tr'))
|
||||||
|
catch
|
||||||
|
let g:exc = v:exception
|
||||||
|
endtry
|
||||||
|
endfunction
|
||||||
|
call dictwatcheradd(g:d, '*', function('W1'))
|
||||||
|
|
||||||
|
function! W2(...)
|
||||||
|
endfunction
|
||||||
|
call dictwatcheradd(g:d, '*', function('W2'))
|
||||||
|
|
||||||
|
let g:d.foo = 23
|
||||||
|
]])
|
||||||
|
eq(23, eval('g:d.foo'))
|
||||||
|
eq("Vim(call):Couldn't find a watcher matching key and callback", eval('g:exc'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('does not call watcher added in callback', function()
|
||||||
|
source([[
|
||||||
|
let g:d = {}
|
||||||
|
let g:calls = []
|
||||||
|
|
||||||
|
function! W1(...) abort
|
||||||
|
call add(g:calls, 'W1')
|
||||||
|
call dictwatcheradd(g:d, '*', function('W2'))
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! W2(...) abort
|
||||||
|
call add(g:calls, 'W2')
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
call dictwatcheradd(g:d, '*', function('W1'))
|
||||||
|
let g:d.foo = 23
|
||||||
|
]])
|
||||||
|
eq(23, eval('g:d.foo'))
|
||||||
|
eq({"W1"}, eval('g:calls'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('calls watcher deleted in callback', function()
|
||||||
|
source([[
|
||||||
|
let g:d = {}
|
||||||
|
let g:calls = []
|
||||||
|
|
||||||
|
function! W1(...) abort
|
||||||
|
call add(g:calls, "W1")
|
||||||
|
call dictwatcherdel(g:d, '*', function('W2'))
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! W2(...) abort
|
||||||
|
call add(g:calls, "W2")
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
call dictwatcheradd(g:d, '*', function('W1'))
|
||||||
|
call dictwatcheradd(g:d, '*', function('W2'))
|
||||||
|
let g:d.foo = 123
|
||||||
|
|
||||||
|
unlet g:d
|
||||||
|
let g:d = {}
|
||||||
|
call dictwatcheradd(g:d, '*', function('W2'))
|
||||||
|
call dictwatcheradd(g:d, '*', function('W1'))
|
||||||
|
let g:d.foo = 123
|
||||||
|
]])
|
||||||
|
eq(123, eval('g:d.foo'))
|
||||||
|
eq({"W1", "W2", "W2", "W1"}, eval('g:calls'))
|
||||||
|
end)
|
||||||
|
|
||||||
end)
|
end)
|
||||||
|
Reference in New Issue
Block a user