diff src/hashtab.c @ 31231:684e6dfa2fba v9.0.0949

patch 9.0.0949: crash when unletting a variable while listing variables Commit: https://github.com/vim/vim/commit/ef2c325f5e3c437b722bb96bf369ba2a5c541163 Author: Bram Moolenaar <Bram@vim.org> 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)
author Bram Moolenaar <Bram@vim.org>
date Fri, 25 Nov 2022 17:45:04 +0100
parents 3ad379c0ab28
children aa2ec7a12146
line wrap: on
line diff
--- 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;
 }