changeset 26482:b115b552071f v8.2.3771

patch 8.2.3771: Vim9: accessing freed memory when checking type Commit: https://github.com/vim/vim/commit/dd297bc11d2793ba61638972778c57f2da14e8b5 Author: Bram Moolenaar <Bram@vim.org> Date: Fri Dec 10 10:37:38 2021 +0000 patch 8.2.3771: Vim9: accessing freed memory when checking type Problem: Vim9: accessing freed memory when checking type. Solution: Make a copy of a function type.
author Bram Moolenaar <Bram@vim.org>
date Fri, 10 Dec 2021 11:45:03 +0100
parents 3e235c3b59ff
children a0a1ec1776e3
files src/evalvars.c src/structs.h src/testdir/test_vim9_func.vim src/version.c src/vim9script.c
diffstat 5 files changed, 49 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -3291,6 +3291,7 @@ set_var_const(
     int		vim9script = in_vim9script();
     int		var_in_vim9script;
     int		flags = flags_arg;
+    int		free_tv_arg = !copy;  // free tv_arg if not used
 
     ht = find_var_ht(name, &varname);
     if (ht == NULL || *varname == NUL)
@@ -3545,6 +3546,7 @@ set_var_const(
 	dest_tv->v_lock = 0;
 	init_tv(tv);
     }
+    free_tv_arg = FALSE;
 
     if (vim9script && type != NULL)
     {
@@ -3573,10 +3575,9 @@ set_var_const(
 	// if the reference count is up to one.  That locks only literal
 	// values.
 	item_lock(dest_tv, DICT_MAXNEST, TRUE, TRUE);
-    return;
 
 failed:
-    if (!copy)
+    if (free_tv_arg)
 	clear_tv(tv_arg);
 }
 
--- a/src/structs.h
+++ b/src/structs.h
@@ -1809,6 +1809,7 @@ struct svar_S {
     char_u	*sv_name;	// points into "sn_all_vars" di_key
     typval_T	*sv_tv;		// points into "sn_vars" or "sn_all_vars" di_tv
     type_T	*sv_type;
+    int		sv_type_allocated;  // call free_type() for sv_type
     int		sv_const;	// 0, ASSIGN_CONST or ASSIGN_FINAL
     int		sv_export;	// "export let var = val"
 };
--- a/src/testdir/test_vim9_func.vim
+++ b/src/testdir/test_vim9_func.vim
@@ -1224,6 +1224,25 @@ def Test_set_opfunc_to_lambda()
   CheckScriptSuccess(lines)
 enddef
 
+def Test_lambda_type_allocated()
+  # Check that unreferencing a partial using a lambda can use the variable type
+  # after the lambda has been freed and does not leak memory.
+  var lines =<< trim END
+    vim9script
+
+    func MyomniFunc1(val, findstart, base)
+      return a:findstart ? 0 : []
+    endfunc
+
+    var Lambda = (a, b) => MyomniFunc1(19, a, b)
+    &omnifunc = Lambda
+    Lambda = (a, b) => MyomniFunc1(20, a, b)
+    &omnifunc = string(Lambda)
+    Lambda = (a, b) => strlen(a)
+  END
+  CheckScriptSuccess(lines)
+enddef
+
 " Default arg and varargs
 def MyDefVarargs(one: string, two = 'foo', ...rest: list<string>): string
   var res = one .. ',' .. two
--- a/src/version.c
+++ b/src/version.c
@@ -754,6 +754,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    3771,
+/**/
     3770,
 /**/
     3769,
--- a/src/vim9script.c
+++ b/src/vim9script.c
@@ -268,6 +268,7 @@ free_all_script_vars(scriptitem_T *si)
     hashitem_T	*hi;
     sallvar_T	*sav;
     sallvar_T	*sav_next;
+    int		idx;
 
     hash_lock(ht);
     todo = (int)ht->ht_used;
@@ -293,6 +294,13 @@ free_all_script_vars(scriptitem_T *si)
     hash_clear(ht);
     hash_init(ht);
 
+    for (idx = 0; idx < si->sn_var_vals.ga_len; ++idx)
+    {
+	svar_T    *sv = ((svar_T *)si->sn_var_vals.ga_data) + idx;
+
+	if (sv->sv_type_allocated)
+	    free_type(sv->sv_type);
+    }
     ga_clear(&si->sn_var_vals);
 
     // existing commands using script variable indexes are no longer valid
@@ -899,7 +907,22 @@ update_vim9_script_var(
     {
 	if (*type == NULL)
 	    *type = typval2type(tv, get_copyID(), &si->sn_type_list, do_member);
-	sv->sv_type = *type;
+	if (sv->sv_type_allocated)
+	    free_type(sv->sv_type);
+	if (*type != NULL && ((*type)->tt_type == VAR_FUNC
+					   || (*type)->tt_type == VAR_PARTIAL))
+	{
+	    // The type probably uses uf_type_list, which is cleared when the
+	    // function is freed, but the script variable may keep the type.
+	    // Make a copy to avoid using freed memory.
+	    sv->sv_type = alloc_type(*type);
+	    sv->sv_type_allocated = TRUE;
+	}
+	else
+	{
+	    sv->sv_type = *type;
+	    sv->sv_type_allocated = FALSE;
+	}
     }
 
     // let ex_export() know the export worked.