# HG changeset patch # User Bram Moolenaar # Date 1669394704 -3600 # Node ID 684e6dfa2fba9b5d126a8a7effda44dd41638266 # Parent e93800d75da2b42c857f37e462eaf90967877334 patch 9.0.0949: crash when unletting a variable while listing variables Commit: https://github.com/vim/vim/commit/ef2c325f5e3c437b722bb96bf369ba2a5c541163 Author: Bram Moolenaar Date: Fri Nov 25 16:31:51 2022 +0000 patch 9.0.0949: crash when unletting a variable while listing variables Problem: Crash when unletting a variable while listing variables. Solution: Disallow changing a hashtable while going over the entries. (closes #11435) diff --git a/src/buffer.c b/src/buffer.c --- a/src/buffer.c +++ b/src/buffer.c @@ -434,7 +434,7 @@ static hashtab_T buf_hashtab; 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)); } @@ -444,7 +444,7 @@ buf_hashtab_remove(buf_T *buf) 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"); } /* @@ -925,7 +925,7 @@ free_buffer(buf_T *buf) 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 diff --git a/src/dict.c b/src/dict.c --- a/src/dict.c +++ b/src/dict.c @@ -122,6 +122,9 @@ hashtab_free_contents(hashtab_T *ht) 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; @@ -132,7 +135,7 @@ hashtab_free_contents(hashtab_T *ht) // 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; } @@ -256,9 +259,10 @@ dictitem_copy(dictitem_T *org) /* * 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; @@ -266,7 +270,7 @@ dictitem_remove(dict_T *dict, dictitem_T 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); } @@ -375,7 +379,7 @@ dict_add(dict_T *d, dictitem_T *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"); } /* @@ -1094,14 +1098,21 @@ dict_extend(dict_T *d1, dict_T *d2, char 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) { @@ -1126,7 +1137,7 @@ dict_extend(dict_T *d1, dict_T *d2, char // 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 { @@ -1406,7 +1417,7 @@ dict_filter_map( 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"); } } } @@ -1453,7 +1464,7 @@ dict_remove(typval_T *argvars, typval_T *rettv = di->di_tv; init_tv(&di->di_tv); - dictitem_remove(d, di); + dictitem_remove(d, di, "remove()"); } typedef enum { diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -3343,3 +3343,5 @@ EXTERN char e_cannot_change_user_command 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)")); diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -217,10 +217,10 @@ evalvars_init(void) // 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()); @@ -562,7 +562,7 @@ prepare_vimvar(int idx, typval_T *save_t *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"); } /* @@ -582,7 +582,7 @@ restore_vimvar(int idx, typval_T *save_t if (HASHITEM_EMPTY(hi)) internal_error("restore_vimvar()"); else - hash_remove(&vimvarht, hi); + hash_remove(&vimvarht, hi, "restore vimvar"); } } @@ -1380,6 +1380,9 @@ list_hashtable_vars( 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) { @@ -1399,6 +1402,8 @@ list_hashtable_vars( list_one_var(di, prefix, first); } } + + ht->ht_flags = save_ht_flags; } /* @@ -2008,7 +2013,7 @@ do_unlet_var( 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; } @@ -2095,7 +2100,8 @@ do_unlet(char_u *name, int forceit) 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); @@ -3554,9 +3560,11 @@ delete_var(hashtab_T *ht, hashitem_T *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); + } } /* @@ -3895,6 +3903,9 @@ set_var_const( 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()) { @@ -3913,7 +3924,7 @@ set_var_const( 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; diff --git a/src/hashtab.c b/src/hashtab.c --- a/src/hashtab.c +++ b/src/hashtab.c @@ -71,6 +71,20 @@ hash_init(hashtab_T *ht) } /* + * 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! */ @@ -201,14 +215,17 @@ hash_debug_results(void) /* * 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)) { @@ -232,7 +249,7 @@ hash_add_item( 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; @@ -266,15 +283,19 @@ hash_set(hashitem_T *hi, char_u *key) /* * 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; } /* @@ -407,11 +428,11 @@ hash_may_resize( 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; @@ -453,7 +474,7 @@ hash_may_resize( ht->ht_mask = newmask; ht->ht_filled = ht->ht_used; ++ht->ht_changed; - ht->ht_error = FALSE; + ht->ht_flags &= ~HTFLAGS_ERROR; return OK; } diff --git a/src/if_lua.c b/src/if_lua.c --- a/src/if_lua.c +++ b/src/if_lua.c @@ -1150,7 +1150,7 @@ luaV_dict_newindex(lua_State *L) 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 @@ -1838,9 +1838,8 @@ luaV_setvar(lua_State *L) 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 { diff --git a/src/if_py_both.h b/src/if_py_both.h --- a/src/if_py_both.h +++ b/src/if_py_both.h @@ -1768,7 +1768,7 @@ DictionaryLength(DictionaryObject *self) return NULL; } - hash_remove(&dict->dv_hashtab, hi); + hash_remove(&dict->dv_hashtab, hi, "Python remove variable"); dictitem_free(di); } @@ -1893,7 +1893,7 @@ DictionaryAssItem( 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; @@ -2194,7 +2194,7 @@ DictionaryPopItem(DictionaryObject *self return NULL; } - hash_remove(&self->dict->dv_hashtab, hi); + hash_remove(&self->dict->dv_hashtab, hi, "Python pop item"); dictitem_free(di); return ret; diff --git a/src/if_ruby.c b/src/if_ruby.c --- a/src/if_ruby.c +++ b/src/if_ruby.c @@ -1799,7 +1799,7 @@ convert_hash2dict(VALUE key, VALUE val, 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; @@ -1879,7 +1879,7 @@ ruby_convert_to_vim_value(VALUE val, typ 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; diff --git a/src/proto/dict.pro b/src/proto/dict.pro --- a/src/proto/dict.pro +++ b/src/proto/dict.pro @@ -10,7 +10,7 @@ void dict_unref(dict_T *d); 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); diff --git a/src/proto/hashtab.pro b/src/proto/hashtab.pro --- a/src/proto/hashtab.pro +++ b/src/proto/hashtab.pro @@ -1,13 +1,14 @@ /* 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); diff --git a/src/sign.c b/src/sign.c --- a/src/sign.c +++ b/src/sign.c @@ -126,7 +126,7 @@ sign_group_unref(char_u *groupname) 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); } } diff --git a/src/spellfile.c b/src/spellfile.c --- a/src/spellfile.c +++ b/src/spellfile.c @@ -2643,7 +2643,7 @@ spell_read_aff(spellinfo_T *spin, char_u 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'); } @@ -2994,7 +2994,7 @@ spell_read_aff(spellinfo_T *spin, char_u p = vim_strsave(items[i]); if (p == NULL) break; - hash_add(&spin->si_commonwords, p); + hash_add(&spin->si_commonwords, p, "spelling"); } } } @@ -3312,7 +3312,7 @@ process_compflags( 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; } diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1313,6 +1313,12 @@ typedef struct hashitem_S // 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 @@ -1321,8 +1327,7 @@ typedef struct hashtable_S 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 diff --git a/src/syntax.c b/src/syntax.c --- a/src/syntax.c +++ b/src/syntax.c @@ -4339,7 +4339,7 @@ syn_clear_keyword(int id, hashtab_T *ht) 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); } diff --git a/src/terminal.c b/src/terminal.c --- a/src/terminal.c +++ b/src/terminal.c @@ -1020,7 +1020,7 @@ term_write_session(FILE *fd, win_T *wp, 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); diff --git a/src/testdir/test_autocmd.vim b/src/testdir/test_autocmd.vim --- a/src/testdir/test_autocmd.vim +++ b/src/testdir/test_autocmd.vim @@ -2326,6 +2326,28 @@ func Test_autocmd_user_clear_group() 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:\") + call TermWait(buf, 50) + call term_sendkeys(buf, "G") + call TermWait(buf, 50) + call term_sendkeys(buf, "\") " for the hit-enter prompt + + call StopVimInTerminal(buf) +endfunc + function s:Before_test_dirchanged() augroup test_dirchanged autocmd! diff --git a/src/textprop.c b/src/textprop.c --- a/src/textprop.c +++ b/src/textprop.c @@ -1789,7 +1789,7 @@ prop_type_set(typval_T *argvars, int add } hash_init(*htp); } - hash_add(*htp, PT2HIKEY(prop)); + hash_add(*htp, PT2HIKEY(prop), "prop type"); } else { @@ -1924,7 +1924,7 @@ f_prop_type_delete(typval_T *argvars, ty ht = buf->b_proptypes; VIM_CLEAR(buf->b_proparray); } - hash_remove(ht, hi); + hash_remove(ht, hi, "prop type delete"); vim_free(prop); } } diff --git a/src/userfunc.c b/src/userfunc.c --- a/src/userfunc.c +++ b/src/userfunc.c @@ -585,7 +585,7 @@ register_cfunc(cfunc_T cb, cfunc_free_T 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; } @@ -1278,7 +1278,7 @@ lambda_function_body( 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; @@ -1572,7 +1572,7 @@ get_lambda_tv( 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: @@ -2128,7 +2128,7 @@ add_nr_var( { 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; @@ -2348,7 +2348,7 @@ func_remove(ufunc_T *fp) 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; } @@ -2510,7 +2510,7 @@ copy_lambda_to_global_func( 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); @@ -2718,7 +2718,7 @@ call_user_func( 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; @@ -2744,7 +2744,7 @@ call_user_func( 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; @@ -2838,10 +2838,10 @@ call_user_func( // 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) { @@ -5060,7 +5060,7 @@ define_function(exarg_T *eap, char_u *na 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; @@ -5462,7 +5462,7 @@ ex_delfunction(exarg_T *eap) { // 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 { diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -696,6 +696,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 949, +/**/ 948, /**/ 947, diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -2366,7 +2366,7 @@ execute_unletindex(isn_T *iptr, ectx_T * NULL, FALSE)) status = FAIL; else - dictitem_remove(d, di); + dictitem_remove(d, di, "unlet"); } } } diff --git a/src/vim9script.c b/src/vim9script.c --- a/src/vim9script.c +++ b/src/vim9script.c @@ -942,7 +942,8 @@ update_vim9_script_var( 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; @@ -1033,7 +1034,7 @@ hide_script_var(scriptitem_T *si, int id 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;