Skip to content

Commit

Permalink
patch 8.2.3560: using freed memory with lambda
Browse files Browse the repository at this point in the history
Problem:    Using freed memory with lambda.
Solution:   Do not free lines early, keep them until the expression is
            finished.
  • Loading branch information
brammool committed Oct 23, 2021
1 parent ee56f3f commit 844fb64
Show file tree
Hide file tree
Showing 8 changed files with 63 additions and 20 deletions.
23 changes: 16 additions & 7 deletions src/eval.c
Expand Up @@ -143,7 +143,7 @@ eval_clear(void)
void
fill_evalarg_from_eap(evalarg_T *evalarg, exarg_T *eap, int skip)
{
CLEAR_FIELD(*evalarg);
init_evalarg(evalarg);
evalarg->eval_flags = skip ? 0 : EVAL_EVALUATE;
if (eap != NULL)
{
Expand Down Expand Up @@ -2137,8 +2137,7 @@ eval_next_line(evalarg_T *evalarg)

// Advanced to the next line, "arg" no longer points into the previous
// line.
VIM_CLEAR(evalarg->eval_tofree_cmdline);

evalarg->eval_using_cmdline = FALSE;
return skipwhite(line);
}

Expand All @@ -2159,6 +2158,16 @@ skipwhite_and_linebreak(char_u *arg, evalarg_T *evalarg)
return p;
}

/*
* Initialize "evalarg" for use.
*/
void
init_evalarg(evalarg_T *evalarg)
{
CLEAR_POINTER(evalarg);
ga_init2(&evalarg->eval_tofree_ga, sizeof(char_u *), 20);
}

/*
* After using "evalarg" filled from "eap": free the memory.
*/
Expand All @@ -2183,7 +2192,7 @@ clear_evalarg(evalarg_T *evalarg, exarg_T *eap)
evalarg->eval_tofree = NULL;
}

VIM_CLEAR(evalarg->eval_tofree_cmdline);
ga_clear_strings(&evalarg->eval_tofree_ga);
VIM_CLEAR(evalarg->eval_tofree_lambda);
}
}
Expand Down Expand Up @@ -2298,7 +2307,7 @@ eval1(char_u **arg, typval_T *rettv, evalarg_T *evalarg)

if (evalarg == NULL)
{
CLEAR_FIELD(local_evalarg);
init_evalarg(&local_evalarg);
evalarg_used = &local_evalarg;
}
orig_flags = evalarg_used->eval_flags;
Expand Down Expand Up @@ -2455,7 +2464,7 @@ eval2(char_u **arg, typval_T *rettv, evalarg_T *evalarg)

if (evalarg == NULL)
{
CLEAR_FIELD(local_evalarg);
init_evalarg(&local_evalarg);
evalarg_used = &local_evalarg;
}
orig_flags = evalarg_used->eval_flags;
Expand Down Expand Up @@ -2581,7 +2590,7 @@ eval3(char_u **arg, typval_T *rettv, evalarg_T *evalarg)

