CVE-2022-0156: Use After Free (CWE-416)
value: vim is vulnerable to Use After Free
CWE description: The product reuses or references memory after it has been freed. At some point afterward, the memory may be allocated again and saved in another pointer, while the original pointer references a location somewhere within the new allocation. Any operations using the original pointer are no longer valid because the memory "belongs" to the code that operates on the new pointer.
CVSS v3 base score: 5.5
Published: 2022-01-10T16:15Z
Repository: https://github.com/vim/vim
Language: C
Files changed: src/alloc.c, src/message.c, src/proto/alloc.pro, src/proto/userfunc.pro, src/testdir/test_vim9_func.vim, src/usercmd.c, src/userfunc.c, src/version.c, src/vim9compile.c, src/viminfo.c
Commit date: 2022-01-08 15:39:39 +0000
Commit message: patch 8.2.4040: keeping track of allocated lines is too complicated

Problem:    Keeping track of allocated lines in user functions is too
            complicated.
Solution:   Instead of freeing individual lines keep them all until the end.

Diff: diff --git a/src/alloc.c b/src/alloc.c
index 3651a2e5996..e3cd8578f6e 100644
--- a/src/alloc.c
+++ b/src/alloc.c
@@ -700,11 +700,11 @@ ga_init(garray_T *gap)
     gap->ga_maxlen = 0;
     gap->ga_len = 0;
 }
 
     void
-ga_init2(garray_T *gap, int itemsize, int growsize)
+ga_init2(garray_T *gap, size_t itemsize, int growsize)
 {
     ga_init(gap);
     gap->ga_itemsize = itemsize;
     gap->ga_growsize = growsize;
 }
@@ -787,11 +787,11 @@ ga_concat_strings(garray_T *gap, char *sep)
 /*
  * Make a copy of string "p" and add it to "gap".
  * When out of memory nothing changes and FAIL is returned.
  */
     int
-ga_add_string(garray_T *gap, char_u *p)
+ga_copy_string(garray_T *gap, char_u *p)
 {
     char_u *cp = vim_strsave(p);
 
     if (cp == NULL)
 	return FAIL;
@@ -803,10 +803,23 @@ ga_add_string(garray_T *gap, char_u *p)
     }
     ((char_u **)(gap->ga_data))[gap->ga_len++] = cp;
     return OK;
 }
 
+/*
+ * Add string "p" to "gap".
+ * When out of memory "p" is freed and FAIL is returned.
+ */
+    int
+ga_add_string(garray_T *gap, char_u *p)
+{
+    if (ga_grow(gap, 1) == FAIL)
+	return FAIL;
+    ((char_u **)(gap->ga_data))[gap->ga_len++] = p;
+    return OK;
+}
+
 /*
  * Concatenate a string to a growarray which contains bytes.
  * When "s" is NULL does not do anything.
  * Note: Does NOT copy the NUL at the end!
  */
diff --git a/src/message.c b/src/message.c
index 17c541f95f9..04a51fbf359 100644
--- a/src/message.c
+++ b/src/message.c
@@ -585,11 +585,11 @@ ignore_error_for_testing(char_u *error)
 	ga_init2(&ignore_error_list, sizeof(char_u *), 1);
 
     if (STRCMP("RESET", error) == 0)
 	ga_clear_strings(&ignore_error_list);
     else
