Problem: Crash when unletting a variable while listing variables.
Solution: Disallow changing a hashtable while going over the entries.
(closes #11435)
buf_hashtab_add(buf_T *buf)
{
sprintf((char *)buf->b_key, "%x", buf->b_fnum);
- if (hash_add(&buf_hashtab, buf->b_key) == FAIL)
+ if (hash_add(&buf_hashtab, buf->b_key, "create buffer") == FAIL)
emsg(_(e_buffer_cannot_be_registered));
}
hashitem_T *hi = hash_find(&buf_hashtab, buf->b_key);
if (!HASHITEM_EMPTY(hi))
- hash_remove(&buf_hashtab, hi);
+ hash_remove(&buf_hashtab, hi, "close buffer");
}
/*
free_buffer_stuff(buf, TRUE);
#ifdef FEAT_EVAL
// b:changedtick uses an item in buf_T, remove it now
- dictitem_remove(buf->b_vars, (dictitem_T *)&buf->b_ct_di);
+ dictitem_remove(buf->b_vars, (dictitem_T *)&buf->b_ct_di, "free buffer");
unref_var_dict(buf->b_vars);
remove_listeners(buf);
#endif
hashitem_T *hi;
dictitem_T *di;
+ if (check_hashtab_frozen(ht, "clear dict"))
+ return;
+
// Lock the hashtab, we don't want it to resize while freeing items.
hash_lock(ht);
todo = (int)ht->ht_used;
// Remove the item before deleting it, just in case there is
// something recursive causing trouble.
di = HI2DI(hi);
- hash_remove(ht, hi);
+ hash_remove(ht, hi, "clear dict");
dictitem_free(di);
--todo;
}
/*
* Remove item "item" from Dictionary "dict" and free it.
+ * "command" is used for the error message when the hashtab if frozen.
*/
void
-dictitem_remove(dict_T *dict, dictitem_T *item)
+dictitem_remove(dict_T *dict, dictitem_T *item, char *command)
{
hashitem_T *hi;
if (HASHITEM_EMPTY(hi))
internal_error("dictitem_remove()");
else
- hash_remove(&dict->dv_hashtab, hi);
+ hash_remove(&dict->dv_hashtab, hi, command);
dictitem_free(item);
}
{
if (dict_wrong_func_name(d, &item->di_tv, item->di_key))
return FAIL;
- return hash_add(&d->dv_hashtab, item->di_key);
+ return hash_add(&d->dv_hashtab, item->di_key, "add to dictionary");
}
/*
char_u *arg_errmsg = (char_u *)N_("extend() argument");
type_T *type;
+ if (check_hashtab_frozen(&d1->dv_hashtab, "extend"))
+ return;
+
+ if (*action == 'm')
+ {
+ if (check_hashtab_frozen(&d2->dv_hashtab, "extend"))
+ return;
+ hash_lock(&d2->dv_hashtab); // don't rehash on hash_remove()
+ }
+
if (d1->dv_type != NULL && d1->dv_type->tt_member != NULL)
type = d1->dv_type->tt_member;
else
type = NULL;
- if (*action == 'm')
- hash_lock(&d2->dv_hashtab); // don't rehash on hash_remove()
-
todo = (int)d2->dv_hashtab.ht_used;
for (hashitem_T *hi2 = d2->dv_hashtab.ht_array; todo > 0; ++hi2)
{
// If dict_add() fails then "d2" won't be empty.
di1 = HI2DI(hi2);
if (dict_add(d1, di1) == OK)
- hash_remove(&d2->dv_hashtab, hi2);
+ hash_remove(&d2->dv_hashtab, hi2, "extend");
}
else
{
if (var_check_fixed(di->di_flags, arg_errmsg, TRUE)
|| var_check_ro(di->di_flags, arg_errmsg, TRUE))
break;
- dictitem_remove(d, di);
+ dictitem_remove(d, di, "filter");
}
}
}
*rettv = di->di_tv;
init_tv(&di->di_tv);
- dictitem_remove(d, di);
+ dictitem_remove(d, di, "remove()");
}
typedef enum {
INIT(= N_("E1311: Cannot change user commands while listing"));
EXTERN char e_not_allowed_to_change_window_layout_in_this_autocmd[]
INIT(= N_("E1312: Not allowed to change the window layout in this autocmd"));
+EXTERN char e_not_allowed_to_add_or_remove_entries_str[]
+ INIT(= N_("E1313: Not allowed to add or remove entries (%s)"));
// add to v: scope dict, unless the value is not always available
if (p->vv_tv_type != VAR_UNKNOWN)
- hash_add(&vimvarht, p->vv_di.di_key);
+ hash_add(&vimvarht, p->vv_di.di_key, "initialization");
if (p->vv_flags & VV_COMPAT)
// add to compat scope dict
- hash_add(&compat_hashtab, p->vv_di.di_key);
+ hash_add(&compat_hashtab, p->vv_di.di_key, "initialization");
}
set_vim_var_nr(VV_VERSION, VIM_VERSION_100);
set_vim_var_nr(VV_VERSIONLONG, VIM_VERSION_100 * 10000 + highest_patch());
*save_tv = vimvars[idx].vv_tv;
vimvars[idx].vv_str = NULL; // don't free it now
if (vimvars[idx].vv_tv_type == VAR_UNKNOWN)
- hash_add(&vimvarht, vimvars[idx].vv_di.di_key);
+ hash_add(&vimvarht, vimvars[idx].vv_di.di_key, "prepare vimvar");
}
/*
if (HASHITEM_EMPTY(hi))
internal_error("restore_vimvar()");
else
- hash_remove(&vimvarht, hi);
+ hash_remove(&vimvarht, hi, "restore vimvar");
}
}
int todo;
char_u buf[IOSIZE];
+ int save_ht_flags = ht->ht_flags;
+ ht->ht_flags |= HTFLAGS_FROZEN;
+
todo = (int)ht->ht_used;
for (hi = ht->ht_array; todo > 0 && !got_int; ++hi)
{
list_one_var(di, prefix, first);
}
}
+
+ ht->ht_flags = save_ht_flags;
}
/*
listitem_remove(lp->ll_list, lp->ll_li);
else
// unlet a Dictionary item.
- dictitem_remove(lp->ll_dict, lp->ll_di);
+ dictitem_remove(lp->ll_dict, lp->ll_di, "unlet");
return ret;
}
di = HI2DI(hi);
if (var_check_fixed(di->di_flags, name, FALSE)
|| var_check_ro(di->di_flags, name, FALSE)
- || value_check_lock(d->dv_lock, name, FALSE))
+ || value_check_lock(d->dv_lock, name, FALSE)
+ || check_hashtab_frozen(ht, "unlet"))
return FAIL;
delete_var(ht, hi);
{
dictitem_T *di = HI2DI(hi);
- hash_remove(ht, hi);
- clear_tv(&di->di_tv);
- vim_free(di);
+ if (hash_remove(ht, hi, "delete variable") == OK)
+ {
+ clear_tv(&di->di_tv);
+ vim_free(di);
+ }
}
/*
goto failed;
}
+ if (check_hashtab_frozen(ht, "add variable"))
+ goto failed;
+
// Can't add "v:" or "a:" variable.
if (ht == &vimvarht || ht == get_funccal_args_ht())
{
if (di == NULL)
goto failed;
STRCPY(di->di_key, varname);
- if (hash_add(ht, DI2HIKEY(di)) == FAIL)
+ if (hash_add(ht, DI2HIKEY(di), "add variable") == FAIL)
{
vim_free(di);
goto failed;
ht->ht_mask = HT_INIT_SIZE - 1;
}
+/*
+ * If "ht->ht_flags" has HTFLAGS_FROZEN then give an error message using
+ * "command" and return TRUE.
+ */
+ int
+check_hashtab_frozen(hashtab_T *ht, char *command)
+{
+ if ((ht->ht_flags & HTFLAGS_FROZEN) == 0)
+ return FALSE;
+
+ semsg(_(e_not_allowed_to_add_or_remove_entries_str), command);
+ return TRUE;
+}
+
/*
* Free the array of a hash table. Does not free the items it contains!
* If "ht" is not freed then you should call hash_init() next!
/*
* Add item with key "key" to hashtable "ht".
+ * "command" is used for the error message when the hashtab if frozen.
* Returns FAIL when out of memory or the key is already present.
*/
int
-hash_add(hashtab_T *ht, char_u *key)
+hash_add(hashtab_T *ht, char_u *key, char *command)
{
hash_T hash = hash_hash(key);
hashitem_T *hi;
+ if (check_hashtab_frozen(ht, command))
+ return FAIL;
hi = hash_lookup(ht, key, hash);
if (!HASHITEM_EMPTY(hi))
{
hash_T hash)
{
// If resizing failed before and it fails again we can't add an item.
- if (ht->ht_error && hash_may_resize(ht, 0) == FAIL)
+ if ((ht->ht_flags & HTFLAGS_ERROR) && hash_may_resize(ht, 0) == FAIL)
return FAIL;
++ht->ht_used;
/*
* Remove item "hi" from hashtable "ht". "hi" must have been obtained with
* hash_lookup().
+ * "command" is used for the error message when the hashtab if frozen.
* The caller must take care of freeing the item itself.
*/
- void
-hash_remove(hashtab_T *ht, hashitem_T *hi)
+ int
+hash_remove(hashtab_T *ht, hashitem_T *hi, char *command)
{
+ if (check_hashtab_frozen(ht, command))
+ return FAIL;
--ht->ht_used;
++ht->ht_changed;
hi->hi_key = HI_KEY_REMOVED;
hash_may_resize(ht, 0);
+ return OK;
}
/*
if (newarray == NULL)
{
// Out of memory. When there are NULL items still return OK.
- // Otherwise set ht_error, because lookup may result in a hang if
- // we add another item.
+ // Otherwise set ht_flags to HTFLAGS_ERROR, because lookup may
+ // result in a hang if we add another item.
if (ht->ht_filled < ht->ht_mask)
return OK;
- ht->ht_error = TRUE;
+ ht->ht_flags |= HTFLAGS_ERROR;
return FAIL;
}
oldarray = ht->ht_array;
ht->ht_mask = newmask;
ht->ht_filled = ht->ht_used;
++ht->ht_changed;
- ht->ht_error = FALSE;
+ ht->ht_flags &= ~HTFLAGS_ERROR;
return OK;
}
if (lua_isnil(L, 3)) // remove?
{
hashitem_T *hi = hash_find(&d->dv_hashtab, di->di_key);
- hash_remove(&d->dv_hashtab, hi);
+ hash_remove(&d->dv_hashtab, hi, "Lua new index");
dictitem_free(di);
}
else
if (di == NULL)
// Doesn't exist, nothing to do
return 0;
- else
- // Delete the entry
- dictitem_remove(dict, di);
+ // Delete the entry
+ dictitem_remove(dict, di, "Lua delete variable");
}
else
{
return NULL;
}
- hash_remove(&dict->dv_hashtab, hi);
+ hash_remove(&dict->dv_hashtab, hi, "Python remove variable");
dictitem_free(di);
}
return -1;
}
hi = hash_find(&dict->dv_hashtab, di->di_key);
- hash_remove(&dict->dv_hashtab, hi);
+ hash_remove(&dict->dv_hashtab, hi, "Python remove item");
dictitem_free(di);
Py_XDECREF(todecref);
return 0;
return NULL;
}
- hash_remove(&self->dict->dv_hashtab, hi);
+ hash_remove(&self->dict->dv_hashtab, hi, "Python pop item");
dictitem_free(di);
return ret;
if (di == NULL || ruby_convert_to_vim_value(val, &di->di_tv) != OK
|| dict_add(d, di) != OK)
{
- d->dv_hashtab.ht_error = TRUE;
+ d->dv_hashtab.ht_flags |= HTFLAGS_ERROR;
return ST_STOP;
}
return ST_CONTINUE;
return FAIL;
rb_hash_foreach(val, convert_hash2dict, (VALUE)d);
- if (d->dv_hashtab.ht_error)
+ if (d->dv_hashtab.ht_flags & HTFLAGS_ERROR)
{
dict_unref(d);
return FAIL;
int dict_free_nonref(int copyID);
void dict_free_items(int copyID);
dictitem_T *dictitem_alloc(char_u *key);
-void dictitem_remove(dict_T *dict, dictitem_T *item);
+void dictitem_remove(dict_T *dict, dictitem_T *item, char *command);
void dictitem_free(dictitem_T *item);
dict_T *dict_copy(dict_T *orig, int deep, int top, int copyID);
int dict_wrong_func_name(dict_T *d, typval_T *tv, char_u *name);
/* hashtab.c */
void hash_init(hashtab_T *ht);
+int check_hashtab_frozen(hashtab_T *ht, char *command);
void hash_clear(hashtab_T *ht);
void hash_clear_all(hashtab_T *ht, int off);
hashitem_T *hash_find(hashtab_T *ht, char_u *key);
hashitem_T *hash_lookup(hashtab_T *ht, char_u *key, hash_T hash);
void hash_debug_results(void);
-int hash_add(hashtab_T *ht, char_u *key);
+int hash_add(hashtab_T *ht, char_u *key, char *command);
int hash_add_item(hashtab_T *ht, hashitem_T *hi, char_u *key, hash_T hash);
-void hash_remove(hashtab_T *ht, hashitem_T *hi);
+int hash_remove(hashtab_T *ht, hashitem_T *hi, char *command);
void hash_lock(hashtab_T *ht);
void hash_lock_size(hashtab_T *ht, int size);
void hash_unlock(hashtab_T *ht);
if (group->sg_refcount == 0)
{
// All the signs in this group are removed
- hash_remove(&sg_table, hi);
+ hash_remove(&sg_table, hi, "sign remove");
vim_free(group);
}
}
smsg(_("Affix also used for BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST in %s line %d: %s"),
fname, lnum, items[1]);
STRCPY(cur_aff->ah_key, items[1]);
- hash_add(tp, cur_aff->ah_key);
+ hash_add(tp, cur_aff->ah_key, "spelling");
cur_aff->ah_combine = (*items[2] == 'Y');
}
p = vim_strsave(items[i]);
if (p == NULL)
break;
- hash_add(&spin->si_commonwords, p);
+ hash_add(&spin->si_commonwords, p, "spelling");
}
}
}
id = spin->si_newcompID--;
} while (vim_strchr((char_u *)"/?*+[]\\-^", id) != NULL);
ci->ci_newID = id;
- hash_add(&aff->af_comp, ci->ci_key);
+ hash_add(&aff->af_comp, ci->ci_key, "spelling");
}
*tp++ = id;
}
// This allows for storing 10 items (2/3 of 16) before a resize is needed.
#define HT_INIT_SIZE 16
+// flags used for ht_flags
+#define HTFLAGS_ERROR 0x01 // Set when growing failed, can't add more
+ // items before growing works.
+#define HTFLAGS_FROZEN 0x02 // Trying to add or remove an item will result
+ // in an error message.
+
typedef struct hashtable_S
{
long_u ht_mask; // mask used for hash value (nr of items in
long_u ht_filled; // number of items used + removed
int ht_changed; // incremented when adding or removing an item
int ht_locked; // counter for hash_lock()
- int ht_error; // when set growing failed, can't add more
- // items before growing works
+ int ht_flags; // HTFLAGS_ values
hashitem_T *ht_array; // points to the array, allocated when it's
// not "ht_smallarray"
hashitem_T ht_smallarray[HT_INIT_SIZE]; // initial array
if (kp_prev == NULL)
{
if (kp_next == NULL)
- hash_remove(ht, hi);
+ hash_remove(ht, hi, "syntax clear keyword");
else
hi->hi_key = KE2HIKEY(kp_next);
}
char *hash_key = alloc(NUMBUFLEN);
vim_snprintf(hash_key, NUMBUFLEN, "%d", bufnr);
- hash_add(terminal_bufs, (char_u *)hash_key);
+ hash_add(terminal_bufs, (char_u *)hash_key, "terminal session");
}
return put_eol(fd);
call StopVimInTerminal(buf)
endfunc
+func Test_autocmd_CmdlineLeave_unlet()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ for i in range(1, 999)
+ exe 'let g:var' .. i '=' i
+ endfor
+ au CmdlineLeave : call timer_start(0, {-> execute('unlet g:var990')})
+ END
+ call writefile(lines, 'XleaveUnlet', 'D')
+ let buf = RunVimInTerminal('-S XleaveUnlet', {'rows': 10})
+
+ " this was using freed memory
+ call term_sendkeys(buf, ":let g:\<CR>")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "G")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "\<CR>") " for the hit-enter prompt
+
+ call StopVimInTerminal(buf)
+endfunc
+
function s:Before_test_dirchanged()
augroup test_dirchanged
autocmd!
}
hash_init(*htp);
}
- hash_add(*htp, PT2HIKEY(prop));
+ hash_add(*htp, PT2HIKEY(prop), "prop type");
}
else
{
ht = buf->b_proptypes;
VIM_CLEAR(buf->b_proparray);
}
- hash_remove(ht, hi);
+ hash_remove(ht, hi, "prop type delete");
vim_free(prop);
}
}
fp->uf_cb_state = state;
set_ufunc_name(fp, name);
- hash_add(&func_hashtab, UF2HIKEY(fp));
+ hash_add(&func_hashtab, UF2HIKEY(fp), "add C function");
return name;
}
if (ufunc == NULL)
goto erret;
set_ufunc_name(ufunc, name);
- if (hash_add(&func_hashtab, UF2HIKEY(ufunc)) == FAIL)
+ if (hash_add(&func_hashtab, UF2HIKEY(ufunc), "add function") == FAIL)
goto erret;
ufunc->uf_flags = FC_LAMBDA;
ufunc->uf_refcount = 1;
rettv->vval.v_partial = pt;
rettv->v_type = VAR_PARTIAL;
- hash_add(&func_hashtab, UF2HIKEY(fp));
+ hash_add(&func_hashtab, UF2HIKEY(fp), "add lambda");
}
theend:
{
STRCPY(v->di_key, name);
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
- hash_add(&dp->dv_hashtab, DI2HIKEY(v));
+ hash_add(&dp->dv_hashtab, DI2HIKEY(v), "add variable");
v->di_tv.v_type = VAR_NUMBER;
v->di_tv.v_lock = VAR_FIXED;
v->di_tv.vval.v_number = nr;
fp->uf_flags |= FC_DEAD;
return FALSE;
}
- hash_remove(&func_hashtab, hi);
+ hash_remove(&func_hashtab, hi, "remove function");
fp->uf_flags |= FC_DELETED;
return TRUE;
}
fp->uf_refcount = 1;
STRCPY(fp->uf_name, global);
- hash_add(&func_hashtab, UF2HIKEY(fp));
+ hash_add(&func_hashtab, UF2HIKEY(fp), "copy lambda");
// the referenced dfunc_T is now used one more time
link_def_function(fp);
name = v->di_key;
STRCPY(name, "self");
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
- hash_add(&fc->fc_l_vars.dv_hashtab, DI2HIKEY(v));
+ hash_add(&fc->fc_l_vars.dv_hashtab, DI2HIKEY(v), "set self dictionary");
v->di_tv.v_type = VAR_DICT;
v->di_tv.v_lock = 0;
v->di_tv.vval.v_dict = selfdict;
name = v->di_key;
STRCPY(name, "000");
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
- hash_add(&fc->fc_l_avars.dv_hashtab, DI2HIKEY(v));
+ hash_add(&fc->fc_l_avars.dv_hashtab, DI2HIKEY(v), "function argument");
v->di_tv.v_type = VAR_LIST;
v->di_tv.v_lock = VAR_FIXED;
v->di_tv.vval.v_list = &fc->fc_l_varlist;
// Named arguments should be accessed without the "a:" prefix in
// lambda expressions. Add to the l: dict.
copy_tv(&v->di_tv, &v->di_tv);
- hash_add(&fc->fc_l_vars.dv_hashtab, DI2HIKEY(v));
+ hash_add(&fc->fc_l_vars.dv_hashtab, DI2HIKEY(v), "local variable");
}
else
- hash_add(&fc->fc_l_avars.dv_hashtab, DI2HIKEY(v));
+ hash_add(&fc->fc_l_avars.dv_hashtab, DI2HIKEY(v), "add variable");
if (ai >= 0 && ai < MAX_FUNC_ARGS)
{
hi = hash_find(&func_hashtab, name);
hi->hi_key = UF2HIKEY(fp);
}
- else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL)
+ else if (hash_add(&func_hashtab, UF2HIKEY(fp), "add function") == FAIL)
{
free_fp = TRUE;
goto erret;
{
// Delete the dict item that refers to the function, it will
// invoke func_unref() and possibly delete the function.
- dictitem_remove(fudi.fd_dict, fudi.fd_di);
+ dictitem_remove(fudi.fd_dict, fudi.fd_di, "delfunction");
}
else
{
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 949,
/**/
948,
/**/
NULL, FALSE))
status = FAIL;
else
- dictitem_remove(d, di);
+ dictitem_remove(d, di, "unlet");
}
}
}
if (HASHITEM_EMPTY(hi))
// new variable name
- hash_add(&si->sn_all_vars.dv_hashtab, newsav->sav_key);
+ hash_add(&si->sn_all_vars.dv_hashtab, newsav->sav_key,
+ "add variable");
else if (sav != NULL)
// existing name in a new block, append to the list
sav->sav_next = newsav;
else
{
if (sav_prev == NULL)
- hash_remove(all_ht, all_hi);
+ hash_remove(all_ht, all_hi, "hide variable");
else
sav_prev->sav_next = sav->sav_next;
sv->sv_name = NULL;