if (evalarg == NULL)
{
CLEAR_FIELD(local_evalarg);
init_evalarg(&local_evalarg);
evalarg_used = &local_evalarg;
}
orig_flags = evalarg_used->eval_flags;
Expand Down
4 changes: 2 additions & 2 deletions src/globals.h
Expand Up @@ -1867,8 +1867,8 @@ EXTERN listitem_T range_list_item;
// Passed to an eval() function to enable evaluation.
EXTERN evalarg_T EVALARG_EVALUATE
# ifdef DO_INIT
= {EVAL_EVALUATE, 0, NULL, NULL, NULL, NULL, {0, 0, 0, 0, NULL},
{0, 0, 0, 0, NULL}, NULL, NULL, NULL}
= {EVAL_EVALUATE, 0, NULL, NULL, NULL, NULL, GA_EMPTY, GA_EMPTY, NULL,
{0, 0, (int)sizeof(char_u *), 20, NULL}, 0, NULL}
# endif
;
#endif
Expand Down
1 change: 1 addition & 0 deletions src/proto/eval.pro
Expand Up @@ -34,6 +34,7 @@ void free_for_info(void *fi_void);
void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx);
int pattern_match(char_u *pat, char_u *text, int ic);
char_u *skipwhite_and_linebreak(char_u *arg, evalarg_T *evalarg);
void init_evalarg(evalarg_T *evalarg);
void clear_evalarg(evalarg_T *evalarg, exarg_T *eap);
int eval0(char_u *arg, typval_T *rettv, exarg_T *eap, evalarg_T *evalarg);
int eval1(char_u **arg, typval_T *rettv, evalarg_T *evalarg);
Expand Down
7 changes: 5 additions & 2 deletions src/structs.h
Expand Up @@ -1906,8 +1906,11 @@ typedef struct {
// pointer to the last line obtained with getsourceline()
char_u *eval_tofree;

// pointer to the last line of an inline function
char_u *eval_tofree_cmdline;
// array with lines of an inline function
garray_T eval_tofree_ga;

// set when "arg" points into the last entry of "eval_tofree_ga"
int eval_using_cmdline;

// pointer to the lines concatenated for a lambda.
char_u *eval_tofree_lambda;
Expand Down
20 changes: 20 additions & 0 deletions src/testdir/test_vim9_func.vim
Expand Up @@ -1133,6 +1133,26 @@ def Test_pass_legacy_lambda_to_def_func()
CheckScriptSuccess(lines)
enddef

def Test_lambda_in_reduce_line_break()
# this was using freed memory
var lines =<< trim END
vim9script
const result: dict<number> =
['Bob', 'Sam', 'Cat', 'Bob', 'Cat', 'Cat']
->reduce((acc, val) => {
if has_key(acc, val)
acc[val] += 1
return acc
else
acc[val] = 1
return acc
endif
}, {})
assert_equal({Bob: 2, Sam: 1, Cat: 3}, result)
END
CheckScriptSuccess(lines)
enddef

" Default arg and varargs
def MyDefVarargs(one: string, two = 'foo', ...rest: list<string>): string
var res = one .. ',' .. two
Expand Down
11 changes: 8 additions & 3 deletions src/userfunc.c
Expand Up @@ -1177,12 +1177,17 @@ lambda_function_body(

if (cmdline != NULL)
{
garray_T *tfgap = &evalarg->eval_tofree_ga;

// Something comes after the "}".
*arg = eap.nextcmd;

// "arg" points into cmdline, need to keep the line and free it later.
vim_free(evalarg->eval_tofree_cmdline);
evalarg->eval_tofree_cmdline = cmdline;
if (ga_grow(tfgap, 1) == OK)
{
((char_u **)(tfgap->ga_data))[tfgap->ga_len++] = cmdline;
evalarg->eval_using_cmdline = TRUE;
}
}
else
*arg = (char_u *)"";
Expand Down Expand Up @@ -4867,7 +4872,7 @@ ex_return(exarg_T *eap)
return;
}

CLEAR_FIELD(evalarg);
init_evalarg(&evalarg);
evalarg.eval_flags = eap->skip ? 0 : EVAL_EVALUATE;

if (eap->skip)
Expand Down
2 changes: 2 additions & 0 deletions src/version.c
Expand Up @@ -757,6 +757,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
/**/
3560,
/**/
3559,
/**/
Expand Down
15 changes: 9 additions & 6 deletions src/vim9compile.c
Expand Up @@ -3702,7 +3702,7 @@ compile_lambda(char_u **arg, cctx_T *cctx)
ufunc_T *ufunc;
evalarg_T evalarg;

CLEAR_FIELD(evalarg);
init_evalarg(&evalarg);
evalarg.eval_flags = EVAL_EVALUATE;
evalarg.eval_cctx = cctx;

Expand Down Expand Up @@ -3733,11 +3733,13 @@ compile_lambda(char_u **arg, cctx_T *cctx)
compile_def_function(ufunc, FALSE, CT_NONE, cctx);
#endif

// evalarg.eval_tofree_cmdline may have a copy of the last line and "*arg"
// points into it. Point to the original line to avoid a dangling pointer.
if (evalarg.eval_tofree_cmdline != NULL)
// The last entry in evalarg.eval_tofree_ga is a copy of the last line and
// "*arg" may point into it. Point into the original line to avoid a
// dangling pointer.
if (evalarg.eval_using_cmdline)
{
size_t off = *arg - evalarg.eval_tofree_cmdline;
garray_T *gap = &evalarg.eval_tofree_ga;
size_t off = *arg - ((char_u **)gap->ga_data)[gap->ga_len - 1];

*arg = ((char_u **)cctx->ctx_ufunc->uf_lines.ga_data)[cctx->ctx_lnum]
+ off;
Expand Down Expand Up @@ -4201,9 +4203,10 @@ skip_expr_cctx(char_u **arg, cctx_T *cctx)
{
evalarg_T evalarg;

CLEAR_FIELD(evalarg);
init_evalarg(&evalarg);
evalarg.eval_cctx = cctx;
skip_expr(arg, &evalarg);
clear_evalarg(&evalarg, NULL);
}

/*
Expand Down

0 comments on commit 844fb64

Please sign in to comment.