mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-03 17:24:29 +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;
 | 
			
		||||
        DictWatcher *watcher = NULL;
 | 
			
		||||
        QUEUE_FOREACH(w, &dd->watchers) {
 | 
			
		||||
        QUEUE_FOREACH(w, &dd->watchers, {
 | 
			
		||||
          watcher = tv_dict_watcher_node_data(w);
 | 
			
		||||
          set_ref_in_callback(&watcher->callback, copyID, ht_stack, list_stack);
 | 
			
		||||
        }
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
      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->callback = callback;
 | 
			
		||||
  watcher->busy = false;
 | 
			
		||||
  watcher->needs_free = false;
 | 
			
		||||
  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;
 | 
			
		||||
  DictWatcher *watcher = NULL;
 | 
			
		||||
  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);
 | 
			
		||||
    if (watcher->busy) {
 | 
			
		||||
      queue_is_busy = true;
 | 
			
		||||
    }
 | 
			
		||||
    if (tv_callback_equal(&watcher->callback, &callback)
 | 
			
		||||
        && watcher->key_pattern_len == key_pattern_len
 | 
			
		||||
        && memcmp(watcher->key_pattern, key_pattern, key_pattern_len) == 0) {
 | 
			
		||||
      matched = true;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  if (!matched) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  QUEUE_REMOVE(w);
 | 
			
		||||
  tv_dict_watcher_free(watcher);
 | 
			
		||||
  if (queue_is_busy) {
 | 
			
		||||
    watcher->needs_free = true;
 | 
			
		||||
  } else {
 | 
			
		||||
    QUEUE_REMOVE(w);
 | 
			
		||||
    tv_dict_watcher_free(watcher);
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1258,9 +1267,10 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key,
 | 
			
		||||
 | 
			
		||||
  typval_T rettv;
 | 
			
		||||
 | 
			
		||||
  bool any_needs_free = false;
 | 
			
		||||
  dict->dv_refcount++;
 | 
			
		||||
  QUEUE *w;
 | 
			
		||||
  QUEUE_FOREACH(w, &dict->watchers) {
 | 
			
		||||
  QUEUE_FOREACH(w, &dict->watchers, {
 | 
			
		||||
    DictWatcher *watcher = tv_dict_watcher_node_data(w);
 | 
			
		||||
    if (!watcher->busy && tv_dict_watcher_matches(watcher, key)) {
 | 
			
		||||
      rettv = TV_INITIAL_VALUE;
 | 
			
		||||
@@ -1268,7 +1278,19 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key,
 | 
			
		||||
      callback_call(&watcher->callback, 3, argv, &rettv);
 | 
			
		||||
      watcher->busy = false;
 | 
			
		||||
      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);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -89,6 +89,7 @@ typedef struct dict_watcher {
 | 
			
		||||
  size_t key_pattern_len;
 | 
			
		||||
  QUEUE node;
 | 
			
		||||
  bool busy;  // prevent recursion if the dict is changed in the callback
 | 
			
		||||
  bool needs_free;
 | 
			
		||||
} DictWatcher;
 | 
			
		||||
 | 
			
		||||
/// Bool variable values
 | 
			
		||||
 
 | 
			
		||||
@@ -119,8 +119,8 @@ static MultiQueue *multiqueue_new(MultiQueue *parent, put_callback put_cb,
 | 
			
		||||
void multiqueue_free(MultiQueue *this)
 | 
			
		||||
{
 | 
			
		||||
  assert(this);
 | 
			
		||||
  while (!QUEUE_EMPTY(&this->headtail)) {
 | 
			
		||||
    QUEUE *q = QUEUE_HEAD(&this->headtail);
 | 
			
		||||
  QUEUE *q;
 | 
			
		||||
  QUEUE_FOREACH(q, &this->headtail, {
 | 
			
		||||
    MultiQueueItem *item = multiqueue_node_data(q);
 | 
			
		||||
    if (this->parent) {
 | 
			
		||||
      QUEUE_REMOVE(&item->data.item.parent_item->node);
 | 
			
		||||
@@ -128,7 +128,7 @@ void multiqueue_free(MultiQueue *this)
 | 
			
		||||
    }
 | 
			
		||||
    QUEUE_REMOVE(q);
 | 
			
		||||
    xfree(item);
 | 
			
		||||
  }
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  xfree(this);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -33,11 +33,17 @@ typedef struct _queue {
 | 
			
		||||
#define QUEUE_DATA(ptr, type, field) \
 | 
			
		||||
  ((type *)((char *)(ptr) - offsetof(type, field)))
 | 
			
		||||
 | 
			
		||||
// Important note: mutating the list while QUEUE_FOREACH is
 | 
			
		||||
// iterating over its elements results in undefined behavior.
 | 
			
		||||
#define QUEUE_FOREACH(q, h) \
 | 
			
		||||
  for (  /* NOLINT(readability/braces) */ \
 | 
			
		||||
      (q) = (h)->next; (q) != (h); (q) = (q)->next)
 | 
			
		||||
// Important note: the node currently being processed can be safely deleted.
 | 
			
		||||
// otherwise, mutating the list while QUEUE_FOREACH is iterating over its
 | 
			
		||||
// elements results in undefined behavior.
 | 
			
		||||
#define QUEUE_FOREACH(q, h, code) \
 | 
			
		||||
  (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.
 | 
			
		||||
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;
 | 
			
		||||
  char *utf8_cmd_line = xmalloc(utf8_cmd_line_len);
 | 
			
		||||
  *utf8_cmd_line = NUL;
 | 
			
		||||
  while (1) {
 | 
			
		||||
    QUEUE *head = QUEUE_HEAD(&args_q);
 | 
			
		||||
    QUEUE_REMOVE(head);
 | 
			
		||||
    ArgNode *arg_node = QUEUE_DATA(head, ArgNode, node);
 | 
			
		||||
  QUEUE *q;
 | 
			
		||||
  QUEUE_FOREACH(q, &args_q, {
 | 
			
		||||
    ArgNode *arg_node = QUEUE_DATA(q, ArgNode, node);
 | 
			
		||||
    xstrlcat(utf8_cmd_line, arg_node->arg, utf8_cmd_line_len);
 | 
			
		||||
    xfree(arg_node->arg);
 | 
			
		||||
    xfree(arg_node);
 | 
			
		||||
    if (QUEUE_EMPTY(&args_q)) {
 | 
			
		||||
      break;
 | 
			
		||||
    } else {
 | 
			
		||||
    QUEUE_REMOVE(q);
 | 
			
		||||
    if (!QUEUE_EMPTY(&args_q)) {
 | 
			
		||||
      xstrlcat(utf8_cmd_line, " ", utf8_cmd_line_len);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  int result = utf8_to_utf16(utf8_cmd_line, -1, 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);
 | 
			
		||||
  wchar_t *pos = *env_block;
 | 
			
		||||
 | 
			
		||||
  QUEUE_FOREACH(q, &env_q) {
 | 
			
		||||
  QUEUE_FOREACH(q, &env_q, {
 | 
			
		||||
    EnvNode *env_node = QUEUE_DATA(q, EnvNode, node);
 | 
			
		||||
    memcpy(pos, env_node->str, env_node->len * sizeof(*pos));
 | 
			
		||||
    pos += env_node->len;
 | 
			
		||||
  }
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  *pos = L'\0';
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -371,4 +371,79 @@ describe('VimL dictionary notifications', function()
 | 
			
		||||
    eq(1, eval('g:called'))
 | 
			
		||||
  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)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user