diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 21:52:58 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2023-11-29 21:52:58 +0000 |
commit | 931bffbda3668ddc609fc1da8f9eb576b170aa52 (patch) | |
tree | d8c1843a95da5ea0bb4acc09f7e37843d9995c86 /src/nvim/eval/userfunc.c | |
parent | 142d9041391780ac15b89886a54015fdc5c73995 (diff) | |
parent | 4a8bf24ac690004aedf5540fa440e788459e5e34 (diff) | |
download | rneovim-931bffbda3668ddc609fc1da8f9eb576b170aa52.tar.gz rneovim-931bffbda3668ddc609fc1da8f9eb576b170aa52.tar.bz2 rneovim-931bffbda3668ddc609fc1da8f9eb576b170aa52.zip |
Merge remote-tracking branch 'upstream/master' into userreguserreg
Diffstat (limited to 'src/nvim/eval/userfunc.c')
-rw-r--r-- | src/nvim/eval/userfunc.c | 1170 |
1 files changed, 715 insertions, 455 deletions
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 22c5b1954d..23b3c4e1b2 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -1,20 +1,17 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - // User defined function support #include <assert.h> #include <ctype.h> #include <inttypes.h> +#include <lauxlib.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -#include "lauxlib.h" -#include "nvim/ascii.h" +#include "nvim/ascii_defs.h" #include "nvim/autocmd.h" -#include "nvim/buffer_defs.h" #include "nvim/charset.h" +#include "nvim/cmdexpand_defs.h" #include "nvim/debugger.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" @@ -26,18 +23,20 @@ #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" +#include "nvim/func_attr.h" +#include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/gettext.h" #include "nvim/globals.h" +#include "nvim/hashtab.h" #include "nvim/insexpand.h" #include "nvim/keycodes.h" #include "nvim/lua/executor.h" -#include "nvim/macros.h" +#include "nvim/macros_defs.h" #include "nvim/mbyte.h" -#include "nvim/memline_defs.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/option_defs.h" +#include "nvim/option_vars.h" #include "nvim/os/input.h" #include "nvim/path.h" #include "nvim/profile.h" @@ -45,14 +44,21 @@ #include "nvim/runtime.h" #include "nvim/search.h" #include "nvim/strings.h" -#include "nvim/types.h" +#include "nvim/types_defs.h" #include "nvim/ui.h" -#include "nvim/vim.h" +#include "nvim/vim_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/userfunc.c.generated.h" #endif +/// structure used as item in "fc_defer" +typedef struct { + char *dr_name; ///< function name, allocated + typval_T dr_argvars[MAX_FUNC_ARGS + 1]; + int dr_argcount; +} defer_T; + static hashtab_T func_hashtab; // Used by get_func_tv() @@ -65,12 +71,21 @@ static funccall_T *current_funccal = NULL; // item in it is still being used. static funccall_T *previous_funccal = NULL; -static char *e_funcexts = N_("E122: Function %s already exists, add ! to replace it"); -static char *e_funcdict = N_("E717: Dictionary entry already exists"); -static char *e_funcref = N_("E718: Funcref required"); -static char *e_nofunc = N_("E130: Unknown function: %s"); -static char e_no_white_space_allowed_before_str_str[] +static const char *e_unknown_function_str = N_("E117: Unknown function: %s"); +static const char *e_funcexts = N_("E122: Function %s already exists, add ! to replace it"); +static const char *e_funcdict = N_("E717: Dictionary entry already exists"); +static const char *e_funcref = N_("E718: Funcref required"); +static const char *e_nofunc = N_("E130: Unknown function: %s"); +static const char e_function_list_was_modified[] + = N_("E454: Function list was modified"); +static const char e_function_nesting_too_deep[] + = N_("E1058: Function nesting too deep"); +static const char e_no_white_space_allowed_before_str_str[] = N_("E1068: No white space allowed before '%s': %s"); +static const char e_missing_heredoc_end_marker_str[] + = N_("E1145: Missing heredoc end marker: %s"); +static const char e_cannot_use_partial_with_dictionary_for_defer[] + = N_("E1300: Cannot use a partial with dictionary for :defer"); void func_init(void) { @@ -90,7 +105,7 @@ static int get_function_args(char **argp, char endchar, garray_T *newargs, int * bool mustend = false; char *arg = *argp; char *p = arg; - char_u c; + uint8_t c; int i; if (newargs != NULL) { @@ -128,7 +143,7 @@ static int get_function_args(char **argp, char endchar, garray_T *newargs, int * } if (newargs != NULL) { ga_grow(newargs, 1); - c = (char_u)(*p); + c = (uint8_t)(*p); *p = NUL; arg = xstrdup(arg); @@ -152,14 +167,14 @@ static int get_function_args(char **argp, char endchar, garray_T *newargs, int * p = skipwhite(p) + 1; p = skipwhite(p); char *expr = p; - if (eval1(&p, &rettv, false) != FAIL) { + if (eval1(&p, &rettv, NULL) != FAIL) { ga_grow(default_args, 1); // trim trailing whitespace while (p > expr && ascii_iswhite(p[-1])) { p--; } - c = (char_u)(*p); + c = (uint8_t)(*p); *p = NUL; expr = xstrdup(expr); ((char **)(default_args->ga_data))[default_args->ga_len] = expr; @@ -223,9 +238,9 @@ static void register_closure(ufunc_T *fp) funccal_unref(fp->uf_scoped, fp, false); fp->uf_scoped = current_funccal; current_funccal->fc_refcount++; - ga_grow(¤t_funccal->fc_funcs, 1); - ((ufunc_T **)current_funccal->fc_funcs.ga_data) - [current_funccal->fc_funcs.ga_len++] = fp; + ga_grow(¤t_funccal->fc_ufuncs, 1); + ((ufunc_T **)current_funccal->fc_ufuncs.ga_data) + [current_funccal->fc_ufuncs.ga_len++] = fp; } /// @return a name for a lambda. Returned in static memory. @@ -252,22 +267,22 @@ static void set_ufunc_name(ufunc_T *fp, char *name) /// Parse a lambda expression and get a Funcref from "*arg". /// /// @return OK or FAIL. Returns NOTDONE for dict or {expr}. -int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate) +int get_lambda_tv(char **arg, typval_T *rettv, evalarg_T *evalarg) { + const bool evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE); garray_T newargs = GA_EMPTY_INIT_VALUE; garray_T *pnewargs; ufunc_T *fp = NULL; partial_T *pt = NULL; int varargs; - int ret; - char *start = skipwhite(*arg + 1); - char *s, *e; bool *old_eval_lavars = eval_lavars_used; bool eval_lavars = false; + char *tofree = NULL; // First, check if this is a lambda expression. "->" must exists. - ret = get_function_args(&start, '-', NULL, NULL, NULL, true); - if (ret == FAIL || *start != '>') { + char *s = skipwhite(*arg + 1); + int ret = get_function_args(&s, '-', NULL, NULL, NULL, true); + if (ret == FAIL || *s != '>') { return NOTDONE; } @@ -290,12 +305,18 @@ int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate) // Get the start and the end of the expression. *arg = skipwhite((*arg) + 1); - s = *arg; - ret = skip_expr(arg); + char *start = *arg; + ret = skip_expr(arg, evalarg); + char *end = *arg; if (ret == FAIL) { goto errret; } - e = *arg; + if (evalarg != NULL) { + // avoid that the expression gets freed when another line break follows + tofree = evalarg->eval_tofree; + evalarg->eval_tofree = NULL; + } + *arg = skipwhite(*arg); if (**arg != '}') { semsg(_("E451: Expected }: %s"), *arg); @@ -317,11 +338,11 @@ int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate) ga_grow(&newlines, 1); // Add "return " before the expression. - size_t len = (size_t)(7 + e - s + 1); + size_t len = (size_t)(7 + end - start + 1); p = xmalloc(len); ((char **)(newlines.ga_data))[newlines.ga_len++] = p; STRCPY(p, "return "); - xstrlcpy(p + 7, s, (size_t)(e - s) + 1); + xstrlcpy(p + 7, start, (size_t)(end - start) + 1); if (strstr(p + 7, "a:") == NULL) { // No a: variables are used for sure. flags |= FC_NOARGS; @@ -359,12 +380,22 @@ int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate) } eval_lavars_used = old_eval_lavars; + if (evalarg != NULL && evalarg->eval_tofree == NULL) { + evalarg->eval_tofree = tofree; + } else { + xfree(tofree); + } return OK; errret: ga_clear_strings(&newargs); xfree(fp); xfree(pt); + if (evalarg != NULL && evalarg->eval_tofree == NULL) { + evalarg->eval_tofree = tofree; + } else { + xfree(tofree); + } eval_lavars_used = old_eval_lavars; return FAIL; } @@ -382,9 +413,11 @@ errret: /// is not needed. /// @param[in] no_autoload If true, do not source autoload scripts if function /// was not found. +/// @param[out] found_var If not NULL and a variable was found set it to true. /// /// @return name of the function. -char *deref_func_name(const char *name, int *lenp, partial_T **const partialp, bool no_autoload) +char *deref_func_name(const char *name, int *lenp, partial_T **const partialp, bool no_autoload, + bool *found_var) FUNC_ATTR_NONNULL_ARG(1, 2) { if (partialp != NULL) { @@ -392,18 +425,25 @@ char *deref_func_name(const char *name, int *lenp, partial_T **const partialp, b } dictitem_T *const v = find_var(name, (size_t)(*lenp), NULL, no_autoload); - if (v != NULL && v->di_tv.v_type == VAR_FUNC) { - if (v->di_tv.vval.v_string == NULL) { // just in case + if (v == NULL) { + return (char *)name; + } + typval_T *const tv = &v->di_tv; + if (found_var != NULL) { + *found_var = true; + } + + if (tv->v_type == VAR_FUNC) { + if (tv->vval.v_string == NULL) { // just in case *lenp = 0; return ""; } - *lenp = (int)strlen(v->di_tv.vval.v_string); - return v->di_tv.vval.v_string; + *lenp = (int)strlen(tv->vval.v_string); + return tv->vval.v_string; } - if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) { - partial_T *const pt = v->di_tv.vval.v_partial; - + if (tv->v_type == VAR_PARTIAL) { + partial_T *const pt = tv->vval.v_partial; if (pt == NULL) { // just in case *lenp = 0; return ""; @@ -421,63 +461,82 @@ char *deref_func_name(const char *name, int *lenp, partial_T **const partialp, b /// Give an error message with a function name. Handle <SNR> things. /// -/// @param ermsg must be passed without translation (use N_() instead of _()). +/// @param errmsg must be passed without translation (use N_() instead of _()). /// @param name function name -void emsg_funcname(char *ermsg, const char *name) +void emsg_funcname(const char *errmsg, const char *name) { - char *p; + char *p = (char *)name; - if ((uint8_t)(*name) == K_SPECIAL) { + if ((uint8_t)name[0] == K_SPECIAL && name[1] != NUL && name[2] != NUL) { p = concat_str("<SNR>", name + 3); - } else { - p = (char *)name; } - semsg(_(ermsg), p); + semsg(_(errmsg), p); if (p != name) { xfree(p); } } -/// Allocate a variable for the result of a function. -/// -/// @param name name of the function -/// @param len length of "name" or -1 to use strlen() -/// @param arg argument, pointing to the '(' -/// @param funcexe various values -/// -/// @return OK or FAIL. -int get_func_tv(const char *name, int len, typval_T *rettv, char **arg, funcexe_T *funcexe) +/// Get function arguments at "*arg" and advance it. +/// Return them in "*argvars[MAX_FUNC_ARGS + 1]" and the count in "argcount". +/// On failure FAIL is returned but the "argvars[argcount]" are still set. +static int get_func_arguments(char **arg, evalarg_T *const evalarg, int partial_argc, + typval_T *argvars, int *argcount) { - char *argp; + char *argp = *arg; int ret = OK; - typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments - int argcount = 0; // number of arguments found // Get the arguments. - argp = *arg; - while (argcount < MAX_FUNC_ARGS - - (funcexe->fe_partial == NULL ? 0 : funcexe->fe_partial->pt_argc)) { + while (*argcount < MAX_FUNC_ARGS - partial_argc) { argp = skipwhite(argp + 1); // skip the '(' or ',' + if (*argp == ')' || *argp == ',' || *argp == NUL) { break; } - if (eval1(&argp, &argvars[argcount], funcexe->fe_evaluate) == FAIL) { + if (eval1(&argp, &argvars[*argcount], evalarg) == FAIL) { ret = FAIL; break; } - argcount++; + (*argcount)++; if (*argp != ',') { break; } } + + argp = skipwhite(argp); if (*argp == ')') { argp++; } else { ret = FAIL; } + *arg = argp; + return ret; +} + +/// Call a function and put the result in "rettv". +/// +/// @param name name of the function +/// @param len length of "name" or -1 to use strlen() +/// @param arg argument, pointing to the '(' +/// @param funcexe various values +/// +/// @return OK or FAIL. +int get_func_tv(const char *name, int len, typval_T *rettv, char **arg, evalarg_T *const evalarg, + funcexe_T *funcexe) +{ + typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments + int argcount = 0; // number of arguments found + const bool evaluate = evalarg == NULL ? false : (evalarg->eval_flags & EVAL_EVALUATE); + + char *argp = *arg; + int ret = get_func_arguments(&argp, evalarg, + (funcexe->fe_partial == NULL + ? 0 + : funcexe->fe_partial->pt_argc), + argvars, &argcount); + assert(ret == OK || ret == FAIL); // suppress clang false positive if (ret == OK) { int i = 0; @@ -495,7 +554,7 @@ int get_func_tv(const char *name, int len, typval_T *rettv, char **arg, funcexe_ ret = call_func(name, len, rettv, argcount, argvars, funcexe); funcargs.ga_len -= i; - } else if (!aborting()) { + } else if (!aborting() && evaluate) { if (argcount == MAX_FUNC_ARGS) { emsg_funcname(N_("E740: Too many arguments for function %s"), name); } else { @@ -594,24 +653,28 @@ ufunc_T *find_func(const char *name) /// Copy the function name of "fp" to buffer "buf". /// "buf" must be able to hold the function name plus three bytes. /// Takes care of script-local function names. -static void cat_func_name(char *buf, ufunc_T *fp) +static void cat_func_name(char *buf, size_t buflen, ufunc_T *fp) { - if ((uint8_t)fp->uf_name[0] == K_SPECIAL) { - STRCPY(buf, "<SNR>"); - STRCAT(buf, fp->uf_name + 3); + int len = -1; + size_t uflen = strlen(fp->uf_name); + assert(uflen > 0); + + if ((uint8_t)fp->uf_name[0] == K_SPECIAL && uflen > 3) { + len = snprintf(buf, buflen, "<SNR>%s", fp->uf_name + 3); } else { - STRCPY(buf, fp->uf_name); + len = snprintf(buf, buflen, "%s", fp->uf_name); } + + (void)len; // Avoid unused warning on release builds + assert(len > 0); } /// Add a number variable "name" to dict "dp" with value "nr". static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr) { -#ifndef __clang_analyzer__ STRCPY(v->di_key, name); -#endif v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - hash_add(&dp->dv_hashtab, (char *)v->di_key); + hash_add(&dp->dv_hashtab, v->di_key); v->di_tv.v_type = VAR_NUMBER; v->di_tv.v_lock = VAR_FIXED; v->di_tv.vval.v_number = nr; @@ -620,8 +683,8 @@ static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr) /// Free "fc" static void free_funccal(funccall_T *fc) { - for (int i = 0; i < fc->fc_funcs.ga_len; i++) { - ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; + for (int i = 0; i < fc->fc_ufuncs.ga_len; i++) { + ufunc_T *fp = ((ufunc_T **)(fc->fc_ufuncs.ga_data))[i]; // When garbage collecting a funccall_T may be freed before the // function that references it, clear its uf_scoped field. @@ -631,9 +694,9 @@ static void free_funccal(funccall_T *fc) fp->uf_scoped = NULL; } } - ga_clear(&fc->fc_funcs); + ga_clear(&fc->fc_ufuncs); - func_ptr_unref(fc->func); + func_ptr_unref(fc->fc_func); xfree(fc); } @@ -643,13 +706,13 @@ static void free_funccal(funccall_T *fc) static void free_funccal_contents(funccall_T *fc) { // Free all l: variables. - vars_clear(&fc->l_vars.dv_hashtab); + vars_clear(&fc->fc_l_vars.dv_hashtab); // Free all a: variables. - vars_clear(&fc->l_avars.dv_hashtab); + vars_clear(&fc->fc_l_avars.dv_hashtab); // Free the a:000 variables. - TV_LIST_ITER(&fc->l_varlist, li, { + TV_LIST_ITER(&fc->fc_l_varlist, li, { tv_clear(TV_LIST_ITEM_TV(li)); }); @@ -663,11 +726,11 @@ static void cleanup_function_call(funccall_T *fc) bool may_free_fc = fc->fc_refcount <= 0; bool free_fc = true; - current_funccal = fc->caller; + current_funccal = fc->fc_caller; // Free all l: variables if not referred. - if (may_free_fc && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT) { - vars_clear(&fc->l_vars.dv_hashtab); + if (may_free_fc && fc->fc_l_vars.dv_refcount == DO_NOT_FREE_CNT) { + vars_clear(&fc->fc_l_vars.dv_hashtab); } else { free_fc = false; } @@ -675,25 +738,25 @@ static void cleanup_function_call(funccall_T *fc) // If the a:000 list and the l: and a: dicts are not referenced and // there is no closure using it, we can free the funccall_T and what's // in it. - if (may_free_fc && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT) { - vars_clear_ext(&fc->l_avars.dv_hashtab, false); + if (may_free_fc && fc->fc_l_avars.dv_refcount == DO_NOT_FREE_CNT) { + vars_clear_ext(&fc->fc_l_avars.dv_hashtab, false); } else { free_fc = false; // Make a copy of the a: variables, since we didn't do that above. - TV_DICT_ITER(&fc->l_avars, di, { + TV_DICT_ITER(&fc->fc_l_avars, di, { tv_copy(&di->di_tv, &di->di_tv); }); } - if (may_free_fc && fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated) + if (may_free_fc && fc->fc_l_varlist.lv_refcount // NOLINT(runtime/deprecated) == DO_NOT_FREE_CNT) { - fc->l_varlist.lv_first = NULL; // NOLINT(runtime/deprecated) + fc->fc_l_varlist.lv_first = NULL; // NOLINT(runtime/deprecated) } else { free_fc = false; // Make a copy of the a:000 items, since we didn't do that above. - TV_LIST_ITER(&fc->l_varlist, li, { + TV_LIST_ITER(&fc->fc_l_varlist, li, { tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li)); }); } @@ -706,7 +769,7 @@ static void cleanup_function_call(funccall_T *fc) // "fc" is still in use. This can happen when returning "a:000", // assigning "l:" to a global variable or defining a closure. // Link "fc" in the list for garbage collection later. - fc->caller = previous_funccal; + fc->fc_caller = previous_funccal; previous_funccal = fc; if (want_garbage_collect) { @@ -729,26 +792,23 @@ static void cleanup_function_call(funccall_T *fc) /// @param[in] force When true, we are exiting. static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force) { - funccall_T **pfc; - int i; - if (fc == NULL) { return; } fc->fc_refcount--; if (force ? fc->fc_refcount <= 0 : !fc_referenced(fc)) { - for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) { + for (funccall_T **pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->fc_caller) { if (fc == *pfc) { - *pfc = fc->caller; + *pfc = fc->fc_caller; free_funccal_contents(fc); return; } } } - for (i = 0; i < fc->fc_funcs.ga_len; i++) { - if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) { - ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL; + for (int i = 0; i < fc->fc_ufuncs.ga_len; i++) { + if (((ufunc_T **)(fc->fc_ufuncs.ga_data))[i] == fp) { + ((ufunc_T **)(fc->fc_ufuncs.ga_data))[i] = NULL; } } } @@ -759,14 +819,13 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force) /// @return true if the entry was deleted, false if it wasn't found. static bool func_remove(ufunc_T *fp) { - hashitem_T *hi = hash_find(&func_hashtab, (char *)UF2HIKEY(fp)); - - if (!HASHITEM_EMPTY(hi)) { - hash_remove(&func_hashtab, hi); - return true; + hashitem_T *hi = hash_find(&func_hashtab, UF2HIKEY(fp)); + if (HASHITEM_EMPTY(hi)) { + return false; } - return false; + hash_remove(&func_hashtab, hi); + return true; } static void func_clear_items(ufunc_T *fp) @@ -825,6 +884,27 @@ static void func_clear_free(ufunc_T *fp, bool force) func_free(fp); } +/// Allocate a funccall_T, link it in current_funccal and fill in "fp" and "rettv". +/// Must be followed by one call to remove_funccal() or cleanup_function_call(). +funccall_T *create_funccal(ufunc_T *fp, typval_T *rettv) +{ + funccall_T *fc = xcalloc(1, sizeof(funccall_T)); + fc->fc_caller = current_funccal; + current_funccal = fc; + fc->fc_func = fp; + func_ptr_ref(fp); + fc->fc_rettv = rettv; + return fc; +} + +/// Restore current_funccal. +void remove_funccal(void) +{ + funccall_T *fc = current_funccal; + current_funccal = fc->fc_caller; + free_funccal(fc); +} + /// Call a user function /// /// @param fp Function to call. @@ -839,11 +919,10 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett FUNC_ATTR_NONNULL_ARG(1, 3, 4) { bool using_sandbox = false; - funccall_T *fc; int save_did_emsg; static int depth = 0; dictitem_T *v; - int fixvar_idx = 0; // index in fixvar[] + int fixvar_idx = 0; // index in fc_fixvar[] int ai; bool islambda = false; char numbuf[NUMBUFLEN]; @@ -874,40 +953,32 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // check for CTRL-C hit line_breakcheck(); // prepare the funccall_T structure - fc = xcalloc(1, sizeof(funccall_T)); - fc->caller = current_funccal; - current_funccal = fc; - fc->func = fp; - fc->rettv = rettv; - fc->level = ex_nesting_level; + funccall_T *fc = create_funccal(fp, rettv); + fc->fc_level = ex_nesting_level; // Check if this function has a breakpoint. - fc->breakpoint = dbg_find_breakpoint(false, (char *)fp->uf_name, (linenr_T)0); - fc->dbg_tick = debug_tick; - + fc->fc_breakpoint = dbg_find_breakpoint(false, fp->uf_name, 0); + fc->fc_dbg_tick = debug_tick; // Set up fields for closure. - ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1); - func_ptr_ref(fp); + ga_init(&fc->fc_ufuncs, sizeof(ufunc_T *), 1); if (strncmp(fp->uf_name, "<lambda>", 8) == 0) { islambda = true; } - // Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables + // Note about using fc->fc_fixvar[]: This is an array of FIXVAR_CNT variables // with names up to VAR_SHORT_LEN long. This avoids having to alloc/free // each argument variable and saves a lot of time. // // Init l: variables. - init_var_dict(&fc->l_vars, &fc->l_vars_var, VAR_DEF_SCOPE); + init_var_dict(&fc->fc_l_vars, &fc->fc_l_vars_var, VAR_DEF_SCOPE); if (selfdict != NULL) { // Set l:self to "selfdict". Use "name" to avoid a warning from // some compiler that checks the destination size. - v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; -#ifndef __clang_analyzer__ + v = (dictitem_T *)&fc->fc_fixvar[fixvar_idx++]; name = (char *)v->di_key; STRCPY(name, "self"); -#endif v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - hash_add(&fc->l_vars.dv_hashtab, (char *)v->di_key); + hash_add(&fc->fc_l_vars.dv_hashtab, v->di_key); v->di_tv.v_type = VAR_DICT; v->di_tv.v_lock = VAR_UNLOCKED; v->di_tv.vval.v_dict = selfdict; @@ -917,38 +988,36 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // Init a: variables, unless none found (in lambda). // Set a:0 to "argcount" less number of named arguments, if >= 0. // Set a:000 to a list with room for the "..." arguments. - init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE); + init_var_dict(&fc->fc_l_avars, &fc->fc_l_avars_var, VAR_SCOPE); if ((fp->uf_flags & FC_NOARGS) == 0) { - add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "0", + add_nr_var(&fc->fc_l_avars, (dictitem_T *)&fc->fc_fixvar[fixvar_idx++], "0", (varnumber_T)(argcount >= fp->uf_args.ga_len ? argcount - fp->uf_args.ga_len : 0)); } - fc->l_avars.dv_lock = VAR_FIXED; + fc->fc_l_avars.dv_lock = VAR_FIXED; if ((fp->uf_flags & FC_NOARGS) == 0) { // Use "name" to avoid a warning from some compiler that checks the // destination size. - v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; -#ifndef __clang_analyzer__ + v = (dictitem_T *)&fc->fc_fixvar[fixvar_idx++]; name = (char *)v->di_key; STRCPY(name, "000"); -#endif v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - hash_add(&fc->l_avars.dv_hashtab, (char *)v->di_key); + hash_add(&fc->fc_l_avars.dv_hashtab, v->di_key); v->di_tv.v_type = VAR_LIST; v->di_tv.v_lock = VAR_FIXED; - v->di_tv.vval.v_list = &fc->l_varlist; + v->di_tv.vval.v_list = &fc->fc_l_varlist; } - tv_list_init_static(&fc->l_varlist); - tv_list_set_lock(&fc->l_varlist, VAR_FIXED); + tv_list_init_static(&fc->fc_l_varlist); + tv_list_set_lock(&fc->fc_l_varlist, VAR_FIXED); // Set a:firstline to "firstline" and a:lastline to "lastline". // Set a:name to named arguments. // Set a:N to the "..." arguments. // Skipped when no a: variables used (in lambda). if ((fp->uf_flags & FC_NOARGS) == 0) { - add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], + add_nr_var(&fc->fc_l_avars, (dictitem_T *)&fc->fc_fixvar[fixvar_idx++], "firstline", (varnumber_T)firstline); - add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], + add_nr_var(&fc->fc_l_avars, (dictitem_T *)&fc->fc_fixvar[fixvar_idx++], "lastline", (varnumber_T)lastline); } bool default_arg_err = false; @@ -974,7 +1043,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett default_expr = ((char **)(fp->uf_def_args.ga_data)) [ai + fp->uf_def_args.ga_len]; - if (eval1(&default_expr, &def_rettv, true) == FAIL) { + if (eval1(&default_expr, &def_rettv, &EVALARG_EVALUATE) == FAIL) { default_arg_err = true; break; } @@ -989,7 +1058,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett name = numbuf; } if (fixvar_idx < FIXVAR_CNT && strlen(name) <= VAR_SHORT_LEN) { - v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; + v = (dictitem_T *)&fc->fc_fixvar[fixvar_idx++]; v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; } else { v = xmalloc(sizeof(dictitem_T) + strlen(name)); @@ -1011,17 +1080,17 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // Named arguments can be accessed without the "a:" prefix in lambda // expressions. Add to the l: dict. tv_copy(&v->di_tv, &v->di_tv); - hash_add(&fc->l_vars.dv_hashtab, (char *)v->di_key); + hash_add(&fc->fc_l_vars.dv_hashtab, v->di_key); } else { - hash_add(&fc->l_avars.dv_hashtab, (char *)v->di_key); + hash_add(&fc->fc_l_avars.dv_hashtab, v->di_key); } if (ai >= 0 && ai < MAX_FUNC_ARGS) { - listitem_T *li = &fc->l_listitems[ai]; + listitem_T *li = &fc->fc_l_listitems[ai]; *TV_LIST_ITEM_TV(li) = argvars[i]; TV_LIST_ITEM_TV(li)->v_lock = VAR_FIXED; - tv_list_append(&fc->l_varlist, li); + tv_list_append(&fc->fc_l_varlist, li); } } @@ -1038,7 +1107,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett no_wait_return++; verbose_enter_scroll(); - smsg(_("calling %s"), SOURCING_NAME); + smsg(0, _("calling %s"), SOURCING_NAME); if (p_verbose >= 14) { msg_puts("("); for (int i = 0; i < argcount; i++) { @@ -1046,7 +1115,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett msg_puts(", "); } if (argvars[i].v_type == VAR_NUMBER) { - msg_outnum((long)argvars[i].vval.v_number); + msg_outnum((int)argvars[i].vval.v_number); } else { // Do not want errors such as E724 here. emsg_off++; @@ -1076,7 +1145,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett bool func_not_yet_profiling_but_should = do_profiling_yes - && !fp->uf_profiling && has_profiling(false, (char *)fp->uf_name, NULL); + && !fp->uf_profiling && has_profiling(false, fp->uf_name, NULL); if (func_not_yet_profiling_but_should) { started_profiling = true; @@ -1086,7 +1155,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett bool func_or_func_caller_profiling = do_profiling_yes && (fp->uf_profiling - || (fc->caller != NULL && fc->caller->func->uf_profiling)); + || (fc->fc_caller != NULL && fc->fc_caller->fc_func->uf_profiling)); if (func_or_func_caller_profiling) { fp->uf_tm_count++; @@ -1111,7 +1180,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // A Lambda always has the command "return {expr}". It is much faster // to evaluate {expr} directly. ex_nesting_level++; - (void)eval1(&p, rettv, true); + (void)eval1(&p, rettv, &EVALARG_EVALUATE); ex_nesting_level--; } else { // call do_cmdline() to execute the lines @@ -1119,6 +1188,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); } + // Invoke functions added with ":defer". + handle_defer_one(current_funccal); + RedrawingDisabled--; // when the function was aborted because of an error, return -1 @@ -1131,15 +1203,15 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett if (func_or_func_caller_profiling) { call_start = profile_end(call_start); - call_start = profile_sub_wait(wait_start, call_start); // -V614 + call_start = profile_sub_wait(wait_start, call_start); fp->uf_tm_total = profile_add(fp->uf_tm_total, call_start); fp->uf_tm_self = profile_self(fp->uf_tm_self, call_start, fp->uf_tm_children); - if (fc->caller != NULL && fc->caller->func->uf_profiling) { - fc->caller->func->uf_tm_children = - profile_add(fc->caller->func->uf_tm_children, call_start); - fc->caller->func->uf_tml_children = - profile_add(fc->caller->func->uf_tml_children, call_start); + if (fc->fc_caller != NULL && fc->fc_caller->fc_func->uf_profiling) { + fc->fc_caller->fc_func->uf_tm_children = + profile_add(fc->fc_caller->fc_func->uf_tm_children, call_start); + fc->fc_caller->fc_func->uf_tml_children = + profile_add(fc->fc_caller->fc_func->uf_tml_children, call_start); } if (started_profiling) { // make a ":profdel func" stop profiling the function @@ -1153,10 +1225,10 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett verbose_enter_scroll(); if (aborting()) { - smsg(_("%s aborted"), SOURCING_NAME); - } else if (fc->rettv->v_type == VAR_NUMBER) { - smsg(_("%s returning #%" PRId64 ""), - SOURCING_NAME, (int64_t)fc->rettv->vval.v_number); + smsg(0, _("%s aborted"), SOURCING_NAME); + } else if (fc->fc_rettv->v_type == VAR_NUMBER) { + smsg(0, _("%s returning #%" PRId64 ""), + SOURCING_NAME, (int64_t)fc->fc_rettv->vval.v_number); } else { char buf[MSG_BUF_LEN]; @@ -1164,7 +1236,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // have some idea how it starts and ends. smsg() would always // truncate it at the end. Don't want errors such as E724 here. emsg_off++; - char *s = encode_tv2string(fc->rettv, NULL); + char *s = encode_tv2string(fc->fc_rettv, NULL); char *tofree = s; emsg_off--; if (s != NULL) { @@ -1172,7 +1244,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN); s = buf; } - smsg(_("%s returning %s"), SOURCING_NAME, s); + smsg(0, _("%s returning %s"), SOURCING_NAME, s); xfree(tofree); } } @@ -1195,7 +1267,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett no_wait_return++; verbose_enter_scroll(); - smsg(_("continuing in %s"), SOURCING_NAME); + smsg(0, _("continuing in %s"), SOURCING_NAME); msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); @@ -1231,6 +1303,21 @@ static bool func_name_refcount(const char *name) return isdigit((uint8_t)(*name)) || *name == '<'; } +/// Check the argument count for user function "fp". +/// @return FCERR_UNKNOWN if OK, FCERR_TOOFEW or FCERR_TOOMANY otherwise. +static int check_user_func_argcount(ufunc_T *fp, int argcount) + FUNC_ATTR_NONNULL_ALL +{ + const int regular_args = fp->uf_args.ga_len; + + if (argcount < regular_args - fp->uf_def_args.ga_len) { + return FCERR_TOOFEW; + } else if (!fp->uf_varargs && argcount > regular_args) { + return FCERR_TOOMANY; + } + return FCERR_UNKNOWN; +} + /// Call a user function after checking the arguments. static int call_user_func_check(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rettv, funcexe_T *funcexe, dict_T *selfdict) @@ -1243,12 +1330,11 @@ static int call_user_func_check(ufunc_T *fp, int argcount, typval_T *argvars, ty if ((fp->uf_flags & FC_RANGE) && funcexe->fe_doesrange != NULL) { *funcexe->fe_doesrange = true; } - int error; - if (argcount < fp->uf_args.ga_len - fp->uf_def_args.ga_len) { - error = FCERR_TOOFEW; - } else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) { - error = FCERR_TOOMANY; - } else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) { + int error = check_user_func_argcount(fp, argcount); + if (error != FCERR_UNKNOWN) { + return error; + } + if ((fp->uf_flags & FC_DICT) && selfdict == NULL) { error = FCERR_DICT; } else { // Call the user function. @@ -1302,8 +1388,8 @@ void free_all_functions(void) // Clean up the current_funccal chain and the funccal stack. while (current_funccal != NULL) { - tv_clear(current_funccal->rettv); - cleanup_function_call(current_funccal); // -V595 + tv_clear(current_funccal->fc_rettv); + cleanup_function_call(current_funccal); if (current_funccal == NULL && funccal_stack != NULL) { restore_funccal(); } @@ -1435,12 +1521,16 @@ varnumber_T callback_call_retnr(Callback *callback, int argcount, typval_T *argv /// Give an error message for the result of a function. /// Nothing if "error" is FCERR_NONE. -static void user_func_error(int error, const char *name) - FUNC_ATTR_NONNULL_ALL +static void user_func_error(int error, const char *name, funcexe_T *funcexe) + FUNC_ATTR_NONNULL_ARG(2) { switch (error) { case FCERR_UNKNOWN: - emsg_funcname(N_("E117: Unknown function: %s"), name); + if (funcexe->fe_found_var) { + semsg(_(e_not_callable_type_str), name); + } else { + emsg_funcname(e_unknown_function_str, name); + } break; case FCERR_NOTMETHOD: emsg_funcname(N_("E276: Cannot use function as a method: %s"), name); @@ -1452,7 +1542,7 @@ static void user_func_error(int error, const char *name) emsg_funcname(_(e_toomanyarg), name); break; case FCERR_TOOFEW: - emsg_funcname(N_("E119: Not enough arguments for function: %s"), name); + emsg_funcname(_(e_toofewarg), name); break; case FCERR_SCRIPT: emsg_funcname(N_("E120: Using <SID> not in a script context: %s"), name); @@ -1511,7 +1601,7 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t int argv_base = 0; partial_T *partial = funcexe->fe_partial; - // Initialize rettv so that it is safe for caller to invoke clear_tv(rettv) + // Initialize rettv so that it is safe for caller to invoke tv_clear(rettv) // even when call_func() returns FAIL. rettv->v_type = VAR_UNKNOWN; @@ -1524,7 +1614,7 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t if (fp == NULL) { // Make a copy of the name, if it comes from a funcref variable it could // be changed or deleted in the called function. - name = xstrnsave(funcname, (size_t)len); + name = xmemdupz(funcname, (size_t)len); fname = fname_trans_sid(name, fname_buf, &tofree, &error); } @@ -1577,7 +1667,7 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t XFREE_CLEAR(name); funcname = "v:lua"; } - } else if (fp != NULL || !builtin_function((const char *)rfname, -1)) { + } else if (fp != NULL || !builtin_function(rfname, -1)) { // User defined function. if (fp == NULL) { fp = find_func(rfname); @@ -1591,8 +1681,7 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t fp = find_func(rfname); } // Try loading a package. - if (fp == NULL && script_autoload((const char *)rfname, strlen(rfname), - true) && !aborting()) { + if (fp == NULL && script_autoload(rfname, strlen(rfname), true) && !aborting()) { // Loaded a package, search for the function again. fp = find_func(rfname); } @@ -1636,7 +1725,7 @@ theend: // Report an error unless the argument evaluation or function call has been // cancelled due to an aborting error, an interrupt, or an exception. if (!aborting()) { - user_func_error(error, (name != NULL) ? name : funcname); + user_func_error(error, (name != NULL) ? name : funcname, funcexe); } // clear the copies made from the partial @@ -1655,22 +1744,41 @@ char *printable_func_name(ufunc_T *fp) return fp->uf_name_exp != NULL ? fp->uf_name_exp : fp->uf_name; } +/// When "prev_ht_changed" does not equal "ht_changed" give an error and return +/// true. Otherwise return false. +static int function_list_modified(const int prev_ht_changed) +{ + if (prev_ht_changed != func_hashtab.ht_changed) { + emsg(_(e_function_list_was_modified)); + return true; + } + return false; +} + /// List the head of the function: "name(arg1, arg2)". /// /// @param[in] fp Function pointer. /// @param[in] indent Indent line. /// @param[in] force Include bang "!" (i.e.: "function!"). -static void list_func_head(ufunc_T *fp, int indent, bool force) +static int list_func_head(ufunc_T *fp, bool indent, bool force) { + const int prev_ht_changed = func_hashtab.ht_changed; + msg_start(); + + // a callback at the more prompt may have deleted the function + if (function_list_modified(prev_ht_changed)) { + return FAIL; + } + if (indent) { msg_puts(" "); } msg_puts(force ? "function! " : "function "); if (fp->uf_name_exp != NULL) { - msg_puts((const char *)fp->uf_name_exp); + msg_puts(fp->uf_name_exp); } else { - msg_puts((const char *)fp->uf_name); + msg_puts(fp->uf_name); } msg_putchar('('); int j; @@ -1678,7 +1786,7 @@ static void list_func_head(ufunc_T *fp, int indent, bool force) if (j) { msg_puts(", "); } - msg_puts((const char *)FUNCARG(fp, j)); + msg_puts(FUNCARG(fp, j)); if (j >= fp->uf_args.ga_len - fp->uf_def_args.ga_len) { msg_puts(" = "); msg_puts(((char **)(fp->uf_def_args.ga_data)) @@ -1708,6 +1816,8 @@ static void list_func_head(ufunc_T *fp, int indent, bool force) if (p_verbose > 0) { last_set_msg(fp->uf_script_ctx); } + + return OK; } /// Get a function name, translating "<SID>" and "<SNR>". @@ -1728,21 +1838,17 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part FUNC_ATTR_NONNULL_ARG(1) { char *name = NULL; - const char *start; - const char *end; - int lead; int len; lval_T lv; if (fdp != NULL) { CLEAR_POINTER(fdp); } - start = *pp; + const char *start = *pp; // Check for hard coded <SNR>: already translated function ID (from a user // command). - if ((unsigned char)(*pp)[0] == K_SPECIAL && (unsigned char)(*pp)[1] == KS_EXTRA - && (*pp)[2] == KE_SNR) { + if ((uint8_t)(*pp)[0] == K_SPECIAL && (uint8_t)(*pp)[1] == KS_EXTRA && (*pp)[2] == KE_SNR) { *pp += 3; len = get_id_len((const char **)pp) + 3; return xmemdupz(start, (size_t)len); @@ -1750,14 +1856,14 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part // A name starting with "<SID>" or "<SNR>" is local to a script. But // don't skip over "s:", get_lval() needs it for "s:dict.func". - lead = eval_fname_script(start); + int lead = eval_fname_script(start); if (lead > 2) { start += lead; } // Note that TFN_ flags use the same values as GLV_ flags. - end = get_lval((char *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY, - lead > 2 ? 0 : FNE_CHECK_START); + const char *end = get_lval((char *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY, + lead > 2 ? 0 : FNE_CHECK_START); if (end == start) { if (!skip) { emsg(_("E129: Function name required")); @@ -1773,7 +1879,7 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part semsg(_(e_invarg2), start); } } else { - *pp = (char *)find_name_end((char *)start, NULL, NULL, FNE_INCL_BR); + *pp = (char *)find_name_end(start, NULL, NULL, FNE_INCL_BR); } goto theend; } @@ -1828,14 +1934,13 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part // Check if the name is a Funcref. If so, use the value. if (lv.ll_exp_name != NULL) { len = (int)strlen(lv.ll_exp_name); - name = deref_func_name(lv.ll_exp_name, &len, partial, - flags & TFN_NO_AUTOLOAD); - if ((const char *)name == lv.ll_exp_name) { + name = deref_func_name(lv.ll_exp_name, &len, partial, flags & TFN_NO_AUTOLOAD, NULL); + if (name == lv.ll_exp_name) { name = NULL; } } else if (!(flags & TFN_NO_DEREF)) { len = (int)(end - *pp); - name = deref_func_name(*pp, &len, partial, flags & TFN_NO_AUTOLOAD); + name = deref_func_name(*pp, &len, partial, flags & TFN_NO_AUTOLOAD, NULL); if (name == *pp) { name = NULL; } @@ -1883,8 +1988,7 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part lead = 0; // do nothing } else if (lead > 0) { lead = 3; - if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) - || eval_fname_sid((const char *)(*pp))) { + if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) || eval_fname_sid(*pp)) { // It's "s:" or "<SID>". if (current_sctx.sc_sid <= 0) { emsg(_(e_usingsid)); @@ -1971,7 +2075,7 @@ char *save_function_name(char **name, bool skip, int flags, funcdict_T *fudi) if (strncmp(p, "<lambda>", 8) == 0) { p += 8; (void)getdigits(&p, false, 0); - saved = xstrndup(*name, (size_t)(p - *name)); + saved = xmemdupz(*name, (size_t)(p - *name)); if (fudi != NULL) { CLEAR_POINTER(fudi); } @@ -1990,7 +2094,7 @@ char *save_function_name(char **name, bool skip, int flags, funcdict_T *fudi) /// Otherwise functions matching "regmatch". static void list_functions(regmatch_T *regmatch) { - const int changed = func_hashtab.ht_changed; + const int prev_ht_changed = func_hashtab.ht_changed; size_t todo = func_hashtab.ht_used; const hashitem_T *const ht_array = func_hashtab.ht_array; @@ -1998,15 +2102,15 @@ static void list_functions(regmatch_T *regmatch) if (!HASHITEM_EMPTY(hi)) { ufunc_T *fp = HI2UF(hi); todo--; - if ((fp->uf_flags & FC_DEAD) == 0 - && (regmatch == NULL - ? (!message_filtered((char *)fp->uf_name) - && !func_name_refcount(fp->uf_name)) - : (!isdigit((uint8_t)(*fp->uf_name)) - && vim_regexec(regmatch, (char *)fp->uf_name, 0)))) { - list_func_head(fp, false, false); - if (changed != func_hashtab.ht_changed) { - emsg(_("E454: function list was modified")); + if (regmatch == NULL + ? (!message_filtered(fp->uf_name) + && !func_name_refcount(fp->uf_name)) + : (!isdigit((uint8_t)(*fp->uf_name)) + && vim_regexec(regmatch, fp->uf_name, 0))) { + if (list_func_head(fp, false, false) == FAIL) { + return; + } + if (function_list_modified(prev_ht_changed)) { return; } } @@ -2019,11 +2123,7 @@ void ex_function(exarg_T *eap) { char *theline; char *line_to_free = NULL; - char c; - int saved_did_emsg; bool saved_wait_return = need_wait_return; - char *name = NULL; - char *p; char *arg; char *line_arg = NULL; garray_T newargs; @@ -2033,16 +2133,9 @@ void ex_function(exarg_T *eap) int flags = 0; ufunc_T *fp; bool overwrite = false; - int indent; - int nesting; - dictitem_T *v; funcdict_T fudi; static int func_nr = 0; // number for nameless function - int paren; hashtab_T *ht; - hashitem_T *hi; - linenr_T sourcing_lnum_off; - linenr_T sourcing_lnum_top; bool is_heredoc = false; char *skip_until = NULL; char *heredoc_trimmed = NULL; @@ -2060,11 +2153,11 @@ void ex_function(exarg_T *eap) // ":function /pat": list functions matching pattern. if (*eap->arg == '/') { - p = skip_regexp(eap->arg + 1, '/', true); + char *p = skip_regexp(eap->arg + 1, '/', true); if (!eap->skip) { regmatch_T regmatch; - c = *p; + char c = *p; *p = NUL; regmatch.regprog = vim_regcomp(eap->arg + 1, RE_MAGIC); *p = c; @@ -2095,9 +2188,9 @@ void ex_function(exarg_T *eap) // "fudi.fd_di" set, "fudi.fd_newkey" == NULL // s:func script-local function name // g:func global function name, same as "func" - p = eap->arg; - name = save_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi); - paren = (vim_strchr(p, '(') != NULL); + char *p = eap->arg; + char *name = save_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi); + int paren = (vim_strchr(p, '(') != NULL); if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { // Return on an invalid expression in braces, unless the expression // evaluation has been cancelled due to an aborting error, an @@ -2114,7 +2207,7 @@ void ex_function(exarg_T *eap) // An error in a function call during evaluation of an expression in magic // braces should not cause the function not to be defined. - saved_did_emsg = did_emsg; + const int saved_did_emsg = did_emsg; did_emsg = false; // @@ -2135,28 +2228,37 @@ void ex_function(exarg_T *eap) if (!eap->skip && !got_int) { fp = find_func(name); if (fp != NULL) { - list_func_head(fp, !eap->forceit, eap->forceit); - for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) { - if (FUNCLINE(fp, j) == NULL) { - continue; - } - msg_putchar('\n'); - if (!eap->forceit) { - msg_outnum((long)j + 1); - if (j < 9) { - msg_putchar(' '); + // Check no function was added or removed from a callback, e.g. at + // the more prompt. "fp" may then be invalid. + const int prev_ht_changed = func_hashtab.ht_changed; + + if (list_func_head(fp, !eap->forceit, eap->forceit) == OK) { + for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) { + if (FUNCLINE(fp, j) == NULL) { + continue; } - if (j < 99) { - msg_putchar(' '); + msg_putchar('\n'); + if (!eap->forceit) { + msg_outnum(j + 1); + if (j < 9) { + msg_putchar(' '); + } + if (j < 99) { + msg_putchar(' '); + } + if (function_list_modified(prev_ht_changed)) { + break; + } + } + msg_prt_line(FUNCLINE(fp, j), false); + line_breakcheck(); // show multiple lines at a time! + } + if (!got_int) { + msg_putchar('\n'); + if (!function_list_modified(prev_ht_changed)) { + msg_puts(eap->forceit ? "endfunction" : " endfunction"); } } - msg_prt_line(FUNCLINE(fp, j), false); - ui_flush(); // show a line at a time - os_breakcheck(); - } - if (!got_int) { - msg_putchar('\n'); - msg_puts(eap->forceit ? "endfunction" : " endfunction"); } } else { emsg_funcname(N_("E123: Undefined function: %s"), name); @@ -2196,7 +2298,7 @@ void ex_function(exarg_T *eap) j++; } if (arg[j] != NUL) { - emsg_funcname((char *)e_invarg2, arg); + emsg_funcname(e_invarg2, arg); } } // Disallow using the g: dict. @@ -2212,11 +2314,11 @@ void ex_function(exarg_T *eap) if (KeyTyped && ui_has(kUICmdline)) { show_block = true; - ui_ext_cmdline_block_append(0, (const char *)eap->cmd); + ui_ext_cmdline_block_append(0, eap->cmd); } // find extra arguments "range", "dict", "abort" and "closure" - for (;;) { + while (true) { p = skipwhite(p); if (strncmp(p, "range", 5) == 0) { flags |= FC_RANGE; @@ -2272,11 +2374,11 @@ void ex_function(exarg_T *eap) } // Save the starting line number. - sourcing_lnum_top = SOURCING_LNUM; + linenr_T sourcing_lnum_top = SOURCING_LNUM; - indent = 2; - nesting = 0; - for (;;) { + int indent = 2; + int nesting = 0; + while (true) { if (KeyTyped) { msg_scroll = true; saved_wait_return = false; @@ -2296,7 +2398,7 @@ void ex_function(exarg_T *eap) } else { xfree(line_to_free); if (eap->getline == NULL) { - theline = getcmdline(':', 0L, indent, do_concat); + theline = getcmdline(':', 0, indent, do_concat); } else { theline = eap->getline(':', eap->cookie, indent, do_concat); } @@ -2306,16 +2408,20 @@ void ex_function(exarg_T *eap) lines_left = Rows - 1; } if (theline == NULL) { - emsg(_("E126: Missing :endfunction")); + if (skip_until != NULL) { + semsg(_(e_missing_heredoc_end_marker_str), skip_until); + } else { + emsg(_("E126: Missing :endfunction")); + } goto erret; } if (show_block) { assert(indent >= 0); - ui_ext_cmdline_block_append((size_t)indent, (const char *)theline); + ui_ext_cmdline_block_append((size_t)indent, theline); } // Detect line continuation: SOURCING_LNUM increased more than one. - sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie); + linenr_T sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie); if (SOURCING_LNUM < sourcing_lnum_off) { sourcing_lnum_off -= SOURCING_LNUM; } else { @@ -2335,7 +2441,7 @@ void ex_function(exarg_T *eap) p = theline; } else if (is_heredoc) { p = skipwhite(theline) == theline - ? theline : theline + strlen(heredoc_trimmed); + ? theline : theline + strlen(heredoc_trimmed); } else { p = theline + strlen(heredoc_trimmed); } @@ -2361,7 +2467,7 @@ void ex_function(exarg_T *eap) } else if (line_arg != NULL && *skipwhite(line_arg) != NUL) { nextcmd = line_arg; } else if (*p != NUL && *p != '"' && p_verbose > 0) { - give_warning2(_("W22: Text found after :endfunction: %s"), p, true); + swmsg(true, _("W22: Text found after :endfunction: %s"), p); } if (nextcmd != NULL) { // Another command follows. If the line came from "eap" we @@ -2393,11 +2499,11 @@ void ex_function(exarg_T *eap) if (*p == '!') { p = skipwhite(p + 1); } - p += eval_fname_script((const char *)p); + p += eval_fname_script(p); xfree(trans_function_name(&p, true, 0, NULL, NULL)); if (*skipwhite(p) == '(') { if (nesting == MAX_FUNC_NESTING - 1) { - emsg(_("E1058: function nesting too deep")); + emsg(_(e_function_nesting_too_deep)); } else { nesting++; indent += 2; @@ -2440,35 +2546,45 @@ void ex_function(exarg_T *eap) && (!ASCII_ISALPHA(p[2]) || p[2] == 's')))) { // ":python <<" continues until a dot, like ":append" p = skipwhite(arg + 2); + if (strncmp(p, "trim", 4) == 0) { + // Ignore leading white space. + p = skipwhite(p + 4); + heredoc_trimmed = xmemdupz(theline, (size_t)(skipwhite(theline) - theline)); + } if (*p == NUL) { skip_until = xstrdup("."); } else { - skip_until = xstrdup(p); + skip_until = xmemdupz(p, (size_t)(skiptowhite(p) - p)); } + do_concat = false; + is_heredoc = true; } // Check for ":let v =<< [trim] EOF" // and ":let [a, b] =<< [trim] EOF" - arg = skipwhite(skiptowhite(p)); - if (*arg == '[') { - arg = vim_strchr(arg, ']'); - } - if (arg != NULL) { - arg = skipwhite(skiptowhite(arg)); - if (arg[0] == '=' - && arg[1] == '<' - && arg[2] == '<' - && (p[0] == 'l' - && p[1] == 'e' - && (!ASCII_ISALNUM(p[2]) - || (p[2] == 't' && !ASCII_ISALNUM(p[3]))))) { + arg = p; + if (checkforcmd(&arg, "let", 2)) { + while (vim_strchr("$@&", *arg) != NULL) { + arg++; + } + arg = skipwhite(find_name_end(arg, NULL, NULL, FNE_INCL_BR)); + if (arg[0] == '=' && arg[1] == '<' && arg[2] == '<') { p = skipwhite(arg + 3); - if (strncmp(p, "trim", 4) == 0) { - // Ignore leading white space. - p = skipwhite(p + 4); - heredoc_trimmed = xstrnsave(theline, (size_t)(skipwhite(theline) - theline)); + while (true) { + if (strncmp(p, "trim", 4) == 0) { + // Ignore leading white space. + p = skipwhite(p + 4); + heredoc_trimmed = xmemdupz(theline, (size_t)(skipwhite(theline) - theline)); + continue; + } + if (strncmp(p, "eval", 4) == 0) { + // Ignore leading white space. + p = skipwhite(p + 4); + continue; + } + break; } - skip_until = xstrnsave(p, (size_t)(skiptowhite(p) - p)); + skip_until = xmemdupz(p, (size_t)(skiptowhite(p) - p)); do_concat = false; is_heredoc = true; } @@ -2504,7 +2620,7 @@ void ex_function(exarg_T *eap) // If there are no errors, add the function if (fudi.fd_dict == NULL) { - v = find_var((const char *)name, strlen(name), &ht, false); + dictitem_T *v = find_var(name, strlen(name), &ht, false); if (v != NULL && v->di_tv.v_type == VAR_FUNC) { emsg_funcname(N_("E707: Function name conflicts with variable: %s"), name); goto erret; @@ -2551,13 +2667,11 @@ void ex_function(exarg_T *eap) goto erret; } if (fudi.fd_di == NULL) { - if (value_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg, - TV_CSTRING)) { + if (value_check_lock(fudi.fd_dict->dv_lock, eap->arg, TV_CSTRING)) { // Can't add a function to a locked dictionary goto erret; } - } else if (value_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg, - TV_CSTRING)) { + } else if (value_check_lock(fudi.fd_di->di_tv.v_lock, eap->arg, TV_CSTRING)) { // Can't change an existing function if it is locked goto erret; } @@ -2565,22 +2679,19 @@ void ex_function(exarg_T *eap) // Give the function a sequential number. Can only be used with a // Funcref! xfree(name); - sprintf(numbuf, "%d", ++func_nr); // NOLINT(runtime/printf) + snprintf(numbuf, sizeof(numbuf), "%d", ++func_nr); name = xstrdup(numbuf); } if (fp == NULL) { if (fudi.fd_dict == NULL && vim_strchr(name, AUTOLOAD_CHAR) != NULL) { - int slen, plen; - char *scriptname; - // Check that the autoload name matches the script name. int j = FAIL; if (SOURCING_NAME != NULL) { - scriptname = autoload_name(name, strlen(name)); + char *scriptname = autoload_name(name, strlen(name)); p = vim_strchr(scriptname, '/'); - plen = (int)strlen(p); - slen = (int)strlen(SOURCING_NAME); + int plen = (int)strlen(p); + int slen = (int)strlen(SOURCING_NAME); if (slen > plen && path_fnamecmp(p, SOURCING_NAME + slen - plen) == 0) { j = OK; } @@ -2598,7 +2709,7 @@ void ex_function(exarg_T *eap) if (fudi.fd_dict != NULL) { if (fudi.fd_di == NULL) { // Add new dict entry - fudi.fd_di = tv_dict_item_alloc((const char *)fudi.fd_newkey); + fudi.fd_di = tv_dict_item_alloc(fudi.fd_newkey); if (tv_dict_add(fudi.fd_dict, fudi.fd_di) == FAIL) { xfree(fudi.fd_di); xfree(fp); @@ -2618,8 +2729,8 @@ void ex_function(exarg_T *eap) // insert the new function in the function list set_ufunc_name(fp, name); if (overwrite) { - hi = hash_find(&func_hashtab, name); - hi->hi_key = (char *)UF2HIKEY(fp); + hashitem_T *hi = hash_find(&func_hashtab, name); + hi->hi_key = UF2HIKEY(fp); } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) { xfree(fp); goto erret; @@ -2688,7 +2799,7 @@ int eval_fname_script(const char *const p) bool translated_function_exists(const char *name) { if (builtin_function(name, -1)) { - return find_internal_func((char *)name) != NULL; + return find_internal_func(name) != NULL; } return find_func(name) != NULL; } @@ -2727,7 +2838,6 @@ char *get_user_func_name(expand_T *xp, int idx) static size_t done; static int changed; static hashitem_T *hi; - ufunc_T *fp; if (idx == 0) { done = 0; @@ -2742,7 +2852,7 @@ char *get_user_func_name(expand_T *xp, int idx) while (HASHITEM_EMPTY(hi)) { hi++; } - fp = HI2UF(hi); + ufunc_T *fp = HI2UF(hi); if ((fp->uf_flags & FC_DICT) || strncmp(fp->uf_name, "<lambda>", 8) == 0) { @@ -2750,14 +2860,14 @@ char *get_user_func_name(expand_T *xp, int idx) } if (strlen(fp->uf_name) + 4 >= IOSIZE) { - return (char *)fp->uf_name; // Prevent overflow. + return fp->uf_name; // Prevent overflow. } - cat_func_name(IObuff, fp); + cat_func_name(IObuff, IOSIZE, fp); if (xp->xp_context != EXPAND_USER_FUNC) { - STRCAT(IObuff, "("); + xstrlcat(IObuff, "(", IOSIZE); if (!fp->uf_varargs && GA_EMPTY(&fp->uf_args)) { - STRCAT(IObuff, ")"); + xstrlcat(IObuff, ")", IOSIZE); } } return IObuff; @@ -2769,12 +2879,10 @@ char *get_user_func_name(expand_T *xp, int idx) void ex_delfunction(exarg_T *eap) { ufunc_T *fp = NULL; - char *p; - char *name; funcdict_T fudi; - p = eap->arg; - name = trans_function_name(&p, eap->skip, 0, &fudi, NULL); + char *p = eap->arg; + char *name = trans_function_name(&p, eap->skip, 0, &fudi, NULL); xfree(fudi.fd_newkey); if (name == NULL) { if (fudi.fd_dict != NULL && !eap->skip) { @@ -2851,13 +2959,11 @@ void ex_delfunction(exarg_T *eap) /// becomes zero. void func_unref(char *name) { - ufunc_T *fp = NULL; - if (name == NULL || !func_name_refcount(name)) { return; } - fp = find_func(name); + ufunc_T *fp = find_func(name); if (fp == NULL && isdigit((uint8_t)(*name))) { #ifdef EXITFREE if (!entered_free_all_mem) { @@ -2893,12 +2999,10 @@ void func_ptr_unref(ufunc_T *fp) /// Count a reference to a Function. void func_ref(char *name) { - ufunc_T *fp; - if (name == NULL || !func_name_refcount(name)) { return; } - fp = find_func(name); + ufunc_T *fp = find_func(name); if (fp != NULL) { (fp->uf_refcount)++; } else if (isdigit((uint8_t)(*name))) { @@ -2925,10 +3029,10 @@ static inline bool fc_referenced(const funccall_T *const fc) FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - return ((fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated) + return ((fc->fc_l_varlist.lv_refcount // NOLINT(runtime/deprecated) != DO_NOT_FREE_CNT) - || fc->l_vars.dv_refcount != DO_NOT_FREE_CNT - || fc->l_avars.dv_refcount != DO_NOT_FREE_CNT + || fc->fc_l_vars.dv_refcount != DO_NOT_FREE_CNT + || fc->fc_l_avars.dv_refcount != DO_NOT_FREE_CNT || fc->fc_refcount > 0); } @@ -2936,9 +3040,9 @@ static inline bool fc_referenced(const funccall_T *const fc) /// referenced from anywhere that is in use. static int can_free_funccal(funccall_T *fc, int copyID) { - return fc->l_varlist.lv_copyID != copyID - && fc->l_vars.dv_copyID != copyID - && fc->l_avars.dv_copyID != copyID + return fc->fc_l_varlist.lv_copyID != copyID + && fc->fc_l_vars.dv_copyID != copyID + && fc->fc_l_avars.dv_copyID != copyID && fc->fc_copyID != copyID; } @@ -2954,13 +3058,15 @@ void ex_return(exarg_T *eap) return; } + evalarg_T evalarg = { .eval_flags = eap->skip ? 0 : EVAL_EVALUATE }; + if (eap->skip) { emsg_skip++; } eap->nextcmd = NULL; if ((*arg != NUL && *arg != '|' && *arg != '\n') - && eval0(arg, &rettv, &eap->nextcmd, !eap->skip) != FAIL) { + && eval0(arg, &rettv, eap, &evalarg) != FAIL) { if (!eap->skip) { returning = do_return(eap, false, true, &rettv); } else { @@ -2989,36 +3095,234 @@ void ex_return(exarg_T *eap) if (eap->skip) { emsg_skip--; } + clear_evalarg(&evalarg, eap); +} + +/// Lower level implementation of "call". Only called when not skipping. +static int ex_call_inner(exarg_T *eap, char *name, char **arg, char *startarg, + const funcexe_T *const funcexe_init, evalarg_T *const evalarg) +{ + bool doesrange; + bool failed = false; + + for (linenr_T lnum = eap->line1; lnum <= eap->line2; lnum++) { + if (eap->addr_count > 0) { + if (lnum > curbuf->b_ml.ml_line_count) { + // If the function deleted lines or switched to another buffer + // the line number may become invalid. + emsg(_(e_invrange)); + break; + } + curwin->w_cursor.lnum = lnum; + curwin->w_cursor.col = 0; + curwin->w_cursor.coladd = 0; + } + *arg = startarg; + + funcexe_T funcexe = *funcexe_init; + funcexe.fe_doesrange = &doesrange; + typval_T rettv; + rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this + if (get_func_tv(name, -1, &rettv, arg, evalarg, &funcexe) == FAIL) { + failed = true; + break; + } + + // Handle a function returning a Funcref, Dictionary or List. + if (handle_subscript((const char **)arg, &rettv, &EVALARG_EVALUATE, true) == FAIL) { + failed = true; + break; + } + + tv_clear(&rettv); + if (doesrange) { + break; + } + + // Stop when immediately aborting on error, or when an interrupt + // occurred or an exception was thrown but not caught. + // get_func_tv() returned OK, so that the check for trailing + // characters below is executed. + if (aborting()) { + break; + } + } + + return failed; +} + +/// Core part of ":defer func(arg)". "arg" points to the "(" and is advanced. +/// +/// @return FAIL or OK. +static int ex_defer_inner(char *name, char **arg, const partial_T *const partial, + evalarg_T *const evalarg) +{ + typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments + int partial_argc = 0; // number of partial arguments + int argcount = 0; // number of arguments found + + if (current_funccal == NULL) { + semsg(_(e_str_not_inside_function), "defer"); + return FAIL; + } + if (partial != NULL) { + if (partial->pt_dict != NULL) { + emsg(_(e_cannot_use_partial_with_dictionary_for_defer)); + return FAIL; + } + if (partial->pt_argc > 0) { + partial_argc = partial->pt_argc; + for (int i = 0; i < partial_argc; i++) { + tv_copy(&partial->pt_argv[i], &argvars[i]); + } + } + } + int r = get_func_arguments(arg, evalarg, false, argvars + partial_argc, &argcount); + argcount += partial_argc; + + if (r == OK) { + if (builtin_function(name, -1)) { + const EvalFuncDef *const fdef = find_internal_func(name); + if (fdef == NULL) { + emsg_funcname(e_unknown_function_str, name); + r = FAIL; + } else if (check_internal_func(fdef, argcount) == -1) { + r = FAIL; + } + } else { + ufunc_T *ufunc = find_func(name); + // we tolerate an unknown function here, it might be defined later + if (ufunc != NULL) { + int error = check_user_func_argcount(ufunc, argcount); + if (error != FCERR_UNKNOWN) { + user_func_error(error, name, NULL); + r = FAIL; + } + } + } + } + + if (r == FAIL) { + while (--argcount >= 0) { + tv_clear(&argvars[argcount]); + } + return FAIL; + } + add_defer(name, argcount, argvars); + return OK; +} + +/// Return true if currently inside a function call. +/// Give an error message and return false when not. +bool can_add_defer(void) +{ + if (get_current_funccal() == NULL) { + semsg(_(e_str_not_inside_function), "defer"); + return false; + } + return true; +} + +/// Add a deferred call for "name" with arguments "argvars[argcount]". +/// Consumes "argvars[]". +/// Caller must check that current_funccal is not NULL. +void add_defer(char *name, int argcount_arg, typval_T *argvars) +{ + char *saved_name = xstrdup(name); + int argcount = argcount_arg; + + if (current_funccal->fc_defer.ga_itemsize == 0) { + ga_init(¤t_funccal->fc_defer, sizeof(defer_T), 10); + } + defer_T *dr = GA_APPEND_VIA_PTR(defer_T, ¤t_funccal->fc_defer); + dr->dr_name = saved_name; + dr->dr_argcount = argcount; + while (argcount > 0) { + argcount--; + dr->dr_argvars[argcount] = argvars[argcount]; + } +} + +/// Invoked after a function has finished: invoke ":defer" functions. +static void handle_defer_one(funccall_T *funccal) +{ + for (int idx = funccal->fc_defer.ga_len - 1; idx >= 0; idx--) { + defer_T *dr = ((defer_T *)funccal->fc_defer.ga_data) + idx; + + if (dr->dr_name == NULL) { + // already being called, can happen if function does ":qa" + continue; + } + + funcexe_T funcexe = { .fe_evaluate = true }; + + typval_T rettv; + rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this + + char *name = dr->dr_name; + dr->dr_name = NULL; + + // If the deferred function is called after an exception, then only the + // first statement in the function will be executed (because of the + // exception). So save and restore the try/catch/throw exception + // state. + exception_state_T estate; + exception_state_save(&estate); + exception_state_clear(); + + call_func(name, -1, &rettv, dr->dr_argcount, dr->dr_argvars, &funcexe); + + exception_state_restore(&estate); + + tv_clear(&rettv); + xfree(name); + for (int i = dr->dr_argcount - 1; i >= 0; i--) { + tv_clear(&dr->dr_argvars[i]); + } + } + ga_clear(&funccal->fc_defer); +} + +/// Called when exiting: call all defer functions. +void invoke_all_defer(void) +{ + for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->fc_caller) { + handle_defer_one(fc); + } + + for (funccal_entry_T *fce = funccal_stack; fce != NULL; fce = fce->next) { + for (funccall_T *fc = fce->top_funccal; fc != NULL; fc = fc->fc_caller) { + handle_defer_one(fc); + } + } } /// ":1,25call func(arg1, arg2)" function call. +/// ":defer func(arg1, arg2)" deferred function call. void ex_call(exarg_T *eap) { char *arg = eap->arg; - char *startarg; - char *name; - char *tofree; - int len; - typval_T rettv; - linenr_T lnum; - bool doesrange; bool failed = false; funcdict_T fudi; partial_T *partial = NULL; + evalarg_T evalarg; + fill_evalarg_from_eap(&evalarg, eap, eap->skip); if (eap->skip) { + typval_T rettv; // trans_function_name() doesn't work well when skipping, use eval0() // instead to skip to any following command, e.g. for: // :if 0 | call dict.foo().bar() | endif. emsg_skip++; - if (eval0(eap->arg, &rettv, &eap->nextcmd, false) != FAIL) { + if (eval0(eap->arg, &rettv, eap, &evalarg) != FAIL) { tv_clear(&rettv); } emsg_skip--; + clear_evalarg(&evalarg, eap); return; } - tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial); + char *tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial); if (fudi.fd_newkey != NULL) { // Still need to give an error message for missing key. semsg(_(e_dictkey), fudi.fd_newkey); @@ -3037,66 +3341,31 @@ void ex_call(exarg_T *eap) // If it is the name of a variable of type VAR_FUNC or VAR_PARTIAL use its // contents. For VAR_PARTIAL get its partial, unless we already have one // from trans_function_name(). - len = (int)strlen(tofree); - name = deref_func_name(tofree, &len, partial != NULL ? NULL : &partial, false); + int len = (int)strlen(tofree); + bool found_var = false; + char *name = deref_func_name(tofree, &len, partial != NULL ? NULL : &partial, false, &found_var); // Skip white space to allow ":call func ()". Not good, but required for // backward compatibility. - startarg = skipwhite(arg); - rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this. + char *startarg = skipwhite(arg); if (*startarg != '(') { semsg(_(e_missingparen), eap->arg); goto end; } - lnum = eap->line1; - for (; lnum <= eap->line2; lnum++) { - if (eap->addr_count > 0) { // -V560 - if (lnum > curbuf->b_ml.ml_line_count) { - // If the function deleted lines or switched to another buffer - // the line number may become invalid. - emsg(_(e_invrange)); - break; - } - curwin->w_cursor.lnum = lnum; - curwin->w_cursor.col = 0; - curwin->w_cursor.coladd = 0; - } + if (eap->cmdidx == CMD_defer) { arg = startarg; - + failed = ex_defer_inner(name, &arg, partial, &evalarg) == FAIL; + } else { funcexe_T funcexe = FUNCEXE_INIT; + funcexe.fe_partial = partial; + funcexe.fe_selfdict = fudi.fd_dict; funcexe.fe_firstline = eap->line1; funcexe.fe_lastline = eap->line2; - funcexe.fe_doesrange = &doesrange; + funcexe.fe_found_var = found_var; funcexe.fe_evaluate = true; - funcexe.fe_partial = partial; - funcexe.fe_selfdict = fudi.fd_dict; - if (get_func_tv(name, -1, &rettv, &arg, &funcexe) == FAIL) { - failed = true; - break; - } - - // Handle a function returning a Funcref, Dictionary or List. - if (handle_subscript((const char **)&arg, &rettv, true, true, - (const char *)name, (const char **)&name) - == FAIL) { - failed = true; - break; - } - - tv_clear(&rettv); - if (doesrange) { - break; - } - - // Stop when immediately aborting on error, or when an interrupt - // occurred or an exception was thrown but not caught. - // get_func_tv() returned OK, so that the check for trailing - // characters below is executed. - if (aborting()) { - break; - } + failed = ex_call_inner(eap, name, &arg, startarg, &funcexe, &evalarg); } // When inside :try we need to check for following "| catch" or "| endtry". @@ -3112,6 +3381,7 @@ void ex_call(exarg_T *eap) eap->nextcmd = check_nextcmd(arg); } } + clear_evalarg(&evalarg, eap); end: tv_dict_unref(fudi.fd_dict); @@ -3130,19 +3400,18 @@ end: /// false when the return gets pending. int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) { - int idx; cstack_T *const cstack = eap->cstack; if (reanimate) { // Undo the return. - current_funccal->returned = false; + current_funccal->fc_returned = false; } // Cleanup (and deactivate) conditionals, but stop when a try conditional // not in its finally clause (which then is to be executed next) is found. // In this case, make the ":return" pending for execution at the ":endtry". // Otherwise, return normally. - idx = cleanup_conditionals(eap->cstack, 0, true); + int idx = cleanup_conditionals(eap->cstack, 0, true); if (idx >= 0) { cstack->cs_pending[idx] = CSTP_RETURN; @@ -3155,8 +3424,8 @@ int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) // When undoing a return in order to make it pending, get the stored // return rettv. if (reanimate) { - assert(current_funccal->rettv); - rettv = current_funccal->rettv; + assert(current_funccal->fc_rettv); + rettv = current_funccal->fc_rettv; } if (rettv != NULL) { @@ -3171,20 +3440,20 @@ int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) // The pending return value could be overwritten by a ":return" // without argument in a finally clause; reset the default // return value. - current_funccal->rettv->v_type = VAR_NUMBER; - current_funccal->rettv->vval.v_number = 0; + current_funccal->fc_rettv->v_type = VAR_NUMBER; + current_funccal->fc_rettv->vval.v_number = 0; } } report_make_pending(CSTP_RETURN, rettv); } else { - current_funccal->returned = true; + current_funccal->fc_returned = true; // If the return is carried out now, store the return value. For // a return immediately after reanimation, the value is already // there. if (!reanimate && rettv != NULL) { - tv_clear(current_funccal->rettv); - *current_funccal->rettv = *(typval_T *)rettv; + tv_clear(current_funccal->fc_rettv); + *current_funccal->fc_rettv = *(typval_T *)rettv; if (!is_cmd) { xfree(rettv); } @@ -3208,7 +3477,7 @@ char *get_return_cmd(void *rettv) s = ""; } - STRCPY(IObuff, ":return "); + xstrlcpy(IObuff, ":return ", IOSIZE); xstrlcpy(IObuff + 8, s, IOSIZE - 8); if (strlen(s) + 8 >= IOSIZE) { STRCPY(IObuff + IOSIZE - 4, "..."); @@ -3224,34 +3493,33 @@ char *get_return_cmd(void *rettv) char *get_func_line(int c, void *cookie, int indent, bool do_concat) { funccall_T *fcp = (funccall_T *)cookie; - ufunc_T *fp = fcp->func; + ufunc_T *fp = fcp->fc_func; char *retval; - garray_T *gap; // growarray with function lines // If breakpoints have been added/deleted need to check for it. - if (fcp->dbg_tick != debug_tick) { - fcp->breakpoint = dbg_find_breakpoint(false, (char *)fp->uf_name, SOURCING_LNUM); - fcp->dbg_tick = debug_tick; + if (fcp->fc_dbg_tick != debug_tick) { + fcp->fc_breakpoint = dbg_find_breakpoint(false, fp->uf_name, SOURCING_LNUM); + fcp->fc_dbg_tick = debug_tick; } if (do_profiling == PROF_YES) { func_line_end(cookie); } - gap = &fp->uf_lines; + garray_T *gap = &fp->uf_lines; // growarray with function lines if (((fp->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try()) - || fcp->returned) { + || fcp->fc_returned) { retval = NULL; } else { // Skip NULL lines (continuation lines). - while (fcp->linenr < gap->ga_len - && ((char **)(gap->ga_data))[fcp->linenr] == NULL) { - fcp->linenr++; + while (fcp->fc_linenr < gap->ga_len + && ((char **)(gap->ga_data))[fcp->fc_linenr] == NULL) { + fcp->fc_linenr++; } - if (fcp->linenr >= gap->ga_len) { + if (fcp->fc_linenr >= gap->ga_len) { retval = NULL; } else { - retval = xstrdup(((char **)(gap->ga_data))[fcp->linenr++]); - SOURCING_LNUM = fcp->linenr; + retval = xstrdup(((char **)(gap->ga_data))[fcp->fc_linenr++]); + SOURCING_LNUM = fcp->fc_linenr; if (do_profiling == PROF_YES) { func_line_start(cookie); } @@ -3259,11 +3527,11 @@ char *get_func_line(int c, void *cookie, int indent, bool do_concat) } // Did we encounter a breakpoint? - if (fcp->breakpoint != 0 && fcp->breakpoint <= SOURCING_LNUM) { - dbg_breakpoint((char *)fp->uf_name, SOURCING_LNUM); + if (fcp->fc_breakpoint != 0 && fcp->fc_breakpoint <= SOURCING_LNUM) { + dbg_breakpoint(fp->uf_name, SOURCING_LNUM); // Find next breakpoint. - fcp->breakpoint = dbg_find_breakpoint(false, (char *)fp->uf_name, SOURCING_LNUM); - fcp->dbg_tick = debug_tick; + fcp->fc_breakpoint = dbg_find_breakpoint(false, fp->uf_name, SOURCING_LNUM); + fcp->fc_dbg_tick = debug_tick; } return retval; @@ -3277,21 +3545,20 @@ int func_has_ended(void *cookie) // Ignore the "abort" flag if the abortion behavior has been changed due to // an error inside a try conditional. - return ((fcp->func->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try()) - || fcp->returned; + return ((fcp->fc_func->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try()) + || fcp->fc_returned; } /// @return true if cookie indicates a function which "abort"s on errors. int func_has_abort(void *cookie) { - return ((funccall_T *)cookie)->func->uf_flags & FC_ABORT; + return ((funccall_T *)cookie)->fc_func->uf_flags & FC_ABORT; } /// Turn "dict.Func" into a partial for "Func" bound to "dict". /// Changes "rettv" in-place. void make_partial(dict_T *const selfdict, typval_T *const rettv) { - char *fname; char *tofree = NULL; ufunc_T *fp; char fname_buf[FLEN_FIXED + 1]; @@ -3300,9 +3567,9 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv) if (rettv->v_type == VAR_PARTIAL && rettv->vval.v_partial->pt_func != NULL) { fp = rettv->vval.v_partial->pt_func; } else { - fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING - ? rettv->vval.v_string - : rettv->vval.v_partial->pt_name; + char *fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING + ? rettv->vval.v_string + : rettv->vval.v_partial->pt_name; // Translate "s:func" to the stored function name. fname = fname_trans_sid(fname, fname_buf, &tofree, &error); fp = find_func(fname); @@ -3321,7 +3588,6 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv) pt->pt_name = rettv->vval.v_string; } else { partial_T *ret_pt = rettv->vval.v_partial; - int i; // Partial: copy the function name, use selfdict and copy // args. Can't take over name or args, the partial might @@ -3337,7 +3603,7 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv) size_t arg_size = sizeof(typval_T) * (size_t)ret_pt->pt_argc; pt->pt_argv = (typval_T *)xmalloc(arg_size); pt->pt_argc = ret_pt->pt_argc; - for (i = 0; i < pt->pt_argc; i++) { + for (int i = 0; i < pt->pt_argc; i++) { tv_copy(&ret_pt->pt_argv[i], &pt->pt_argv[i]); } } @@ -3351,31 +3617,31 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv) /// @return the name of the executed function. char *func_name(void *cookie) { - return ((funccall_T *)cookie)->func->uf_name; + return ((funccall_T *)cookie)->fc_func->uf_name; } /// @return the address holding the next breakpoint line for a funccall cookie. linenr_T *func_breakpoint(void *cookie) { - return &((funccall_T *)cookie)->breakpoint; + return &((funccall_T *)cookie)->fc_breakpoint; } /// @return the address holding the debug tick for a funccall cookie. int *func_dbg_tick(void *cookie) { - return &((funccall_T *)cookie)->dbg_tick; + return &((funccall_T *)cookie)->fc_dbg_tick; } /// @return the nesting level for a funccall cookie. int func_level(void *cookie) { - return ((funccall_T *)cookie)->level; + return ((funccall_T *)cookie)->fc_level; } /// @return true when a function was ended by a ":return" command. int current_func_returned(void) { - return current_funccal->returned; + return current_funccal->fc_returned; } bool free_unref_funccal(int copyID, int testing) @@ -3386,12 +3652,12 @@ bool free_unref_funccal(int copyID, int testing) for (funccall_T **pfc = &previous_funccal; *pfc != NULL;) { if (can_free_funccal(*pfc, copyID)) { funccall_T *fc = *pfc; - *pfc = fc->caller; + *pfc = fc->fc_caller; free_funccal_contents(fc); did_free = true; did_free_funccal = true; } else { - pfc = &(*pfc)->caller; + pfc = &(*pfc)->fc_caller; } } if (did_free_funccal) { @@ -3408,7 +3674,7 @@ funccall_T *get_funccal(void) funccall_T *funccal = current_funccal; if (debug_backtrace_level > 0) { for (int i = 0; i < debug_backtrace_level; i++) { - funccall_T *temp_funccal = funccal->caller; + funccall_T *temp_funccal = funccal->fc_caller; if (temp_funccal) { funccal = temp_funccal; } else { @@ -3428,7 +3694,7 @@ hashtab_T *get_funccal_local_ht(void) if (current_funccal == NULL) { return NULL; } - return &get_funccal()->l_vars.dv_hashtab; + return &get_funccal()->fc_l_vars.dv_hashtab; } /// @return the l: scope variable or @@ -3438,7 +3704,7 @@ dictitem_T *get_funccal_local_var(void) if (current_funccal == NULL) { return NULL; } - return (dictitem_T *)&get_funccal()->l_vars_var; + return (dictitem_T *)&get_funccal()->fc_l_vars_var; } /// @return the hashtable used for argument in the current funccal or @@ -3448,7 +3714,7 @@ hashtab_T *get_funccal_args_ht(void) if (current_funccal == NULL) { return NULL; } - return &get_funccal()->l_avars.dv_hashtab; + return &get_funccal()->fc_l_avars.dv_hashtab; } /// @return the a: scope variable or @@ -3458,14 +3724,14 @@ dictitem_T *get_funccal_args_var(void) if (current_funccal == NULL) { return NULL; } - return (dictitem_T *)¤t_funccal->l_avars_var; + return (dictitem_T *)¤t_funccal->fc_l_avars_var; } /// List function variables, if there is a function. void list_func_vars(int *first) { if (current_funccal != NULL) { - list_hashtable_vars(¤t_funccal->l_vars.dv_hashtab, "l:", false, + list_hashtable_vars(¤t_funccal->fc_l_vars.dv_hashtab, "l:", false, first); } } @@ -3474,8 +3740,8 @@ void list_func_vars(int *first) /// funccal, return the dict that contains it. Otherwise return NULL. dict_T *get_current_funccal_dict(hashtab_T *ht) { - if (current_funccal != NULL && ht == ¤t_funccal->l_vars.dv_hashtab) { - return ¤t_funccal->l_vars; + if (current_funccal != NULL && ht == ¤t_funccal->fc_l_vars.dv_hashtab) { + return ¤t_funccal->fc_l_vars; } return NULL; } @@ -3483,7 +3749,7 @@ dict_T *get_current_funccal_dict(hashtab_T *ht) /// Search hashitem in parent scope. hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht) { - if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) { + if (current_funccal == NULL || current_funccal->fc_func->uf_scoped == NULL) { return NULL; } @@ -3493,7 +3759,7 @@ hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht) const char *varname; // Search in parent scope which is possible to reference from lambda - current_funccal = current_funccal->func->uf_scoped; + current_funccal = current_funccal->fc_func->uf_scoped; while (current_funccal != NULL) { hashtab_T *ht = find_var_ht(name, namelen, &varname); if (ht != NULL && *varname != NUL) { @@ -3503,10 +3769,10 @@ hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht) break; } } - if (current_funccal == current_funccal->func->uf_scoped) { + if (current_funccal == current_funccal->fc_func->uf_scoped) { break; } - current_funccal = current_funccal->func->uf_scoped; + current_funccal = current_funccal->fc_func->uf_scoped; } current_funccal = old_current_funccal; @@ -3516,7 +3782,7 @@ hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht) /// Search variable in parent scope. dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, int no_autoload) { - if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) { + if (current_funccal == NULL || current_funccal->fc_func->uf_scoped == NULL) { return NULL; } @@ -3525,7 +3791,7 @@ dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, int no const char *varname; // Search in parent scope which is possible to reference from lambda - current_funccal = current_funccal->func->uf_scoped; + current_funccal = current_funccal->fc_func->uf_scoped; while (current_funccal) { hashtab_T *ht = find_var_ht(name, namelen, &varname); if (ht != NULL && *varname != NUL) { @@ -3535,10 +3801,10 @@ dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, int no break; } } - if (current_funccal == current_funccal->func->uf_scoped) { + if (current_funccal == current_funccal->fc_func->uf_scoped) { break; } - current_funccal = current_funccal->func->uf_scoped; + current_funccal = current_funccal->fc_func->uf_scoped; } current_funccal = old_current_funccal; @@ -3549,11 +3815,11 @@ dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, int no bool set_ref_in_previous_funccal(int copyID) { for (funccall_T *fc = previous_funccal; fc != NULL; - fc = fc->caller) { + fc = fc->fc_caller) { fc->fc_copyID = copyID + 1; - if (set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, NULL) - || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, NULL) - || set_ref_in_list(&fc->l_varlist, copyID + 1, NULL)) { + if (set_ref_in_ht(&fc->fc_l_vars.dv_hashtab, copyID + 1, NULL) + || set_ref_in_ht(&fc->fc_l_avars.dv_hashtab, copyID + 1, NULL) + || set_ref_in_list(&fc->fc_l_varlist, copyID + 1, NULL)) { return true; } } @@ -3564,10 +3830,10 @@ static bool set_ref_in_funccal(funccall_T *fc, int copyID) { if (fc->fc_copyID != copyID) { fc->fc_copyID = copyID; - if (set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL) - || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL) - || set_ref_in_list(&fc->l_varlist, copyID, NULL) - || set_ref_in_func(NULL, fc->func, copyID)) { + if (set_ref_in_ht(&fc->fc_l_vars.dv_hashtab, copyID, NULL) + || set_ref_in_ht(&fc->fc_l_avars.dv_hashtab, copyID, NULL) + || set_ref_in_list(&fc->fc_l_varlist, copyID, NULL) + || set_ref_in_func(NULL, fc->fc_func, copyID)) { return true; } } @@ -3578,7 +3844,7 @@ static bool set_ref_in_funccal(funccall_T *fc, int copyID) bool set_ref_in_call_stack(int copyID) { for (funccall_T *fc = current_funccal; fc != NULL; - fc = fc->caller) { + fc = fc->fc_caller) { if (set_ref_in_funccal(fc, copyID)) { return true; } @@ -3588,7 +3854,7 @@ bool set_ref_in_call_stack(int copyID) for (funccal_entry_T *entry = funccal_stack; entry != NULL; entry = entry->next) { for (funccall_T *fc = entry->top_funccal; fc != NULL; - fc = fc->caller) { + fc = fc->fc_caller) { if (set_ref_in_funccal(fc, copyID)) { return true; } @@ -3601,15 +3867,11 @@ bool set_ref_in_call_stack(int copyID) /// Set "copyID" in all functions available by name. bool set_ref_in_functions(int copyID) { - int todo; - hashitem_T *hi = NULL; - ufunc_T *fp; - - todo = (int)func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) { + int todo = (int)func_hashtab.ht_used; + for (hashitem_T *hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) { if (!HASHITEM_EMPTY(hi)) { todo--; - fp = HI2UF(hi); + ufunc_T *fp = HI2UF(hi); if (!func_name_refcount(fp->uf_name) && set_ref_in_func(NULL, fp, copyID)) { return true; @@ -3639,22 +3901,20 @@ bool set_ref_in_func_args(int copyID) bool set_ref_in_func(char *name, ufunc_T *fp_in, int copyID) { ufunc_T *fp = fp_in; - funccall_T *fc; int error = FCERR_NONE; char fname_buf[FLEN_FIXED + 1]; char *tofree = NULL; - char *fname; bool abort = false; if (name == NULL && fp_in == NULL) { return false; } if (fp_in == NULL) { - fname = fname_trans_sid(name, fname_buf, &tofree, &error); + char *fname = fname_trans_sid(name, fname_buf, &tofree, &error); fp = find_func(fname); } if (fp != NULL) { - for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped) { + for (funccall_T *fc = fp->uf_scoped; fc != NULL; fc = fc->fc_func->uf_scoped) { abort = abort || set_ref_in_funccal(fc, copyID); } } |