# HG changeset patch # User Bram Moolenaar # Date 1634993104 -7200 # Node ID e861c5aaedd82b6a22df3c64e238dc685b381ed7 # Parent 351359bff011a2ccb5d899bfbbce8626e517b138 patch 8.2.3560: using freed memory with lambda Commit: https://github.com/vim/vim/commit/844fb64a605d60131827503a001b2d1aa232b078 Author: Bram Moolenaar Date: Sat Oct 23 13:32:30 2021 +0100 patch 8.2.3560: using freed memory with lambda Problem: Using freed memory with lambda. Solution: Do not free lines early, keep them until the expression is finished. diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -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) { @@ -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); } @@ -2160,6 +2159,16 @@ skipwhite_and_linebreak(char_u *arg, eva } /* + * 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. */ void @@ -2183,7 +2192,7 @@ clear_evalarg(evalarg_T *evalarg, exarg_ evalarg->eval_tofree = NULL; } - VIM_CLEAR(evalarg->eval_tofree_cmdline); + ga_clear_strings(&evalarg->eval_tofree_ga); VIM_CLEAR(evalarg->eval_tofree_lambda); } } @@ -2298,7 +2307,7 @@ eval1(char_u **arg, typval_T *rettv, eva if (evalarg == NULL) { - CLEAR_FIELD(local_evalarg); + init_evalarg(&local_evalarg); evalarg_used = &local_evalarg; } orig_flags = evalarg_used->eval_flags; @@ -2455,7 +2464,7 @@ eval2(char_u **arg, typval_T *rettv, eva if (evalarg == NULL) { - CLEAR_FIELD(local_evalarg); + init_evalarg(&local_evalarg); evalarg_used = &local_evalarg; } orig_flags = evalarg_used->eval_flags; @@ -2581,7 +2590,7 @@ eval3(char_u **arg, typval_T *rettv, eva if (evalarg == NULL) { - CLEAR_FIELD(local_evalarg); + init_evalarg(&local_evalarg); evalarg_used = &local_evalarg; } orig_flags = evalarg_used->eval_flags; diff --git a/src/globals.h b/src/globals.h --- a/src/globals.h +++ b/src/globals.h @@ -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 diff --git a/src/proto/eval.pro b/src/proto/eval.pro --- a/src/proto/eval.pro +++ b/src/proto/eval.pro @@ -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); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -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; diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -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 = + ['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 var res = one .. ',' .. two diff --git a/src/userfunc.c b/src/userfunc.c --- a/src/userfunc.c +++ b/src/userfunc.c @@ -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 *)""; @@ -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) diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -758,6 +758,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 3560, +/**/ 3559, /**/ 3558, diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -3702,7 +3702,7 @@ compile_lambda(char_u **arg, cctx_T *cct ufunc_T *ufunc; evalarg_T evalarg; - CLEAR_FIELD(evalarg); + init_evalarg(&evalarg); evalarg.eval_flags = EVAL_EVALUATE; evalarg.eval_cctx = cctx; @@ -3733,11 +3733,13 @@ compile_lambda(char_u **arg, cctx_T *cct 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) - { - size_t off = *arg - evalarg.eval_tofree_cmdline; + // 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) + { + 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; @@ -4201,9 +4203,10 @@ skip_expr_cctx(char_u **arg, cctx_T *cct { evalarg_T evalarg; - CLEAR_FIELD(evalarg); + init_evalarg(&evalarg); evalarg.eval_cctx = cctx; skip_expr(arg, &evalarg); + clear_evalarg(&evalarg, NULL); } /*