-	ga_add_string(&ignore_error_list, error);
+	ga_copy_string(&ignore_error_list, error);
 }
 
     static int
 ignore_error(char_u *msg)
 {
diff --git a/src/proto/alloc.pro b/src/proto/alloc.pro
index 2069ca8ca28..18281adaaf8 100644
--- a/src/proto/alloc.pro
+++ b/src/proto/alloc.pro
@@ -15,14 +15,15 @@ char_u *vim_memsave(char_u *p, size_t len);
 void vim_free(void *x);
 void ga_clear(garray_T *gap);
 void ga_clear_strings(garray_T *gap);
 int ga_copy_strings(garray_T *from, garray_T *to);
 void ga_init(garray_T *gap);
-void ga_init2(garray_T *gap, int itemsize, int growsize);
+void ga_init2(garray_T *gap, size_t itemsize, int growsize);
 int ga_grow(garray_T *gap, int n);
 int ga_grow_inner(garray_T *gap, int n);
 char_u *ga_concat_strings(garray_T *gap, char *sep);
+int ga_copy_string(garray_T *gap, char_u *p);
 int ga_add_string(garray_T *gap, char_u *p);
 void ga_concat(garray_T *gap, char_u *s);
 void ga_concat_len(garray_T *gap, char_u *s, size_t len);
 void ga_append(garray_T *gap, int c);
 void append_ga_line(garray_T *gap);
diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro
index 311cee2e128..6b054ffe61c 100644
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -36,11 +36,11 @@ char_u *printable_func_name(ufunc_T *fp);
 char_u *trans_function_name(char_u **pp, int *is_global, int skip, int flags, funcdict_T *fdp, partial_T **partial, type_T **type);
 char_u *untrans_function_name(char_u *name);
 char_u *get_scriptlocal_funcname(char_u *funcname);
 char_u *save_function_name(char_u **name, int *is_global, int skip, int flags, funcdict_T *fudi);
 void list_functions(regmatch_T *regmatch);
-ufunc_T *define_function(exarg_T *eap, char_u *name_arg, char_u **line_to_free);
+ufunc_T *define_function(exarg_T *eap, char_u *name_arg, garray_T *lines_to_free);
 void ex_function(exarg_T *eap);
 void ex_defcompile(exarg_T *eap);
 int eval_fname_script(char_u *p);
 int translated_function_exists(char_u *name, int is_global);
 int has_varargs(ufunc_T *ufunc);
diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim
index 9e420ade748..75d0dd0b65e 100644
--- a/src/testdir/test_vim9_func.vim
+++ b/src/testdir/test_vim9_func.vim
@@ -1755,10 +1755,25 @@ def Test_nested_function_with_args_split()
       defcompile
   END
   CheckScriptFailure(lines, 'E1173: Text found after endfunction: BBBB')
 enddef
 
+def Test_error_in_function_args()
+  var lines =<< trim END
+      def FirstFunction()
+        def SecondFunction(J  =
+        # Nois
+        # one
+         
+         enddef|BBBB
+      enddef
+      # Compile all functions
+      defcompile
+  END
+  CheckScriptFailure(lines, 'E488:')
+enddef
+
 def Test_return_type_wrong()
   CheckScriptFailure([
         'def Func(): number',
         'return "a"',
         'enddef',
@@ -2046,11 +2061,10 @@ func Test_free_dict_while_in_funcstack()
   CheckUnix
   call Run_Test_free_dict_while_in_funcstack()
 endfunc
 
 def Run_Test_free_dict_while_in_funcstack()
-
   # this was freeing the TermRun() default argument dictionary while it was
   # still referenced in a funcstack_T
   var lines =<< trim END
       vim9script
 
diff --git a/src/usercmd.c b/src/usercmd.c
index de985f8a24b..40790a1f563 100644
--- a/src/usercmd.c
+++ b/src/usercmd.c
@@ -1019,11 +1019,11 @@ may_get_cmd_block(exarg_T *eap, char_u *p, char_u **tofree, int *flags)
     {
 	garray_T    ga;
 	char_u	    *line = NULL;
 
 	ga_init2(&ga, sizeof(char_u *), 10);
-	if (ga_add_string(&ga, p) == FAIL)
+	if (ga_copy_string(&ga, p) == FAIL)
 	    return retp;
 
 	// If the argument ends in "}" it must have been concatenated already
 	// for ISN_EXEC.
 	if (p[STRLEN(p) - 1] != '}')
@@ -1036,11 +1036,11 @@ may_get_cmd_block(exarg_T *eap, char_u *p, char_u **tofree, int *flags)
 					   0, GETLINE_CONCAT_CONTBAR)) == NULL)
 		{
 		    emsg(_(e_missing_rcurly));
 		    break;
 		}
-		if (ga_add_string(&ga, line) == FAIL)
+		if (ga_copy_string(&ga, line) == FAIL)
 		    break;
 		if (*skipwhite(line) == '}')
 		    break;
 	    }
 	vim_free(line);
diff --git a/src/userfunc.c b/src/userfunc.c
index 3617e7b1061..020d1bcfcb1 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -164,17 +164,17 @@ one_function_arg(
     return p;
 }
 
 /*
  * Handle line continuation in function arguments or body.
- * Get a next line, store it in "eap" if appropriate and use "line_to_free" to
- * handle freeing the line later.
+ * Get a next line, store it in "eap" if appropriate and put the line in
+ * "lines_to_free" to free the line later.
  */
     static char_u *
 get_function_line(
 	exarg_T		*eap,
-	char_u		**line_to_free,
+	garray_T	*lines_to_free,
 	int		indent,
 	getline_opt_T	getline_options)
 {
     char_u *theline;
 
@@ -182,14 +182,15 @@ get_function_line(
 	theline = getcmdline(':', 0L, indent, 0);
     else
 	theline = eap->getline(':', eap->cookie, indent, getline_options);
     if (theline != NULL)
     {
-	if (*eap->cmdlinep == *line_to_free)
+	if (lines_to_free->ga_len > 0
+		&& *eap->cmdlinep == ((char_u **)lines_to_free->ga_data)
+						   [lines_to_free->ga_len - 1])
 	    *eap->cmdlinep = theline;
-	vim_free(*line_to_free);
-	*line_to_free = theline;
+	ga_add_string(lines_to_free, theline);
     }
 
     return theline;
 }
 
@@ -208,11 +209,11 @@ get_function_args(
     evalarg_T	*evalarg,	// context or NULL
     int		*varargs,
     garray_T	*default_args,
     int		skip,
     exarg_T	*eap,
-    char_u	**line_to_free)
+    garray_T	*lines_to_free)
 {
     int		mustend = FALSE;
     char_u	*arg;
     char_u	*p;
     int		c;
@@ -239,11 +240,11 @@ get_function_args(
     {
 	while (eap != NULL && eap->getline != NULL
 			 && (*p == NUL || (VIM_ISWHITE(*whitep) && *p == '#')))
 	{
 	    // End of the line, get the next one.
-	    char_u *theline = get_function_line(eap, line_to_free, 0,
+	    char_u *theline = get_function_line(eap, lines_to_free, 0,
 							  GETLINE_CONCAT_CONT);
 
 	    if (theline == NULL)
 		break;
 	    whitep = (char_u *)" ";
@@ -675,11 +676,11 @@ function_using_block_scopes(ufunc_T *fp, cstack_T *cstack)
     static int
 get_function_body(
 	exarg_T	    *eap,
 	garray_T    *newlines,
 	char_u	    *line_arg_in,
-	char_u	    **line_to_free)
+	garray_T    *lines_to_free)
 {
     linenr_T	sourcing_lnum_top = SOURCING_LNUM;
     linenr_T	sourcing_lnum_off;
     int		saved_wait_return = need_wait_return;
     char_u	*line_arg = line_arg_in;
@@ -742,11 +743,11 @@ get_function_body(
 		line_arg = p + 1;
 	    }
 	}
 	else
 	{
-	    theline = get_function_line(eap, line_to_free, indent,
+	    theline = get_function_line(eap, lines_to_free, indent,
 							      getline_options);
 	}
 	if (KeyTyped)
 	    lines_left = Rows - 1;
 	if (theline == NULL)
@@ -852,18 +853,24 @@ get_function_body(
 		    }
 		    if (nextcmd != NULL && *skipwhite(nextcmd) != NUL)
 		    {
 			// Another command follows. If the line came from "eap"
 			// we can simply point into it, otherwise we need to
-			// change "eap->cmdlinep".
+			// change "eap->cmdlinep" to point to the last fetched
+			// line.
 			eap->nextcmd = nextcmd;
-			if (*line_to_free != NULL
-					    && *eap->cmdlinep != *line_to_free)
+			if (lines_to_free->ga_len > 0
+				&& *eap->cmdlinep !=
+					    ((char_u **)lines_to_free->ga_data)
+						   [lines_to_free->ga_len - 1])
 			{
+			    // *cmdlinep will be freed later, thus remove the
+			    // line from lines_to_free.
 			    vim_free(*eap->cmdlinep);
-			    *eap->cmdlinep = *line_to_free;
-			    *line_to_free = NULL;
+			    *eap->cmdlinep = ((char_u **)lines_to_free->ga_data)
+						   [lines_to_free->ga_len - 1];
+			    --lines_to_free->ga_len;
 			}
 		    }
 		    break;
 		}
 	    }
@@ -1116,11 +1123,10 @@ lambda_function_body(
     ufunc_T	*ufunc = NULL;
     exarg_T	eap;
     garray_T	newlines;
     char_u	*cmdline = NULL;
     int		ret = FAIL;
-    char_u	*line_to_free = NULL;
     partial_T	*pt;
     char_u	*name;
     int		lnum_save = -1;
     linenr_T	sourcing_lnum_top = SOURCING_LNUM;
 
@@ -1142,16 +1148,13 @@ lambda_function_body(
 	eap.getline = evalarg->eval_getline;
 	eap.cookie = evalarg->eval_cookie;
     }
 
     ga_init2(&newlines, (int)sizeof(char_u *), 10);
-    if (get_function_body(&eap, &newlines, NULL, &line_to_free) == FAIL)
-    {
-	if (cmdline != line_to_free)
-	    vim_free(cmdline);
+    if (get_function_body(&eap, &newlines, NULL,
+					     &evalarg->eval_tofree_ga) == FAIL)
 	goto erret;
-    }
 
     // When inside a lambda must add the function lines to evalarg.eval_ga.
     evalarg->eval_break_count += newlines.ga_len;
     if (gap->ga_itemsize > 0)
     {
@@ -1206,12 +1209,10 @@ lambda_function_body(
 	// "arg" points into cmdline, need to keep the line and free it later.
 	if (ga_grow(tfgap, 1) == OK)
 	{
 	    ((char_u **)(tfgap->ga_data))[tfgap->ga_len++] = cmdline;
 	    evalarg->eval_using_cmdline = TRUE;
-	    if (cmdline == line_to_free)
-		line_to_free = NULL;
 	}
     }
     else
 	*arg = (char_u *)"";
 
@@ -1276,11 +1277,10 @@ lambda_function_body(
     ret = OK;
 
 erret:
     if (lnum_save >= 0)
 	SOURCING_LNUM = lnum_save;
-    vim_free(line_to_free);
     ga_clear_strings(&newlines);
     if (newargs != NULL)
 	ga_clear_strings(newargs);
     ga_clear_strings(default_args);
     if (ufunc != NULL)
@@ -3955,14 +3955,15 @@ list_functions(regmatch_T *regmatch)
 
 /*
  * ":function" also supporting nested ":def".
  * When "name_arg" is not NULL this is a nested function, using "name_arg" for
  * the function name.
+ * "lines_to_free" is a list of strings to be freed later.
  * Returns a pointer to the function or NULL if no function defined.
  */
     ufunc_T *
-define_function(exarg_T *eap, char_u *name_arg, char_u **line_to_free)
+define_function(exarg_T *eap, char_u *name_arg, garray_T *lines_to_free)
 {
     int		j;
     int		c;
     int		saved_did_emsg;
     char_u	*name = name_arg;
@@ -4227,11 +4228,11 @@ define_function(exarg_T *eap, char_u *name_arg, char_u **line_to_free)
     // invalid.
     ++p;
     if (get_function_args(&p, ')', &newargs,
 			eap->cmdidx == CMD_def ? &argtypes : NULL, FALSE,
 			 NULL, &varargs, &default_args, eap->skip,
-			 eap, line_to_free) == FAIL)
+			 eap, lines_to_free) == FAIL)
 	goto errret_2;
     whitep = p;
 
     if (eap->cmdidx == CMD_def)
     {
@@ -4337,11 +4338,11 @@ define_function(exarg_T *eap, char_u *name_arg, char_u **line_to_free)
     // Save the starting line number.
     sourcing_lnum_top = SOURCING_LNUM;
 
     // Do not define the function when getting the body fails and when
     // skipping.
-    if (get_function_body(eap, &newlines, line_arg, line_to_free) == FAIL
+    if (get_function_body(eap, &newlines, line_arg, lines_to_free) == FAIL
 	    || eap->skip)
 	goto erret;
 
     /*
      * If there are no errors, add the function
@@ -4643,14 +4644,15 @@ ret_free:
  * ":function"
  */
     void
 ex_function(exarg_T *eap)
 {
-    char_u *line_to_free = NULL;
+    garray_T lines_to_free;
 
-    (void)define_function(eap, NULL, &line_to_free);
-    vim_free(line_to_free);
+    ga_init2(&lines_to_free, sizeof(char_u *), 50);
+    (void)define_function(eap, NULL, &lines_to_free);
+    ga_clear_strings(&lines_to_free);
 }
 
 /*
  * :defcompile - compile all :def functions in the current script that need to
  * be compiled.  Except dead functions.  Doesn't do profiling.
diff --git a/src/version.c b/src/version.c
index 542227c903a..9e4ae19cb02 100644
--- a/src/version.c
+++ b/src/version.c
@@ -748,10 +748,12 @@ static char *(features[]) =
 	NULL
 };
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    4040,
 /**/
     4039,
 /**/
     4038,
 /**/
diff --git a/src/vim9compile.c b/src/vim9compile.c
index 5af30fd719b..90d7adbb798 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -808,11 +808,11 @@ func_needs_compiling(ufunc_T *ufunc, compiletype_T compile_type)
 
 /*
  * Compile a nested :def command.
  */
     static char_u *
-compile_nested_function(exarg_T *eap, cctx_T *cctx, char_u **line_to_free)
+compile_nested_function(exarg_T *eap, cctx_T *cctx, garray_T *lines_to_free)
 {
     int		is_global = *eap->arg == 'g' && eap->arg[1] == ':';
     char_u	*name_start = eap->arg;
     char_u	*name_end = to_name_end(eap->arg, TRUE);
     int		off;
@@ -874,11 +874,11 @@ compile_nested_function(exarg_T *eap, cctx_T *cctx, char_u **line_to_free)
     {
 	r = FAIL;
 	goto theend;
     }
 
-    ufunc = define_function(eap, lambda_name, line_to_free);
+    ufunc = define_function(eap, lambda_name, lines_to_free);
     if (ufunc == NULL)
     {
 	r = eap->skip ? OK : FAIL;
 	goto theend;
     }
@@ -2494,11 +2494,11 @@ compile_def_function(
 	int		check_return_type,
 	compiletype_T   compile_type,
 	cctx_T		*outer_cctx)
 {
     char_u	*line = NULL;
-    char_u	*line_to_free = NULL;
+    garray_T	lines_to_free;
     char_u	*p;
     char	*errormsg = NULL;	// error message
     cctx_T	cctx;
     garray_T	*instr;
     int		did_emsg_before = did_emsg;
@@ -2512,10 +2512,13 @@ compile_def_function(
 #ifdef FEAT_PROFILE
     int		prof_lnum = -1;
 #endif
     int		debug_lnum = -1;
 
+    // allocated lines are freed at the end
+    ga_init2(&lines_to_free, sizeof(char_u *), 50);
+
     // When using a function that was compiled before: Free old instructions.
     // The index is reused.  Otherwise add a new entry in "def_functions".
     if (ufunc->uf_dfunc_idx > 0)
     {
 	dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
@@ -2679,12 +2682,12 @@ compile_def_function(
 	    // Make a copy, splitting off nextcmd and removing trailing spaces
 	    // may change it.
 	    if (line != NULL)
 	    {
 		line = vim_strsave(line);
-		vim_free(line_to_free);
-		line_to_free = line;
+		if (ga_add_string(&lines_to_free, line) == FAIL)
+		    goto erret;
 	    }
 	}
 
 	CLEAR_FIELD(ea);
 	ea.cmdlinep = &line;
@@ -2924,11 +2927,11 @@ compile_def_function(
 	switch (ea.cmdidx)
 	{
 	    case CMD_def:
 	    case CMD_function:
 		    ea.arg = p;
-		    line = compile_nested_function(&ea, &cctx, &line_to_free);
+		    line = compile_nested_function(&ea, &cctx, &lines_to_free);
 		    break;
 
 	    case CMD_return:
 		    line = compile_return(p, check_return_type,
 				 local_cmdmod.cmod_flags & CMOD_LEGACY, &cctx);
@@ -3234,11 +3237,11 @@ erret:
     estack_compiling = save_estack_compiling;
     cmdmod.cmod_flags =	save_cmod_flags;
     if (do_estack_push)
 	estack_pop();
 
-    vim_free(line_to_free);
+    ga_clear_strings(&lines_to_free);
     free_imported(&cctx);
     free_locals(&cctx);
     ga_clear(&cctx.ctx_type_stack);
     return ret;
 }
diff --git a/src/viminfo.c b/src/viminfo.c
index 3f9e5f3beb7..ac0d3856e7f 100644
--- a/src/viminfo.c
+++ b/src/viminfo.c
@@ -2728,11 +2728,11 @@ read_viminfo_barline(vir_T *virp, int got_encoding, int force, int writing)
     //     |<{value},{value}
     if (*p == '<')
     {
 	// Continuation line of an unrecognized item.
 	if (writing)
-	    ga_add_string(&virp->vir_barlines, virp->vir_line);
+	    ga_copy_string(&virp->vir_barlines, virp->vir_line);
     }
     else
     {
 	ga_init2(&values, sizeof(bval_T), 20);
 	bartype = getdigits(&p);
@@ -2767,11 +2767,11 @@ read_viminfo_barline(vir_T *virp, int got_encoding, int force, int writing)
 		break;
 
 	    default:
 		// copy unrecognized line (for future use)
 		if (writing)
-		    ga_add_string(&virp->vir_barlines, virp->vir_line);
+		    ga_copy_string(&virp->vir_barlines, virp->vir_line);
 	}
 	for (i = 0; i < values.ga_len; ++i)
 	{
 	    vp = (bval_T *)values.ga_data + i;
 	    if (vp->bv_type == BVAL_STRING && vp->bv_allocated)

Vulnerable code: ga_init2(garray_T *gap, int itemsize, int growsize)
ga_add_string(garray_T *gap, char_u *p)
	ga_add_string(&ignore_error_list, error);
void ga_init2(garray_T *gap, int itemsize, int growsize);
ufunc_T *define_function(exarg_T *eap, char_u *name_arg, char_u **line_to_free);

	if (ga_add_string(&ga, p) == FAIL)
		if (ga_add_string(&ga, line) == FAIL)
 * Get a next line, store it in "eap" if appropriate and use "line_to_free" to
 * handle freeing the line later.
	char_u		**line_to_free,
	if (*eap->cmdlinep == *line_to_free)
	vim_free(*line_to_free);
	*line_to_free = theline;
    char_u	**line_to_free)
	    char_u *theline = get_function_line(eap, line_to_free, 0,
	char_u	    **line_to_free)
	    theline = get_function_line(eap, line_to_free, indent,
			// change "eap->cmdlinep".
			if (*line_to_free != NULL
					    && *eap->cmdlinep != *line_to_free)
			    *eap->cmdlinep = *line_to_free;
			    *line_to_free = NULL;
    char_u	*line_to_free = NULL;
    if (get_function_body(&eap, &newlines, NULL, &line_to_free) == FAIL)
    {
	if (cmdline != line_to_free)
	    vim_free(cmdline);
    }
	    if (cmdline == line_to_free)
		line_to_free = NULL;
    vim_free(line_to_free);
define_function(exarg_T *eap, char_u *name_arg, char_u **line_to_free)
			 eap, line_to_free) == FAIL)
    if (get_function_body(eap, &newlines, line_arg, line_to_free) == FAIL
    char_u *line_to_free = NULL;
    (void)define_function(eap, NULL, &line_to_free);
    vim_free(line_to_free);
compile_nested_function(exarg_T *eap, cctx_T *cctx, char_u **line_to_free)
    ufunc = define_function(eap, lambda_name, line_to_free);
    char_u	*line_to_free = NULL;
		vim_free(line_to_free);
		line_to_free = line;
		    line = compile_nested_function(&ea, &cctx, &line_to_free);
    vim_free(line_to_free);
	    ga_add_string(&virp->vir_barlines, virp->vir_line);
		    ga_add_string(&virp->vir_barlines, virp->vir_line);
Fixed code: ga_init2(garray_T *gap, size_t itemsize, int growsize)
ga_copy_string(garray_T *gap, char_u *p)
/*
 * Add string "p" to "gap".
 * When out of memory "p" is freed and FAIL is returned.
 */
    int
ga_add_string(garray_T *gap, char_u *p)
{
    if (ga_grow(gap, 1) == FAIL)
	return FAIL;
    ((char_u **)(gap->ga_data))[gap->ga_len++] = p;
    return OK;
}

	ga_copy_string(&ignore_error_list, error);
void ga_init2(garray_T *gap, size_t itemsize, int growsize);
int ga_copy_string(garray_T *gap, char_u *p);
ufunc_T *define_function(exarg_T *eap, char_u *name_arg, garray_T *lines_to_free);
def Test_error_in_function_args()
  var lines =<< trim END
      def FirstFunction()
        def SecondFunction(J  =
        # Nois
        # one
         
         enddef|BBBB
      enddef
      # Compile all functions
      defcompile
  END
  CheckScriptFailure(lines, 'E488:')
enddef

	if (ga_copy_string(&ga, p) == FAIL)
		if (ga_copy_string(&ga, line) == FAIL)
 * Get a next line, store it in "eap" if appropriate and put the line in
 * "lines_to_free" to free the line later.
	garray_T	*lines_to_free,
	if (lines_to_free->ga_len > 0
		&& *eap->cmdlinep == ((char_u **)lines_to_free->ga_data)
						   [lines_to_free->ga_len - 1])
	ga_add_string(lines_to_free, theline);
    garray_T	*lines_to_free)
	    char_u *theline = get_function_line(eap, lines_to_free, 0,
	garray_T    *lines_to_free)
	    theline = get_function_line(eap, lines_to_free, indent,
			// change "eap->cmdlinep" to point to the last fetched
			// line.
			if (lines_to_free->ga_len > 0
				&& *eap->cmdlinep !=
					    ((char_u **)lines_to_free->ga_data)
						   [lines_to_free->ga_len - 1])
			    // *cmdlinep will be freed later, thus remove the
			    // line from lines_to_free.
			    *eap->cmdlinep = ((char_u **)lines_to_free->ga_data)
						   [lines_to_free->ga_len - 1];
			    --lines_to_free->ga_len;
    if (get_function_body(&eap, &newlines, NULL,
					     &evalarg->eval_tofree_ga) == FAIL)
 * "lines_to_free" is a list of strings to be freed later.
define_function(exarg_T *eap, char_u *name_arg, garray_T *lines_to_free)
			 eap, lines_to_free) == FAIL)
    if (get_function_body(eap, &newlines, line_arg, lines_to_free) == FAIL
    garray_T lines_to_free;
    ga_init2(&lines_to_free, sizeof(char_u *), 50);
    (void)define_function(eap, NULL, &lines_to_free);
    ga_clear_strings(&lines_to_free);
/**/
    4040,
compile_nested_function(exarg_T *eap, cctx_T *cctx, garray_T *lines_to_free)
    ufunc = define_function(eap, lambda_name, lines_to_free);
    garray_T	lines_to_free;
    // allocated lines are freed at the end
    ga_init2(&lines_to_free, sizeof(char_u *), 50);

		if (ga_add_string(&lines_to_free, line) == FAIL)
		    goto erret;
		    line = compile_nested_function(&ea, &cctx, &lines_to_free);
    ga_clear_strings(&lines_to_free);
	    ga_copy_string(&virp->vir_barlines, virp->vir_line);
		    ga_copy_string(&virp->vir_barlines, virp->vir_line);