# HG changeset patch # User Bram Moolenaar # Date 1584726304 -3600 # Node ID ad37a198a708da2454bdb400f3d903834b094f78 # Parent 700b7d225e0200f343c1cc4337f2f192a4591284 patch 8.2.0419: various memory leaks in Vim9 script code Commit: https://github.com/vim/vim/commit/20431c9dbb592ebe0666bf042af7d2b373107372 Author: Bram Moolenaar Date: Fri Mar 20 18:39:46 2020 +0100 patch 8.2.0419: various memory leaks in Vim9 script code Problem: Various memory leaks in Vim9 script code. Solution: Fix the leaks. (Ozaki Kiichi, closes https://github.com/vim/vim/issues/5814) diff --git a/src/proto/vim9compile.pro b/src/proto/vim9compile.pro --- a/src/proto/vim9compile.pro +++ b/src/proto/vim9compile.pro @@ -9,6 +9,7 @@ imported_T *find_imported(char_u *name, char_u *to_name_const_end(char_u *arg); int assignment_len(char_u *p, int *heredoc); void compile_def_function(ufunc_T *ufunc, int set_return_type); +void delete_instr(isn_T *isn); void delete_def_function(ufunc_T *ufunc); void free_def_functions(void); /* vim: set ft=c : */ diff --git a/src/scriptfile.c b/src/scriptfile.c --- a/src/scriptfile.c +++ b/src/scriptfile.c @@ -1526,6 +1526,7 @@ free_scriptnames(void) vim_free(si->sn_vars); vim_free(si->sn_name); + free_imports(i); free_string_option(si->sn_save_cpo); # ifdef FEAT_PROFILE ga_clear(&si->sn_prl_ga); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1308,6 +1308,7 @@ typedef struct { int cb_free_name; // cb_name was allocated } callback_T; +typedef struct isn_S isn_T; // instruction typedef struct dfunc_S dfunc_T; // :def function typedef struct jobvar_S job_T; diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim --- a/src/testdir/test_vim9_script.vim +++ b/src/testdir/test_vim9_script.vim @@ -942,6 +942,16 @@ def Test_while_loop() assert_equal('1_3_', result) enddef +def Test_interrupt_loop() + let x = 0 + while 1 + x += 1 + if x == 100 + feedkeys("\", 'L') + endif + endwhile +enddef + def Test_substitute_cmd() new setline(1, 'something') @@ -964,4 +974,24 @@ def Test_substitute_cmd() delete('Xvim9lines') enddef +def Test_redef_failure() + call writefile(['def Func0(): string', 'return "Func0"', 'enddef'], 'Xdef') + so Xdef + call writefile(['def Func1(): string', 'return "Func1"', 'enddef'], 'Xdef') + so Xdef + call writefile(['def! Func0(): string', 'enddef'], 'Xdef') + call assert_fails('so Xdef', 'E1027:') + call writefile(['def Func2(): string', 'return "Func2"', 'enddef'], 'Xdef') + so Xdef + call delete('Xdef') + + call assert_equal(0, Func0()) + call assert_equal('Func1', Func1()) + call assert_equal('Func2', Func2()) + + delfunc! Func0 + delfunc! Func1 + delfunc! Func2 +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -739,6 +739,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 419, +/**/ 418, /**/ 417, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -203,7 +203,7 @@ typedef struct { /* * Instruction */ -typedef struct { +struct isn_S { isntype_T isn_type; int isn_lnum; union { @@ -231,7 +231,7 @@ typedef struct { loadstore_T loadstore; script_T script; } isn_arg; -} isn_T; +}; /* * Info about a function defined with :def. Used in "def_functions". diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -129,6 +129,7 @@ static char e_syntax_at[] = N_("E1002: S static int compile_expr1(char_u **arg, cctx_T *cctx); static int compile_expr2(char_u **arg, cctx_T *cctx); static int compile_expr3(char_u **arg, cctx_T *cctx); +static void delete_def_function_contents(dfunc_T *dfunc); /* * Lookup variable "name" in the local scope and return the index. @@ -1306,6 +1307,36 @@ reserve_local(cctx_T *cctx, char_u *name } /* + * Remove local variables above "new_top". + */ + static void +unwind_locals(cctx_T *cctx, int new_top) +{ + if (cctx->ctx_locals.ga_len > new_top) + { + int idx; + lvar_T *lvar; + + for (idx = new_top; idx < cctx->ctx_locals.ga_len; ++idx) + { + lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx; + vim_free(lvar->lv_name); + } + } + cctx->ctx_locals.ga_len = new_top; +} + +/* + * Free all local variables. + */ + static void +free_local(cctx_T *cctx) +{ + unwind_locals(cctx, 0); + ga_clear(&cctx->ctx_locals); +} + +/* * Skip over a type definition and return a pointer to just after it. */ char_u * @@ -1672,6 +1703,23 @@ find_imported(char_u *name, size_t len, } /* + * Free all imported variables. + */ + static void +free_imported(cctx_T *cctx) +{ + int idx; + + for (idx = 0; idx < cctx->ctx_imports.ga_len; ++idx) + { + imported_T *import = ((imported_T *)cctx->ctx_imports.ga_data) + idx; + + vim_free(import->imp_name); + } + ga_clear(&cctx->ctx_imports); +} + +/* * Generate an instruction to load script-local variable "name". */ static int @@ -2127,7 +2175,10 @@ compile_lambda(char_u **arg, cctx_T *cct // Get the funcref in "rettv". if (get_lambda_tv(arg, &rettv, TRUE) == FAIL) return FAIL; + ufunc = rettv.vval.v_partial->pt_func; + ++ufunc->uf_refcount; + clear_tv(&rettv); // The function will have one line: "return {expr}". // Compile it into instructions. @@ -2169,10 +2220,12 @@ compile_lambda_call(char_u **arg, cctx_T return FAIL; } + ufunc = rettv.vval.v_partial->pt_func; + ++ufunc->uf_refcount; + clear_tv(&rettv); + // The function will have one line: "return {expr}". // Compile it into instructions. - ufunc = rettv.vval.v_partial->pt_func; - ++ufunc->uf_refcount; compile_def_function(ufunc, TRUE); // compile the arguments @@ -2181,7 +2234,6 @@ compile_lambda_call(char_u **arg, cctx_T // call the compiled function ret = generate_CALL(cctx, ufunc, argcount); - clear_tv(&rettv); return ret; } @@ -3398,7 +3450,6 @@ compile_assignment(char_u *arg, exarg_T { int cc; long numval; - char_u *stringval = NULL; dest = dest_option; if (cmdidx == CMD_const) @@ -3420,7 +3471,7 @@ compile_assignment(char_u *arg, exarg_T } cc = *p; *p = NUL; - opt_type = get_option_value(arg + 1, &numval, &stringval, opt_flags); + opt_type = get_option_value(arg + 1, &numval, NULL, opt_flags); *p = cc; if (opt_type == -3) { @@ -4217,7 +4268,7 @@ compile_elseif(char_u *arg, cctx_T *cctx emsg(_(e_elseif_without_if)); return NULL; } - cctx->ctx_locals.ga_len = scope->se_local_count; + unwind_locals(cctx, scope->se_local_count); if (cctx->ctx_skip == MAYBE) { @@ -4265,7 +4316,7 @@ compile_else(char_u *arg, cctx_T *cctx) emsg(_(e_else_without_if)); return NULL; } - cctx->ctx_locals.ga_len = scope->se_local_count; + unwind_locals(cctx, scope->se_local_count); // jump from previous block to the end, unless the else block is empty if (cctx->ctx_skip == MAYBE) @@ -4307,7 +4358,7 @@ compile_endif(char_u *arg, cctx_T *cctx) } ifscope = &scope->se_u.se_if; cctx->ctx_scope = scope->se_outer; - cctx->ctx_locals.ga_len = scope->se_local_count; + unwind_locals(cctx, scope->se_local_count); if (scope->se_u.se_if.is_if_label >= 0) { @@ -4435,7 +4486,7 @@ compile_endfor(char_u *arg, cctx_T *cctx } forscope = &scope->se_u.se_for; cctx->ctx_scope = scope->se_outer; - cctx->ctx_locals.ga_len = scope->se_local_count; + unwind_locals(cctx, scope->se_local_count); // At end of ":for" scope jump back to the FOR instruction. generate_JUMP(cctx, JUMP_ALWAYS, forscope->fs_top_label); @@ -4506,7 +4557,7 @@ compile_endwhile(char_u *arg, cctx_T *cc return NULL; } cctx->ctx_scope = scope->se_outer; - cctx->ctx_locals.ga_len = scope->se_local_count; + unwind_locals(cctx, scope->se_local_count); // At end of ":for" scope jump back to the FOR instruction. generate_JUMP(cctx, JUMP_ALWAYS, scope->se_u.se_while.ws_top_label); @@ -4599,7 +4650,7 @@ compile_endblock(cctx_T *cctx) scope_T *scope = cctx->ctx_scope; cctx->ctx_scope = scope->se_outer; - cctx->ctx_locals.ga_len = scope->se_local_count; + unwind_locals(cctx, scope->se_local_count); vim_free(scope); } @@ -4942,9 +4993,11 @@ compile_def_function(ufunc_T *ufunc, int if (ufunc->uf_dfunc_idx >= 0) { - // redefining a function that was compiled before + // Redefining a function that was compiled before. dfunc = ((dfunc_T *)def_functions.ga_data) + ufunc->uf_dfunc_idx; - dfunc->df_deleted = FALSE; + + // Free old instructions. + delete_def_function_contents(dfunc); } else { @@ -5305,6 +5358,7 @@ compile_def_function(ufunc_T *ufunc, int generate_instr(&cctx, ISN_RETURN); } + dfunc->df_deleted = FALSE; dfunc->df_instr = instr->ga_data; dfunc->df_instr_count = instr->ga_len; dfunc->df_varcount = cctx.ctx_max_local; @@ -5314,27 +5368,35 @@ compile_def_function(ufunc_T *ufunc, int erret: if (ret == FAIL) { + int idx; + + for (idx = 0; idx < instr->ga_len; ++idx) + delete_instr(((isn_T *)instr->ga_data) + idx); ga_clear(instr); + ufunc->uf_dfunc_idx = -1; - --def_functions.ga_len; + if (!dfunc->df_deleted) + --def_functions.ga_len; + + // Don't execute this function body. + ga_clear_strings(&ufunc->uf_lines); + if (errormsg != NULL) emsg(errormsg); else if (called_emsg == called_emsg_before) emsg(_("E1028: compile_def_function failed")); - - // don't execute this function body - ufunc->uf_lines.ga_len = 0; } current_sctx = save_current_sctx; + free_imported(&cctx); + free_local(&cctx); ga_clear(&cctx.ctx_type_stack); - ga_clear(&cctx.ctx_locals); } /* * Delete an instruction, free what it contains. */ - static void + void delete_instr(isn_T *isn) { switch (isn->isn_type) @@ -5443,32 +5505,57 @@ delete_instr(isn_T *isn) } /* + * Free all instructions for "dfunc". + */ + static void +delete_def_function_contents(dfunc_T *dfunc) +{ + int idx; + + ga_clear(&dfunc->df_def_args_isn); + + if (dfunc->df_instr != NULL) + { + for (idx = 0; idx < dfunc->df_instr_count; ++idx) + delete_instr(dfunc->df_instr + idx); + VIM_CLEAR(dfunc->df_instr); + } + + dfunc->df_deleted = TRUE; +} + +/* * When a user function is deleted, delete any associated def function. */ void delete_def_function(ufunc_T *ufunc) { - int idx; - if (ufunc->uf_dfunc_idx >= 0) { dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + ufunc->uf_dfunc_idx; - ga_clear(&dfunc->df_def_args_isn); - - for (idx = 0; idx < dfunc->df_instr_count; ++idx) - delete_instr(dfunc->df_instr + idx); - VIM_CLEAR(dfunc->df_instr); - - dfunc->df_deleted = TRUE; + + delete_def_function_contents(dfunc); } } #if defined(EXITFREE) || defined(PROTO) +/* + * Free all functions defined with ":def". + */ void free_def_functions(void) { - vim_free(def_functions.ga_data); + int idx; + + for (idx = 0; idx < def_functions.ga_len; ++idx) + { + dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + idx; + + delete_def_function_contents(dfunc); + } + + ga_clear(&def_functions); } #endif diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -283,6 +283,7 @@ call_ufunc(ufunc_T *ufunc, int argcount, // that was defined later: we can call it directly next time. if (iptr != NULL) { + delete_instr(iptr); iptr->isn_type = ISN_DCALL; iptr->isn_arg.dfunc.cdf_idx = ufunc->uf_dfunc_idx; iptr->isn_arg.dfunc.cdf_argcount = argcount; @@ -480,11 +481,21 @@ call_def_function( for (;;) { isn_T *iptr; - trycmd_T *trycmd = NULL; + + veryfast_breakcheck(); + if (got_int) + { + // Turn CTRL-C into an exception. + got_int = FALSE; + if (throw_exception("Vim:Interrupt", ET_INTERRUPT, NULL) != FAIL) + goto failed; + did_throw = TRUE; + } if (did_throw && !ectx.ec_in_catch) { garray_T *trystack = &ectx.ec_trystack; + trycmd_T *trycmd = NULL; // An exception jumps to the first catch, finally, or returns from // the current function. @@ -782,8 +793,9 @@ call_def_function( // store $ENV case ISN_STOREENV: --ectx.ec_stack.ga_len; - vim_setenv_ext(iptr->isn_arg.string, - tv_get_string(STACK_TV_BOT(0))); + tv = STACK_TV_BOT(0); + vim_setenv_ext(iptr->isn_arg.string, tv_get_string(tv)); + clear_tv(tv); break; // store @r @@ -1038,6 +1050,7 @@ call_def_function( case ISN_RETURN: { garray_T *trystack = &ectx.ec_trystack; + trycmd_T *trycmd = NULL; if (trystack->ga_len > 0) trycmd = ((trycmd_T *)trystack->ga_data) @@ -1146,6 +1159,8 @@ call_def_function( // start of ":try" block case ISN_TRY: { + trycmd_T *trycmd = NULL; + if (ga_grow(&ectx.ec_trystack, 1) == FAIL) goto failed; trycmd = ((trycmd_T *)ectx.ec_trystack.ga_data) @@ -1180,7 +1195,7 @@ call_def_function( if (trystack->ga_len > 0) { - trycmd = ((trycmd_T *)trystack->ga_data) + trycmd_T *trycmd = ((trycmd_T *)trystack->ga_data) + trystack->ga_len - 1; trycmd->tcd_caught = TRUE; } @@ -1196,6 +1211,8 @@ call_def_function( if (trystack->ga_len > 0) { + trycmd_T *trycmd = NULL; + --trystack->ga_len; --trylevel; trycmd = ((trycmd_T *)trystack->ga_data) @@ -1677,6 +1694,7 @@ failed: for (idx = 0; idx < ectx.ec_stack.ga_len; ++idx) clear_tv(STACK_TV(idx)); vim_free(ectx.ec_stack.ga_data); + vim_free(ectx.ec_trystack.ga_data); return ret; } diff --git a/src/vim9script.c b/src/vim9script.c --- a/src/vim9script.c +++ b/src/vim9script.c @@ -119,11 +119,13 @@ free_imports(int sid) for (idx = 0; idx < si->sn_imports.ga_len; ++idx) { - imported_T *imp = ((imported_T *)si->sn_imports.ga_data + idx); + imported_T *imp = ((imported_T *)si->sn_imports.ga_data) + idx; vim_free(imp->imp_name); } ga_clear(&si->sn_imports); + ga_clear(&si->sn_var_vals); + ga_clear(&si->sn_type_list); } /*