From 91c7cbfe31bbef57d5fcf7d76989fc159f73ef15 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Thu, 18 Aug 2022 13:28:31 +0100 Subject: [PATCH] patch 9.0.0225: using freed memory with multiple line breaks in expression Problem: Using freed memory with multiple line breaks in expression. Solution: Free eval_tofree later. --- src/eval.c | 102 ++++++++++++++++++------------- src/proto/eval.pro | 4 +- src/testdir/test_vim9_script.vim | 13 ++++ src/userfunc.c | 15 ----- src/version.c | 2 + 5 files changed, 77 insertions(+), 59 deletions(-) diff --git a/src/eval.c b/src/eval.c index 42b883e9b00bd..60daca51ce9de 100644 --- a/src/eval.c +++ b/src/eval.c @@ -353,6 +353,63 @@ eval_to_string_skip( return retval; } +/* + * Initialize "evalarg" for use. + */ + void +init_evalarg(evalarg_T *evalarg) +{ + CLEAR_POINTER(evalarg); + ga_init2(&evalarg->eval_tofree_ga, sizeof(char_u *), 20); +} + +/* + * If "evalarg->eval_tofree" is not NULL free it later. + * Caller is expected to overwrite "evalarg->eval_tofree" next. + */ + static void +free_eval_tofree_later(evalarg_T *evalarg) +{ + if (evalarg->eval_tofree != NULL) + { + if (ga_grow(&evalarg->eval_tofree_ga, 1) == OK) + ((char_u **)evalarg->eval_tofree_ga.ga_data) + [evalarg->eval_tofree_ga.ga_len++] + = evalarg->eval_tofree; + else + vim_free(evalarg->eval_tofree); + } +} + +/* + * After using "evalarg" filled from "eap": free the memory. + */ + void +clear_evalarg(evalarg_T *evalarg, exarg_T *eap) +{ + if (evalarg != NULL) + { + if (evalarg->eval_tofree != NULL) + { + if (eap != NULL) + { + // We may need to keep the original command line, e.g. for + // ":let" it has the variable names. But we may also need the + // new one, "nextcmd" points into it. Keep both. + vim_free(eap->cmdline_tofree); + eap->cmdline_tofree = *eap->cmdlinep; + *eap->cmdlinep = evalarg->eval_tofree; + } + else + vim_free(evalarg->eval_tofree); + evalarg->eval_tofree = NULL; + } + + ga_clear_strings(&evalarg->eval_tofree_ga); + VIM_CLEAR(evalarg->eval_tofree_lambda); + } +} + /* * Skip over an expression at "*pp". * Return FAIL for an error, OK otherwise. @@ -435,8 +492,8 @@ skip_expr_concatenate( // Do not free the first line, the caller can still use it. *((char_u **)gap->ga_data) = NULL; // Do not free the last line, "arg" points into it, free it - // later. - vim_free(evalarg->eval_tofree); + // later. Also free "eval_tofree" later if needed. + free_eval_tofree_later(evalarg); evalarg->eval_tofree = ((char_u **)gap->ga_data)[gap->ga_len - 1]; ((char_u **)gap->ga_data)[gap->ga_len - 1] = NULL; @@ -2274,7 +2331,7 @@ eval_next_line(char_u *arg, evalarg_T *evalarg) } else if (evalarg->eval_cookie != NULL) { - vim_free(evalarg->eval_tofree); + free_eval_tofree_later(evalarg); evalarg->eval_tofree = line; } @@ -2301,45 +2358,6 @@ 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. - */ - void -clear_evalarg(evalarg_T *evalarg, exarg_T *eap) -{ - if (evalarg != NULL) - { - if (evalarg->eval_tofree != NULL) - { - if (eap != NULL) - { - // We may need to keep the original command line, e.g. for - // ":let" it has the variable names. But we may also need the - // new one, "nextcmd" points into it. Keep both. - vim_free(eap->cmdline_tofree); - eap->cmdline_tofree = *eap->cmdlinep; - *eap->cmdlinep = evalarg->eval_tofree; - } - else - vim_free(evalarg->eval_tofree); - evalarg->eval_tofree = NULL; - } - - ga_clear_strings(&evalarg->eval_tofree_ga); - VIM_CLEAR(evalarg->eval_tofree_lambda); - } -} - /* * The "evaluate" argument: When FALSE, the argument is only parsed but not * executed. The function may return OK, but the rettv will be of type diff --git a/src/proto/eval.pro b/src/proto/eval.pro index e6cd8928d19c1..27a13c9498bae 100644 --- a/src/proto/eval.pro +++ b/src/proto/eval.pro @@ -9,6 +9,8 @@ int eval_expr_valid_arg(typval_T *tv); int eval_expr_typval(typval_T *expr, typval_T *argv, int argc, typval_T *rettv); int eval_expr_to_bool(typval_T *expr, int *error); char_u *eval_to_string_skip(char_u *arg, exarg_T *eap, int skip); +void init_evalarg(evalarg_T *evalarg); +void clear_evalarg(evalarg_T *evalarg, exarg_T *eap); int skip_expr(char_u **pp, evalarg_T *evalarg); int skip_expr_concatenate(char_u **arg, char_u **start, char_u **end, evalarg_T *evalarg); char_u *typval2string(typval_T *tv, int convert); @@ -34,8 +36,6 @@ int pattern_match(char_u *pat, char_u *text, int ic); char_u *eval_next_non_blank(char_u *arg, evalarg_T *evalarg, int *getnext); char_u *eval_next_line(char_u *arg, evalarg_T *evalarg); 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 eval0_retarg(char_u *arg, typval_T *rettv, exarg_T *eap, evalarg_T *evalarg, char_u **retarg); int eval1(char_u **arg, typval_T *rettv, evalarg_T *evalarg); diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim index 56a39efcf79bc..597e31ec1c26d 100644 --- a/src/testdir/test_vim9_script.vim +++ b/src/testdir/test_vim9_script.vim @@ -1560,6 +1560,19 @@ def Test_func_redefine_fails() v9.CheckScriptFailure(lines, 'E1073:') enddef +def Test_lambda_split() + # this was using freed memory, because of the split expression + var lines =<< trim END + vim9script + try + 0 + 0->(0 + ->a.0( + ->u + END + v9.CheckScriptFailure(lines, 'E1050:') +enddef + def Test_fixed_size_list() # will be allocated as one piece of memory, check that changes work var l = [1, 2, 3, 4] diff --git a/src/userfunc.c b/src/userfunc.c index f612160fc872c..e0bdc3fda9113 100644 --- a/src/userfunc.c +++ b/src/userfunc.c @@ -1372,7 +1372,6 @@ get_lambda_tv( char_u *start, *end; int *old_eval_lavars = eval_lavars_used; int eval_lavars = FALSE; - char_u *tofree1 = NULL; char_u *tofree2 = NULL; int equal_arrow = **arg == '('; int white_error = FALSE; @@ -1457,12 +1456,6 @@ get_lambda_tv( ret = skip_expr_concatenate(arg, &start, &end, evalarg); if (ret == FAIL) goto errret; - if (evalarg != NULL) - { - // avoid that the expression gets freed when another line break follows - tofree1 = evalarg->eval_tofree; - evalarg->eval_tofree = NULL; - } if (!equal_arrow) { @@ -1585,10 +1578,6 @@ get_lambda_tv( theend: eval_lavars_used = old_eval_lavars; - if (evalarg != NULL && evalarg->eval_tofree == NULL) - evalarg->eval_tofree = tofree1; - else - vim_free(tofree1); vim_free(tofree2); if (types_optional) ga_clear_strings(&argtypes); @@ -1607,10 +1596,6 @@ get_lambda_tv( } vim_free(fp); vim_free(pt); - if (evalarg != NULL && evalarg->eval_tofree == NULL) - evalarg->eval_tofree = tofree1; - else - vim_free(tofree1); vim_free(tofree2); eval_lavars_used = old_eval_lavars; return FAIL; diff --git a/src/version.c b/src/version.c index e477f9e315d0e..b2fc962bed8ce 100644 --- a/src/version.c +++ b/src/version.c @@ -731,6 +731,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 225, /**/ 224, /**/