diff options
Diffstat (limited to 'src/nvim/eval.c')
| -rw-r--r-- | src/nvim/eval.c | 3528 |
1 files changed, 2259 insertions, 1269 deletions
diff --git a/src/nvim/eval.c b/src/nvim/eval.c index bbb6565509..672c9158ce 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -30,6 +30,7 @@ #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" +#include "nvim/os/fileio.h" #include "nvim/func_attr.h" #include "nvim/fold.h" #include "nvim/getchar.h" @@ -170,6 +171,8 @@ static char *e_letwrong = N_("E734: Wrong variable type for %s="); static char *e_nofunc = N_("E130: Unknown function: %s"); static char *e_illvar = N_("E461: Illegal variable name: %s"); static char *e_float_as_string = N_("E806: using Float as a String"); +static const char *e_readonlyvar = N_( + "E46: Cannot change read-only variable \"%.*s\""); static char_u * const empty_string = (char_u *)""; static char_u * const namespace_char = (char_u *)"abglstvw"; @@ -200,22 +203,33 @@ static garray_T ga_scripts = {0, 0, sizeof(scriptvar_T *), 4, NULL}; static int echo_attr = 0; /* attributes used for ":echo" */ -/* Values for trans_function_name() argument: */ -#define TFN_INT 1 /* internal function name OK */ -#define TFN_QUIET 2 /* no error messages */ -#define TFN_NO_AUTOLOAD 4 /* do not use script autoloading */ - -/* Values for get_lval() flags argument: */ -#define GLV_QUIET TFN_QUIET /* no error messages */ -#define GLV_NO_AUTOLOAD TFN_NO_AUTOLOAD /* do not use script autoloading */ - -/* function flags */ -#define FC_ABORT 1 /* abort function on error */ -#define FC_RANGE 2 /* function accepts range */ -#define FC_DICT 4 /* Dict function, uses "self" */ - -/* The names of packages that once were loaded are remembered. */ -static garray_T ga_loaded = {0, 0, sizeof(char_u *), 4, NULL}; +/// trans_function_name() flags +typedef enum { + TFN_INT = 1, ///< May use internal function name + TFN_QUIET = 2, ///< Do not emit error messages. + TFN_NO_AUTOLOAD = 4, ///< Do not use script autoloading. + TFN_NO_DEREF = 8, ///< Do not dereference a Funcref. + TFN_READ_ONLY = 16, ///< Will not change the variable. +} TransFunctionNameFlags; + +/// get_lval() flags +typedef enum { + GLV_QUIET = TFN_QUIET, ///< Do not emit error messages. + GLV_NO_AUTOLOAD = TFN_NO_AUTOLOAD, ///< Do not use script autoloading. + GLV_READ_ONLY = TFN_READ_ONLY, ///< Indicates that caller will not change + ///< the value (prevents error message). +} GetLvalFlags; + +// function flags +#define FC_ABORT 0x01 // abort function on error +#define FC_RANGE 0x02 // function accepts range +#define FC_DICT 0x04 // Dict function, uses "self" +#define FC_CLOSURE 0x08 // closure, uses outer scope variables +#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0 +#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0 + +// The names of packages that once were loaded are remembered. +static garray_T ga_loaded = { 0, 0, sizeof(char_u *), 4, NULL }; // List heads for garbage collection. Although there can be a reference loop // from partial to dict to partial, we don't need to keep track of the partial, @@ -223,38 +237,11 @@ static garray_T ga_loaded = {0, 0, sizeof(char_u *), 4, NULL}; static dict_T *first_dict = NULL; // list of all dicts static list_T *first_list = NULL; // list of all lists +#define FLEN_FIXED 40 + #define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] #define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j] -#define VAR_SHORT_LEN 20 /* short variable name length */ -#define FIXVAR_CNT 12 /* number of fixed variables */ - -/* structure to hold info for a function that is currently being executed. */ -typedef struct funccall_S funccall_T; - -struct funccall_S { - ufunc_T *func; /* function being called */ - int linenr; /* next line to be executed */ - int returned; /* ":return" used */ - struct /* fixed variables for arguments */ - { - dictitem_T var; /* variable (without room for name) */ - char_u room[VAR_SHORT_LEN]; /* room for the name */ - } fixvar[FIXVAR_CNT]; - dict_T l_vars; /* l: local function variables */ - dictitem_T l_vars_var; /* variable for l: scope */ - dict_T l_avars; /* a: argument variables */ - dictitem_T l_avars_var; /* variable for a: scope */ - list_T l_varlist; /* list for a:000 */ - listitem_T l_listitems[MAX_FUNC_ARGS]; /* listitems for a:000 */ - typval_T *rettv; /* return value */ - linenr_T breakpoint; /* next line with breakpoint or zero */ - int dbg_tick; /* debug_tick when breakpoint was set */ - int level; /* top nesting level of executed function */ - proftime_T prof_child; /* time spent in a child */ - funccall_T *caller; /* calling function or NULL */ -}; - /* * Info used by a ":for" loop. */ @@ -266,15 +253,6 @@ typedef struct { } forinfo_T; /* - * Struct used by trans_function_name() - */ -typedef struct { - dict_T *fd_dict; /* Dictionary used */ - char_u *fd_newkey; /* new key in "dict" in allocated memory */ - dictitem_T *fd_di; /* Dictionary item used */ -} funcdict_T; - -/* * enum used by var_flavour() */ typedef enum { @@ -389,6 +367,7 @@ static struct vimvar { VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO), VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO), VV(VV_VIM_DID_ENTER, "vim_did_enter", VAR_NUMBER, VV_RO), + VV(VV_TESTING, "testing", VAR_NUMBER, 0), VV(VV_TYPE_NUMBER, "t_number", VAR_NUMBER, VV_RO), VV(VV_TYPE_STRING, "t_string", VAR_NUMBER, VV_RO), VV(VV_TYPE_FUNC, "t_func", VAR_NUMBER, VV_RO), @@ -571,8 +550,8 @@ void eval_init(void) list_T *const type_list = list_alloc(); type_list->lv_lock = VAR_FIXED; type_list->lv_refcount = 1; - dictitem_T *const di = dictitem_alloc((char_u *) msgpack_type_names[i]); - di->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + dictitem_T *const di = dictitem_alloc((char_u *)msgpack_type_names[i]); + di->di_flags |= DI_FLAGS_RO|DI_FLAGS_FIX; di->di_tv = (typval_T) { .v_type = VAR_LIST, .vval = { .v_list = type_list, }, @@ -648,12 +627,11 @@ void eval_clear(void) xfree(SCRIPT_SV(i)); ga_clear(&ga_scripts); - /* unreferenced lists and dicts */ - (void)garbage_collect(); + // unreferenced lists and dicts + (void)garbage_collect(false); - /* functions */ + // functions free_all_functions(); - hash_clear(&func_hashtab); } #endif @@ -1229,8 +1207,8 @@ int call_vim_function( ++sandbox; } - rettv->v_type = VAR_UNKNOWN; /* clear_tv() uses this */ - ret = call_func(func, (int)STRLEN(func), rettv, argc, argvars, + rettv->v_type = VAR_UNKNOWN; // clear_tv() uses this + ret = call_func(func, (int)STRLEN(func), rettv, argc, argvars, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &doesrange, true, NULL, NULL); if (safe) { @@ -1454,13 +1432,13 @@ void ex_let(exarg_T *eap) if (*expr != '=' && !(vim_strchr((char_u *)"+-.", *expr) != NULL && expr[1] == '=')) { // ":let" without "=": list variables - if (*arg == '[') + if (*arg == '[') { EMSG(_(e_invarg)); - else if (!ends_excmd(*arg)) - /* ":let var1 var2" */ - arg = list_arg_vars(eap, arg, &first); - else if (!eap->skip) { - /* ":let" */ + } else if (!ends_excmd(*arg)) { + // ":let var1 var2" + arg = (char_u *)list_arg_vars(eap, (const char *)arg, &first); + } else if (!eap->skip) { + // ":let" list_glob_vars(&first); list_buf_vars(&first); list_win_vars(&first); @@ -1644,7 +1622,8 @@ static char_u *skip_var_one(char_u *arg) * List variables for hashtab "ht" with prefix "prefix". * If "empty" is TRUE also list NULL strings as empty strings. */ -static void list_hashtable_vars(hashtab_T *ht, char_u *prefix, int empty, int *first) +static void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty, + int *first) { hashitem_T *hi; dictitem_T *di; @@ -1653,11 +1632,12 @@ static void list_hashtable_vars(hashtab_T *ht, char_u *prefix, int empty, int *f todo = (int)ht->ht_used; for (hi = ht->ht_array; todo > 0 && !got_int; ++hi) { if (!HASHITEM_EMPTY(hi)) { - --todo; + todo--; di = HI2DI(hi); if (empty || di->di_tv.v_type != VAR_STRING - || di->di_tv.vval.v_string != NULL) + || di->di_tv.vval.v_string != NULL) { list_one_var(di, prefix, first); + } } } } @@ -1667,7 +1647,7 @@ static void list_hashtable_vars(hashtab_T *ht, char_u *prefix, int empty, int *f */ static void list_glob_vars(int *first) { - list_hashtable_vars(&globvarht, (char_u *)"", TRUE, first); + list_hashtable_vars(&globvarht, "", true, first); } /* @@ -1675,14 +1655,7 @@ static void list_glob_vars(int *first) */ static void list_buf_vars(int *first) { - char_u numbuf[NUMBUFLEN]; - - list_hashtable_vars(&curbuf->b_vars->dv_hashtab, (char_u *)"b:", - TRUE, first); - - sprintf((char *)numbuf, "%" PRId64, (int64_t)curbuf->b_changedtick); - list_one_var_a((char_u *)"b:", (char_u *)"changedtick", VAR_NUMBER, - numbuf, first); + list_hashtable_vars(&curbuf->b_vars->dv_hashtab, "b:", true, first); } /* @@ -1690,8 +1663,7 @@ static void list_buf_vars(int *first) */ static void list_win_vars(int *first) { - list_hashtable_vars(&curwin->w_vars->dv_hashtab, - (char_u *)"w:", TRUE, first); + list_hashtable_vars(&curwin->w_vars->dv_hashtab, "w:", true, first); } /* @@ -1699,8 +1671,7 @@ static void list_win_vars(int *first) */ static void list_tab_vars(int *first) { - list_hashtable_vars(&curtab->tp_vars->dv_hashtab, - (char_u *)"t:", TRUE, first); + list_hashtable_vars(&curtab->tp_vars->dv_hashtab, "t:", true, first); } /* @@ -1708,7 +1679,7 @@ static void list_tab_vars(int *first) */ static void list_vim_vars(int *first) { - list_hashtable_vars(&vimvarht, (char_u *)"v:", FALSE, first); + list_hashtable_vars(&vimvarht, "v:", false, first); } /* @@ -1716,9 +1687,9 @@ static void list_vim_vars(int *first) */ static void list_script_vars(int *first) { - if (current_SID > 0 && current_SID <= ga_scripts.ga_len) - list_hashtable_vars(&SCRIPT_VARS(current_SID), - (char_u *)"s:", FALSE, first); + if (current_SID > 0 && current_SID <= ga_scripts.ga_len) { + list_hashtable_vars(&SCRIPT_VARS(current_SID), "s:", false, first); + } } /* @@ -1726,36 +1697,37 @@ static void list_script_vars(int *first) */ static void list_func_vars(int *first) { - if (current_funccal != NULL) - list_hashtable_vars(¤t_funccal->l_vars.dv_hashtab, - (char_u *)"l:", FALSE, first); + if (current_funccal != NULL) { + list_hashtable_vars(¤t_funccal->l_vars.dv_hashtab, "l:", false, + first); + } } /* * List variables in "arg". */ -static char_u *list_arg_vars(exarg_T *eap, char_u *arg, int *first) +static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first) { int error = FALSE; int len; - char_u *name; - char_u *name_start; - char_u *arg_subsc; - char_u *tofree; + const char *name; + const char *name_start; typval_T tv; while (!ends_excmd(*arg) && !got_int) { if (error || eap->skip) { - arg = find_name_end(arg, NULL, NULL, FNE_INCL_BR | FNE_CHECK_START); + arg = (const char *)find_name_end((char_u *)arg, NULL, NULL, + FNE_INCL_BR | FNE_CHECK_START); if (!ascii_iswhite(*arg) && !ends_excmd(*arg)) { emsg_severe = TRUE; EMSG(_(e_trailing)); break; } } else { - /* get_name_len() takes care of expanding curly braces */ + // get_name_len() takes care of expanding curly braces name_start = name = arg; - len = get_name_len(&arg, &tofree, TRUE, TRUE); + char *tofree; + len = get_name_len(&arg, &tofree, true, true); if (len <= 0) { /* This is mainly to keep test 49 working: when expanding * curly braces fails overrule the exception error message. */ @@ -1769,14 +1741,15 @@ static char_u *list_arg_vars(exarg_T *eap, char_u *arg, int *first) if (tofree != NULL) { name = tofree; } - if (get_var_tv(name, len, &tv, NULL, true, false) == FAIL) { + if (get_var_tv((const char *)name, len, &tv, NULL, true, false) + == FAIL) { error = true; } else { // handle d.key, l[idx], f(expr) - arg_subsc = arg; - if (handle_subscript(&arg, &tv, TRUE, TRUE) == FAIL) - error = TRUE; - else { + const char *const arg_subsc = arg; + if (handle_subscript(&arg, &tv, true, true) == FAIL) { + error = true; + } else { if (arg == arg_subsc && len == 2 && name[1] == ':') { switch (*name) { case 'g': list_glob_vars(first); break; @@ -1790,17 +1763,15 @@ static char_u *list_arg_vars(exarg_T *eap, char_u *arg, int *first) EMSG2(_("E738: Can't list variables for %s"), name); } } else { - int c; - - char_u *s = (char_u *) encode_tv2echo(&tv, NULL); - c = *arg; - *arg = NUL; - list_one_var_a((char_u *)"", - arg == arg_subsc ? name : name_start, - tv.v_type, - s == NULL ? (char_u *)"" : s, - first); - *arg = c; + char *const s = encode_tv2echo(&tv, NULL); + const char *const used_name = (arg == arg_subsc + ? name + : name_start); + const ptrdiff_t name_size = (used_name == tofree + ? (ptrdiff_t)strlen(used_name) + : (arg - used_name)); + list_one_var_a("", used_name, name_size, + tv.v_type, s == NULL ? "" : s, first); xfree(s); } clear_tv(&tv); @@ -1811,7 +1782,7 @@ static char_u *list_arg_vars(exarg_T *eap, char_u *arg, int *first) xfree(tofree); } - arg = skipwhite(arg); + arg = (const char *)skipwhite((const char_u *)arg); } return arg; @@ -1831,9 +1802,7 @@ ex_let_one ( char_u *op /* "+", "-", "." or NULL*/ ) { - int c1; char_u *name; - char_u *p; char_u *arg_end = NULL; int len; int opt_flags; @@ -1847,18 +1816,18 @@ ex_let_one ( ++arg; name = arg; len = get_env_len(&arg); - if (len == 0) + if (len == 0) { EMSG2(_(e_invarg2), name - 1); - else { - if (op != NULL && (*op == '+' || *op == '-')) + } else { + if (op != NULL && (*op == '+' || *op == '-')) { EMSG2(_(e_letwrong), op); - else if (endchars != NULL - && vim_strchr(endchars, *skipwhite(arg)) == NULL) + } else if (endchars != NULL + && vim_strchr(endchars, *skipwhite(arg)) == NULL) { EMSG(_(e_letunexp)); - else if (!check_secure()) { - c1 = name[len]; + } else if (!check_secure()) { + const char_u c1 = name[len]; name[len] = NUL; - p = get_tv_string_chk(tv); + char_u *p = get_tv_string_chk(tv); if (p != NULL && op != NULL && *op == '.') { char *s = vim_getenv((char *)name); @@ -1882,43 +1851,41 @@ ex_let_one ( xfree(tofree); } } - } - /* - * ":let &option = expr": Set option value. - * ":let &l:option = expr": Set local option value. - * ":let &g:option = expr": Set global option value. - */ - else if (*arg == '&') { - /* Find the end of the name. */ - p = find_option_end(&arg, &opt_flags); - if (p == NULL || (endchars != NULL - && vim_strchr(endchars, *skipwhite(p)) == NULL)) + // ":let &option = expr": Set option value. + // ":let &l:option = expr": Set local option value. + // ":let &g:option = expr": Set global option value. + } else if (*arg == '&') { + // Find the end of the name. + char *const p = (char *)find_option_end((const char **)&arg, &opt_flags); + if (p == NULL + || (endchars != NULL + && vim_strchr(endchars, *skipwhite((const char_u *)p)) == NULL)) { EMSG(_(e_letunexp)); - else { + } else { long n; int opt_type; long numval; - char_u *stringval = NULL; - char_u *s; + char_u *stringval = NULL; + char_u *s; - c1 = *p; + const char c1 = *p; *p = NUL; n = get_tv_number(tv); s = get_tv_string_chk(tv); /* != NULL if number or string */ if (s != NULL && op != NULL && *op != '=') { - opt_type = get_option_value(arg, &numval, - &stringval, opt_flags); + opt_type = get_option_value(arg, &numval, &stringval, opt_flags); if ((opt_type == 1 && *op == '.') - || (opt_type == 0 && *op != '.')) + || (opt_type == 0 && *op != '.')) { EMSG2(_(e_letwrong), op); - else { - if (opt_type == 1) { /* number */ - if (*op == '+') + } else { + if (opt_type == 1) { // number + if (*op == '+') { n = numval + n; - else + } else { n = numval - n; - } else if (opt_type == 0 && stringval != NULL) { /* string */ + } + } else if (opt_type == 0 && stringval != NULL) { // string s = concat_str(stringval, s); xfree(stringval); stringval = s; @@ -1927,7 +1894,7 @@ ex_let_one ( } if (s != NULL) { set_option_value(arg, n, s, opt_flags); - arg_end = p; + arg_end = (char_u *)p; } *p = c1; xfree(stringval); @@ -1947,7 +1914,7 @@ ex_let_one ( char_u *ptofree = NULL; char_u *s; - p = get_tv_string_chk(tv); + char_u *p = get_tv_string_chk(tv); if (p != NULL && op != NULL && *op == '.') { s = get_reg_contents(*arg == '@' ? '"' : *arg, kGRegExprSrc); if (s != NULL) { @@ -1969,7 +1936,7 @@ ex_let_one ( else if (eval_isnamec1(*arg) || *arg == '{') { lval_T lv; - p = get_lval(arg, tv, &lv, FALSE, FALSE, 0, FNE_CHECK_START); + char_u *const p = get_lval(arg, tv, &lv, false, false, 0, FNE_CHECK_START); if (p != NULL && lv.ll_name != NULL) { if (endchars != NULL && vim_strchr(endchars, *skipwhite(p)) == NULL) EMSG(_(e_letunexp)); @@ -1985,46 +1952,33 @@ ex_let_one ( return arg_end; } -/* - * If "arg" is equal to "b:changedtick" give an error and return TRUE. - */ -static int check_changedtick(char_u *arg) -{ - if (STRNCMP(arg, "b:changedtick", 13) == 0 && !eval_isnamec(arg[13])) { - EMSG2(_(e_readonlyvar), arg); - return TRUE; - } - return FALSE; -} - -/* - * Get an lval: variable, Dict item or List item that can be assigned a value - * to: "name", "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]", - * "name.key", "name.key[expr]" etc. - * Indexing only works if "name" is an existing List or Dictionary. - * "name" points to the start of the name. - * If "rettv" is not NULL it points to the value to be assigned. - * "unlet" is TRUE for ":unlet": slightly different behavior when something is - * wrong; must end in space or cmd separator. - * - * flags: - * GLV_QUIET: do not give error messages - * GLV_NO_AUTOLOAD: do not use script autoloading - * - * Returns a pointer to just after the name, including indexes. - * When an evaluation error occurs "lp->ll_name" is NULL; - * Returns NULL for a parsing error. Still need to free items in "lp"! - */ -static char_u * -get_lval ( - char_u *name, - typval_T *rettv, - lval_T *lp, - int unlet, - int skip, - int flags, /* GLV_ values */ - int fne_flags /* flags for find_name_end() */ -) +/// Get an lvalue +/// +/// Lvalue may be +/// - variable: "name", "na{me}" +/// - dictionary item: "dict.key", "dict['key']" +/// - list item: "list[expr]" +/// - list slice: "list[expr:expr]" +/// +/// Indexing only works if trying to use it with an existing List or Dictionary. +/// +/// @param[in] name Name to parse. +/// @param rettv Pointer to the value to be assigned or NULL. +/// @param[out] lp Lvalue definition. When evaluation errors occur `->ll_name` +/// is NULL. +/// @param[in] unlet True if using `:unlet`. This results in slightly +/// different behaviour when something is wrong; must end in +/// space or cmd separator. +/// @param[in] skip True when skipping. +/// @param[in] flags @see GetLvalFlags. +/// @param[in] fne_flags Flags for find_name_end(). +/// +/// @return A pointer to just after the name, including indexes. Returns NULL +/// for a parsing error, but it is still needed to free items in lp. +static char_u *get_lval(char_u *const name, typval_T *const rettv, + lval_T *const lp, const bool unlet, const bool skip, + const int flags, const int fne_flags) + FUNC_ATTR_NONNULL_ARG(1, 3) { char_u *p; char_u *expr_start, *expr_end; @@ -2079,9 +2033,11 @@ get_lval ( cc = *p; *p = NUL; - v = find_var(lp->ll_name, &ht, flags & GLV_NO_AUTOLOAD); - if (v == NULL && !quiet) + v = find_var((const char *)lp->ll_name, STRLEN(lp->ll_name), &ht, + flags & GLV_NO_AUTOLOAD); + if (v == NULL && !quiet) { EMSG2(_(e_undefvar), lp->ll_name); + } *p = cc; if (v == NULL) return NULL; @@ -2241,8 +2197,13 @@ get_lval ( if (len == -1) clear_tv(&var1); break; - } else if (var_check_ro(lp->ll_di->di_flags, name, false)) { - // existing variable, need to check if it can be changed + // existing variable, need to check if it can be changed + } else if (!(flags & GLV_READ_ONLY) && var_check_ro(lp->ll_di->di_flags, + (const char *)name, + (size_t)(p - name))) { + if (len == -1) { + clear_tv(&var1); + } return NULL; } @@ -2333,32 +2294,33 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int copy, ch dictitem_T *di; if (lp->ll_tv == NULL) { - if (!check_changedtick(lp->ll_name)) { - cc = *endp; - *endp = NUL; - if (op != NULL && *op != '=') { - typval_T tv; - - // handle +=, -= and .= - di = NULL; - if (get_var_tv(lp->ll_name, (int)STRLEN(lp->ll_name), - &tv, &di, true, false) == OK) { - if ((di == NULL - || (!var_check_ro(di->di_flags, lp->ll_name, false) - && !tv_check_lock(di->di_tv.v_lock, lp->ll_name, false))) - && tv_op(&tv, rettv, op) == OK) { - set_var(lp->ll_name, &tv, false); - } - clear_tv(&tv); + cc = *endp; + *endp = NUL; + if (op != NULL && *op != '=') { + typval_T tv; + + // handle +=, -= and .= + di = NULL; + if (get_var_tv((const char *)lp->ll_name, (int)STRLEN(lp->ll_name), + &tv, &di, true, false) == OK) { + if ((di == NULL + || (!var_check_ro(di->di_flags, (const char *)lp->ll_name, + STRLEN(lp->ll_name)) + && !tv_check_lock(di->di_tv.v_lock, (const char *)lp->ll_name, + STRLEN(lp->ll_name)))) + && tv_op(&tv, rettv, op) == OK) { + set_var(lp->ll_name, &tv, false); } - } else - set_var(lp->ll_name, rettv, copy); - *endp = cc; + clear_tv(&tv); + } + } else { + set_var(lp->ll_name, rettv, copy); } + *endp = cc; } else if (tv_check_lock(lp->ll_newkey == NULL ? lp->ll_tv->v_lock : lp->ll_tv->vval.v_dict->dv_lock, - lp->ll_name, false)) { + (const char *)lp->ll_name, STRLEN(lp->ll_name))) { } else if (lp->ll_range) { listitem_T *ll_li = lp->ll_li; int ll_n1 = lp->ll_n1; @@ -2366,7 +2328,8 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int copy, ch // Check whether any of the list items is locked for (listitem_T *ri = rettv->vval.v_list->lv_first; ri != NULL && ll_li != NULL; ) { - if (tv_check_lock(ll_li->li_tv.v_lock, lp->ll_name, false)) { + if (tv_check_lock(ll_li->li_tv.v_lock, (const char *)lp->ll_name, + STRLEN(lp->ll_name))) { return; } ri = ri->li_next; @@ -2817,7 +2780,7 @@ void ex_call(exarg_T *eap) // 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, + name = deref_func_name((const char *)tofree, &len, partial != NULL ? NULL : &partial, false); /* Skip white space to allow ":call func ()". Not good, but required for @@ -2855,9 +2818,10 @@ void ex_call(exarg_T *eap) break; } - /* Handle a function returning a Funcref, Dictionary or List. */ - if (handle_subscript(&arg, &rettv, !eap->skip, TRUE) == FAIL) { - failed = TRUE; + // Handle a function returning a Funcref, Dictionary or List. + if (handle_subscript((const char **)&arg, &rettv, !eap->skip, true) + == FAIL) { + failed = true; break; } @@ -2971,16 +2935,18 @@ static int do_unlet_var(lval_T *lp, char_u *name_end, int forceit) cc = *name_end; *name_end = NUL; - /* Normal name or expanded name. */ - if (check_changedtick(lp->ll_name)) - ret = FAIL; - else if (do_unlet(lp->ll_name, forceit) == FAIL) + // Normal name or expanded name. + if (do_unlet(lp->ll_name, forceit) == FAIL) { ret = FAIL; + } *name_end = cc; } else if ((lp->ll_list != NULL - && tv_check_lock(lp->ll_list->lv_lock, lp->ll_name, false)) + && tv_check_lock(lp->ll_list->lv_lock, (const char *)lp->ll_name, + STRLEN(lp->ll_name))) || (lp->ll_dict != NULL - && tv_check_lock(lp->ll_dict->dv_lock, lp->ll_name, false))) { + && tv_check_lock(lp->ll_dict->dv_lock, + (const char *)lp->ll_name, + STRLEN(lp->ll_name)))) { return FAIL; } else if (lp->ll_range) { listitem_T *li; @@ -2989,7 +2955,8 @@ static int do_unlet_var(lval_T *lp, char_u *name_end, int forceit) while (ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= ll_n1)) { li = ll_li->li_next; - if (tv_check_lock(ll_li->li_tv.v_lock, lp->ll_name, false)) { + if (tv_check_lock(ll_li->li_tv.v_lock, (const char *)lp->ll_name, + STRLEN(lp->ll_name))) { return false; } ll_li = li; @@ -3046,7 +3013,8 @@ int do_unlet(char_u *name, int forceit) dict_T *d; dictitem_T *di; dict_T *dict; - ht = find_var_ht_dict(name, &varname, &dict); + ht = find_var_ht_dict((const char *)name, STRLEN(name), + (const char **)&varname, &dict); if (ht != NULL && *varname != NUL) { if (ht == &globvarht) { @@ -3057,7 +3025,7 @@ int do_unlet(char_u *name, int forceit) } else if (ht == &compat_hashtab) { d = &vimvardict; } else { - di = find_var_in_ht(ht, *name, (char_u *)"", false); + di = find_var_in_ht(ht, *name, "", 0, false); d = di->di_tv.vval.v_dict; } if (d == NULL) { @@ -3065,15 +3033,19 @@ int do_unlet(char_u *name, int forceit) return FAIL; } hi = hash_find(ht, varname); - if (!HASHITEM_EMPTY(hi)) { + if (HASHITEM_EMPTY(hi)) { + hi = find_hi_in_scoped_ht((const char *)name, &ht); + } + if (hi != NULL && !HASHITEM_EMPTY(hi)) { di = HI2DI(hi); - if (var_check_fixed(di->di_flags, name, false) - || var_check_ro(di->di_flags, name, false) - || tv_check_lock(d->dv_lock, name, false)) { + if (var_check_fixed(di->di_flags, (const char *)name, STRLEN(name)) + || var_check_ro(di->di_flags, (const char *)name, STRLEN(name)) + || tv_check_lock(d->dv_lock, (const char *)name, STRLEN(name))) { return FAIL; } - if (d == NULL || tv_check_lock(d->dv_lock, name, false)) { + if (d == NULL + || tv_check_lock(d->dv_lock, (const char *)name, STRLEN(name))) { return FAIL; } @@ -3107,32 +3079,33 @@ int do_unlet(char_u *name, int forceit) static int do_lock_var(lval_T *lp, char_u *name_end, int deep, int lock) { int ret = OK; - int cc; - dictitem_T *di; - if (deep == 0) /* nothing to do */ + if (deep == 0) { // Nothing to do. return OK; + } if (lp->ll_tv == NULL) { - cc = *name_end; - *name_end = NUL; - - /* Normal name or expanded name. */ - if (check_changedtick(lp->ll_name)) + // Normal name or expanded name. + const size_t name_len = (size_t)(name_end - lp->ll_name); + dictitem_T *const di = find_var( + (const char *)lp->ll_name, name_len, NULL, + true); + if (di == NULL) { ret = FAIL; - else { - di = find_var(lp->ll_name, NULL, TRUE); - if (di == NULL) - ret = FAIL; - else { - if (lock) - di->di_flags |= DI_FLAGS_LOCK; - else - di->di_flags &= ~DI_FLAGS_LOCK; - item_lock(&di->di_tv, deep, lock); + } else if ((di->di_flags & DI_FLAGS_FIX) + && di->di_tv.v_type != VAR_DICT + && di->di_tv.v_type != VAR_LIST) { + // For historical reasons this error is not given for Lists and + // Dictionaries. E.g. b: dictionary may be locked/unlocked. + emsgf(_("E940: Cannot lock or unlock variable %s"), lp->ll_name); + } else { + if (lock) { + di->di_flags |= DI_FLAGS_LOCK; + } else { + di->di_flags &= ~DI_FLAGS_LOCK; } + item_lock(&di->di_tv, deep, lock); } - *name_end = cc; } else if (lp->ll_range) { listitem_T *li = lp->ll_li; @@ -3159,68 +3132,72 @@ static int do_lock_var(lval_T *lp, char_u *name_end, int deep, int lock) static void item_lock(typval_T *tv, int deep, int lock) { static int recurse = 0; - list_T *l; - listitem_T *li; - dict_T *d; - hashitem_T *hi; - int todo; if (recurse >= DICT_MAXNEST) { EMSG(_("E743: variable nested too deep for (un)lock")); return; } - if (deep == 0) + if (deep == 0) { return; - ++recurse; + } + recurse++; - /* lock/unlock the item itself */ - if (lock) - tv->v_lock |= VAR_LOCKED; - else - tv->v_lock &= ~VAR_LOCKED; + // lock/unlock the item itself +#define CHANGE_LOCK(var, lock) \ + do { \ + var = ((VarLockStatus[]) { \ + [VAR_UNLOCKED] = (lock ? VAR_LOCKED : VAR_UNLOCKED), \ + [VAR_LOCKED] = (lock ? VAR_LOCKED : VAR_UNLOCKED), \ + [VAR_FIXED] = VAR_FIXED, \ + })[var]; \ + } while (0) + CHANGE_LOCK(tv->v_lock, lock); switch (tv->v_type) { - case VAR_LIST: - if ((l = tv->vval.v_list) != NULL) { - if (lock) - l->lv_lock |= VAR_LOCKED; - else - l->lv_lock &= ~VAR_LOCKED; - if (deep < 0 || deep > 1) - /* recursive: lock/unlock the items the List contains */ - for (li = l->lv_first; li != NULL; li = li->li_next) - item_lock(&li->li_tv, deep - 1, lock); + case VAR_LIST: { + list_T *const l = tv->vval.v_list; + if (l != NULL) { + CHANGE_LOCK(l->lv_lock, lock); + if (deep < 0 || deep > 1) { + // Recursive: lock/unlock the items the List contains. + for (listitem_T *li = l->lv_first; li != NULL; li = li->li_next) { + item_lock(&li->li_tv, deep - 1, lock); + } + } + } + break; } - break; - case VAR_DICT: - if ((d = tv->vval.v_dict) != NULL) { - if (lock) - d->dv_lock |= VAR_LOCKED; - else - d->dv_lock &= ~VAR_LOCKED; - if (deep < 0 || deep > 1) { - /* recursive: lock/unlock the items the List contains */ - todo = (int)d->dv_hashtab.ht_used; - for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) { - if (!HASHITEM_EMPTY(hi)) { - --todo; - item_lock(&HI2DI(hi)->di_tv, deep - 1, lock); + case VAR_DICT: { + dict_T *const d = tv->vval.v_dict; + if (d != NULL) { + CHANGE_LOCK(d->dv_lock, lock); + if (deep < 0 || deep > 1) { + // Recursive: lock/unlock the items the List contains. + int todo = (int)d->dv_hashtab.ht_used; + for (hashitem_T *hi = d->dv_hashtab.ht_array; todo > 0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + todo--; + item_lock(&HI2DI(hi)->di_tv, deep - 1, lock); + } } } } + break; + } + case VAR_NUMBER: + case VAR_FLOAT: + case VAR_STRING: + case VAR_FUNC: + case VAR_PARTIAL: + case VAR_SPECIAL: { + break; + } + case VAR_UNKNOWN: { + assert(false); } - break; - case VAR_NUMBER: - case VAR_FLOAT: - case VAR_STRING: - case VAR_FUNC: - case VAR_PARTIAL: - case VAR_SPECIAL: - break; - case VAR_UNKNOWN: - assert(false); } - --recurse; +#undef CHANGE_LOCK + recurse--; } /* @@ -3330,10 +3307,6 @@ char_u *get_user_var_name(expand_T *xp, int idx) ++hi; return cat_prefix_varname('b', hi->hi_key); } - if (bdone == ht->ht_used) { - ++bdone; - return (char_u *)"b:changedtick"; - } /* w: variables */ ht = &curwin->w_vars->dv_hashtab; @@ -4300,14 +4273,19 @@ static int eval7( case '[': ret = get_list_tv(arg, rettv, evaluate); break; + // Lambda: {arg, arg -> expr} // Dictionary: {key: val, key: val} - case '{': ret = get_dict_tv(arg, rettv, evaluate); + case '{': ret = get_lambda_tv(arg, rettv, evaluate); + if (ret == NOTDONE) { + ret = get_dict_tv(arg, rettv, evaluate); + } break; // Option value: &name - case '&': ret = get_option_tv(arg, rettv, evaluate); + case '&': { + ret = get_option_tv((const char **)arg, rettv, evaluate); break; - + } // Environment variable: $VAR. case '$': ret = get_env_tv(arg, rettv, evaluate); break; @@ -4343,7 +4321,7 @@ static int eval7( // Must be a variable or function name. // Can also be a curly-braces kind of name: {expr}. s = *arg; - len = get_name_len(arg, &alias, evaluate, true); + len = get_name_len((const char **)arg, (char **)&alias, evaluate, true); if (alias != NULL) { s = alias; } @@ -4353,9 +4331,14 @@ static int eval7( } else { if (**arg == '(') { // recursive! partial_T *partial; + + if (!evaluate) { + check_vars((const char *)s, len); + } + // If "s" is the name of a variable of type VAR_FUNC // use its contents. - s = deref_func_name(s, &len, &partial, !evaluate); + s = deref_func_name((const char *)s, &len, &partial, !evaluate); // Invoke the function. ret = get_func_tv(s, len, rettv, arg, @@ -4380,8 +4363,9 @@ static int eval7( ret = FAIL; } } else if (evaluate) { - ret = get_var_tv(s, len, rettv, NULL, true, false); + ret = get_var_tv((const char *)s, len, rettv, NULL, true, false); } else { + check_vars((const char *)s, len); ret = OK; } } @@ -4393,7 +4377,7 @@ static int eval7( // Handle following '[', '(' and '.' for expr[expr], expr.name, // expr(expr). if (ret == OK) { - ret = handle_subscript(arg, rettv, evaluate, true); + ret = handle_subscript((const char **)arg, rettv, evaluate, true); } // Apply logical NOT and unary '-', from right to left, ignore '+'. @@ -4694,35 +4678,32 @@ eval_index ( return OK; } -/* - * Get an option value. - * "arg" points to the '&' or '+' before the option name. - * "arg" is advanced to character after the option name. - * Return OK or FAIL. - */ -static int -get_option_tv ( - char_u **arg, - typval_T *rettv, /* when NULL, only check if option exists */ - int evaluate -) +/// Get an option value +/// +/// @param[in,out] arg Points to the '&' or '+' before the option name. Is +/// advanced to the character after the option name. +/// @param[out] rettv Location where result is saved. +/// @param[in] evaluate If not true, rettv is not populated. +/// +/// @return OK or FAIL. +static int get_option_tv(const char **const arg, typval_T *const rettv, + const bool evaluate) + FUNC_ATTR_NONNULL_ARG(1) { - char_u *option_end; long numval; char_u *stringval; int opt_type; int c; - int working = (**arg == '+'); /* has("+option") */ + bool working = (**arg == '+'); // has("+option") int ret = OK; int opt_flags; - /* - * Isolate the option name and find its value. - */ - option_end = find_option_end(arg, &opt_flags); + // Isolate the option name and find its value. + char *option_end = (char *)find_option_end(arg, &opt_flags); if (option_end == NULL) { - if (rettv != NULL) + if (rettv != NULL) { EMSG2(_("E112: Option name missing: %s"), *arg); + } return FAIL; } @@ -4733,8 +4714,8 @@ get_option_tv ( c = *option_end; *option_end = NUL; - opt_type = get_option_value(*arg, &numval, - rettv == NULL ? NULL : &stringval, opt_flags); + opt_type = get_option_value((char_u *)(*arg), &numval, + rettv == NULL ? NULL : &stringval, opt_flags); if (opt_type == -3) { /* invalid name */ if (rettv != NULL) @@ -4938,6 +4919,15 @@ static int get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate) return OK; } +/// @return the function name of the partial. +char_u *partial_name(partial_T *pt) +{ + if (pt->pt_name != NULL) { + return pt->pt_name; + } + return pt->pt_func->uf_name; +} + static void partial_free(partial_T *pt) { for (int i = 0; i < pt->pt_argc; i++) { @@ -4945,8 +4935,12 @@ static void partial_free(partial_T *pt) } xfree(pt->pt_argv); dict_unref(pt->pt_dict); - func_unref(pt->pt_name); - xfree(pt->pt_name); + if (pt->pt_name != NULL) { + func_unref(pt->pt_name); + xfree(pt->pt_name); + } else { + func_ptr_unref(pt->pt_func); + } xfree(pt); } @@ -5210,12 +5204,12 @@ static bool func_equal( // empty and NULL function name considered the same s1 = tv1->v_type == VAR_FUNC ? tv1->vval.v_string - : tv1->vval.v_partial->pt_name; + : partial_name(tv1->vval.v_partial); if (s1 != NULL && *s1 == NUL) { s1 = NULL; } s2 = tv2->v_type == VAR_FUNC ? tv2->vval.v_string - : tv2->vval.v_partial->pt_name; + : partial_name(tv2->vval.v_partial); if (s2 != NULL && *s2 == NUL) { s2 = NULL; } @@ -5284,7 +5278,8 @@ tv_equal ( return TRUE; } - // For VAR_FUNC and VAR_PARTIAL only compare the function name. + // For VAR_FUNC and VAR_PARTIAL compare the function name, bound dict and + // arguments. if ((tv1->v_type == VAR_FUNC || (tv1->v_type == VAR_PARTIAL && tv1->vval.v_partial != NULL)) && (tv2->v_type == VAR_FUNC @@ -5813,6 +5808,9 @@ int get_copyID(void) return current_copyID; } +// Used by get_func_tv() +static garray_T funcargs = GA_EMPTY_INIT_VALUE; + /* * Garbage collection for lists and dictionaries. * @@ -5835,19 +5833,22 @@ int get_copyID(void) /// Do garbage collection for lists and dicts. /// +/// @param testing true if called from test_garbagecollect_now(). /// @returns true if some memory was freed. -bool garbage_collect(void) +bool garbage_collect(bool testing) { bool abort = false; #define ABORTING(func) abort = abort || func - // Only do this once. - want_garbage_collect = false; - may_garbage_collect = false; - garbage_collect_at_exit = false; + if (!testing) { + // Only do this once. + want_garbage_collect = false; + may_garbage_collect = false; + garbage_collect_at_exit = false; + } - // We advance by two because we add one for items referenced through - // previous_funccal. + // We advance by two (COPYID_INC) because we add one for items referenced + // through previous_funccal. const int copyID = get_copyID(); // 1. Go through all accessible variables and mark all lists and dicts @@ -5857,6 +5858,7 @@ bool garbage_collect(void) // referenced through previous_funccal. This must be first, because if // the item is referenced elsewhere the funccal must not be freed. for (funccall_T *fc = previous_funccal; fc != NULL; fc = fc->caller) { + fc->fc_copyID = copyID + 1; ABORTING(set_ref_in_ht)(&fc->l_vars.dv_hashtab, copyID + 1, NULL); ABORTING(set_ref_in_ht)(&fc->l_avars.dv_hashtab, copyID + 1, NULL); } @@ -5932,10 +5934,14 @@ bool garbage_collect(void) // function-local variables for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) { + fc->fc_copyID = copyID; ABORTING(set_ref_in_ht)(&fc->l_vars.dv_hashtab, copyID, NULL); ABORTING(set_ref_in_ht)(&fc->l_avars.dv_hashtab, copyID, NULL); } + // named functions (matters for closures) + ABORTING(set_ref_in_functions(copyID)); + // Jobs { TerminalJobData *data; @@ -5954,6 +5960,12 @@ bool garbage_collect(void) }) } + // function call arguments, if v:testing is set. + for (int i = 0; i < funcargs.ga_len; i++) { + ABORTING(set_ref_in_item)(((typval_T **)funcargs.ga_data)[i], + copyID, NULL, NULL); + } + // v: vars ABORTING(set_ref_in_ht)(&vimvarht, copyID, NULL); @@ -6008,7 +6020,7 @@ bool garbage_collect(void) if (did_free_funccal) { // When a funccal was freed some more items might be garbage // collected, so run again. - (void)garbage_collect(); + (void)garbage_collect(testing); } } else if (p_verbose > 0) { verb_msg((char_u *)_( @@ -6237,6 +6249,7 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, // A partial does not have a copyID, because it cannot contain itself. if (pt != NULL) { + abort = set_ref_in_func(pt->pt_name, pt->pt_func, copyID); if (pt->pt_dict != NULL) { typval_T dtv; @@ -6253,6 +6266,8 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, break; } case VAR_FUNC: + abort = set_ref_in_func(tv->vval.v_string, NULL, copyID); + break; case VAR_UNKNOWN: case VAR_SPECIAL: case VAR_FLOAT: @@ -6264,6 +6279,29 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, return abort; } +/// Set "copyID" in all functions available by name. +bool set_ref_in_functions(int copyID) +{ + int todo; + hashitem_T *hi = NULL; + bool abort = false; + ufunc_T *fp; + + todo = (int)func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) { + if (!HASHITEM_EMPTY(hi)) { + todo--; + fp = HI2UF(hi); + if (!func_name_refcount(fp->uf_name)) { + abort = abort || set_ref_in_func(NULL, fp, copyID); + } + } + } + return abort; +} + + + /// Mark all lists and dicts referenced in given mark /// /// @returns true if setting references failed somehow. @@ -6310,9 +6348,20 @@ static inline bool set_ref_dict(dict_T *dict, int copyID) return false; } -/* - * Allocate an empty header for a dictionary. - */ +static bool set_ref_in_funccal(funccall_T *fc, int copyID) +{ + bool abort = false; + + if (fc->fc_copyID != copyID) { + fc->fc_copyID = copyID; + abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL); + abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL); + abort = abort || set_ref_in_func(NULL, fc->func, copyID); + } + return abort; +} + +/// Allocate an empty header for a dictionary. dict_T *dict_alloc(void) FUNC_ATTR_NONNULL_RET { dict_T *d = xmalloc(sizeof(dict_T)); @@ -6325,7 +6374,7 @@ dict_T *dict_alloc(void) FUNC_ATTR_NONNULL_RET first_dict = d; hash_init(&d->dv_hashtab); - d->dv_lock = 0; + d->dv_lock = VAR_UNLOCKED; d->dv_scope = 0; d->dv_refcount = 0; d->dv_copyID = 0; @@ -6398,9 +6447,8 @@ static void dict_free_contents(dict_T *d) { * something recursive causing trouble. */ di = HI2DI(hi); hash_remove(&d->dv_hashtab, hi); - clear_tv(&di->di_tv); - xfree(di); - --todo; + dictitem_free(di); + todo--; } } @@ -6857,6 +6905,232 @@ failret: return OK; } +/// Get function arguments. +static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, + int *varargs, bool skip) +{ + bool mustend = false; + char_u *arg = *argp; + char_u *p = arg; + int c; + int i; + + if (newargs != NULL) { + ga_init(newargs, (int)sizeof(char_u *), 3); + } + + if (varargs != NULL) { + *varargs = false; + } + + // Isolate the arguments: "arg1, arg2, ...)" + while (*p != endchar) { + if (p[0] == '.' && p[1] == '.' && p[2] == '.') { + if (varargs != NULL) { + *varargs = true; + } + p += 3; + mustend = true; + } else { + arg = p; + while (ASCII_ISALNUM(*p) || *p == '_') { + p++; + } + if (arg == p || isdigit(*arg) + || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0) + || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) { + if (!skip) { + EMSG2(_("E125: Illegal argument: %s"), arg); + } + break; + } + if (newargs != NULL) { + ga_grow(newargs, 1); + c = *p; + *p = NUL; + arg = vim_strsave(arg); + if (arg == NULL) { + *p = c; + goto err_ret; + } + + // Check for duplicate argument name. + for (i = 0; i < newargs->ga_len; i++) { + if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0) { + EMSG2(_("E853: Duplicate argument name: %s"), arg); + xfree(arg); + goto err_ret; + } + } + ((char_u **)(newargs->ga_data))[newargs->ga_len] = arg; + newargs->ga_len++; + + *p = c; + } + if (*p == ',') { + p++; + } else { + mustend = true; + } + } + p = skipwhite(p); + if (mustend && *p != endchar) { + if (!skip) { + EMSG2(_(e_invarg2), *argp); + } + break; + } + } + if (*p != endchar) { + goto err_ret; + } + p++; // skip "endchar" + + *argp = p; + return OK; + +err_ret: + if (newargs != NULL) { + ga_clear_strings(newargs); + } + return FAIL; +} + +/// Register function "fp" as using "current_funccal" as its scope. +static void register_closure(ufunc_T *fp) +{ + if (fp->uf_scoped == current_funccal) { + // no change + return; + } + 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; +} + +/// Parse a lambda expression and get a Funcref from "*arg". +/// +/// @return OK or FAIL. Returns NOTDONE for dict or {expr}. +static int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) +{ + garray_T newargs = GA_EMPTY_INIT_VALUE; + garray_T *pnewargs; + ufunc_T *fp = NULL; + int varargs; + int ret; + char_u *start = skipwhite(*arg + 1); + char_u *s, *e; + static int lambda_no = 0; + int *old_eval_lavars = eval_lavars_used; + int eval_lavars = false; + + // First, check if this is a lambda expression. "->" must exists. + ret = get_function_args(&start, '-', NULL, NULL, true); + if (ret == FAIL || *start != '>') { + return NOTDONE; + } + + // Parse the arguments again. + if (evaluate) { + pnewargs = &newargs; + } else { + pnewargs = NULL; + } + *arg = skipwhite(*arg + 1); + ret = get_function_args(arg, '-', pnewargs, &varargs, false); + if (ret == FAIL || **arg != '>') { + goto errret; + } + + // Set up a flag for checking local variables and arguments. + if (evaluate) { + eval_lavars_used = &eval_lavars; + } + + // Get the start and the end of the expression. + *arg = skipwhite(*arg + 1); + s = *arg; + ret = skip_expr(arg); + if (ret == FAIL) { + goto errret; + } + e = *arg; + *arg = skipwhite(*arg); + if (**arg != '}') { + goto errret; + } + (*arg)++; + + if (evaluate) { + int len, flags = 0; + char_u *p; + char_u name[20]; + partial_T *pt; + garray_T newlines; + + lambda_no++; + snprintf((char *)name, sizeof(name), "<lambda>%d", lambda_no); + + fp = (ufunc_T *)xcalloc(1, sizeof(ufunc_T) + STRLEN(name)); + pt = (partial_T *)xcalloc(1, sizeof(partial_T)); + if (pt == NULL) { + xfree(fp); + goto errret; + } + + ga_init(&newlines, (int)sizeof(char_u *), 1); + ga_grow(&newlines, 1); + + // Add "return " before the expression. + len = 7 + e - s + 1; + p = (char_u *)xmalloc(len); + ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p; + STRCPY(p, "return "); + STRLCPY(p + 7, s, e - s + 1); + + fp->uf_refcount = 1; + STRCPY(fp->uf_name, name); + hash_add(&func_hashtab, UF2HIKEY(fp)); + fp->uf_args = newargs; + fp->uf_lines = newlines; + if (current_funccal != NULL && eval_lavars) { + flags |= FC_CLOSURE; + register_closure(fp); + } else { + fp->uf_scoped = NULL; + } + + fp->uf_tml_count = NULL; + fp->uf_tml_total = NULL; + fp->uf_tml_self = NULL; + fp->uf_profiling = false; + if (prof_def_func()) { + func_do_profile(fp); + } + fp->uf_varargs = true; + fp->uf_flags = flags; + fp->uf_calls = 0; + fp->uf_script_ID = current_SID; + + pt->pt_func = fp; + pt->pt_refcount = 1; + rettv->vval.v_partial = pt; + rettv->v_type = VAR_PARTIAL; + } + + eval_lavars_used = old_eval_lavars; + return OK; + +errret: + ga_clear_strings(&newargs); + xfree(fp); + eval_lavars_used = old_eval_lavars; + return FAIL; +} + /// Convert the string to a floating point number /// /// This uses strtod(). setlocale(LC_NUMERIC, "C") has been used earlier to @@ -6979,9 +7253,6 @@ char_u *get_expr_name(expand_T *xp, int idx) return get_user_var_name(xp, ++intidx); } - - - /// Find internal function in hash functions /// /// @param[in] name Name of the function. @@ -6994,49 +7265,55 @@ static VimLFuncDef *find_internal_func(const char *const name) return find_internal_func_gperf(name, len); } -/// Check if "name" is a variable of type VAR_FUNC. If so, return the function -/// name it contains, otherwise return "name". -/// If "partialp" is not NULL, and "name" is of type VAR_PARTIAL also set -/// "partialp". -static char_u *deref_func_name( - char_u *name, int *lenp, - partial_T **partialp, bool no_autoload -) +/// Return name of the function corresponding to `name` +/// +/// If `name` points to variable that is either a function or partial then +/// corresponding function name is returned. Otherwise it returns `name` itself. +/// +/// @param[in] name Function name to check. +/// @param[in,out] lenp Location where length of the returned name is stored. +/// Must be set to the length of the `name` argument. +/// @param[out] partialp Location where partial will be stored if found +/// function appears to be a partial. May be NULL if this +/// is not needed. +/// @param[in] no_autoload If true, do not source autoload scripts if function +/// was not found. +/// +/// @return name of the function. +static char_u *deref_func_name(const char *name, int *lenp, + partial_T **const partialp, bool no_autoload) + FUNC_ATTR_NONNULL_ARG(1, 2) { - dictitem_T *v; - int cc; if (partialp != NULL) { *partialp = NULL; } - cc = name[*lenp]; - name[*lenp] = NUL; - v = find_var(name, NULL, no_autoload); - name[*lenp] = cc; + 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) { + if (v->di_tv.vval.v_string == NULL) { // just in case *lenp = 0; - return (char_u *)""; /* just in case */ + return (char_u *)""; } *lenp = (int)STRLEN(v->di_tv.vval.v_string); return v->di_tv.vval.v_string; } if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) { - partial_T *pt = v->di_tv.vval.v_partial; + partial_T *const pt = v->di_tv.vval.v_partial; - if (pt == NULL) { + if (pt == NULL) { // just in case *lenp = 0; - return (char_u *)""; // just in case + return (char_u *)""; } if (partialp != NULL) { *partialp = pt; } - *lenp = (int)STRLEN(pt->pt_name); - return pt->pt_name; + char_u *s = partial_name(pt); + *lenp = (int)STRLEN(s); + return s; } - return name; + return (char_u *)name; } /* @@ -7085,9 +7362,24 @@ get_func_tv ( ret = FAIL; if (ret == OK) { - ret = call_func(name, len, rettv, argcount, argvars, + int i = 0; + + if (get_vim_var_nr(VV_TESTING)) { + // Prepare for calling garbagecollect_for_testing(), need to know + // what variables are used on the call stack. + if (funcargs.ga_itemsize == 0) { + ga_init(&funcargs, (int)sizeof(typval_T *), 50); + } + for (i = 0; i < argcount; i++) { + ga_grow(&funcargs, 1); + ((typval_T **)funcargs.ga_data)[funcargs.ga_len++] = &argvars[i]; + } + } + ret = call_func(name, len, rettv, argcount, argvars, NULL, firstline, lastline, doesrange, evaluate, partial, selfdict); + + funcargs.ga_len -= i; } else if (!aborting()) { if (argcount == MAX_FUNC_ARGS) { emsg_funcname(N_("E740: Too many arguments for function %s"), name); @@ -7103,33 +7395,49 @@ get_func_tv ( return ret; } -#define ERROR_UNKNOWN 0 -#define ERROR_TOOMANY 1 -#define ERROR_TOOFEW 2 -#define ERROR_SCRIPT 3 -#define ERROR_DICT 4 -#define ERROR_NONE 5 -#define ERROR_OTHER 6 -#define ERROR_BOTH 7 +typedef enum { + ERROR_UNKNOWN = 0, + ERROR_TOOMANY, + ERROR_TOOFEW, + ERROR_SCRIPT, + ERROR_DICT, + ERROR_NONE, + ERROR_OTHER, + ERROR_BOTH, + ERROR_DELETED, +} FnameTransError; + #define FLEN_FIXED 40 -/// In a script change <SID>name() and s:name() to K_SNR 123_name(). -/// Change <SNR>123_name() to K_SNR 123_name(). -/// Use "fname_buf[FLEN_FIXED + 1]" when it fits, otherwise allocate memory -/// (slow). -static char_u * -fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error) { - int llen; +/// In a script transform script-local names into actually used names +/// +/// Transforms "<SID>" and "s:" prefixes to `K_SNR {N}` (e.g. K_SNR "123") and +/// "<SNR>" prefix to `K_SNR`. Uses `fname_buf` buffer that is supposed to have +/// #FLEN_FIXED + 1 length when it fits, otherwise it allocates memory. +/// +/// @param[in] name Name to transform. +/// @param fname_buf Buffer to save resulting function name to, if it fits. +/// Must have at least #FLEN_FIXED + 1 length. +/// @param[out] tofree Location where pointer to an allocated memory is saved +/// in case result does not fit into fname_buf. +/// @param[out] error Location where error type is saved, @see +/// FnameTransError. +/// +/// @return transformed name: either `fname_buf` or a pointer to an allocated +/// memory. +static char_u *fname_trans_sid(const char_u *const name, + char_u *const fname_buf, + char_u **const tofree, int *const error) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ char_u *fname; - int i; - - llen = eval_fname_script(name); + const int llen = eval_fname_script((const char *)name); if (llen > 0) { fname_buf[0] = K_SPECIAL; fname_buf[1] = KS_EXTRA; fname_buf[2] = (int)KE_SNR; - i = 3; - if (eval_fname_sid(name)) { // "<SID>" or "s:" + int i = 3; + if (eval_fname_sid((const char *)name)) { // "<SID>" or "s:" if (current_SID <= 0) { *error = ERROR_SCRIPT; } else { @@ -7152,13 +7460,49 @@ fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error) { } } } else { - fname = name; + fname = (char_u *)name; } return fname; } +/// Mark all lists and dicts referenced through function "name" with "copyID". +/// "list_stack" is used to add lists to be marked. Can be NULL. +/// "ht_stack" is used to add hashtabs to be marked. Can be NULL. +/// +/// @return true if setting references failed somehow. +bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID) +{ + ufunc_T *fp = fp_in; + funccall_T *fc; + int error = ERROR_NONE; + char_u fname_buf[FLEN_FIXED + 1]; + char_u *tofree = NULL; + char_u *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); + fp = find_func(fname); + } + if (fp != NULL) { + for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped) { + abort = abort || set_ref_in_funccal(fc, copyID); + } + } + xfree(tofree); + return abort; +} + /// Call a function with its resolved parameters +/// +/// "argv_func", when not NULL, can be used to fill in arguments only when the +/// invoked function uses them. It is called like this: +/// new_argcount = argv_func(current_argcount, argv, called_func_argcount) +/// /// Return FAIL when the function can't be called, OK otherwise. /// Also returns OK when an error was encountered while executing the function. int @@ -7169,6 +7513,7 @@ call_func( int argcount_in, // number of "argvars" typval_T *argvars_in, // vars for arguments, must have "argcount" // PLUS ONE elements! + ArgvFunc argv_func, // function to fill in argvars linenr_T firstline, // first line of range linenr_T lastline, // last line of range int *doesrange, // return: function handled range @@ -7235,44 +7580,53 @@ call_func( rettv->vval.v_number = 0; error = ERROR_UNKNOWN; - if (!builtin_function(rfname, -1)) { - /* - * User defined function. - */ - fp = find_func(rfname); + if (!builtin_function((const char *)rfname, -1)) { + // User defined function. + if (partial != NULL && partial->pt_func != NULL) { + fp = partial->pt_func; + } else { + fp = find_func(rfname); + } - /* Trigger FuncUndefined event, may load the function. */ + // Trigger FuncUndefined event, may load the function. if (fp == NULL && apply_autocmds(EVENT_FUNCUNDEFINED, rfname, rfname, TRUE, NULL) && !aborting()) { /* executed an autocommand, search for the function again */ fp = find_func(rfname); } - /* Try loading a package. */ - if (fp == NULL && script_autoload(rfname, TRUE) && !aborting()) { - /* loaded a package, search for the function again */ + // Try loading a package. + if (fp == NULL && script_autoload((const char *)rfname, STRLEN(rfname), + true) && !aborting()) { + // Loaded a package, search for the function again. fp = find_func(rfname); } - if (fp != NULL) { - if (fp->uf_flags & FC_RANGE) - *doesrange = TRUE; - if (argcount < fp->uf_args.ga_len) + if (fp != NULL && (fp->uf_flags & FC_DELETED)) { + error = ERROR_DELETED; + } else if (fp != NULL) { + if (argv_func != NULL) { + argcount = argv_func(argcount, argvars, fp->uf_args.ga_len); + } + if (fp->uf_flags & FC_RANGE) { + *doesrange = true; + } + if (argcount < fp->uf_args.ga_len) { error = ERROR_TOOFEW; - else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) + } else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) { error = ERROR_TOOMANY; - else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) + } else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) { error = ERROR_DICT; - else { + } else { // Call the user function. call_user_func(fp, argcount, argvars, rettv, firstline, lastline, - (fp->uf_flags & FC_DICT) ? selfdict : NULL); + (fp->uf_flags & FC_DICT) ? selfdict : NULL); error = ERROR_NONE; } } } else { // Find the function name in the table, call its implementation. - VimLFuncDef *const fdef = find_internal_func((char *)fname); + VimLFuncDef *const fdef = find_internal_func((const char *)fname); if (fdef != NULL) { if (argcount < fdef->min_argc) { error = ERROR_TOOFEW; @@ -7309,6 +7663,9 @@ call_func( case ERROR_UNKNOWN: emsg_funcname(N_("E117: Unknown function: %s"), name); break; + case ERROR_DELETED: + emsg_funcname(N_("E933: Function was deleted: %s"), name); + break; case ERROR_TOOMANY: emsg_funcname(e_toomanyarg, name); break; @@ -7463,9 +7820,10 @@ static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = 1; /* Default: Failed */ if (argvars[0].v_type == VAR_LIST) { + const char *const arg_errmsg = _("add() argument"); + const size_t arg_errmsg_len = strlen(arg_errmsg); if ((l = argvars[0].vval.v_list) != NULL - && !tv_check_lock(l->lv_lock, - (char_u *)N_("add() argument"), true)) { + && !tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) { list_append_tv(l, &argvars[1]); copy_tv(&argvars[0], rettv); } @@ -7952,17 +8310,17 @@ static buf_T *get_buf_tv(typval_T *tv, int curtab_only) */ static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - buf_T *buf; - - (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ - ++emsg_off; - buf = get_buf_tv(&argvars[0], FALSE); rettv->v_type = VAR_STRING; - if (buf != NULL && buf->b_fname != NULL) - rettv->vval.v_string = vim_strsave(buf->b_fname); - else - rettv->vval.v_string = NULL; - --emsg_off; + rettv->vval.v_string = NULL; + if (!tv_check_str_or_nr(&argvars[0])) { + return; + } + emsg_off++; + const buf_T *const buf = get_buf_tv(&argvars[0], false); + emsg_off--; + if (buf != NULL && buf->b_fname != NULL) { + rettv->vval.v_string = (char_u *)xstrdup((char *)buf->b_fname); + } } /* @@ -7970,36 +8328,36 @@ static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - buf_T *buf; - int error = FALSE; - char_u *name; + int error = false; + char_u *name; - (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ - ++emsg_off; - buf = get_buf_tv(&argvars[0], FALSE); - --emsg_off; + rettv->vval.v_number = -1; + if (!tv_check_str_or_nr(&argvars[0])) { + return; + } + emsg_off++; + const buf_T *buf = get_buf_tv(&argvars[0], false); + emsg_off--; - /* If the buffer isn't found and the second argument is not zero create a - * new buffer. */ + // If the buffer isn't found and the second argument is not zero create a + // new buffer. if (buf == NULL && argvars[1].v_type != VAR_UNKNOWN && get_tv_number_chk(&argvars[1], &error) != 0 && !error && (name = get_tv_string_chk(&argvars[0])) != NULL - && !error) + && !error) { buf = buflist_new(name, NULL, (linenr_T)1, 0); + } - if (buf != NULL) + if (buf != NULL) { rettv->vval.v_number = buf->b_fnum; - else - rettv->vval.v_number = -1; + } } static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr) { - int error = false; - (void)get_tv_number_chk(&argvars[0], &error); // issue errmsg if type error - if (error) { // the argument has an invalid type + if (!tv_check_str_or_nr(&argvars[0])) { rettv->vval.v_number = -1; return; } @@ -8043,14 +8401,13 @@ static void f_bufwinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_byte2line(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - long boff = 0; - - boff = get_tv_number(&argvars[0]) - 1; /* boff gets -1 on type error */ - if (boff < 0) + long boff = get_tv_number(&argvars[0]) - 1; + if (boff < 0) { rettv->vval.v_number = -1; - else - rettv->vval.v_number = ml_find_line_or_offset(curbuf, - (linenr_T)0, &boff); + } else { + rettv->vval.v_number = (varnumber_T)ml_find_line_or_offset(curbuf, 0, + &boff); + } } static void byteidx(typval_T *argvars, typval_T *rettv, int comp) @@ -8115,7 +8472,7 @@ int func_call(char_u *name, typval_T *args, partial_T *partial, } if (item == NULL) { - r = call_func(name, (int)STRLEN(name), rettv, argc, argv, + r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, true, partial, selfdict); } @@ -8145,7 +8502,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) func = argvars[0].vval.v_string; } else if (argvars[0].v_type == VAR_PARTIAL) { partial = argvars[0].vval.v_partial; - func = partial->pt_name; + func = partial_name(partial); } else { func = get_tv_string(&argvars[0]); } @@ -8578,11 +8935,6 @@ static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - if (argvars[2].v_type != VAR_FUNC && argvars[2].v_type != VAR_STRING) { - EMSG2(e_invarg2, "funcref"); - return; - } - char *key_pattern = (char *)get_tv_string_chk(argvars + 1); assert(key_pattern); const size_t key_len = STRLEN(argvars[1].vval.v_string); @@ -8594,6 +8946,7 @@ static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) Callback callback; if (!callback_from_typval(&callback, &argvars[2])) { + EMSG2(e_invarg2, "funcref"); return; } @@ -8914,53 +9267,55 @@ static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - char_u *p; - char_u *name; - int n = FALSE; + int n = false; int len = 0; - p = get_tv_string(&argvars[0]); - if (*p == '$') { /* environment variable */ - /* first try "normal" environment variables (fast) */ - if (os_getenv((char *)(p + 1)) != NULL) - n = TRUE; - else { - /* try expanding things like $VIM and ${HOME} */ - p = expand_env_save(p); - if (p != NULL && *p != '$') - n = TRUE; + char *p = (char *)get_tv_string(&argvars[0]); + if (*p == '$') { // Environment variable. + // First try "normal" environment variables (fast). + if (os_getenv(p + 1) != NULL) { + n = true; + } else { + // Try expanding things like $VIM and ${HOME}. + p = (char *)expand_env_save((char_u *)p); + if (p != NULL && *p != '$') { + n = true; + } xfree(p); } - } else if (*p == '&' || *p == '+') { /* option */ - n = (get_option_tv(&p, NULL, TRUE) == OK); - if (*skipwhite(p) != NUL) - n = FALSE; /* trailing garbage */ - } else if (*p == '*') { /* internal or user defined function */ - n = function_exists(p + 1); + } else if (*p == '&' || *p == '+') { // Option. + n = (get_option_tv((const char **)&p, NULL, true) == OK); + if (*skipwhite((const char_u *)p) != NUL) { + n = false; // Trailing garbage. + } + } else if (*p == '*') { // Internal or user defined function. + n = function_exists(p + 1, false); } else if (*p == ':') { n = cmd_exists(p + 1); } else if (*p == '#') { - if (p[1] == '#') + if (p[1] == '#') { n = autocmd_supported(p + 2); - else + } else { n = au_exists(p + 1); - } else { /* internal variable */ - char_u *tofree; + } + } else { // Internal variable. typval_T tv; - /* get_name_len() takes care of expanding curly braces */ - name = p; - len = get_name_len(&p, &tofree, TRUE, FALSE); + // get_name_len() takes care of expanding curly braces + const char *name = p; + char *tofree; + len = get_name_len((const char **)&p, &tofree, true, false); if (len > 0) { if (tofree != NULL) { name = tofree; } n = (get_var_tv(name, len, &tv, NULL, false, true) == OK); if (n) { - /* handle d.key, l[idx], f(expr) */ - n = (handle_subscript(&p, &tv, TRUE, FALSE) == OK); - if (n) + // Handle d.key, l[idx], f(expr). + n = (handle_subscript((const char **)&p, &tv, true, false) == OK); + if (n) { clear_tv(&tv); + } } } if (*p != NUL) @@ -9045,7 +9400,8 @@ void dict_extend(dict_T *d1, dict_T *d2, char_u *action) hashitem_T *hi2; int todo; bool watched = is_watched(d1); - char_u *arg_errmsg = (char_u *)N_("extend() argument"); + const char *const arg_errmsg = _("extend() argument"); + const size_t arg_errmsg_len = strlen(arg_errmsg); todo = (int)d2->dv_hashtab.ht_used; for (hi2 = d2->dv_hashtab.ht_array; todo > 0; ++hi2) { @@ -9079,8 +9435,8 @@ void dict_extend(dict_T *d1, dict_T *d2, char_u *action) } else if (*action == 'f' && HI2DI(hi2) != di1) { typval_T oldtv; - if (tv_check_lock(di1->di_tv.v_lock, arg_errmsg, true) - || var_check_ro(di1->di_flags, arg_errmsg, true)) { + if (tv_check_lock(di1->di_tv.v_lock, arg_errmsg, arg_errmsg_len) + || var_check_ro(di1->di_flags, arg_errmsg, arg_errmsg_len)) { break; } @@ -9106,7 +9462,8 @@ void dict_extend(dict_T *d1, dict_T *d2, char_u *action) */ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - char_u *arg_errmsg = (char_u *)N_("extend() argument"); + const char *const arg_errmsg = N_("extend() argument"); + const size_t arg_errmsg_len = strlen(arg_errmsg); if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) { list_T *l1, *l2; @@ -9116,7 +9473,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) l1 = argvars[0].vval.v_list; l2 = argvars[1].vval.v_list; - if (l1 != NULL && !tv_check_lock(l1->lv_lock, arg_errmsg, true) + if (l1 != NULL && !tv_check_lock(l1->lv_lock, arg_errmsg, arg_errmsg_len) && l2 != NULL) { if (argvars[2].v_type != VAR_UNKNOWN) { before = get_tv_number_chk(&argvars[2], &error); @@ -9146,7 +9503,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) d1 = argvars[0].vval.v_dict; d2 = argvars[1].vval.v_dict; - if (d1 != NULL && !tv_check_lock(d1->dv_lock, arg_errmsg, true) + if (d1 != NULL && !tv_check_lock(d1->dv_lock, arg_errmsg, arg_errmsg_len) && d2 != NULL) { /* Check the third argument. */ if (argvars[2].v_type != VAR_UNKNOWN) { @@ -9278,8 +9635,7 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) */ static void filter_map(typval_T *argvars, typval_T *rettv, int map) { - char_u buf[NUMBUFLEN]; - char_u *expr; + typval_T *expr; listitem_T *li, *nli; list_T *l = NULL; dictitem_T *di; @@ -9290,20 +9646,22 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) typval_T save_key; int rem = false; int todo; - char_u *ermsg = (char_u *)(map ? "map()" : "filter()"); - char_u *arg_errmsg = (char_u *)(map ? N_("map() argument") - : N_("filter() argument")); + char_u *ermsg = (char_u *)(map ? "map()" : "filter()"); + const char *const arg_errmsg = (map + ? _("map() argument") + : _("filter() argument")); + const size_t arg_errmsg_len = strlen(arg_errmsg); int save_did_emsg; int idx = 0; if (argvars[0].v_type == VAR_LIST) { if ((l = argvars[0].vval.v_list) == NULL - || (!map && tv_check_lock(l->lv_lock, arg_errmsg, true))) { + || (!map && tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len))) { return; } } else if (argvars[0].v_type == VAR_DICT) { if ((d = argvars[0].vval.v_dict) == NULL - || (!map && tv_check_lock(d->dv_lock, arg_errmsg, true))) { + || (!map && tv_check_lock(d->dv_lock, arg_errmsg, arg_errmsg_len))) { return; } } else { @@ -9311,16 +9669,15 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) return; } - expr = get_tv_string_buf_chk(&argvars[1], buf); - /* On type errors, the preceding call has already displayed an error - * message. Avoid a misleading error message for an empty string that - * was not passed as argument. */ - if (expr != NULL) { + expr = &argvars[1]; + // On type errors, the preceding call has already displayed an error + // message. Avoid a misleading error message for an empty string that + // was not passed as argument. + if (expr->v_type != VAR_UNKNOWN) { prepare_vimvar(VV_VAL, &save_val); - expr = skipwhite(expr); - /* We reset "did_emsg" to be able to detect whether an error - * occurred during evaluation of the expression. */ + // We reset "did_emsg" to be able to detect whether an error + // occurred during evaluation of the expression. save_did_emsg = did_emsg; did_emsg = FALSE; @@ -9337,8 +9694,8 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) di = HI2DI(hi); if (map - && (tv_check_lock(di->di_tv.v_lock, arg_errmsg, true) - || var_check_ro(di->di_flags, arg_errmsg, true))) { + && (tv_check_lock(di->di_tv.v_lock, arg_errmsg, arg_errmsg_len) + || var_check_ro(di->di_flags, arg_errmsg, arg_errmsg_len))) { break; } @@ -9348,8 +9705,8 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) if (r == FAIL || did_emsg) break; if (!map && rem) { - if (var_check_fixed(di->di_flags, arg_errmsg, true) - || var_check_ro(di->di_flags, arg_errmsg, true)) { + if (var_check_fixed(di->di_flags, arg_errmsg, arg_errmsg_len) + || var_check_ro(di->di_flags, arg_errmsg, arg_errmsg_len)) { break; } dictitem_remove(d, di); @@ -9361,7 +9718,8 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) vimvars[VV_KEY].vv_type = VAR_NUMBER; for (li = l->lv_first; li != NULL; li = nli) { - if (map && tv_check_lock(li->li_tv.v_lock, arg_errmsg, true)) { + if (map + && tv_check_lock(li->li_tv.v_lock, arg_errmsg, arg_errmsg_len)) { break; } nli = li->li_next; @@ -9384,20 +9742,46 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) copy_tv(&argvars[0], rettv); } -static int filter_map_one(typval_T *tv, char_u *expr, int map, int *remp) +static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) { typval_T rettv; + typval_T argv[3]; + char_u buf[NUMBUFLEN]; char_u *s; int retval = FAIL; + int dummy; copy_tv(tv, &vimvars[VV_VAL].vv_tv); - s = expr; - if (eval1(&s, &rettv, TRUE) == FAIL) - goto theend; - if (*s != NUL) { /* check for trailing chars after expr */ - EMSG2(_(e_invexpr2), s); - clear_tv(&rettv); - goto theend; + argv[0] = vimvars[VV_KEY].vv_tv; + argv[1] = vimvars[VV_VAL].vv_tv; + if (expr->v_type == VAR_FUNC) { + s = expr->vval.v_string; + if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL, + 0L, 0L, &dummy, true, NULL, NULL) == FAIL) { + goto theend; + } + } else if (expr->v_type == VAR_PARTIAL) { + partial_T *partial = expr->vval.v_partial; + + s = partial_name(partial); + if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL, + 0L, 0L, &dummy, true, partial, NULL) == FAIL) { + goto theend; + } + } else { + s = get_tv_string_buf_chk(expr, buf); + if (s == NULL) { + goto theend; + } + s = skipwhite(s); + if (eval1(&s, &rettv, true) == FAIL) { + goto theend; + } + + if (*s != NUL) { // check for trailing chars after expr + EMSG2(_(e_invexpr2), s); + goto theend; + } } if (map) { /* map(): replace the list item value */ @@ -9656,15 +10040,14 @@ static void f_foreground(typval_T *argvars, typval_T *rettv, FunPtr fptr) { } -/* - * "function()" function - */ -static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void common_function(typval_T *argvars, typval_T *rettv, + bool is_funcref, FunPtr fptr) { char_u *s; char_u *name; bool use_string = false; partial_T *arg_pt = NULL; + char_u *trans_name = NULL; if (argvars[0].v_type == VAR_FUNC) { // function(MyFunc, [arg], dict) @@ -9673,18 +10056,29 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) && argvars[0].vval.v_partial != NULL) { // function(dict.MyFunc, [arg]) arg_pt = argvars[0].vval.v_partial; - s = arg_pt->pt_name; + s = partial_name(arg_pt); } else { // function('MyFunc', [arg], dict) s = get_tv_string(&argvars[0]); use_string = true; } - if (s == NULL || *s == NUL || (use_string && ascii_isdigit(*s))) { - EMSG2(_(e_invarg2), s); - } else if (use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL - && !function_exists(s)) { + if ((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) || is_funcref) { + name = s; + trans_name = trans_function_name(&name, false, + TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD + | TFN_NO_DEREF, NULL, NULL); + if (*name != NUL) { + s = NULL; + } + } + if (s == NULL || *s == NUL || (use_string && ascii_isdigit(*s)) + || (is_funcref && trans_name == NULL)) { + EMSG2(_(e_invarg2), use_string ? get_tv_string(&argvars[0]) : s); // Don't check an autoload name for existence here. + } else if (trans_name != NULL + && (is_funcref ? find_func(trans_name) == NULL + : !translated_function_exists((const char *)trans_name))) { EMSG2(_("E700: Unknown function: %s"), s); } else { int dict_idx = 0; @@ -9725,7 +10119,7 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[dict_idx].v_type != VAR_DICT) { EMSG(_("E922: expected a dict")); xfree(name); - return; + goto theend; } if (argvars[dict_idx].vval.v_dict == NULL) { dict_idx = 0; @@ -9736,7 +10130,7 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG(_("E923: Second argument of function() must be " "a list or a dict")); xfree(name); - return; + goto theend; } list = argvars[arg_idx].vval.v_list; if (list == NULL || list->lv_len == 0) { @@ -9744,7 +10138,7 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } } - if (dict_idx > 0 || arg_idx > 0 || arg_pt != NULL) { + if (dict_idx > 0 || arg_idx > 0 || arg_pt != NULL || is_funcref) { partial_T *const pt = xcalloc(1, sizeof(*pt)); // result is a VAR_PARTIAL @@ -9757,18 +10151,17 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (pt->pt_argv == NULL) { xfree(pt); xfree(name); - return; - } else { - int i = 0; - for (; i < arg_len; i++) { - copy_tv(&arg_pt->pt_argv[i], &pt->pt_argv[i]); - } - if (lv_len > 0) { - for (listitem_T *li = list->lv_first; - li != NULL; - li = li->li_next) { - copy_tv(&li->li_tv, &pt->pt_argv[i++]); - } + goto theend; + } + int i = 0; + for (; i < arg_len; i++) { + copy_tv(&arg_pt->pt_argv[i], &pt->pt_argv[i]); + } + if (lv_len > 0) { + for (listitem_T *li = list->lv_first; + li != NULL; + li = li->li_next) { + copy_tv(&li->li_tv, &pt->pt_argv[i++]); } } } @@ -9790,8 +10183,18 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) } pt->pt_refcount = 1; - pt->pt_name = name; - func_ref(pt->pt_name); + if (arg_pt != NULL && arg_pt->pt_func != NULL) { + pt->pt_func = arg_pt->pt_func; + func_ptr_ref(pt->pt_func); + xfree(name); + } else if (is_funcref) { + pt->pt_func = find_func(trans_name); + func_ptr_ref(pt->pt_func); + xfree(name); + } else { + pt->pt_name = name; + func_ref(name); + } rettv->v_type = VAR_PARTIAL; rettv->vval.v_partial = pt; @@ -9802,6 +10205,18 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) func_ref(name); } } +theend: + xfree(trans_name); +} + +static void f_funcref(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + common_function(argvars, rettv, true, fptr); +} + +static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + common_function(argvars, rettv, false, fptr); } /// "garbagecollect()" function @@ -9855,11 +10270,18 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (pt != NULL) { char_u *what = get_tv_string(&argvars[1]); + char_u *n; if (STRCMP(what, "func") == 0 || STRCMP(what, "name") == 0) { rettv->v_type = (*what == 'f' ? VAR_FUNC : VAR_STRING); - if (pt->pt_name != NULL) { - rettv->vval.v_string = vim_strsave(pt->pt_name); + n = partial_name(pt); + if (n == NULL) { + rettv->vval.v_string = NULL; + } else { + rettv->vval.v_string = vim_strsave(n); + if (rettv->v_type == VAR_FUNC) { + func_ref(rettv->vval.v_string); + } } } else if (STRCMP(what, "dict") == 0) { rettv->v_type = VAR_DICT; @@ -10052,20 +10474,22 @@ static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) { linenr_T lnum; linenr_T end; - buf_T *buf; + buf_T *buf = NULL; - (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ - ++emsg_off; - buf = get_buf_tv(&argvars[0], FALSE); - --emsg_off; + if (tv_check_str_or_nr(&argvars[0])) { + emsg_off++; + buf = get_buf_tv(&argvars[0], false); + emsg_off--; + } lnum = get_tv_lnum_buf(&argvars[1], buf); - if (argvars[2].v_type == VAR_UNKNOWN) + if (argvars[2].v_type == VAR_UNKNOWN) { end = lnum; - else + } else { end = get_tv_lnum_buf(&argvars[2], buf); + } - get_buffer_lines(buf, lnum, end, TRUE, rettv); + get_buffer_lines(buf, lnum, end, true, rettv); } /* @@ -10073,26 +10497,25 @@ static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - buf_T *buf; - buf_T *save_curbuf; - char_u *varname; - dictitem_T *v; - int done = FALSE; - - (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ - varname = get_tv_string_chk(&argvars[1]); - ++emsg_off; - buf = get_buf_tv(&argvars[0], FALSE); + bool done = false; rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; + if (!tv_check_str_or_nr(&argvars[0])) { + goto f_getbufvar_end; + } + + const char *varname = (const char *)get_tv_string_chk(&argvars[1]); + emsg_off++; + buf_T *const buf = get_buf_tv(&argvars[0], false); + if (buf != NULL && varname != NULL) { - /* set curbuf to be our buf, temporarily */ - save_curbuf = curbuf; + // set curbuf to be our buf, temporarily + buf_T *const save_curbuf = curbuf; curbuf = buf; - if (*varname == '&') { // buffer-local-option + if (*varname == '&') { // buffer-local-option if (varname[1] == NUL) { // get all buffer-local options in a dict dict_T *opts = get_winbuf_options(true); @@ -10107,30 +10530,27 @@ static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) // buffer-local-option done = true; } - } else if (STRCMP(varname, "changedtick") == 0) { - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = curbuf->b_changedtick; - done = true; } else { - /* Look up the variable. */ - /* Let getbufvar({nr}, "") return the "b:" dictionary. */ - v = find_var_in_ht(&curbuf->b_vars->dv_hashtab, - 'b', varname, FALSE); + // Look up the variable. + // Let getbufvar({nr}, "") return the "b:" dictionary. + dictitem_T *const v = find_var_in_ht(&curbuf->b_vars->dv_hashtab, 'b', + varname, strlen(varname), false); if (v != NULL) { copy_tv(&v->di_tv, rettv); - done = TRUE; + done = true; } } - /* restore previous notion of curbuf */ + // restore previous notion of curbuf curbuf = save_curbuf; } + emsg_off--; - if (!done && argvars[2].v_type != VAR_UNKNOWN) - /* use the default value */ +f_getbufvar_end: + if (!done && argvars[2].v_type != VAR_UNKNOWN) { + // use the default value copy_tv(&argvars[2], rettv); - - --emsg_off; + } } /* @@ -10645,6 +11065,37 @@ static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr) get_buffer_lines(curbuf, lnum, end, retlist, rettv); } +static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, + typval_T *rettv) +{ + if (what_arg->v_type == VAR_UNKNOWN) { + rettv_list_alloc(rettv); + if (is_qf || wp != NULL) { + (void)get_errorlist(wp, -1, rettv->vval.v_list); + } + } else { + rettv_dict_alloc(rettv); + if (is_qf || wp != NULL) { + if (what_arg->v_type == VAR_DICT) { + dict_T *d = what_arg->vval.v_dict; + + if (d != NULL) { + get_errorlist_properties(wp, d, rettv->vval.v_dict); + } + } else { + EMSG(_(e_dictreq)); + } + } + } +} + +/// "getloclist()" function +static void f_getloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *wp = find_win_by_nr(&argvars[0], NULL); + get_qf_loc_list(false, wp, &argvars[1], rettv); +} + /* * "getmatches()" function */ @@ -10746,20 +11197,10 @@ static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) getpos_both(argvars, rettv, false); } -/* - * "getqflist()" and "getloclist()" functions - */ +/// "getqflist()" functions static void f_getqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - rettv_list_alloc(rettv); - win_T *wp = NULL; - if (argvars[0].v_type != VAR_UNKNOWN) { /* getloclist() */ - wp = find_win_by_nr(&argvars[0], NULL); - if (wp == NULL) { - return; - } - } - (void)get_errorlist(wp, rettv->vval.v_list); + get_qf_loc_list(true, NULL, &argvars[0], rettv); } /// "getreg()" function @@ -10898,13 +11339,12 @@ static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) win_T *oldcurwin; tabpage_T *tp, *oldtabpage; dictitem_T *v; - char_u *varname; bool done = false; rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; - varname = get_tv_string_chk(&argvars[1]); + const char *const varname = (const char *)get_tv_string_chk(&argvars[1]); tp = find_tabpage((int)get_tv_number_chk(&argvars[0], NULL)); if (tp != NULL && varname != NULL) { // Set tp to be our tabpage, temporarily. Also set the window to the @@ -10913,7 +11353,8 @@ static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (switch_win(&oldcurwin, &oldtabpage, window, tp, true) == OK) { // look up the variable // Let gettabvar({nr}, "") return the "t:" dictionary. - v = find_var_in_ht(&tp->tp_vars->dv_hashtab, 't', varname, FALSE); + v = find_var_in_ht(&tp->tp_vars->dv_hashtab, 't', + varname, strlen(varname), false); if (v != NULL) { copy_tv(&v->di_tv, rettv); done = true; @@ -11092,7 +11533,6 @@ getwinvar ( ) { win_T *win, *oldcurwin; - char_u *varname; dictitem_T *v; tabpage_T *tp = NULL; tabpage_T *oldtabpage = NULL; @@ -11103,12 +11543,13 @@ getwinvar ( else tp = curtab; win = find_win_by_nr(&argvars[off], tp); - varname = get_tv_string_chk(&argvars[off + 1]); - ++emsg_off; + const char *varname = (const char *)get_tv_string_chk( + &argvars[off + 1]); rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; + emsg_off++; if (win != NULL && varname != NULL) { // Set curwin to be our win, temporarily. Also set the tabpage, // otherwise the window is not valid. Only do this when needed, @@ -11134,7 +11575,8 @@ getwinvar ( } else { // Look up the variable. // Let getwinvar({nr}, "") return the "w:" dictionary. - v = find_var_in_ht(&win->w_vars->dv_hashtab, 'w', varname, FALSE); + v = find_var_in_ht(&win->w_vars->dv_hashtab, 'w', varname, + strlen(varname), false); if (v != NULL) { copy_tv(&v->di_tv, rettv); done = true; @@ -11147,12 +11589,12 @@ getwinvar ( restore_win(oldcurwin, oldtabpage, true); } } + emsg_off--; - if (!done && argvars[off + 2].v_type != VAR_UNKNOWN) - /* use the default return value */ + if (!done && argvars[off + 2].v_type != VAR_UNKNOWN) { + // use the default return value copy_tv(&argvars[off + 2], rettv); - - --emsg_off; + } } /* @@ -11317,6 +11759,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) "insert_expand", "jumplist", "keymap", + "lambda", "langmap", "libcall", "linebreak", @@ -11421,6 +11864,10 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) #endif } else if (STRICMP(name, "syntax_items") == 0) { n = syntax_present(curwin); +#ifdef UNIX + } else if (STRICMP(name, "unnamedplus") == 0) { + n = eval_has_provider("clipboard"); +#endif } } @@ -11817,8 +12264,8 @@ static void get_user_input(typval_T *argvars, typval_T *rettv, int inputdialog) *p = NUL; msg_start(); msg_clr_eos(); - msg_puts_attr(prompt, echo_attr); - msg_didout = FALSE; + msg_puts_attr((const char *)prompt, echo_attr); + msg_didout = false; msg_starthere(); *p = c; } @@ -11910,7 +12357,7 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) msg_clr_eos(); for (li = argvars[0].vval.v_list->lv_first; li != NULL; li = li->li_next) { - msg_puts(get_tv_string(&li->li_tv)); + msg_puts((const char *)get_tv_string(&li->li_tv)); msg_putchar('\n'); } @@ -11971,13 +12418,14 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) long before = 0; listitem_T *item; list_T *l; - int error = FALSE; + int error = false; + const char *const arg_errmsg = _("insert() argument"); + const size_t arg_errmsg_len = strlen(arg_errmsg); if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listarg), "insert()"); } else if ((l = argvars[0].vval.v_list) != NULL - && !tv_check_lock(l->lv_lock, - (char_u *)N_("insert() argument"), true)) { + && !tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) { if (argvars[2].v_type != VAR_UNKNOWN) { before = get_tv_number_chk(&argvars[2], &error); } @@ -12028,37 +12476,33 @@ static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) dictitem_T *di; rettv->vval.v_number = -1; - end = get_lval(get_tv_string(&argvars[0]), NULL, &lv, FALSE, FALSE, - GLV_NO_AUTOLOAD, FNE_CHECK_START); + end = get_lval(get_tv_string(&argvars[0]), NULL, &lv, false, false, + GLV_NO_AUTOLOAD|GLV_READ_ONLY, FNE_CHECK_START); if (end != NULL && lv.ll_name != NULL) { if (*end != NUL) EMSG(_(e_trailing)); else { if (lv.ll_tv == NULL) { - if (check_changedtick(lv.ll_name)) - rettv->vval.v_number = 1; /* always locked */ - else { - di = find_var(lv.ll_name, NULL, TRUE); - if (di != NULL) { - /* Consider a variable locked when: - * 1. the variable itself is locked - * 2. the value of the variable is locked. - * 3. the List or Dict value is locked. - */ - rettv->vval.v_number = ((di->di_flags & DI_FLAGS_LOCK) - || tv_islocked(&di->di_tv)); - } + di = find_var((const char *)lv.ll_name, STRLEN(lv.ll_name), NULL, true); + if (di != NULL) { + // Consider a variable locked when: + // 1. the variable itself is locked + // 2. the value of the variable is locked. + // 3. the List or Dict value is locked. + rettv->vval.v_number = ((di->di_flags & DI_FLAGS_LOCK) + || tv_islocked(&di->di_tv)); } - } else if (lv.ll_range) + } else if (lv.ll_range) { EMSG(_("E786: Range not allowed")); - else if (lv.ll_newkey != NULL) + } else if (lv.ll_newkey != NULL) { EMSG2(_(e_dictkey), lv.ll_newkey); - else if (lv.ll_list != NULL) - /* List item. */ + } else if (lv.ll_list != NULL) { + // List item. rettv->vval.v_number = tv_islocked(&lv.ll_li->li_tv); - else - /* Dictionary item. */ + } else { + // Dictionary item. rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv); + } } } @@ -12879,9 +13323,9 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) rettv_dict_alloc(rettv); if (rhs != NULL) { // Return a dictionary. - char_u *lhs = str2special_save(mp->m_keys, TRUE); - char_u *mapmode = map_mode_to_chars(mp->m_mode); - dict_T *dict = rettv->vval.v_dict; + char_u *lhs = str2special_save(mp->m_keys, true); + char *const mapmode = map_mode_to_chars(mp->m_mode); + dict_T *dict = rettv->vval.v_dict; dict_add_nr_str(dict, "lhs", 0L, lhs); dict_add_nr_str(dict, "rhs", 0L, mp->m_orig_str); @@ -12891,7 +13335,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) dict_add_nr_str(dict, "sid", (long)mp->m_script_ID, NULL); dict_add_nr_str(dict, "buffer", (long)buffer_local, NULL); dict_add_nr_str(dict, "nowait", mp->m_nowait ? 1L : 0L, NULL); - dict_add_nr_str(dict, "mode", 0L, mapmode); + dict_add_nr_str(dict, "mode", 0L, (char_u *)mapmode); xfree(lhs); xfree(mapmode); @@ -14005,20 +14449,21 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) char_u *key; dict_T *d; dictitem_T *di; - char_u *arg_errmsg = (char_u *)N_("remove() argument"); + const char *const arg_errmsg = _("remove() argument"); + const size_t arg_errmsg_len = strlen(arg_errmsg); if (argvars[0].v_type == VAR_DICT) { if (argvars[2].v_type != VAR_UNKNOWN) { EMSG2(_(e_toomanyarg), "remove()"); } else if ((d = argvars[0].vval.v_dict) != NULL - && !tv_check_lock(d->dv_lock, arg_errmsg, true)) { + && !tv_check_lock(d->dv_lock, arg_errmsg, arg_errmsg_len)) { key = get_tv_string_chk(&argvars[1]); if (key != NULL) { di = dict_find(d, key, -1); if (di == NULL) { EMSG2(_(e_dictkey), key); - } else if (!var_check_fixed(di->di_flags, arg_errmsg, true) - && !var_check_ro(di->di_flags, arg_errmsg, true)) { + } else if (!var_check_fixed(di->di_flags, arg_errmsg, arg_errmsg_len) + && !var_check_ro(di->di_flags, arg_errmsg, arg_errmsg_len)) { *rettv = di->di_tv; init_tv(&di->di_tv); dictitem_remove(d, di); @@ -14031,7 +14476,7 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listdictarg), "remove()"); } else if ((l = argvars[0].vval.v_list) != NULL - && !tv_check_lock(l->lv_lock, arg_errmsg, true)) { + && !tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) { int error = (int)false; idx = get_tv_number_chk(&argvars[1], &error); @@ -14302,19 +14747,19 @@ fail: */ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - list_T *l; - listitem_T *li, *ni; + const char *const arg_errmsg = _("reverse() argument"); + const size_t arg_errmsg_len = strlen(arg_errmsg); + list_T *l; if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listarg), "reverse()"); } else if ((l = argvars[0].vval.v_list) != NULL - && !tv_check_lock(l->lv_lock, - (char_u *)N_("reverse() argument"), true)) { - li = l->lv_last; + && !tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) { + listitem_T *li = l->lv_last; l->lv_first = l->lv_last = NULL; l->lv_len = 0; while (li != NULL) { - ni = li->li_prev; + listitem_T *const ni = li->li_prev; list_append(l, li); li = ni; } @@ -15108,44 +15553,45 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - buf_T *buf; - char_u *varname, *bufvarname; - typval_T *varp; char_u nbuf[NUMBUFLEN]; - if (check_restricted() || check_secure()) + if (check_restricted() + || check_secure() + || !tv_check_str_or_nr(&argvars[0])) { return; - (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ - varname = get_tv_string_chk(&argvars[1]); - buf = get_buf_tv(&argvars[0], FALSE); - varp = &argvars[2]; + } + const char *varname = (const char *)get_tv_string_chk(&argvars[1]); + buf_T *const buf = get_buf_tv(&argvars[0], false); + typval_T *varp = &argvars[2]; if (buf != NULL && varname != NULL && varp != NULL) { if (*varname == '&') { long numval; - char_u *strval; + char_u *strval; int error = false; - aco_save_T aco; + aco_save_T aco; // set curbuf to be our buf, temporarily aucmd_prepbuf(&aco, buf); - ++varname; + varname++; numval = get_tv_number_chk(varp, &error); strval = get_tv_string_buf_chk(varp, nbuf); - if (!error && strval != NULL) - set_option_value(varname, numval, strval, OPT_LOCAL); + if (!error && strval != NULL) { + set_option_value((char_u *)varname, numval, strval, OPT_LOCAL); + } // reset notion of buffer aucmd_restbuf(&aco); } else { buf_T *save_curbuf = curbuf; - bufvarname = xmalloc(STRLEN(varname) + 3); + const size_t varname_len = STRLEN(varname); + char_u *const bufvarname = xmalloc(STRLEN(varname) + 3); curbuf = buf; - STRCPY(bufvarname, "b:"); - STRCPY(bufvarname + 2, varname); - set_var(bufvarname, varp, TRUE); + memcpy(bufvarname, "b:", 2); + memcpy(bufvarname + 2, varname, varname_len + 1); + set_var(bufvarname, varp, true); xfree(bufvarname); curbuf = save_curbuf; } @@ -15296,7 +15742,7 @@ static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// Create quickfix/location list from VimL values /// /// Used by `setqflist()` and `setloclist()` functions. Accepts invalid -/// list_arg, action_arg and title_arg arguments in which case errors out, +/// list_arg, action_arg and what_arg arguments in which case errors out, /// including VAR_UNKNOWN parameters. /// /// @param[in,out] wp Window to create location list for. May be NULL in @@ -15313,6 +15759,7 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv) char_u *title = NULL; int action = ' '; rettv->vval.v_number = -1; + dict_T *d = NULL; typval_T *list_arg = &args[0]; if (list_arg->v_type != VAR_LIST) { @@ -15340,10 +15787,16 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv) if (title_arg->v_type == VAR_UNKNOWN) { // Option argument was not given. goto skip_args; - } - title = get_tv_string_chk(title_arg); - if (!title) { - // Type error. Error already printed by get_tv_string_chk(). + } else if (title_arg->v_type == VAR_STRING) { + title = get_tv_string_chk(title_arg); + if (!title) { + // Type error. Error already printed by get_tv_string_chk(). + return; + } + } else if (title_arg->v_type == VAR_DICT) { + d = title_arg->vval.v_dict; + } else { + EMSG(_(e_dictreq)); return; } @@ -15353,7 +15806,7 @@ skip_args: } list_T *l = list_arg->vval.v_list; - if (l && set_errorlist(wp, l, action, title) == OK) { + if (l && set_errorlist(wp, l, action, title, d) == OK) { rettv->vval.v_number = 0; } } @@ -15896,8 +16349,9 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) if (partial == NULL) { func_name = sortinfo->item_compare_func; } else { - func_name = partial->pt_name; + func_name = partial_name(partial); } + // Copy the values. This is needed to be able to set v_lock to VAR_FIXED // in the copy without changing the original list items. copy_tv(&si1->item->li_tv, &argv[0]); @@ -15906,7 +16360,7 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) rettv.v_type = VAR_UNKNOWN; // clear_tv() uses this res = call_func(func_name, (int)STRLEN(func_name), - &rettv, 2, argv, 0L, 0L, &dummy, true, + &rettv, 2, argv, NULL, 0L, 0L, &dummy, true, partial, sortinfo->item_compare_selfdict); clear_tv(&argv[0]); clear_tv(&argv[1]); @@ -15957,16 +16411,17 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) sortinfo_T *old_sortinfo = sortinfo; sortinfo = &info; + const char *const arg_errmsg = (sort + ? _("sort() argument") + : _("uniq() argument")); + const size_t arg_errmsg_len = strlen(arg_errmsg); + if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listarg), sort ? "sort()" : "uniq()"); } else { l = argvars[0].vval.v_list; if (l == NULL - || tv_check_lock(l->lv_lock, - (char_u *)(sort - ? N_("sort() argument") - : N_("uniq() argument")), - true)) { + || tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) { goto theend; } rettv->vval.v_list = l; @@ -16714,12 +17169,16 @@ static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + if (no < 0 || no >= NSUBEXP) { + EMSGN(_("E935: invalid submatch number: %d"), no); + return; + } int retList = 0; if (argvars[1].v_type != VAR_UNKNOWN) { retList = get_tv_number_chk(&argvars[1], &error); if (error) { - return; + return; } } @@ -16743,19 +17202,26 @@ static void f_substitute(typval_T *argvars, typval_T *rettv, FunPtr fptr) char_u *str = get_tv_string_chk(&argvars[0]); char_u *pat = get_tv_string_buf_chk(&argvars[1], patbuf); - char_u *sub = get_tv_string_buf_chk(&argvars[2], subbuf); + char_u *sub = NULL; + typval_T *expr = NULL; char_u *flg = get_tv_string_buf_chk(&argvars[3], flagsbuf); + if (argvars[2].v_type == VAR_FUNC || argvars[2].v_type == VAR_PARTIAL) { + expr = &argvars[2]; + } else { + sub = get_tv_string_buf_chk(&argvars[2], subbuf); + } + rettv->v_type = VAR_STRING; - if (str == NULL || pat == NULL || sub == NULL || flg == NULL) + if (str == NULL || pat == NULL || (sub == NULL && expr == NULL) + || flg == NULL) { rettv->vval.v_string = NULL; - else - rettv->vval.v_string = do_string_sub(str, pat, sub, flg); + } else { + rettv->vval.v_string = do_string_sub(str, pat, sub, expr, flg); + } } -/* - * "synID(lnum, col, trans)" function - */ +/// "synID(lnum, col, trans)" function static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int id = 0; @@ -16820,8 +17286,8 @@ static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) p = highlight_has_attr(id, HL_ITALIC, modec); break; - case 'n': /* name */ - p = get_highlight_name(NULL, id - 1); + case 'n': // name + p = (char_u *)get_highlight_name(NULL, id - 1); break; case 'r': /* reverse */ @@ -17294,10 +17760,10 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) (void)setfname(curbuf, (uint8_t *)buf, NULL, true); // Save the job id and pid in b:terminal_job_{id,pid} Error err; - dict_set_value(curbuf->b_vars, cstr_as_string("terminal_job_id"), - INTEGER_OBJ(rettv->vval.v_number), false, false, &err); - dict_set_value(curbuf->b_vars, cstr_as_string("terminal_job_pid"), - INTEGER_OBJ(pid), false, false, &err); + dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_id"), + INTEGER_OBJ(rettv->vval.v_number), false, false, &err); + dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_pid"), + INTEGER_OBJ(pid), false, false, &err); Terminal *term = terminal_open(topts); data->term = term; @@ -17306,6 +17772,15 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } +// "test_garbagecollect_now()" function +static void f_test_garbagecollect_now(typval_T *argvars, + typval_T *rettv, FunPtr fptr) +{ + // This is dangerous, any Lists and Dicts used internally may be freed + // while still in use. + garbage_collect(true); +} + static bool callback_from_typval(Callback *callback, typval_T *arg) { if (arg->v_type == VAR_PARTIAL && arg->vval.v_partial != NULL) { @@ -17384,7 +17859,7 @@ static bool callback_call(Callback *callback, int argcount_in, case kCallbackPartial: partial = callback->data.partial; - name = partial->pt_name; + name = partial_name(partial); break; case kCallbackNone: @@ -17397,7 +17872,7 @@ static bool callback_call(Callback *callback, int argcount_in, int dummy; return call_func(name, (int)STRLEN(name), rettv, argcount_in, argvars_in, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, + NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, true, partial, NULL); } @@ -18021,29 +18496,88 @@ static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// Writes list of strings to file -static bool write_list(FILE *fd, list_T *list, bool binary) -{ - int ret = true; - - for (listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { - for (char_u *s = get_tv_string(&li->li_tv); *s != NUL; ++s) { - if (putc(*s == '\n' ? NUL : *s, fd) == EOF) { - ret = false; - break; +/// +/// @param fp File to write to. +/// @param[in] list List to write. +/// @param[in] binary Whether to write in binary mode. +/// +/// @return true in case of success, false otherwise. +static bool write_list(FileDescriptor *const fp, const list_T *const list, + const bool binary) +{ + int error = 0; + for (const listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { + const char *const s = (const char *)get_tv_string_chk( + (typval_T *)&li->li_tv); + if (s == NULL) { + return false; + } + const char *hunk_start = s; + for (const char *p = hunk_start;; p++) { + if (*p == NUL || *p == NL) { + if (p != hunk_start) { + const ptrdiff_t written = file_write(fp, hunk_start, + (size_t)(p - hunk_start)); + if (written < 0) { + error = (int)written; + goto write_list_error; + } + } + if (*p == NUL) { + break; + } else { + hunk_start = p + 1; + const ptrdiff_t written = file_write(fp, (char[]){ NUL }, 1); + if (written < 0) { + error = (int)written; + break; + } + } } } if (!binary || li->li_next != NULL) { - if (putc('\n', fd) == EOF) { - ret = false; - break; + const ptrdiff_t written = file_write(fp, "\n", 1); + if (written < 0) { + error = (int)written; + goto write_list_error; } } - if (ret == false) { - EMSG(_(e_write)); - break; + } + if ((error = file_fsync(fp)) != 0) { + goto write_list_error; + } + return true; +write_list_error: + emsgf(_("E80: Error while writing: %s"), os_strerror(error)); + return false; +} + +/// Initializes a static list with 10 items. +void init_static_list(staticList10_T *sl) +{ + list_T *l = &sl->sl_list; + + memset(sl, 0, sizeof(staticList10_T)); + l->lv_first = &sl->sl_items[0]; + l->lv_last = &sl->sl_items[9]; + l->lv_refcount = DO_NOT_FREE_CNT; + l->lv_lock = VAR_FIXED; + sl->sl_list.lv_len = 10; + + for (int i = 0; i < 10; i++) { + listitem_T *li = &sl->sl_items[i]; + + if (i == 0) { + li->li_prev = NULL; + } else { + li->li_prev = li - 1; + } + if (i == 9) { + li->li_next = NULL; + } else { + li->li_next = li + 1; } } - return ret; } /// Saves a typval_T as a string. @@ -18143,27 +18677,42 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool binary = false; bool append = false; if (argvars[2].v_type != VAR_UNKNOWN) { - if (vim_strchr(get_tv_string(&argvars[2]), 'b')) { + const char *const flags = (const char *)get_tv_string_chk(&argvars[2]); + if (flags == NULL) { + return; + } + if (strchr(flags, 'b')) { binary = true; } - if (vim_strchr(get_tv_string(&argvars[2]), 'a')) { + if (strchr(flags, 'a')) { append = true; } } - // Always open the file in binary mode, library functions have a mind of - // their own about CR-LF conversion. - char_u *fname = get_tv_string(&argvars[1]); - FILE *fd; - if (*fname == NUL || (fd = mch_fopen((char *)fname, - append ? APPENDBIN : WRITEBIN)) == NULL) { - EMSG2(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname); - rettv->vval.v_number = -1; + const char buf[NUMBUFLEN]; + const char *const fname = (const char *)get_tv_string_buf_chk(&argvars[1], + (char_u *)buf); + if (fname == NULL) { + return; + } + FileDescriptor *fp; + int error; + rettv->vval.v_number = -1; + if (*fname == NUL) { + EMSG(_("E482: Can't open file with an empty name")); + } else if ((fp = file_open_new(&error, fname, + ((append ? kFileAppend : kFileTruncate) + | kFileCreate), 0666)) == NULL) { + emsgf(_("E482: Can't open file %s for writing: %s"), + fname, os_strerror(error)); } else { - if (write_list(fd, argvars[0].vval.v_list, binary) == false) { - rettv->vval.v_number = -1; + if (write_list(fp, argvars[0].vval.v_list, binary)) { + rettv->vval.v_number = 0; + } + if ((error = file_free(fp)) != 0) { + emsgf(_("E80: Error when closing file %s: %s"), + fname, os_strerror(error)); } - fclose(fd); } } /* @@ -18353,11 +18902,12 @@ static int get_env_len(char_u **arg) // Get the length of the name of a function or internal variable. // "arg" is advanced to the first non-white character after the name. // Return 0 if something is wrong. -static int get_id_len(char_u **arg) { - char_u *p; +static int get_id_len(const char **const arg) +{ int len; // Find the end of the name. + const char *p; for (p = *arg; eval_isnamec(*p); p++) { if (*p == ':') { // "s:" is start of "s:var", but "n:" is not and can be used in @@ -18374,7 +18924,7 @@ static int get_id_len(char_u **arg) { } len = (int)(p - *arg); - *arg = skipwhite(p); + *arg = (const char *)skipwhite((const char_u *)p); return len; } @@ -18388,18 +18938,20 @@ static int get_id_len(char_u **arg) { * If the name contains 'magic' {}'s, expand them and return the * expanded name in an allocated string via 'alias' - caller must free. */ -static int get_name_len(char_u **arg, char_u **alias, int evaluate, int verbose) +static int get_name_len(const char **const arg, + char **alias, + int evaluate, + int verbose) { int len; - char_u *p; char_u *expr_start; char_u *expr_end; *alias = NULL; /* default to no alias */ - if ((*arg)[0] == K_SPECIAL && (*arg)[1] == KS_EXTRA - && (*arg)[2] == (int)KE_SNR) { - /* hard coded <SNR>, already translated */ + if ((*arg)[0] == (char)K_SPECIAL && (*arg)[1] == (char)KS_EXTRA + && (*arg)[2] == (char)KE_SNR) { + // Hard coded <SNR>, already translated. *arg += 3; return get_id_len(arg) + 3; } @@ -18412,14 +18964,14 @@ static int get_name_len(char_u **arg, char_u **alias, int evaluate, int verbose) /* * Find the end of the name; check for {} construction. */ - p = find_name_end(*arg, &expr_start, &expr_end, - len > 0 ? 0 : FNE_CHECK_START); + const char *p = (const char *)find_name_end((char_u *)(*arg), + &expr_start, + &expr_end, + len > 0 ? 0 : FNE_CHECK_START); if (expr_start != NULL) { - char_u *temp_string; - if (!evaluate) { len += (int)(p - *arg); - *arg = skipwhite(p); + *arg = (const char *)skipwhite((const char_u *)p); return len; } @@ -18427,11 +18979,13 @@ static int get_name_len(char_u **arg, char_u **alias, int evaluate, int verbose) * Include any <SID> etc in the expanded string: * Thus the -len here. */ - temp_string = make_expanded_name(*arg - len, expr_start, expr_end, p); - if (temp_string == NULL) + char_u *temp_string = make_expanded_name((char_u *)(*arg) - len, expr_start, + expr_end, (char_u *)p); + if (temp_string == NULL) { return -1; - *alias = temp_string; - *arg = skipwhite(p); + } + *alias = (char *)temp_string; + *arg = (const char *)skipwhite((const char_u *)p); return (int)STRLEN(temp_string); } @@ -18845,9 +19399,8 @@ char_u *set_cmdarg(exarg_T *eap, char_u *oldarg) * Get the value of internal variable "name". * Return OK or FAIL. */ -static int -get_var_tv ( - char_u *name, +static int get_var_tv( + const char *name, int len, // length of "name" typval_T *rettv, // NULL when only checking existence dictitem_T **dip, // non-NULL when typval's dict item is needed @@ -18857,55 +19410,52 @@ get_var_tv ( { int ret = OK; typval_T *tv = NULL; - typval_T atv; dictitem_T *v; - int cc; - - /* truncate the name, so that we can use strcmp() */ - cc = name[len]; - name[len] = NUL; - /* - * Check for "b:changedtick". - */ - if (STRCMP(name, "b:changedtick") == 0) { - atv.v_type = VAR_NUMBER; - atv.vval.v_number = curbuf->b_changedtick; - tv = &atv; - } - /* - * Check for user-defined variables. - */ - else { - v = find_var(name, NULL, no_autoload); - if (v != NULL) { - tv = &v->di_tv; - if (dip != NULL) { - *dip = v; - } + v = find_var(name, (size_t)len, NULL, no_autoload); + if (v != NULL) { + tv = &v->di_tv; + if (dip != NULL) { + *dip = v; } } if (tv == NULL) { - if (rettv != NULL && verbose) - EMSG2(_(e_undefvar), name); + if (rettv != NULL && verbose) { + emsgf(_("E121: Undefined variable: %.*s"), len, name); + } ret = FAIL; - } else if (rettv != NULL) + } else if (rettv != NULL) { copy_tv(tv, rettv); - - name[len] = cc; + } return ret; } -/* - * Handle expr[expr], expr[expr:expr] subscript and .name lookup. - * Also handle function call with Funcref variable: func(expr) - * Can all be combined: dict.func(expr)[idx]['func'](expr) - */ -static int -handle_subscript ( - char_u **arg, +/// Check if variable "name[len]" is a local variable or an argument. +/// If so, "*eval_lavars_used" is set to TRUE. +static void check_vars(const char *name, size_t len) +{ + if (eval_lavars_used == NULL) { + return; + } + + const char *varname; + hashtab_T *ht = find_var_ht(name, len, &varname); + + if (ht == get_funccal_local_ht() || ht == get_funccal_args_ht()) { + if (find_var(name, len, NULL, true) != NULL) { + *eval_lavars_used = true; + } + } +} + +/// Handle expr[expr], expr[expr:expr] subscript and .name lookup. +/// Also handle function call with Funcref variable: func(expr) +/// Can all be combined: dict.func(expr)[idx]['func'](expr) +static int +handle_subscript( + const char **const arg, typval_T *rettv, int evaluate, /* do more than finding the end */ int verbose /* give error messages */ @@ -18933,14 +19483,14 @@ handle_subscript ( // Invoke the function. Recursive! if (functv.v_type == VAR_PARTIAL) { pt = functv.vval.v_partial; - s = pt->pt_name; + s = partial_name(pt); } else { s = functv.vval.v_string; } } else { s = (char_u *)""; } - ret = get_func_tv(s, (int)STRLEN(s), rettv, arg, + ret = get_func_tv(s, (int)STRLEN(s), rettv, (char_u **)arg, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &len, evaluate, pt, selfdict); @@ -18967,7 +19517,7 @@ handle_subscript ( ++selfdict->dv_refcount; } else selfdict = NULL; - if (eval_index(arg, rettv, evaluate, verbose) == FAIL) { + if (eval_index((char_u **)arg, rettv, evaluate, verbose) == FAIL) { clear_tv(rettv); ret = FAIL; } @@ -18993,19 +19543,23 @@ static void set_selfdict(typval_T *rettv, dict_T *selfdict) && rettv->vval.v_partial->pt_dict != NULL) { return; } - char_u *fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING - ? rettv->vval.v_string - : rettv->vval.v_partial->pt_name; + char_u *fname; char_u *tofree = NULL; ufunc_T *fp; char_u fname_buf[FLEN_FIXED + 1]; int error; - // Translate "s:func" to the stored function name. - fname = fname_trans_sid(fname, fname_buf, &tofree, &error); - - fp = find_func(fname); - xfree(tofree); + 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; + // Translate "s:func" to the stored function name. + fname = fname_trans_sid(fname, fname_buf, &tofree, &error); + fp = find_func(fname); + xfree(tofree); + } // Turn "dict.Func" into a partial for "Func" with "dict". if (fp != NULL && (fp->uf_flags & FC_DICT)) { @@ -19026,8 +19580,13 @@ static void set_selfdict(typval_T *rettv, dict_T *selfdict) // Partial: copy the function name, use selfdict and copy // args. Can't take over name or args, the partial might // be referenced elsewhere. - pt->pt_name = vim_strsave(ret_pt->pt_name); - func_ref(pt->pt_name); + if (ret_pt->pt_name != NULL) { + pt->pt_name = vim_strsave(ret_pt->pt_name); + func_ref(pt->pt_name); + } else { + pt->pt_func = ret_pt->pt_func; + func_ptr_ref(pt->pt_func); + } if (ret_pt->pt_argc > 0) { size_t arg_size = sizeof(typval_T) * ret_pt->pt_argc; pt->pt_argv = (typval_T *)xmalloc(arg_size); @@ -19331,6 +19890,53 @@ static void init_tv(typval_T *varp) memset(varp, 0, sizeof(typval_T)); } +/// Check that given value is a number or string +/// +/// Error messages are compatible with get_tv_number() previously used for the +/// same purpose in buf*() functions. Special values are not accepted (previous +/// behaviour: silently fail to find buffer). +/// +/// @param[in] tv Value to check. +/// +/// @return true if everything is OK, false otherwise. +bool tv_check_str_or_nr(const typval_T *const tv) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE +{ + switch (tv->v_type) { + case VAR_NUMBER: + case VAR_STRING: { + return true; + } + case VAR_FLOAT: { + EMSG(_("E805: Expected a Number or a String, Float found")); + return false; + } + case VAR_PARTIAL: + case VAR_FUNC: { + EMSG(_("E703: Expected a Number or a String, Funcref found")); + return false; + } + case VAR_LIST: { + EMSG(_("E745: Expected a Number or a String, List found")); + return false; + } + case VAR_DICT: { + EMSG(_("E728: Expected a Number or a String, Dictionary found")); + return false; + } + case VAR_SPECIAL: { + EMSG(_("E5300: Expected a Number or a String")); + return false; + } + case VAR_UNKNOWN: { + EMSG2(_(e_intern2), "tv_check_str_or_nr(UNKNOWN)"); + return false; + } + } + assert(false); + return false; +} + /* * Get the number value of a variable. * If it is a String variable, uses vim_str2nr(). @@ -19494,7 +20100,7 @@ char_u *get_tv_string_chk(const typval_T *varp) return get_tv_string_buf_chk(varp, mybuf); } -static char_u *get_tv_string_buf_chk(const typval_T *varp, char_u *buf) +char_u *get_tv_string_buf_chk(const typval_T *varp, char_u *buf) FUNC_ATTR_NONNULL_ALL { switch (varp->v_type) { @@ -19535,58 +20141,82 @@ static char_u *get_tv_string_buf_chk(const typval_T *varp, char_u *buf) * When "htp" is not NULL we are writing to the variable, set "htp" to the * hashtab_T used. */ -static dictitem_T *find_var(char_u *name, hashtab_T **htp, int no_autoload) +static dictitem_T *find_var(const char *const name, const size_t name_len, + hashtab_T **htp, int no_autoload) { - char_u *varname; - hashtab_T *ht; - - ht = find_var_ht(name, &varname); - if (htp != NULL) + const char *varname; + hashtab_T *ht = find_var_ht(name, name_len, &varname); + if (htp != NULL) { *htp = ht; - if (ht == NULL) + } + if (ht == NULL) { return NULL; - return find_var_in_ht(ht, *name, varname, no_autoload || htp != NULL); + } + dictitem_T *ret = find_var_in_ht(ht, *name, + varname, name_len - (size_t)(varname - name), + no_autoload || htp != NULL); + if (ret != NULL) { + return ret; + } + + // Search in parent scope for lambda + return find_var_in_scoped_ht(name, name_len, no_autoload || htp != NULL); } -/// Find variable "varname" in hashtab "ht" with name "htname". -/// Returns NULL if not found. -static dictitem_T *find_var_in_ht(hashtab_T *ht, int htname, - char_u *varname, bool no_autoload) +/// Find variable in hashtab +/// +/// @param[in] ht Hashtab to find variable in. +/// @param[in] htname Hashtab name (first character). +/// @param[in] varname Variable name. +/// @param[in] varname_len Variable name length. +/// @param[in] no_autoload If true then autoload scripts will not be sourced +/// if autoload variable was not found. +/// +/// @return pointer to the dictionary item with the found variable or NULL if it +/// was not found. +static dictitem_T *find_var_in_ht(hashtab_T *const ht, + int htname, + const char *const varname, + const size_t varname_len, + int no_autoload) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { hashitem_T *hi; - if (*varname == NUL) { - /* Must be something like "s:", otherwise "ht" would be NULL. */ + if (varname_len == 0) { + // Must be something like "s:", otherwise "ht" would be NULL. switch (htname) { - case 's': return &SCRIPT_SV(current_SID)->sv_var; - case 'g': return &globvars_var; - case 'v': return &vimvars_var; - case 'b': return &curbuf->b_bufvar; - case 'w': return &curwin->w_winvar; - case 't': return &curtab->tp_winvar; - case 'l': return current_funccal == NULL - ? NULL : ¤t_funccal->l_vars_var; - case 'a': return current_funccal == NULL - ? NULL : ¤t_funccal->l_avars_var; + case 's': return (dictitem_T *)&SCRIPT_SV(current_SID)->sv_var; + case 'g': return (dictitem_T *)&globvars_var; + case 'v': return (dictitem_T *)&vimvars_var; + case 'b': return (dictitem_T *)&curbuf->b_bufvar; + case 'w': return (dictitem_T *)&curwin->w_winvar; + case 't': return (dictitem_T *)&curtab->tp_winvar; + case 'l': return (current_funccal == NULL + ? NULL : (dictitem_T *)¤t_funccal->l_vars_var); + case 'a': return (current_funccal == NULL + ? NULL : (dictitem_T *)¤t_funccal->l_avars_var); } return NULL; } - hi = hash_find(ht, varname); + hi = hash_find_len(ht, varname, varname_len); if (HASHITEM_EMPTY(hi)) { - /* For global variables we may try auto-loading the script. If it - * worked find the variable again. Don't auto-load a script if it was - * loaded already, otherwise it would be loaded every time when - * checking if a function name is a Funcref variable. */ + // For global variables we may try auto-loading the script. If it + // worked find the variable again. Don't auto-load a script if it was + // loaded already, otherwise it would be loaded every time when + // checking if a function name is a Funcref variable. if (ht == &globvarht && !no_autoload) { - /* Note: script_autoload() may make "hi" invalid. It must either - * be obtained again or not used. */ - if (!script_autoload(varname, FALSE) || aborting()) + // Note: script_autoload() may make "hi" invalid. It must either + // be obtained again or not used. + if (!script_autoload(varname, varname_len, false) || aborting()) { return NULL; - hi = hash_find(ht, varname); + } + hi = hash_find_len(ht, varname, varname_len); } - if (HASHITEM_EMPTY(hi)) + if (HASHITEM_EMPTY(hi)) { return NULL; + } } return HI2DI(hi); } @@ -19610,17 +20240,45 @@ static funccall_T *get_funccal(void) return funccal; } -// Find the dict and hashtable used for a variable name. Set "varname" to the -// start of name without ':'. -static hashtab_T *find_var_ht_dict(char_u *name, uint8_t **varname, dict_T **d) +/// Return the hashtable used for argument in the current funccal. +/// Return NULL if there is no current funccal. +static hashtab_T *get_funccal_args_ht(void) { - hashitem_T *hi; + if (current_funccal == NULL) { + return NULL; + } + return &get_funccal()->l_avars.dv_hashtab; +} + +/// Return the hashtable used for local variables in the current funccal. +/// Return NULL if there is no current funccal. +static hashtab_T *get_funccal_local_ht(void) +{ + if (current_funccal == NULL) { + return NULL; + } + return &get_funccal()->l_vars.dv_hashtab; +} + +/// Find the dict and hashtable used for a variable +/// +/// @param[in] name Variable name, possibly with scope prefix. +/// @param[in] name_len Variable name length. +/// @param[out] varname Will be set to the start of the name without scope +/// prefix. +/// @param[out] d Scope dictionary. +/// +/// @return Scope hashtab, NULL if name is not valid. +static hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, + const char **varname, dict_T **d) +{ + hashitem_T *hi; *d = NULL; - if (name[0] == NUL) { + if (name_len == 0) { return NULL; } - if (name[1] != ':') { + if (name_len == 1 || (name_len >= 2 && name[1] != ':')) { // name has implicit scope if (name[0] == ':' || name[0] == AUTOLOAD_CHAR) { // The name must not start with a colon or #. @@ -19629,7 +20287,7 @@ static hashtab_T *find_var_ht_dict(char_u *name, uint8_t **varname, dict_T **d) *varname = name; // "version" is "v:version" in all scopes - hi = hash_find(&compat_hashtab, name); + hi = hash_find_len(&compat_hashtab, name, name_len); if (!HASHITEM_EMPTY(hi)) { return &compat_hashtab; } @@ -19645,26 +20303,27 @@ static hashtab_T *find_var_ht_dict(char_u *name, uint8_t **varname, dict_T **d) *varname = name + 2; if (*name == 'g') { // global variable *d = &globvardict; - } else if (vim_strchr(name + 2, ':') != NULL - || vim_strchr(name + 2, AUTOLOAD_CHAR) != NULL) { + } else if (name_len > 2 + && (memchr(name + 2, ':', name_len - 2) != NULL + || memchr(name + 2, AUTOLOAD_CHAR, name_len - 2) != NULL)) { // There must be no ':' or '#' in the rest of the name if g: was not used return NULL; } - if (*name == 'b') { // buffer variable + if (*name == 'b') { // buffer variable *d = curbuf->b_vars; - } else if (*name == 'w') { // window variable + } else if (*name == 'w') { // window variable *d = curwin->w_vars; - } else if (*name == 't') { // tab page variable + } else if (*name == 't') { // tab page variable *d = curtab->tp_vars; - } else if (*name == 'v') { // v: variable + } else if (*name == 'v') { // v: variable *d = &vimvardict; } else if (*name == 'a' && current_funccal != NULL) { // function argument *d = &get_funccal()->l_avars; } else if (*name == 'l' && current_funccal != NULL) { // local variable *d = &get_funccal()->l_vars; - } else if (*name == 's' // script variable - && current_SID > 0 && current_SID <= ga_scripts.ga_len) { + } else if (*name == 's' // script variable + && current_SID > 0 && current_SID <= ga_scripts.ga_len) { *d = &SCRIPT_SV(current_SID)->sv_dict; } @@ -19672,13 +20331,19 @@ end: return *d ? &(*d)->dv_hashtab : NULL; } -// Find the hashtab used for a variable name. -// Return NULL if the name is not valid. -// Set "varname" to the start of name without ':'. -static hashtab_T *find_var_ht(uint8_t *name, uint8_t **varname) +/// Find the hashtable used for a variable +/// +/// @param[in] name Variable name, possibly with scope prefix. +/// @param[in] name_len Variable name length. +/// @param[out] varname Will be set to the start of the name without scope +/// prefix. +/// +/// @return Scope hashtab, NULL if name is not valid. +static hashtab_T *find_var_ht(const char *name, const size_t name_len, + const char **varname) { dict_T *d; - return find_var_ht_dict(name, varname, &d); + return find_var_ht_dict(name, name_len, varname, &d); } /* @@ -19686,13 +20351,14 @@ static hashtab_T *find_var_ht(uint8_t *name, uint8_t **varname) * Note: see get_tv_string() for how long the pointer remains valid. * Returns NULL when it doesn't exist. */ -char_u *get_var_value(char_u *name) +char_u *get_var_value(const char *const name) { dictitem_T *v; - v = find_var(name, NULL, FALSE); - if (v == NULL) + v = find_var(name, strlen(name), NULL, false); + if (v == NULL) { return NULL; + } return get_tv_string(&v->di_tv); } @@ -19733,7 +20399,7 @@ void new_script_vars(scid_T id) void init_var_dict(dict_T *dict, dictitem_T *dict_var, int scope) { hash_init(&dict->dv_hashtab); - dict->dv_lock = 0; + dict->dv_lock = VAR_UNLOCKED; dict->dv_scope = scope; dict->dv_refcount = DO_NOT_FREE_CNT; dict->dv_copyID = 0; @@ -19813,28 +20479,27 @@ static void delete_var(hashtab_T *ht, hashitem_T *hi) /* * List the value of one internal variable. */ -static void list_one_var(dictitem_T *v, char_u *prefix, int *first) +static void list_one_var(dictitem_T *v, const char *prefix, int *first) { - char_u *s = (char_u *) encode_tv2echo(&v->di_tv, NULL); - list_one_var_a(prefix, v->di_key, v->di_tv.v_type, - s == NULL ? (char_u *)"" : s, first); + char *const s = encode_tv2echo(&v->di_tv, NULL); + list_one_var_a(prefix, (const char *)v->di_key, STRLEN(v->di_key), + v->di_tv.v_type, (s == NULL ? "" : s), first); xfree(s); } -static void -list_one_var_a ( - char_u *prefix, - char_u *name, - int type, - char_u *string, - int *first /* when TRUE clear rest of screen and set to FALSE */ -) +/// @param[in] name_len Length of the name. May be -1, in this case strlen() +/// will be used. +/// @param[in,out] first When true clear rest of screen and set to false. +static void list_one_var_a(const char *prefix, const char *name, + const ptrdiff_t name_len, const int type, + const char *string, int *first) { - /* don't use msg() or msg_attr() to avoid overwriting "v:statusmsg" */ + // don't use msg() or msg_attr() to avoid overwriting "v:statusmsg" msg_start(); msg_puts(prefix); - if (name != NULL) /* "a:" vars don't have a name stored */ - msg_puts(name); + if (name != NULL) { // "a:" vars don't have a name stored + msg_puts_attr_len(name, name_len, 0); + } msg_putchar(' '); msg_advance(22); if (type == VAR_NUMBER) { @@ -19852,10 +20517,10 @@ list_one_var_a ( } else msg_putchar(' '); - msg_outtrans(string); + msg_outtrans((char_u *)string); if (type == VAR_FUNC || type == VAR_PARTIAL) { - msg_puts((char_u *)"()"); + msg_puts("()"); } if (*first) { msg_clr_eos(); @@ -19876,12 +20541,14 @@ set_var ( ) { dictitem_T *v; - char_u *varname; hashtab_T *ht; typval_T oldtv; dict_T *dict; - ht = find_var_ht_dict(name, &varname, &dict); + const size_t name_len = STRLEN(name); + char_u *varname; + ht = find_var_ht_dict((const char *)name, name_len, (const char **)&varname, + &dict); bool watched = is_watched(dict); if (watched) { @@ -19892,7 +20559,14 @@ set_var ( EMSG2(_(e_illvar), name); return; } - v = find_var_in_ht(ht, 0, varname, TRUE); + v = find_var_in_ht(ht, 0, + (const char *)varname, name_len - (size_t)(varname - name), + true); + + // Search in parent scope which is possible to reference from lambda + if (v == NULL) { + v = find_var_in_scoped_ht((const char *)name, name_len, true); + } if ((tv->v_type == VAR_FUNC || tv->v_type == VAR_PARTIAL) && var_check_func_name(name, v == NULL)) { @@ -19901,8 +20575,8 @@ set_var ( if (v != NULL) { // existing variable, need to clear the value - if (var_check_ro(v->di_flags, name, false) - || tv_check_lock(v->di_tv.v_lock, name, false)) { + if (var_check_ro(v->di_flags, (const char *)name, name_len) + || tv_check_lock(v->di_tv.v_lock, (const char *)name, name_len)) { return; } @@ -19975,28 +20649,47 @@ set_var ( } } -// Return true if di_flags "flags" indicates variable "name" is read-only. -// Also give an error message. -static bool var_check_ro(int flags, char_u *name, bool use_gettext) +/// Check whether variable is read-only (DI_FLAGS_RO, DI_FLAGS_RO_SBX) +/// +/// Also gives an error message. +/// +/// @param[in] flags di_flags attribute value. +/// @param[in] name Variable name, for use in error message. +/// @param[in] name_len Variable name length. +/// +/// @return True if variable is read-only: either always or in sandbox when +/// sandbox is enabled, false otherwise. +static bool var_check_ro(const int flags, const char *const name, + const size_t name_len) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { if (flags & DI_FLAGS_RO) { - EMSG2(_(e_readonlyvar), use_gettext ? (char_u *)_(name) : name); + emsgf(_(e_readonlyvar), (int)name_len, name); return true; } if ((flags & DI_FLAGS_RO_SBX) && sandbox) { - EMSG2(_(e_readonlysbx), use_gettext ? (char_u *)_(name) : name); + emsgf(_("E794: Cannot set variable in the sandbox: \"%.*s\""), + (int)name_len, name); return true; } return false; } -// Return true if di_flags "flags" indicates variable "name" is fixed. -// Also give an error message. -static bool var_check_fixed(int flags, char_u *name, bool use_gettext) +/// Check whether variable is fixed (DI_FLAGS_FIX) +/// +/// Also gives an error message. +/// +/// @param[in] flags di_flags attribute value. +/// @param[in] name Variable name, for use in error message. +/// @param[in] name_len Variable name length. +/// +/// @return True if variable is fixed, false otherwise. +static bool var_check_fixed(const int flags, const char *const name, + const size_t name_len) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { if (flags & DI_FLAGS_FIX) { - EMSG2(_("E795: Cannot delete variable %s"), - use_gettext ? (char_u *)_(name) : name); + emsgf(_("E795: Cannot delete variable %.*s"), (int)name_len, name); return true; } return false; @@ -20019,15 +20712,15 @@ var_check_func_name ( EMSG2(_("E704: Funcref variable name must start with a capital: %s"), name); return TRUE; } - /* Don't allow hiding a function. When "v" is not NULL we might be - * assigning another function to the same var, the type is checked - * below. */ - if (new_var && function_exists(name)) { + // Don't allow hiding a function. When "v" is not NULL we might be + // assigning another function to the same var, the type is checked + // below. + if (new_var && function_exists((const char *)name, false)) { EMSG2(_("E705: Variable name conflicts with existing function: %s"), - name); - return TRUE; + name); + return true; } - return FALSE; + return false; } /* @@ -20047,28 +20740,42 @@ static int valid_varname(char_u *varname) return TRUE; } -// Return true if typeval "tv" is set to be locked (immutable). -// Also give an error message, using "name" or _("name") when use_gettext is -// true. -static bool tv_check_lock(int lock, char_u *name, bool use_gettext) +/// Check whether typval is locked +/// +/// Also gives an error message. +/// +/// @param[in] lock Lock status. +/// @param[in] name Value name, for use in error message. +/// @param[in] name_len Value name length. +/// +/// @return True if value is locked. +static bool tv_check_lock(const VarLockStatus lock, + const char *const name, + const size_t name_len) + FUNC_ATTR_WARN_UNUSED_RESULT { - if (lock & VAR_LOCKED) { - EMSG2(_("E741: Value is locked: %s"), - name == NULL - ? (char_u *)_("Unknown") - : use_gettext ? (char_u *)_(name) - : name); - return true; - } - if (lock & VAR_FIXED) { - EMSG2(_("E742: Cannot change value of %s"), - name == NULL - ? (char_u *)_("Unknown") - : use_gettext ? (char_u *)_(name) - : name); - return true; + const char *error_message = NULL; + switch (lock) { + case VAR_UNLOCKED: { + return false; + } + case VAR_LOCKED: { + error_message = N_("E741: Value is locked: %.*s"); + break; + } + case VAR_FIXED: { + error_message = N_("E742: Cannot change value of %.*s"); + break; + } } - return false; + assert(error_message != NULL); + + const char *const unknown_name = _("Unknown"); + + emsgf(_(error_message), (name != NULL ? name_len : strlen(unknown_name)), + (name != NULL ? name : unknown_name)); + + return true; } /* @@ -20222,7 +20929,6 @@ void ex_echo(exarg_T *eap) { char_u *arg = eap->arg; typval_T rettv; - char_u *p; bool needclr = true; bool atstart = true; @@ -20233,19 +20939,20 @@ void ex_echo(exarg_T *eap) * still need to be cleared. E.g., "echo 22,44". */ need_clr_eos = needclr; - p = arg; - if (eval1(&arg, &rettv, !eap->skip) == FAIL) { - /* - * Report the invalid expression unless the expression evaluation - * has been cancelled due to an aborting error, an interrupt, or an - * exception. - */ - if (!aborting()) - EMSG2(_(e_invexpr2), p); - need_clr_eos = FALSE; - break; + { + char_u *p = arg; + if (eval1(&arg, &rettv, !eap->skip) == FAIL) { + // Report the invalid expression unless the expression evaluation + // has been cancelled due to an aborting error, an interrupt, or an + // exception. + if (!aborting()) { + EMSG2(_(e_invexpr2), p); + } + need_clr_eos = false; + break; + } + need_clr_eos = false; } - need_clr_eos = FALSE; if (!eap->skip) { if (atstart) { @@ -20259,9 +20966,11 @@ void ex_echo(exarg_T *eap) msg_sb_eol(); msg_start(); } - } else if (eap->cmdidx == CMD_echo) - msg_puts_attr((char_u *)" ", echo_attr); - char_u *tofree = p = (char_u *) encode_tv2echo(&rettv, NULL); + } else if (eap->cmdidx == CMD_echo) { + msg_puts_attr(" ", echo_attr); + } + char *tofree = encode_tv2echo(&rettv, NULL); + const char *p = tofree; if (p != NULL) { for (; *p != NUL && !got_int; ++p) { if (*p == '\n' || *p == '\r' || *p == TAB) { @@ -20270,15 +20979,16 @@ void ex_echo(exarg_T *eap) msg_clr_eos(); needclr = false; } - msg_putchar_attr(*p, echo_attr); + msg_putchar_attr((uint8_t)(*p), echo_attr); } else { if (has_mbyte) { - int i = (*mb_ptr2len)(p); + int i = (*mb_ptr2len)((const char_u *)p); - (void)msg_outtrans_len_attr(p, i, echo_attr); + (void)msg_outtrans_len_attr((char_u *)p, i, echo_attr); p += i - 1; - } else - (void)msg_outtrans_len_attr(p, 1, echo_attr); + } else { + (void)msg_outtrans_len_attr((char_u *)p, 1, echo_attr); + } } } } @@ -20392,9 +21102,9 @@ void ex_execute(exarg_T *eap) * Returns NULL when no option name found. Otherwise pointer to the char * after the option name. */ -static char_u *find_option_end(char_u **arg, int *opt_flags) +static const char *find_option_end(const char **const arg, int *const opt_flags) { - char_u *p = *arg; + const char *p = *arg; ++p; if (*p == 'g' && p[1] == ':') { @@ -20433,10 +21143,10 @@ void ex_function(exarg_T *eap) char_u *line_arg = NULL; garray_T newargs; garray_T newlines; - int varargs = FALSE; - int mustend = FALSE; + int varargs = false; int flags = 0; ufunc_T *fp; + bool overwrite = false; int indent; int nesting; char_u *skip_until = NULL; @@ -20459,8 +21169,9 @@ void ex_function(exarg_T *eap) if (!HASHITEM_EMPTY(hi)) { --todo; fp = HI2UF(hi); - if (!isdigit(*fp->uf_name)) - list_func_head(fp, FALSE); + if (!func_name_refcount(fp->uf_name)) { + list_func_head(fp, false); + } } } } @@ -20569,7 +21280,7 @@ void ex_function(exarg_T *eap) } if (!got_int) { msg_putchar('\n'); - msg_puts((char_u *)" endfunction"); + msg_puts(" endfunction"); } } else emsg_funcname(N_("E123: Undefined function: %s"), name); @@ -20617,59 +21328,11 @@ void ex_function(exarg_T *eap) EMSG(_("E862: Cannot use g: here")); } - /* - * Isolate the arguments: "arg1, arg2, ...)" - */ - while (*p != ')') { - if (p[0] == '.' && p[1] == '.' && p[2] == '.') { - varargs = TRUE; - p += 3; - mustend = TRUE; - } else { - arg = p; - while (ASCII_ISALNUM(*p) || *p == '_') - ++p; - if (arg == p || isdigit(*arg) - || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0) - || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) { - if (!eap->skip) - EMSG2(_("E125: Illegal argument: %s"), arg); - break; - } - ga_grow(&newargs, 1); - c = *p; - *p = NUL; - arg = vim_strsave(arg); - - /* Check for duplicate argument name. */ - for (int i = 0; i < newargs.ga_len; ++i) - if (STRCMP(((char_u **)(newargs.ga_data))[i], arg) == 0) { - EMSG2(_("E853: Duplicate argument name: %s"), arg); - xfree(arg); - goto erret; - } - - ((char_u **)(newargs.ga_data))[newargs.ga_len] = arg; - *p = c; - newargs.ga_len++; - if (*p == ',') - ++p; - else - mustend = TRUE; - } - p = skipwhite(p); - if (mustend && *p != ')') { - if (!eap->skip) - EMSG2(_(e_invarg2), eap->arg); - break; - } - } - if (*p != ')') { - goto erret; + if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL) { + goto errret_2; } - ++p; // skip the ')' - /* find extra arguments "range", "dict" and "abort" */ + // find extra arguments "range", "dict", "abort" and "closure" for (;; ) { p = skipwhite(p); if (STRNCMP(p, "range", 5) == 0) { @@ -20681,8 +21344,18 @@ void ex_function(exarg_T *eap) } else if (STRNCMP(p, "abort", 5) == 0) { flags |= FC_ABORT; p += 5; - } else + } else if (STRNCMP(p, "closure", 7) == 0) { + flags |= FC_CLOSURE; + p += 7; + if (current_funccal == NULL) { + emsg_funcname(N_ + ("E932 Closure function should not be at top level: %s"), + name == NULL ? (char_u *)"" : name); + goto erret; + } + } else { break; + } } /* When there is a line break use what follows for the function body. @@ -20784,7 +21457,7 @@ void ex_function(exarg_T *eap) if (*p == '!') { p = skipwhite(p + 1); } - p += eval_fname_script(p); + p += eval_fname_script((const char *)p); xfree(trans_function_name(&p, true, 0, NULL, NULL)); if (*skipwhite(p) == '(') { nesting++; @@ -20859,7 +21532,7 @@ void ex_function(exarg_T *eap) * If there are no errors, add the function */ if (fudi.fd_dict == NULL) { - v = find_var(name, &ht, FALSE); + v = find_var((const char *)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); @@ -20872,16 +21545,25 @@ void ex_function(exarg_T *eap) emsg_funcname(e_funcexts, name); goto erret; } - if (fp->uf_refcount > 1 || fp->uf_calls > 0) { + if (fp->uf_calls > 0) { emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"), name); goto erret; } - /* redefine existing function */ - ga_clear_strings(&(fp->uf_args)); - ga_clear_strings(&(fp->uf_lines)); - xfree(name); - name = NULL; + if (fp->uf_refcount > 1) { + // This function is referenced somewhere, don't redefine it but + // create a new one. + (fp->uf_refcount)--; + fp->uf_flags |= FC_REMOVED; + fp = NULL; + overwrite = true; + } else { + // redefine existing function + ga_clear_strings(&(fp->uf_args)); + ga_clear_strings(&(fp->uf_lines)); + xfree(name); + name = NULL; + } } } else { char numbuf[20]; @@ -20892,11 +21574,13 @@ void ex_function(exarg_T *eap) goto erret; } if (fudi.fd_di == NULL) { - if (tv_check_lock(fudi.fd_dict->dv_lock, eap->arg, false)) { + if (tv_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg, + STRLEN(eap->arg))) { // Can't add a function to a locked dictionary goto erret; } - } else if (tv_check_lock(fudi.fd_di->di_tv.v_lock, eap->arg, false)) { + } else if (tv_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg, + STRLEN(eap->arg))) { // Can't change an existing function if it is locked goto erret; } @@ -20916,7 +21600,7 @@ void ex_function(exarg_T *eap) /* Check that the autoload name matches the script name. */ int j = FAIL; if (sourcing_name != NULL) { - scriptname = autoload_name(name); + scriptname = (char_u *)autoload_name((const char *)name, STRLEN(name)); p = vim_strchr(scriptname, '/'); plen = (int)STRLEN(p); slen = (int)STRLEN(sourcing_name); @@ -20933,7 +21617,7 @@ void ex_function(exarg_T *eap) } } - fp = xmalloc(sizeof(ufunc_T) + STRLEN(name)); + fp = xcalloc(1, sizeof(ufunc_T) + STRLEN(name)); if (fudi.fd_dict != NULL) { if (fudi.fd_di == NULL) { @@ -20957,14 +21641,22 @@ void ex_function(exarg_T *eap) /* insert the new function in the function list */ STRCPY(fp->uf_name, name); - if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) { - xfree(fp); - goto erret; + if (overwrite) { + hi = hash_find(&func_hashtab, name); + hi->hi_key = UF2HIKEY(fp); + } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) { + xfree(fp); + goto erret; } + fp->uf_refcount = 1; } - fp->uf_refcount = 1; fp->uf_args = newargs; fp->uf_lines = newlines; + if ((flags & FC_CLOSURE) != 0) { + register_closure(fp); + } else { + fp->uf_scoped = NULL; + } fp->uf_tml_count = NULL; fp->uf_tml_total = NULL; fp->uf_tml_self = NULL; @@ -20979,6 +21671,7 @@ void ex_function(exarg_T *eap) erret: ga_clear_strings(&newargs); +errret_2: ga_clear_strings(&newlines); ret_free: xfree(skip_until); @@ -20988,18 +21681,18 @@ ret_free: need_wait_return |= saved_wait_return; } -/* - * Get a function name, translating "<SID>" and "<SNR>". - * Also handles a Funcref in a List or Dictionary. - * Returns the function name in allocated memory, or NULL for failure. - * flags: - * TFN_INT: internal function name OK - * TFN_QUIET: be quiet - * TFN_NO_AUTOLOAD: do not use script autoloading - * Advances "pp" to just after the function name (if no error). - */ -static char_u * -trans_function_name ( +/// Get a function name, translating "<SID>" and "<SNR>". +/// Also handles a Funcref in a List or Dictionary. +/// flags: +/// TFN_INT: internal function name OK +/// TFN_QUIET: be quiet +/// TFN_NO_AUTOLOAD: do not use script autoloading +/// TFN_NO_DEREF: do not dereference a Funcref +/// Advances "pp" to just after the function name (if no error). +/// +/// @return the function name in allocated memory, or NULL for failure. +char_u * +trans_function_name( char_u **pp, int skip, /* only find the end, don't evaluate */ int flags, @@ -21024,15 +21717,16 @@ trans_function_name ( if ((*pp)[0] == K_SPECIAL && (*pp)[1] == KS_EXTRA && (*pp)[2] == (int)KE_SNR) { *pp += 3; - len = get_id_len(pp) + 3; - return vim_strnsave(start, len); + len = get_id_len((const char **)pp) + 3; + return (char_u *)xmemdupz(start, len); } /* 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); - if (lead > 2) + lead = eval_fname_script((const char *)start); + if (lead > 2) { start += lead; + } /* Note that TFN_ flags use the same values as GLV_ flags. */ end = get_lval(start, NULL, &lv, FALSE, skip, flags, @@ -21068,7 +21762,7 @@ trans_function_name ( *pp = end; } else if (lv.ll_tv->v_type == VAR_PARTIAL && lv.ll_tv->vval.v_partial != NULL) { - name = vim_strsave(lv.ll_tv->vval.v_partial->pt_name); + name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial)); *pp = end; if (partial != NULL) { *partial = lv.ll_tv->vval.v_partial; @@ -21095,14 +21789,15 @@ trans_function_name ( /* 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, + name = deref_func_name((const char *)lv.ll_exp_name, &len, partial, flags & TFN_NO_AUTOLOAD); if (name == lv.ll_exp_name) { name = NULL; } - } else { + } 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((const char *)(*pp), &len, partial, + flags & TFN_NO_AUTOLOAD); if (name == *pp) { name = NULL; } @@ -21147,9 +21842,9 @@ trans_function_name ( 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(*pp)) { - /* It's "s:" or "<SID>" */ + if ((lv.ll_exp_name != NULL && eval_fname_sid((const char *)lv.ll_exp_name)) + || eval_fname_sid((const char *)(*pp))) { + // It's "s:" or "<SID>". if (current_SID <= 0) { EMSG(_(e_usingsid)); goto theend; @@ -21157,13 +21852,14 @@ trans_function_name ( sprintf((char *)sid_buf, "%" PRId64 "_", (int64_t)current_SID); lead += (int)STRLEN(sid_buf); } - } else if (!(flags & TFN_INT) && builtin_function(lv.ll_name, len)) { + } else if (!(flags & TFN_INT) + && builtin_function((const char *)lv.ll_name, len)) { EMSG2(_("E128: Function name must start with a capital or \"s:\": %s"), start); goto theend; } - if (!skip && !(flags & TFN_QUIET)) { + if (!skip && !(flags & TFN_QUIET) && !(flags & TFN_NO_DEREF)) { char_u *cp = vim_strchr(lv.ll_name, ':'); if (cp != NULL && cp < end) { @@ -21194,12 +21890,13 @@ theend: * Return 2 if "p" starts with "s:". * Return 0 otherwise. */ -static int eval_fname_script(char_u *p) +static int eval_fname_script(const char *const p) { // Use mb_stricmp() because in Turkish comparing the "I" may not work with // the standard library function. - if (p[0] == '<' && (mb_strnicmp(p + 1, (char_u *)"SID>", 4) == 0 - || mb_strnicmp(p + 1, (char_u *)"SNR>", 4) == 0)) { + if (p[0] == '<' + && (mb_strnicmp((char_u *)p + 1, (char_u *)"SID>", 4) == 0 + || mb_strnicmp((char_u *)p + 1, (char_u *)"SNR>", 4) == 0)) { return 5; } if (p[0] == 's' && p[1] == ':') { @@ -21208,13 +21905,19 @@ static int eval_fname_script(char_u *p) return 0; } -/* - * Return TRUE if "p" starts with "<SID>" or "s:". - * Only works if eval_fname_script() returned non-zero for "p"! - */ -static int eval_fname_sid(char_u *p) +/// Check whether function name starts with <SID> or s: +/// +/// Only works for names previously checked by eval_fname_script(), if it +/// returned non-zero. +/// +/// @param[in] name Name to check. +/// +/// @return true if it starts with <SID> or s:, false otherwise. +static inline bool eval_fname_sid(const char *const name) + FUNC_ATTR_PURE FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ALL { - return *p == 's' || TOUPPER_ASC(p[2]) == 'I'; + return *name == 's' || TOUPPER_ASC(name[2]) == 'I'; } /* @@ -21228,38 +21931,45 @@ static void list_func_head(ufunc_T *fp, int indent) MSG_PUTS("function "); if (fp->uf_name[0] == K_SPECIAL) { MSG_PUTS_ATTR("<SNR>", hl_attr(HLF_8)); - msg_puts(fp->uf_name + 3); - } else - msg_puts(fp->uf_name); + msg_puts((const char *)fp->uf_name + 3); + } else { + msg_puts((const char *)fp->uf_name); + } msg_putchar('('); int j; - for (j = 0; j < fp->uf_args.ga_len; ++j) { - if (j) - MSG_PUTS(", "); - msg_puts(FUNCARG(fp, j)); + for (j = 0; j < fp->uf_args.ga_len; j++) { + if (j) { + msg_puts(", "); + } + msg_puts((const char *)FUNCARG(fp, j)); } if (fp->uf_varargs) { - if (j) - MSG_PUTS(", "); - MSG_PUTS("..."); + if (j) { + msg_puts(", "); + } + msg_puts("..."); } msg_putchar(')'); - if (fp->uf_flags & FC_ABORT) - MSG_PUTS(" abort"); - if (fp->uf_flags & FC_RANGE) - MSG_PUTS(" range"); - if (fp->uf_flags & FC_DICT) - MSG_PUTS(" dict"); + if (fp->uf_flags & FC_ABORT) { + msg_puts(" abort"); + } + if (fp->uf_flags & FC_RANGE) { + msg_puts(" range"); + } + if (fp->uf_flags & FC_DICT) { + msg_puts(" dict"); + } + if (fp->uf_flags & FC_CLOSURE) { + msg_puts(" closure"); + } msg_clr_eos(); if (p_verbose > 0) last_set_msg(fp->uf_script_ID); } -/* - * Find a function by name, return pointer to it in ufuncs. - * Return NULL for unknown function. - */ -static ufunc_T *find_func(char_u *name) +/// Find a function by name, return pointer to it in ufuncs. +/// @return NULL for unknown function. +static ufunc_T *find_func(const char_u *name) { hashitem_T *hi; @@ -21273,61 +21983,117 @@ static ufunc_T *find_func(char_u *name) void free_all_functions(void) { hashitem_T *hi; + ufunc_T *fp; + uint64_t skipped = 0; + uint64_t todo = 1; + uint64_t used; + + // First clear what the functions contain. Since this may lower the + // reference count of a function, it may also free a function and change + // the hash table. Restart if that happens. + while (todo > 0) { + todo = func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + // Only free functions that are not refcounted, those are + // supposed to be freed when no longer referenced. + fp = HI2UF(hi); + if (func_name_refcount(fp->uf_name)) { + skipped++; + } else { + used = func_hashtab.ht_used; + func_clear(fp, true); + if (used != func_hashtab.ht_used) { + skipped = 0; + break; + } + } + todo--; + } + } + } - /* Need to start all over every time, because func_free() may change the - * hash table. */ - while (func_hashtab.ht_used > 0) - for (hi = func_hashtab.ht_array;; ++hi) + // Now actually free the functions. Need to start all over every time, + // because func_free() may change the hash table. + skipped = 0; + while (func_hashtab.ht_used > skipped) { + todo = func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0; hi++) { if (!HASHITEM_EMPTY(hi)) { - func_free(HI2UF(hi)); - break; + todo--; + // Only free functions that are not refcounted, those are + // supposed to be freed when no longer referenced. + fp = HI2UF(hi); + if (func_name_refcount(fp->uf_name)) { + skipped++; + } else { + func_free(fp); + skipped = 0; + break; + } } + } + } + if (skipped == 0) { + hash_clear(&func_hashtab); + } } #endif -int translated_function_exists(char_u *name) +bool translated_function_exists(const char *name) { if (builtin_function(name, -1)) { return find_internal_func((char *)name) != NULL; } - return find_func(name) != NULL; + return find_func((const char_u *)name) != NULL; } -/* - * Return TRUE if a function "name" exists. - */ -static int function_exists(char_u *name) +/// Check whether function with the given name exists +/// +/// @param[in] name Function name. +/// @param[in] no_deref Whether to dereference a Funcref. +/// +/// @return True if it exists, false otherwise. +static bool function_exists(const char *const name, bool no_deref) { - char_u *nm = name; - char_u *p; - int n = FALSE; + char_u *nm = (char_u *)name; + bool n = false; + int flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD; - p = trans_function_name(&nm, false, TFN_INT|TFN_QUIET|TFN_NO_AUTOLOAD, - NULL, NULL); + if (no_deref) { + flag |= TFN_NO_DEREF; + } + char *const p = (char *)trans_function_name(&nm, false, flag, NULL, NULL); nm = skipwhite(nm); /* Only accept "funcname", "funcname ", "funcname (..." and * "funcname(...", not "funcname!...". */ - if (p != NULL && (*nm == NUL || *nm == '(')) + if (p != NULL && (*nm == NUL || *nm == '(')) { n = translated_function_exists(p); + } xfree(p); return n; } -/// Return TRUE if "name" looks like a builtin function name: starts with a +/// Checks if a builtin function with the given name exists. +/// +/// @param[in] name name of the builtin function to check. +/// @param[in] len length of "name", or -1 for NUL terminated. +/// +/// @return true if "name" looks like a builtin function name: starts with a /// lower case letter and doesn't contain AUTOLOAD_CHAR. -/// "len" is the length of "name", or -1 for NUL terminated. -static bool builtin_function(char_u *name, int len) +static bool builtin_function(const char *name, int len) { if (!ASCII_ISLOWER(name[0])) { - return FALSE; + return false; } - char_u *p = vim_strchr(name, AUTOLOAD_CHAR); + const char *p = (len == -1 + ? strchr(name, AUTOLOAD_CHAR) + : memchr(name, AUTOLOAD_CHAR, (size_t)len)); - return p == NULL - || (len > 0 && p > name + len); + return p == NULL; } /* @@ -21493,44 +22259,45 @@ static int prof_self_cmp(const void *s1, const void *s2) } -/* - * If "name" has a package name try autoloading the script for it. - * Return TRUE if a package was loaded. - */ -static int -script_autoload ( - char_u *name, - int reload /* load script again when already loaded */ -) +/// If name has a package name try autoloading the script for it +/// +/// @param[in] name Variable/function name. +/// @param[in] name_len Name length. +/// @param[in] reload If true, load script again when already loaded. +/// +/// @return true if a package was loaded. +static bool script_autoload(const char *const name, const size_t name_len, + const bool reload) { - char_u *p; - char_u *scriptname, *tofree; - int ret = FALSE; - int i; - - /* If there is no '#' after name[0] there is no package name. */ - p = vim_strchr(name, AUTOLOAD_CHAR); - if (p == NULL || p == name) - return FALSE; + // If there is no '#' after name[0] there is no package name. + const char *p = memchr(name, AUTOLOAD_CHAR, name_len); + if (p == NULL || p == name) { + return false; + } - tofree = scriptname = autoload_name(name); + bool ret = false; + char *tofree = autoload_name(name, name_len); + char *scriptname = tofree; - /* Find the name in the list of previously loaded package names. Skip - * "autoload/", it's always the same. */ - for (i = 0; i < ga_loaded.ga_len; ++i) - if (STRCMP(((char_u **)ga_loaded.ga_data)[i] + 9, scriptname + 9) == 0) + // Find the name in the list of previously loaded package names. Skip + // "autoload/", it's always the same. + int i = 0; + for (; i < ga_loaded.ga_len; i++) { + if (STRCMP(((char **)ga_loaded.ga_data)[i] + 9, scriptname + 9) == 0) { break; - if (!reload && i < ga_loaded.ga_len) - ret = FALSE; /* was loaded already */ - else { - /* Remember the name if it wasn't loaded already. */ + } + } + if (!reload && i < ga_loaded.ga_len) { + ret = false; // Was loaded already. + } else { + // Remember the name if it wasn't loaded already. if (i == ga_loaded.ga_len) { - GA_APPEND(char_u *, &ga_loaded, scriptname); + GA_APPEND(char *, &ga_loaded, scriptname); tofree = NULL; } // Try loading the package from $VIMRUNTIME/autoload/<name>.vim - if (source_runtime(scriptname, 0) == OK) { + if (source_runtime((char_u *)scriptname, 0) == OK) { ret = true; } } @@ -21539,21 +22306,29 @@ script_autoload ( return ret; } -/* - * Return the autoload script name for a function or variable name. - */ -static char_u *autoload_name(char_u *name) +/// Return the autoload script name for a function or variable name +/// +/// @param[in] name Variable/function name. +/// @param[in] name_len Name length. +/// +/// @return [allocated] autoload script name. +static char *autoload_name(const char *const name, const size_t name_len) + FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT { - /* Get the script file name: replace '#' with '/', append ".vim". */ - char_u *scriptname = xmalloc(STRLEN(name) + 14); - STRCPY(scriptname, "autoload/"); - STRCAT(scriptname, name); - *vim_strrchr(scriptname, AUTOLOAD_CHAR) = NUL; - STRCAT(scriptname, ".vim"); - - char_u *p; - while ((p = vim_strchr(scriptname, AUTOLOAD_CHAR)) != NULL) - *p = '/'; + // Get the script file name: replace '#' with '/', append ".vim". + char *const scriptname = xmalloc(name_len + sizeof("autoload/.vim")); + memcpy(scriptname, "autoload/", sizeof("autoload/") - 1); + memcpy(scriptname + sizeof("autoload/") - 1, name, name_len); + size_t auchar_idx = 0; + for (size_t i = sizeof("autoload/") - 1; + i - sizeof("autoload/") + 1 < name_len; + i++) { + if (scriptname[i] == AUTOLOAD_CHAR) { + scriptname[i] = '/'; + auchar_idx = i; + } + } + memcpy(scriptname + auchar_idx, ".vim", sizeof(".vim")); return scriptname; } @@ -21581,8 +22356,10 @@ char_u *get_user_func_name(expand_T *xp, int idx) ++hi; fp = HI2UF(hi); - if (fp->uf_flags & FC_DICT) - return (char_u *)""; /* don't show dict functions */ + if ((fp->uf_flags & FC_DICT) + || STRNCMP(fp->uf_name, "<lambda>", 8) == 0) { + return (char_u *)""; // don't show dict and lambda functions + } if (STRLEN(fp->uf_name) + 4 >= IOSIZE) return fp->uf_name; /* prevents overflow */ @@ -21613,9 +22390,18 @@ static void cat_func_name(char_u *buf, ufunc_T *fp) STRCPY(buf, fp->uf_name); } -/* - * ":delfunction {name}" - */ +/// There are two kinds of function names: +/// 1. ordinary names, function defined with :function +/// 2. numbered functions and lambdas +/// For the first we only count the name stored in func_hashtab as a reference, +/// using function() does not count as a reference, because the function is +/// looked up by name. +static bool func_name_refcount(char_u *name) +{ + return isdigit(*name) || *name == '<'; +} + +/// ":delfunction {name}" void ex_delfunction(exarg_T *eap) { ufunc_T *fp = NULL; @@ -21665,98 +22451,167 @@ void ex_delfunction(exarg_T *eap) /* Delete the dict item that refers to the function, it will * invoke func_unref() and possibly delete the function. */ dictitem_remove(fudi.fd_dict, fudi.fd_di); - } else - func_free(fp); + } else { + // A normal function (not a numbered function or lambda) has a + // refcount of 1 for the entry in the hashtable. When deleting + // it and the refcount is more than one, it should be kept. + // A numbered function or lambda should be kept if the refcount is + // one or more. + if (fp->uf_refcount > (func_name_refcount(fp->uf_name) ? 0 : 1)) { + // Function is still referenced somewhere. Don't free it but + // do remove it from the hashtable. + if (func_remove(fp)) { + fp->uf_refcount--; + } + fp->uf_flags |= FC_DELETED; + } else { + func_clear_free(fp, false); + } + } } } -/* - * Free a function and remove it from the list of functions. - */ -static void func_free(ufunc_T *fp) +/// Remove the function from the function hashtable. If the function was +/// deleted while it still has references this was already done. +/// +/// @return true if the entry was deleted, false if it wasn't found. +static bool func_remove(ufunc_T *fp) { - hashitem_T *hi; + hashitem_T *hi = hash_find(&func_hashtab, UF2HIKEY(fp)); + + if (!HASHITEM_EMPTY(hi)) { + hash_remove(&func_hashtab, hi); + return true; + } + + return false; +} + +/// Free all things that a function contains. Does not free the function +/// itself, use func_free() for that. +/// +/// param[in] force When true, we are exiting. +static void func_clear(ufunc_T *fp, bool force) +{ + if (fp->uf_cleared) { + return; + } + fp->uf_cleared = true; - /* clear this function */ + // clear this function ga_clear_strings(&(fp->uf_args)); ga_clear_strings(&(fp->uf_lines)); xfree(fp->uf_tml_count); xfree(fp->uf_tml_total); xfree(fp->uf_tml_self); + funccal_unref(fp->uf_scoped, fp, force); +} - /* remove the function from the function hashtable */ - hi = hash_find(&func_hashtab, UF2HIKEY(fp)); - if (HASHITEM_EMPTY(hi)) - EMSG2(_(e_intern2), "func_free()"); - else - hash_remove(&func_hashtab, hi); - +/// Free a function and remove it from the list of functions. Does not free +/// what a function contains, call func_clear() first. +/// +/// param[in] fp The function to free. +static void func_free(ufunc_T *fp) +{ + // only remove it when not done already, otherwise we would remove a newer + // version of the function + if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0) { + func_remove(fp); + } xfree(fp); } +/// Free all things that a function contains and free the function itself. +/// +/// param[in] force When true, we are exiting. +static void func_clear_free(ufunc_T *fp, bool force) +{ + func_clear(fp, force); + func_free(fp); +} + /* * Unreference a Function: decrement the reference count and free it when it - * becomes zero. Only for numbered functions. + * becomes zero. */ void func_unref(char_u *name) { - ufunc_T *fp; + ufunc_T *fp = NULL; - if (name != NULL && isdigit(*name)) { - fp = find_func(name); - if (fp == NULL) { + if (name == NULL || !func_name_refcount(name)) { + return; + } + + fp = find_func(name); + if (fp == NULL && isdigit(*name)) { #ifdef EXITFREE - if (!entered_free_all_mem) { - EMSG2(_(e_intern2), "func_unref()"); - } + if (!entered_free_all_mem) { + EMSG2(_(e_intern2), "func_unref()"); + abort(); + } #else EMSG2(_(e_intern2), "func_unref()"); + abort(); #endif - } else { - user_func_unref(fp); + } + if (fp != NULL && --fp->uf_refcount <= 0) { + // Only delete it when it's not being used. Otherwise it's done + // when "uf_calls" becomes zero. + if (fp->uf_calls == 0) { + func_clear_free(fp, false); } } } -static void user_func_unref(ufunc_T *fp) +/// Unreference a Function: decrement the reference count and free it when it +/// becomes zero. +void func_ptr_unref(ufunc_T *fp) { - if (--fp->uf_refcount <= 0) { - // Only delete it when it's not being used. Otherwise it's done + if (fp != NULL && --fp->uf_refcount <= 0) { + // Only delete it when it's not being used. Otherwise it's done // when "uf_calls" becomes zero. if (fp->uf_calls == 0) { - func_free(fp); + func_clear_free(fp, false); } } } -/* - * Count a reference to a Function. - */ +/// Count a reference to a Function. void func_ref(char_u *name) { ufunc_T *fp; - if (name != NULL && isdigit(*name)) { - fp = find_func(name); - if (fp == NULL) - EMSG2(_(e_intern2), "func_ref()"); - else - ++fp->uf_refcount; + if (name == NULL || !func_name_refcount(name)) { + return; + } + fp = find_func(name); + if (fp != NULL) { + (fp->uf_refcount)++; + } else if (isdigit(*name)) { + // Only give an error for a numbered function. + // Fail silently, when named or lambda function isn't found. + EMSG2(_(e_intern2), "func_ref()"); } } -/* - * Call a user function. - */ -static void -call_user_func ( - ufunc_T *fp, /* pointer to function */ - int argcount, /* nr of args */ - typval_T *argvars, /* arguments */ - typval_T *rettv, /* return value */ - linenr_T firstline, /* first line of range */ - linenr_T lastline, /* last line of range */ - dict_T *selfdict /* Dictionary for "self" */ +/// Count a reference to a Function. +void func_ptr_ref(ufunc_T *fp) +{ + if (fp != NULL) { + (fp->uf_refcount)++; + } +} + +/// Call a user function. +static void +call_user_func( + ufunc_T *fp, // pointer to function + int argcount, // nr of args + typval_T *argvars, // arguments + typval_T *rettv, // return value + linenr_T firstline, // first line of range + linenr_T lastline, // last line of range + dict_T *selfdict // Dictionary for "self" ) { char_u *save_sourcing_name; @@ -21768,6 +22623,7 @@ call_user_func ( dictitem_T *v; int fixvar_idx = 0; /* index in fixvar[] */ int ai; + bool islambda = false; char_u numbuf[NUMBUFLEN]; char_u *name; proftime_T wait_start; @@ -21805,14 +22661,21 @@ call_user_func ( fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0); fc->dbg_tick = debug_tick; - /* - * Note about using 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. - */ + // Set up fields for closure. + fc->fc_refcount = 0; + fc->fc_copyID = 0; + ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1); + func_ptr_ref(fp); + + if (STRNCMP(fp->uf_name, "<lambda>", 8) == 0) { + islambda = true; + } + + // Note about using 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); if (selfdict != NULL) { /* Set l:self to "selfdict". Use "name" to avoid a warning from @@ -21854,23 +22717,26 @@ call_user_func ( fc->l_varlist.lv_refcount = DO_NOT_FREE_CNT; fc->l_varlist.lv_lock = VAR_FIXED; - /* - * Set a:firstline to "firstline" and a:lastline to "lastline". - * Set a:name to named arguments. - * Set a:N to the "..." arguments. - */ + // Set a:firstline to "firstline" and a:lastline to "lastline". + // Set a:name to named arguments. + // Set a:N to the "..." arguments. add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "firstline", - (varnumber_T)firstline); + (varnumber_T)firstline); add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "lastline", - (varnumber_T)lastline); - for (int i = 0; i < argcount; ++i) { + (varnumber_T)lastline); + for (int i = 0; i < argcount; i++) { + bool addlocal = false; + ai = i - fp->uf_args.ga_len; - if (ai < 0) - /* named argument a:name */ + if (ai < 0) { + // named argument a:name name = FUNCARG(fp, i); - else { - /* "..." argument a:1, a:2, etc. */ - sprintf((char *)numbuf, "%d", ai + 1); + if (islambda) { + addlocal = true; + } + } else { + // "..." argument a:1, a:2, etc. + snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1); name = numbuf; } if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) { @@ -21881,13 +22747,21 @@ call_user_func ( v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC; } STRCPY(v->di_key, name); - hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v)); - /* Note: the values are copied directly to avoid alloc/free. - * "argvars" must have VAR_FIXED for v_lock. */ + // Note: the values are copied directly to avoid alloc/free. + // "argvars" must have VAR_FIXED for v_lock. v->di_tv = argvars[i]; v->di_tv.v_lock = VAR_FIXED; + if (addlocal) { + // Named arguments can be accessed without the "a:" prefix in lambda + // expressions. Add to the l: dict. + copy_tv(&v->di_tv, &v->di_tv); + hash_add(&fc->l_vars.dv_hashtab, DI2HIKEY(v)); + } else { + hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v)); + } + if (ai >= 0 && ai < MAX_FUNC_ARGS) { list_append(&fc->l_varlist, &fc->l_listitems[ai]); fc->l_listitems[ai].li_tv = argvars[i]; @@ -21927,24 +22801,24 @@ call_user_func ( smsg(_("calling %s"), sourcing_name); if (p_verbose >= 14) { - char_u buf[MSG_BUF_LEN]; - - msg_puts((char_u *)"("); - for (int i = 0; i < argcount; ++i) { + msg_puts("("); + for (int i = 0; i < argcount; i++) { if (i > 0) { - msg_puts((char_u *)", "); + msg_puts(", "); } if (argvars[i].v_type == VAR_NUMBER) { msg_outnum((long)argvars[i].vval.v_number); } else { // Do not want errors such as E724 here. emsg_off++; - char_u *s = (char_u *) encode_tv2string(&argvars[i], NULL); - char_u *tofree = s; + char *tofree = encode_tv2string(&argvars[i], NULL); + char *s = tofree; emsg_off--; if (s != NULL) { - if (vim_strsize(s) > MSG_BUF_CLEN) { - trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN); + if (vim_strsize((char_u *)s) > MSG_BUF_CLEN) { + char buf[MSG_BUF_LEN]; + trunc_string((char_u *)s, (char_u *)buf, MSG_BUF_CLEN, + sizeof(buf)); s = buf; } msg_puts(s); @@ -21952,9 +22826,9 @@ call_user_func ( } } } - msg_puts((char_u *)")"); + msg_puts(")"); } - msg_puts((char_u *)"\n"); /* don't overwrite this either */ + msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); --no_wait_return; @@ -22045,7 +22919,7 @@ call_user_func ( xfree(tofree); } } - msg_puts((char_u *)"\n"); /* don't overwrite this either */ + msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); --no_wait_return; @@ -22063,7 +22937,7 @@ call_user_func ( verbose_enter_scroll(); smsg(_("continuing in %s"), sourcing_name); - msg_puts((char_u *)"\n"); /* don't overwrite this either */ + msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); --no_wait_return; @@ -22073,20 +22947,21 @@ call_user_func ( current_funccal = fc->caller; --depth; - /* If the a:000 list and the l: and a: dicts are not referenced we can - * free the funccall_T and what's in it. */ + // 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 (fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT - && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT) { - free_funccal(fc, FALSE); + && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT + && fc->fc_refcount <= 0) { + free_funccal(fc, false); } else { hashitem_T *hi; listitem_T *li; int todo; - /* "fc" is still in use. This can happen when returning "a:000" or - * assigning "l:" to a global variable. - * Link "fc" in the list for garbage collection later. */ + // "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; previous_funccal = fc; @@ -22105,9 +22980,9 @@ call_user_func ( copy_tv(&li->li_tv, &li->li_tv); } - if (--fp->uf_calls <= 0 && isdigit(*fp->uf_name) && fp->uf_refcount <= 0) { + if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) { // Function was unreferenced while being used, free it now. - func_free(fp); + func_clear_free(fp, false); } // restore search patterns and redo buffer if (did_save_redo) { @@ -22116,15 +22991,46 @@ call_user_func ( restore_search_patterns(); } -/* - * Return TRUE if items in "fc" do not have "copyID". That means they are not - * referenced from anywhere that is in use. - */ +/// Unreference "fc": decrement the reference count and free it when it +/// becomes zero. "fp" is detached from "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; + } + + if (--fc->fc_refcount <= 0 && (force || ( + fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT + && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT + && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT))) { + for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) { + if (fc == *pfc) { + *pfc = fc->caller; + free_funccal(fc, true); + 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; + } + } +} + +/// @return true if items in "fc" do not have "copyID". That means they are not +/// 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; + && fc->l_avars.dv_copyID != copyID + && fc->fc_copyID != copyID; } /* @@ -22138,18 +23044,34 @@ free_funccal ( { listitem_T *li; - /* The a: variables typevals may not have been allocated, only free the - * allocated variables. */ + for (int i = 0; i < fc->fc_funcs.ga_len; i++) { + ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; + + // When garbage collecting a funccall_T may be freed before the + // function that references it, clear its uf_scoped field. + // The function may have been redefined and point to another + // funccal_T, don't clear it then. + if (fp != NULL && fp->uf_scoped == fc) { + fp->uf_scoped = NULL; + } + } + ga_clear(&fc->fc_funcs); + + // The a: variables typevals may not have been allocated, only free the + // allocated variables. vars_clear_ext(&fc->l_avars.dv_hashtab, free_val); /* free all l: variables */ vars_clear(&fc->l_vars.dv_hashtab); - /* Free the a:000 variables if they were allocated. */ - if (free_val) - for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next) + // Free the a:000 variables if they were allocated. + if (free_val) { + for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next) { clear_tv(&li->li_tv); + } + } + func_ptr_unref(fc->func); xfree(fc); } @@ -22468,6 +23390,72 @@ static var_flavour_T var_flavour(char_u *varname) } } +/// 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) { + return NULL; + } + + funccall_T *old_current_funccal = current_funccal; + hashitem_T *hi = NULL; + const size_t namelen = strlen(name); + const char *varname; + + // Search in parent scope which is possible to reference from lambda + current_funccal = current_funccal->func->uf_scoped; + while (current_funccal != NULL) { + hashtab_T *ht = find_var_ht(name, namelen, &varname); + if (ht != NULL && *varname != NUL) { + hi = hash_find_len(ht, varname, namelen - (varname - name)); + if (!HASHITEM_EMPTY(hi)) { + *pht = ht; + break; + } + } + if (current_funccal == current_funccal->func->uf_scoped) { + break; + } + current_funccal = current_funccal->func->uf_scoped; + } + current_funccal = old_current_funccal; + + return hi; +} + +/// 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) { + return NULL; + } + + dictitem_T *v = NULL; + funccall_T *old_current_funccal = current_funccal; + const char *varname; + + // Search in parent scope which is possible to reference from lambda + current_funccal = current_funccal->func->uf_scoped; + while (current_funccal) { + hashtab_T *ht = find_var_ht(name, namelen, &varname); + if (ht != NULL && *varname != NUL) { + v = find_var_in_ht(ht, *name, varname, + namelen - (size_t)(varname - name), no_autoload); + if (v != NULL) { + break; + } + } + if (current_funccal == current_funccal->func->uf_scoped) { + break; + } + current_funccal = current_funccal->func->uf_scoped; + } + current_funccal = old_current_funccal; + + return v; +} + /// Iterate over global variables /// /// @warning No modifications to global variable dictionary must be performed @@ -22867,7 +23855,7 @@ repeat: sub = vim_strnsave(s, (int)(p - s)); str = vim_strnsave(*fnamep, *fnamelen); *usedlen = (size_t)(p + 1 - src); - s = do_string_sub(str, pat, sub, flags); + s = do_string_sub(str, pat, sub, NULL, flags); *fnamep = s; *fnamelen = STRLEN(s); xfree(*bufp); @@ -22903,12 +23891,12 @@ repeat: return valid; } -/* - * Perform a substitution on "str" with pattern "pat" and substitute "sub". - * "flags" can be "g" to do a global substitute. - * Returns an allocated string, NULL for error. - */ -char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, char_u *flags) +/// Perform a substitution on "str" with pattern "pat" and substitute "sub". +/// When "sub" is NULL "expr" is used, must be a VAR_FUNC or VAR_PARTIAL. +/// "flags" can be "g" to do a global substitute. +/// Returns an allocated string, NULL for error. +char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, + typval_T *expr, char_u *flags) { int sublen; regmatch_T regmatch; @@ -22946,23 +23934,21 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, char_u *flags) zero_width = regmatch.startp[0]; } - /* - * Get some space for a temporary buffer to do the substitution - * into. It will contain: - * - The text up to where the match is. - * - The substituted text. - * - The text after the match. - */ - sublen = vim_regsub(®match, sub, tail, FALSE, TRUE, FALSE); + // Get some space for a temporary buffer to do the substitution + // into. It will contain: + // - The text up to where the match is. + // - The substituted text. + // - The text after the match. + sublen = vim_regsub(®match, sub, expr, tail, false, true, false); ga_grow(&ga, (int)((end - tail) + sublen - (regmatch.endp[0] - regmatch.startp[0]))); /* copy the text up to where the match is */ int i = (int)(regmatch.startp[0] - tail); memmove((char_u *)ga.ga_data + ga.ga_len, tail, (size_t)i); - /* add the substituted text */ - (void)vim_regsub(®match, sub, (char_u *)ga.ga_data - + ga.ga_len + i, TRUE, TRUE, FALSE); + // add the substituted text + (void)vim_regsub(®match, sub, expr, (char_u *)ga.ga_data + + ga.ga_len + i, true, true, false); ga.ga_len += i + sublen - 1; tail = regmatch.endp[0]; if (*tail == NUL) @@ -22979,11 +23965,12 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, char_u *flags) char_u *ret = vim_strsave(ga.ga_data == NULL ? str : (char_u *)ga.ga_data); ga_clear(&ga); - if (p_cpo == empty_option) + if (p_cpo == empty_option) { p_cpo = save_cpo; - else - /* Darn, evaluating {sub} expression changed the value. */ + } else { + // Darn, evaluating {sub} expression or {expr} changed the value. free_string_option(save_cpo); + } return ret; } @@ -23349,6 +24336,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) &rettv, 2, argvars, + NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, @@ -23369,10 +24357,12 @@ bool eval_has_provider(char *name) { #define check_provider(name) \ if (has_##name == -1) { \ - has_##name = !!find_func((uint8_t *)"provider#" #name "#Call"); \ + has_##name = !!find_func((char_u *)"provider#" #name "#Call"); \ if (!has_##name) { \ - script_autoload((uint8_t *)"provider#" #name "#Call", false); \ - has_##name = !!find_func((uint8_t *)"provider#" #name "#Call"); \ + script_autoload("provider#" #name "#Call", \ + sizeof("provider#" #name "#Call") - 1, \ + false); \ + has_##name = !!find_func((char_u *)"provider#" #name "#Call"); \ } \ } |