diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2025-02-05 23:09:29 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2025-02-05 23:09:29 +0000 |
commit | d5f194ce780c95821a855aca3c19426576d28ae0 (patch) | |
tree | d45f461b19f9118ad2bb1f440a7a08973ad18832 /src/nvim/eval/userfunc.c | |
parent | c5d770d311841ea5230426cc4c868e8db27300a8 (diff) | |
parent | 44740e561fc93afe3ebecfd3618bda2d2abeafb0 (diff) | |
download | rneovim-d5f194ce780c95821a855aca3c19426576d28ae0.tar.gz rneovim-d5f194ce780c95821a855aca3c19426576d28ae0.tar.bz2 rneovim-d5f194ce780c95821a855aca3c19426576d28ae0.zip |
Diffstat (limited to 'src/nvim/eval/userfunc.c')
-rw-r--r-- | src/nvim/eval/userfunc.c | 793 |
1 files changed, 448 insertions, 345 deletions
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 0050a77fe3..84ee27493c 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -78,7 +78,6 @@ static funccall_T *current_funccal = NULL; // item in it is still being used. static funccall_T *previous_funccal = NULL; -static const char *e_unknown_function_str = N_("E117: Unknown function: %s"); static const char *e_funcexts = N_("E122: Function %s already exists, add ! to replace it"); static const char *e_funcdict = N_("E717: Dictionary entry already exists"); static const char *e_funcref = N_("E718: Funcref required"); @@ -105,6 +104,48 @@ hashtab_T *func_tbl_get(void) return &func_hashtab; } +/// Get one function argument. +/// Return a pointer to after the type. +/// When something is wrong return "arg". +static char *one_function_arg(char *arg, garray_T *newargs, bool skip) +{ + char *p = arg; + + while (ASCII_ISALNUM(*p) || *p == '_') { + p++; + } + if (arg == p || isdigit((uint8_t)(*arg)) + || (p - arg == 9 && strncmp(arg, "firstline", 9) == 0) + || (p - arg == 8 && strncmp(arg, "lastline", 8) == 0)) { + if (!skip) { + semsg(_("E125: Illegal argument: %s"), arg); + } + return arg; + } + + if (newargs != NULL) { + ga_grow(newargs, 1); + uint8_t c = (uint8_t)(*p); + *p = NUL; + char *arg_copy = xstrdup(arg); + + // Check for duplicate argument name. + for (int i = 0; i < newargs->ga_len; i++) { + if (strcmp(((char **)(newargs->ga_data))[i], arg_copy) == 0) { + semsg(_("E853: Duplicate argument name: %s"), arg_copy); + xfree(arg_copy); + return arg; + } + } + ((char **)(newargs->ga_data))[newargs->ga_len] = arg_copy; + newargs->ga_len++; + + *p = (char)c; + } + + return p; +} + /// Get function arguments. static int get_function_args(char **argp, char endchar, garray_T *newargs, int *varargs, garray_T *default_args, bool skip) @@ -135,36 +176,11 @@ static int get_function_args(char **argp, char endchar, garray_T *newargs, int * mustend = true; } else { arg = p; - while (ASCII_ISALNUM(*p) || *p == '_') { - p++; - } - if (arg == p || isdigit((uint8_t)(*arg)) - || (p - arg == 9 && strncmp(arg, "firstline", 9) == 0) - || (p - arg == 8 && strncmp(arg, "lastline", 8) == 0)) { - if (!skip) { - semsg(_("E125: Illegal argument: %s"), arg); - } + p = one_function_arg(p, newargs, skip); + if (p == arg) { break; } - if (newargs != NULL) { - ga_grow(newargs, 1); - uint8_t c = (uint8_t)(*p); - *p = NUL; - arg = xstrdup(arg); - - // Check for duplicate argument name. - for (int i = 0; i < newargs->ga_len; i++) { - if (strcmp(((char **)(newargs->ga_data))[i], arg) == 0) { - semsg(_("E853: Duplicate argument name: %s"), arg); - xfree(arg); - goto err_ret; - } - } - ((char **)(newargs->ga_data))[newargs->ga_len] = arg; - newargs->ga_len++; - *p = (char)c; - } if (*skipwhite(p) == '=' && default_args != NULL) { typval_T rettv; @@ -248,25 +264,47 @@ static void register_closure(ufunc_T *fp) [current_funccal->fc_ufuncs.ga_len++] = fp; } +static char lambda_name[8 + NUMBUFLEN]; +static size_t lambda_namelen = 0; + /// @return a name for a lambda. Returned in static memory. char *get_lambda_name(void) { - static char name[30]; static int lambda_no = 0; - snprintf(name, sizeof(name), "<lambda>%d", ++lambda_no); - return name; + int n = snprintf(lambda_name, sizeof(lambda_name), "<lambda>%d", ++lambda_no); + if (n < 1) { + lambda_namelen = 0; + } else if (n >= (int)sizeof(lambda_name)) { + lambda_namelen = sizeof(lambda_name) - 1; + } else { + lambda_namelen = (size_t)n; + } + + return lambda_name; } -static void set_ufunc_name(ufunc_T *fp, char *name) +/// Get the length of the last lambda name. +size_t get_lambda_name_len(void) { + return lambda_namelen; +} + +/// Allocate a "ufunc_T" for a function called "name". +static ufunc_T *alloc_ufunc(const char *name, size_t namelen) +{ + size_t len = offsetof(ufunc_T, uf_name) + namelen + 1; + ufunc_T *fp = xcalloc(1, len); STRCPY(fp->uf_name, name); + fp->uf_namelen = namelen; if ((uint8_t)name[0] == K_SPECIAL) { - fp->uf_name_exp = xmalloc(strlen(name) + 3); - STRCPY(fp->uf_name_exp, "<SNR>"); - strcat(fp->uf_name_exp, fp->uf_name + 3); + len = namelen + 3; + fp->uf_name_exp = xmalloc(len); + snprintf(fp->uf_name_exp, len, "<SNR>%s", fp->uf_name + 3); } + + return fp; } /// Parse a lambda expression and get a Funcref from "*arg". @@ -334,8 +372,9 @@ int get_lambda_tv(char **arg, typval_T *rettv, evalarg_T *evalarg) garray_T newlines; char *name = get_lambda_name(); + size_t namelen = get_lambda_name_len(); - fp = xcalloc(1, offsetof(ufunc_T, uf_name) + strlen(name) + 1); + fp = alloc_ufunc(name, namelen); pt = xcalloc(1, sizeof(partial_T)); ga_init(&newlines, (int)sizeof(char *), 1); @@ -353,7 +392,6 @@ int get_lambda_tv(char **arg, typval_T *rettv, evalarg_T *evalarg) } fp->uf_refcount = 1; - set_ufunc_name(fp, name); hash_add(&func_hashtab, UF2HIKEY(fp)); fp->uf_args = newargs; ga_init(&fp->uf_def_args, (int)sizeof(char *), 1); @@ -393,7 +431,10 @@ int get_lambda_tv(char **arg, typval_T *rettv, evalarg_T *evalarg) errret: ga_clear_strings(&newargs); - xfree(fp); + if (fp != NULL) { + xfree(fp->uf_name_exp); + xfree(fp); + } xfree(pt); if (evalarg != NULL && evalarg->eval_tofree == NULL) { evalarg->eval_tofree = tofree; @@ -611,33 +652,36 @@ static char *fname_trans_sid(const char *const name, char *const fname_buf, char int *const error) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - const int llen = eval_fname_script(name); - if (llen == 0) { + const char *script_name = name + eval_fname_script(name); + if (script_name == name) { return (char *)name; // no prefix } fname_buf[0] = (char)K_SPECIAL; fname_buf[1] = (char)KS_EXTRA; fname_buf[2] = KE_SNR; - int i = 3; - if (eval_fname_sid(name)) { // "<SID>" or "s:" + size_t fname_buflen = 3; + if (!eval_fname_sid(name)) { // "<SID>" or "s:" + fname_buf[fname_buflen] = NUL; + } else { if (current_sctx.sc_sid <= 0) { *error = FCERR_SCRIPT; } else { - snprintf(fname_buf + i, (size_t)(FLEN_FIXED + 1 - i), "%" PRId64 "_", - (int64_t)current_sctx.sc_sid); - i = (int)strlen(fname_buf); + fname_buflen += (size_t)snprintf(fname_buf + fname_buflen, + FLEN_FIXED + 1 - fname_buflen, + "%" PRIdSCID "_", + current_sctx.sc_sid); } } + size_t fnamelen = fname_buflen + strlen(script_name); char *fname; - if ((size_t)i + strlen(name + llen) < FLEN_FIXED) { - STRCPY(fname_buf + i, name + llen); + if (fnamelen < FLEN_FIXED) { + STRCPY(fname_buf + fname_buflen, script_name); fname = fname_buf; } else { - fname = xmalloc((size_t)i + strlen(name + llen) + 1); + fname = xmalloc(fnamelen + 1); *tofree = fname; - memmove(fname, fname_buf, (size_t)i); - STRCPY(fname + i, name + llen); + snprintf(fname, fnamelen + 1, "%s%s", fname_buf, script_name); } return fname; } @@ -695,20 +739,20 @@ ufunc_T *find_func(const char *name) /// Copy the function name of "fp" to buffer "buf". /// "buf" must be able to hold the function name plus three bytes. /// Takes care of script-local function names. -static void cat_func_name(char *buf, size_t buflen, ufunc_T *fp) +static int cat_func_name(char *buf, size_t bufsize, ufunc_T *fp) { int len = -1; - size_t uflen = strlen(fp->uf_name); + size_t uflen = fp->uf_namelen; assert(uflen > 0); if ((uint8_t)fp->uf_name[0] == K_SPECIAL && uflen > 3) { - len = snprintf(buf, buflen, "<SNR>%s", fp->uf_name + 3); + len = snprintf(buf, bufsize, "<SNR>%s", fp->uf_name + 3); } else { - len = snprintf(buf, buflen, "%s", fp->uf_name); + len = snprintf(buf, bufsize, "%s", fp->uf_name); } - (void)len; // Avoid unused warning on release builds assert(len > 0); + return (len >= (int)bufsize) ? (int)bufsize - 1 : len; } /// Add a number variable "name" to dict "dp" with value "nr". @@ -875,7 +919,6 @@ static void func_clear_items(ufunc_T *fp) ga_clear_strings(&(fp->uf_args)); ga_clear_strings(&(fp->uf_def_args)); ga_clear_strings(&(fp->uf_lines)); - XFREE_CLEAR(fp->uf_name_exp); if (fp->uf_flags & FC_LUAREF) { api_free_luaref(fp->uf_luaref); @@ -914,6 +957,8 @@ static void func_free(ufunc_T *fp) if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0) { func_remove(fp); } + + XFREE_CLEAR(fp->uf_name_exp); xfree(fp); } @@ -967,6 +1012,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett bool islambda = false; char numbuf[NUMBUFLEN]; char *name; + size_t namelen; typval_T *tv_to_free[MAX_FUNC_ARGS]; int tv_to_free_len = 0; proftime_T wait_start; @@ -1092,23 +1138,25 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett break; } } + + namelen = strlen(name); } else { if ((fp->uf_flags & FC_NOARGS) != 0) { // Bail out if no a: arguments used (in lambda). break; } // "..." argument a:1, a:2, etc. - snprintf(numbuf, sizeof(numbuf), "%d", ai + 1); + namelen = (size_t)snprintf(numbuf, sizeof(numbuf), "%d", ai + 1); name = numbuf; } - if (fixvar_idx < FIXVAR_CNT && strlen(name) <= VAR_SHORT_LEN) { + if (fixvar_idx < FIXVAR_CNT && namelen <= VAR_SHORT_LEN) { v = (dictitem_T *)&fc->fc_fixvar[fixvar_idx++]; v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + STRCPY(v->di_key, name); } else { - v = xmalloc(sizeof(dictitem_T) + strlen(name)); - v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC; + v = tv_dict_item_alloc_len(name, namelen); + v->di_flags |= DI_FLAGS_RO | DI_FLAGS_FIX; } - STRCPY(v->di_key, name); // Note: the values are copied directly to avoid alloc/free. // "argvars" must have VAR_FIXED for v_lock. @@ -2085,7 +2133,7 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part len = (int)(end - lv.ll_name); } - size_t sid_buf_len = 0; + size_t sid_buflen = 0; char sid_buf[20]; // Copy the function name to allocated memory. @@ -2095,15 +2143,16 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part lead = 0; // do nothing } else if (lead > 0) { lead = 3; - if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) || eval_fname_sid(*pp)) { + if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) + || eval_fname_sid(*pp)) { // It's "s:" or "<SID>". if (current_sctx.sc_sid <= 0) { emsg(_(e_usingsid)); goto theend; } - sid_buf_len = - (size_t)snprintf(sid_buf, sizeof(sid_buf), "%" PRIdSCID "_", current_sctx.sc_sid); - lead += (int)sid_buf_len; + sid_buflen = (size_t)snprintf(sid_buf, sizeof(sid_buf), "%" PRIdSCID "_", + current_sctx.sc_sid); + lead += (int)sid_buflen; } } else if (!(flags & TFN_INT) && builtin_function(lv.ll_name, (int)lv.ll_name_len)) { semsg(_("E128: Function name must start with a capital or \"s:\": %s"), @@ -2125,8 +2174,8 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part name[0] = (char)K_SPECIAL; name[1] = (char)KS_EXTRA; name[2] = KE_SNR; - if (sid_buf_len > 0) { // If it's "<SID>" - memcpy(name + 3, sid_buf, sid_buf_len); + if (sid_buflen > 0) { // If it's "<SID>" + memcpy(name + 3, sid_buf, sid_buflen); } } memmove(name + lead, lv.ll_name, (size_t)len); @@ -2162,12 +2211,12 @@ char *get_scriptlocal_funcname(char *funcname) char sid_buf[25]; // Expand s: and <SID> prefix into <SNR>nr_<name> - snprintf(sid_buf, sizeof(sid_buf), "<SNR>%" PRId64 "_", - (int64_t)current_sctx.sc_sid); + size_t sid_buflen = (size_t)snprintf(sid_buf, sizeof(sid_buf), "<SNR>%" PRIdSCID "_", + current_sctx.sc_sid); const int off = *funcname == 's' ? 2 : 5; - char *newname = xmalloc(strlen(sid_buf) + strlen(funcname + off) + 1); - STRCPY(newname, sid_buf); - strcat(newname, funcname + off); + size_t newnamesize = sid_buflen + strlen(funcname + off) + 1; + char *newname = xmalloc(newnamesize); + snprintf(newname, newnamesize, "%s%s", sid_buf, funcname + off); return newname; } @@ -2193,8 +2242,6 @@ char *save_function_name(char **name, bool skip, int flags, funcdict_T *fudi) return saved; } -#define MAX_FUNC_NESTING 50 - /// List functions. /// /// @param regmatch When NULL, all of them. @@ -2225,12 +2272,291 @@ static void list_functions(regmatch_T *regmatch) } } +#define MAX_FUNC_NESTING 50 + +/// Read the body of a function, put every line in "newlines". +/// This stops at "endfunction". +/// "newlines" must already have been initialized. +static int get_function_body(exarg_T *eap, garray_T *newlines, char *line_arg_in, + char **line_to_free, bool show_block) +{ + bool saved_wait_return = need_wait_return; + char *line_arg = line_arg_in; + int indent = 2; + int nesting = 0; + char *skip_until = NULL; + int ret = FAIL; + bool is_heredoc = false; + char *heredoc_trimmed = NULL; + size_t heredoc_trimmedlen = 0; + bool do_concat = true; + + while (true) { + if (KeyTyped) { + msg_scroll = true; + saved_wait_return = false; + } + need_wait_return = false; + + char *theline; + char *p; + char *arg; + + if (line_arg != NULL) { + // Use eap->arg, split up in parts by line breaks. + theline = line_arg; + p = vim_strchr(theline, '\n'); + if (p == NULL) { + line_arg += strlen(line_arg); + } else { + *p = NUL; + line_arg = p + 1; + } + } else { + xfree(*line_to_free); + if (eap->ea_getline == NULL) { + theline = getcmdline(':', 0, indent, do_concat); + } else { + theline = eap->ea_getline(':', eap->cookie, indent, do_concat); + } + *line_to_free = theline; + } + if (KeyTyped) { + lines_left = Rows - 1; + } + if (theline == NULL) { + if (skip_until != NULL) { + semsg(_(e_missing_heredoc_end_marker_str), skip_until); + } else { + emsg(_("E126: Missing :endfunction")); + } + goto theend; + } + if (show_block) { + assert(indent >= 0); + ui_ext_cmdline_block_append((size_t)indent, theline); + } + + // Detect line continuation: SOURCING_LNUM increased more than one. + linenr_T sourcing_lnum_off = get_sourced_lnum(eap->ea_getline, eap->cookie); + if (SOURCING_LNUM < sourcing_lnum_off) { + sourcing_lnum_off -= SOURCING_LNUM; + } else { + sourcing_lnum_off = 0; + } + + if (skip_until != NULL) { + // Don't check for ":endfunc" between + // * ":append" and "." + // * ":python <<EOF" and "EOF" + // * ":let {var-name} =<< [trim] {marker}" and "{marker}" + if (heredoc_trimmed == NULL + || (is_heredoc && skipwhite(theline) == theline) + || strncmp(theline, heredoc_trimmed, heredoc_trimmedlen) == 0) { + if (heredoc_trimmed == NULL) { + p = theline; + } else if (is_heredoc) { + p = skipwhite(theline) == theline ? theline : theline + heredoc_trimmedlen; + } else { + p = theline + heredoc_trimmedlen; + } + if (strcmp(p, skip_until) == 0) { + XFREE_CLEAR(skip_until); + XFREE_CLEAR(heredoc_trimmed); + heredoc_trimmedlen = 0; + do_concat = true; + is_heredoc = false; + } + } + } else { + // skip ':' and blanks + for (p = theline; ascii_iswhite(*p) || *p == ':'; p++) {} + + // Check for "endfunction". + if (checkforcmd(&p, "endfunction", 4) && nesting-- == 0) { + if (*p == '!') { + p++; + } + char *nextcmd = NULL; + if (*p == '|') { + nextcmd = p + 1; + } else if (line_arg != NULL && *skipwhite(line_arg) != NUL) { + nextcmd = line_arg; + } else if (*p != NUL && *p != '"' && p_verbose > 0) { + swmsg(true, _("W22: Text found after :endfunction: %s"), p); + } + if (nextcmd != NULL) { + // Another command follows. If the line came from "eap" we + // can simply point into it, otherwise we need to change + // "eap->cmdlinep". + eap->nextcmd = nextcmd; + if (*line_to_free != NULL) { + xfree(*eap->cmdlinep); + *eap->cmdlinep = *line_to_free; + *line_to_free = NULL; + } + } + break; + } + + // Increase indent inside "if", "while", "for" and "try", decrease + // at "end". + if (indent > 2 && strncmp(p, "end", 3) == 0) { + indent -= 2; + } else if (strncmp(p, "if", 2) == 0 + || strncmp(p, "wh", 2) == 0 + || strncmp(p, "for", 3) == 0 + || strncmp(p, "try", 3) == 0) { + indent += 2; + } + + // Check for defining a function inside this function. + if (checkforcmd(&p, "function", 2)) { + if (*p == '!') { + p = skipwhite(p + 1); + } + p += eval_fname_script(p); + xfree(trans_function_name(&p, true, 0, NULL, NULL)); + if (*skipwhite(p) == '(') { + if (nesting == MAX_FUNC_NESTING - 1) { + emsg(_(e_function_nesting_too_deep)); + } else { + nesting++; + indent += 2; + } + } + } + + // Check for ":append", ":change", ":insert". + p = skip_range(p, NULL); + if ((p[0] == 'a' && (!ASCII_ISALPHA(p[1]) || p[1] == 'p')) + || (p[0] == 'c' + && (!ASCII_ISALPHA(p[1]) + || (p[1] == 'h' && (!ASCII_ISALPHA(p[2]) + || (p[2] == 'a' + && (strncmp(&p[3], "nge", 3) != 0 + || !ASCII_ISALPHA(p[6]))))))) + || (p[0] == 'i' + && (!ASCII_ISALPHA(p[1]) || (p[1] == 'n' + && (!ASCII_ISALPHA(p[2]) + || (p[2] == 's')))))) { + skip_until = xmemdupz(".", 1); + } + + // heredoc: Check for ":python <<EOF", ":lua <<EOF", etc. + arg = skipwhite(skiptowhite(p)); + if (arg[0] == '<' && arg[1] == '<' + && ((p[0] == 'p' && p[1] == 'y' + && (!ASCII_ISALNUM(p[2]) || p[2] == 't' + || ((p[2] == '3' || p[2] == 'x') + && !ASCII_ISALPHA(p[3])))) + || (p[0] == 'p' && p[1] == 'e' + && (!ASCII_ISALPHA(p[2]) || p[2] == 'r')) + || (p[0] == 't' && p[1] == 'c' + && (!ASCII_ISALPHA(p[2]) || p[2] == 'l')) + || (p[0] == 'l' && p[1] == 'u' && p[2] == 'a' + && !ASCII_ISALPHA(p[3])) + || (p[0] == 'r' && p[1] == 'u' && p[2] == 'b' + && (!ASCII_ISALPHA(p[3]) || p[3] == 'y')) + || (p[0] == 'm' && p[1] == 'z' + && (!ASCII_ISALPHA(p[2]) || p[2] == 's')))) { + // ":python <<" continues until a dot, like ":append" + p = skipwhite(arg + 2); + if (strncmp(p, "trim", 4) == 0 + && (p[4] == NUL || ascii_iswhite(p[4]))) { + // Ignore leading white space. + p = skipwhite(p + 4); + heredoc_trimmedlen = (size_t)(skipwhite(theline) - theline); + heredoc_trimmed = xmemdupz(theline, heredoc_trimmedlen); + } + if (*p == NUL) { + skip_until = xmemdupz(".", 1); + } else { + skip_until = xmemdupz(p, (size_t)(skiptowhite(p) - p)); + } + do_concat = false; + is_heredoc = true; + } + + if (!is_heredoc) { + // Check for ":let v =<< [trim] EOF" + // and ":let [a, b] =<< [trim] EOF" + arg = p; + if (checkforcmd(&arg, "let", 2)) { + int var_count = 0; + int semicolon = 0; + arg = (char *)skip_var_list(arg, &var_count, &semicolon, true); + if (arg != NULL) { + arg = skipwhite(arg); + } + if (arg != NULL && strncmp(arg, "=<<", 3) == 0) { + p = skipwhite(arg + 3); + bool has_trim = false; + while (true) { + if (strncmp(p, "trim", 4) == 0 + && (p[4] == NUL || ascii_iswhite(p[4]))) { + // Ignore leading white space. + p = skipwhite(p + 4); + has_trim = true; + continue; + } + if (strncmp(p, "eval", 4) == 0 + && (p[4] == NUL || ascii_iswhite(p[4]))) { + // Ignore leading white space. + p = skipwhite(p + 4); + continue; + } + break; + } + if (has_trim) { + heredoc_trimmedlen = (size_t)(skipwhite(theline) - theline); + heredoc_trimmed = xmemdupz(theline, heredoc_trimmedlen); + } + skip_until = xmemdupz(p, (size_t)(skiptowhite(p) - p)); + do_concat = false; + is_heredoc = true; + } + } + } + } + + // Add the line to the function. + ga_grow(newlines, 1 + (int)sourcing_lnum_off); + + // Copy the line to newly allocated memory. get_one_sourceline() + // allocates 250 bytes per line, this saves 80% on average. The cost + // is an extra alloc/free. + p = xstrdup(theline); + ((char **)(newlines->ga_data))[newlines->ga_len++] = p; + + // Add NULL lines for continuation lines, so that the line count is + // equal to the index in the growarray. + while (sourcing_lnum_off-- > 0) { + ((char **)(newlines->ga_data))[newlines->ga_len++] = NULL; + } + + // Check for end of eap->arg. + if (line_arg != NULL && *line_arg == NUL) { + line_arg = NULL; + } + } + + // Return OK when no error was detected. + if (!did_emsg) { + ret = OK; + } + +theend: + xfree(skip_until); + xfree(heredoc_trimmed); + need_wait_return |= saved_wait_return; + return ret; +} + /// ":function" void ex_function(exarg_T *eap) { - char *theline; char *line_to_free = NULL; - bool saved_wait_return = need_wait_return; char *arg; char *line_arg = NULL; garray_T newargs; @@ -2238,16 +2564,13 @@ void ex_function(exarg_T *eap) garray_T newlines; int varargs = false; int flags = 0; - ufunc_T *fp; + ufunc_T *fp = NULL; + bool free_fp = false; bool overwrite = false; funcdict_T fudi; static int func_nr = 0; // number for nameless function hashtab_T *ht; - bool is_heredoc = false; - char *skip_until = NULL; - char *heredoc_trimmed = NULL; bool show_block = false; - bool do_concat = true; // ":function" without argument: list functions. if (ends_excmd(*eap->arg)) { @@ -2494,251 +2817,14 @@ void ex_function(exarg_T *eap) // Save the starting line number. linenr_T sourcing_lnum_top = SOURCING_LNUM; - int indent = 2; - int nesting = 0; - while (true) { - if (KeyTyped) { - msg_scroll = true; - saved_wait_return = false; - } - need_wait_return = false; - - if (line_arg != NULL) { - // Use eap->arg, split up in parts by line breaks. - theline = line_arg; - p = vim_strchr(theline, '\n'); - if (p == NULL) { - line_arg += strlen(line_arg); - } else { - *p = NUL; - line_arg = p + 1; - } - } else { - xfree(line_to_free); - if (eap->ea_getline == NULL) { - theline = getcmdline(':', 0, indent, do_concat); - } else { - theline = eap->ea_getline(':', eap->cookie, indent, do_concat); - } - line_to_free = theline; - } - if (KeyTyped) { - lines_left = Rows - 1; - } - if (theline == NULL) { - if (skip_until != NULL) { - semsg(_(e_missing_heredoc_end_marker_str), skip_until); - } else { - emsg(_("E126: Missing :endfunction")); - } - goto erret; - } - if (show_block) { - assert(indent >= 0); - ui_ext_cmdline_block_append((size_t)indent, theline); - } - - // Detect line continuation: SOURCING_LNUM increased more than one. - linenr_T sourcing_lnum_off = get_sourced_lnum(eap->ea_getline, eap->cookie); - if (SOURCING_LNUM < sourcing_lnum_off) { - sourcing_lnum_off -= SOURCING_LNUM; - } else { - sourcing_lnum_off = 0; - } - - if (skip_until != NULL) { - // Don't check for ":endfunc" between - // * ":append" and "." - // * ":python <<EOF" and "EOF" - // * ":let {var-name} =<< [trim] {marker}" and "{marker}" - if (heredoc_trimmed == NULL - || (is_heredoc && skipwhite(theline) == theline) - || strncmp(theline, heredoc_trimmed, - strlen(heredoc_trimmed)) == 0) { - if (heredoc_trimmed == NULL) { - p = theline; - } else if (is_heredoc) { - p = skipwhite(theline) == theline - ? theline : theline + strlen(heredoc_trimmed); - } else { - p = theline + strlen(heredoc_trimmed); - } - if (strcmp(p, skip_until) == 0) { - XFREE_CLEAR(skip_until); - XFREE_CLEAR(heredoc_trimmed); - do_concat = true; - is_heredoc = false; - } - } - } else { - // skip ':' and blanks - for (p = theline; ascii_iswhite(*p) || *p == ':'; p++) {} - - // Check for "endfunction". - if (checkforcmd(&p, "endfunction", 4) && nesting-- == 0) { - if (*p == '!') { - p++; - } - char *nextcmd = NULL; - if (*p == '|') { - nextcmd = p + 1; - } else if (line_arg != NULL && *skipwhite(line_arg) != NUL) { - nextcmd = line_arg; - } else if (*p != NUL && *p != '"' && p_verbose > 0) { - swmsg(true, _("W22: Text found after :endfunction: %s"), p); - } - if (nextcmd != NULL) { - // Another command follows. If the line came from "eap" we - // can simply point into it, otherwise we need to change - // "eap->cmdlinep". - eap->nextcmd = nextcmd; - if (line_to_free != NULL) { - xfree(*eap->cmdlinep); - *eap->cmdlinep = line_to_free; - line_to_free = NULL; - } - } - break; - } - - // Increase indent inside "if", "while", "for" and "try", decrease - // at "end". - if (indent > 2 && strncmp(p, "end", 3) == 0) { - indent -= 2; - } else if (strncmp(p, "if", 2) == 0 - || strncmp(p, "wh", 2) == 0 - || strncmp(p, "for", 3) == 0 - || strncmp(p, "try", 3) == 0) { - indent += 2; - } - - // Check for defining a function inside this function. - if (checkforcmd(&p, "function", 2)) { - if (*p == '!') { - p = skipwhite(p + 1); - } - p += eval_fname_script(p); - xfree(trans_function_name(&p, true, 0, NULL, NULL)); - if (*skipwhite(p) == '(') { - if (nesting == MAX_FUNC_NESTING - 1) { - emsg(_(e_function_nesting_too_deep)); - } else { - nesting++; - indent += 2; - } - } - } - - // Check for ":append", ":change", ":insert". - p = skip_range(p, NULL); - if ((p[0] == 'a' && (!ASCII_ISALPHA(p[1]) || p[1] == 'p')) - || (p[0] == 'c' - && (!ASCII_ISALPHA(p[1]) - || (p[1] == 'h' && (!ASCII_ISALPHA(p[2]) - || (p[2] == 'a' - && (strncmp(&p[3], "nge", 3) != 0 - || !ASCII_ISALPHA(p[6]))))))) - || (p[0] == 'i' - && (!ASCII_ISALPHA(p[1]) || (p[1] == 'n' - && (!ASCII_ISALPHA(p[2]) - || (p[2] == 's')))))) { - skip_until = xstrdup("."); - } - - // heredoc: Check for ":python <<EOF", ":lua <<EOF", etc. - arg = skipwhite(skiptowhite(p)); - if (arg[0] == '<' && arg[1] == '<' - && ((p[0] == 'p' && p[1] == 'y' - && (!ASCII_ISALNUM(p[2]) || p[2] == 't' - || ((p[2] == '3' || p[2] == 'x') - && !ASCII_ISALPHA(p[3])))) - || (p[0] == 'p' && p[1] == 'e' - && (!ASCII_ISALPHA(p[2]) || p[2] == 'r')) - || (p[0] == 't' && p[1] == 'c' - && (!ASCII_ISALPHA(p[2]) || p[2] == 'l')) - || (p[0] == 'l' && p[1] == 'u' && p[2] == 'a' - && !ASCII_ISALPHA(p[3])) - || (p[0] == 'r' && p[1] == 'u' && p[2] == 'b' - && (!ASCII_ISALPHA(p[3]) || p[3] == 'y')) - || (p[0] == 'm' && p[1] == 'z' - && (!ASCII_ISALPHA(p[2]) || p[2] == 's')))) { - // ":python <<" continues until a dot, like ":append" - p = skipwhite(arg + 2); - if (strncmp(p, "trim", 4) == 0) { - // Ignore leading white space. - p = skipwhite(p + 4); - heredoc_trimmed = xmemdupz(theline, (size_t)(skipwhite(theline) - theline)); - } - if (*p == NUL) { - skip_until = xstrdup("."); - } else { - skip_until = xmemdupz(p, (size_t)(skiptowhite(p) - p)); - } - do_concat = false; - is_heredoc = true; - } - - // Check for ":let v =<< [trim] EOF" - // and ":let [a, b] =<< [trim] EOF" - arg = p; - if (checkforcmd(&arg, "let", 2)) { - int var_count = 0; - int semicolon = 0; - arg = (char *)skip_var_list(arg, &var_count, &semicolon, true); - if (arg != NULL) { - arg = skipwhite(arg); - } - if (arg != NULL && strncmp(arg, "=<<", 3) == 0) { - p = skipwhite(arg + 3); - while (true) { - if (strncmp(p, "trim", 4) == 0) { - // Ignore leading white space. - p = skipwhite(p + 4); - heredoc_trimmed = xmemdupz(theline, (size_t)(skipwhite(theline) - theline)); - continue; - } - if (strncmp(p, "eval", 4) == 0) { - // Ignore leading white space. - p = skipwhite(p + 4); - continue; - } - break; - } - skip_until = xmemdupz(p, (size_t)(skiptowhite(p) - p)); - do_concat = false; - is_heredoc = true; - } - } - } - - // Add the line to the function. - ga_grow(&newlines, 1 + (int)sourcing_lnum_off); - - // Copy the line to newly allocated memory. get_one_sourceline() - // allocates 250 bytes per line, this saves 80% on average. The cost - // is an extra alloc/free. - p = xstrdup(theline); - ((char **)(newlines.ga_data))[newlines.ga_len++] = p; - - // Add NULL lines for continuation lines, so that the line count is - // equal to the index in the growarray. - while (sourcing_lnum_off-- > 0) { - ((char **)(newlines.ga_data))[newlines.ga_len++] = NULL; - } - - // Check for end of eap->arg. - if (line_arg != NULL && *line_arg == NUL) { - line_arg = NULL; - } - } - - // Don't define the function when skipping commands or when an error was - // detected. - if (eap->skip || did_emsg) { + // Do not define the function when getting the body fails and when skipping. + if (get_function_body(eap, &newlines, line_arg, &line_to_free, show_block) == FAIL + || eap->skip) { goto erret; } // If there are no errors, add the function + size_t namelen = 0; if (fudi.fd_dict == NULL) { dictitem_T *v = find_var(name, strlen(name), &ht, false); if (v != NULL && v->di_tv.v_type == VAR_FUNC) { @@ -2754,11 +2840,11 @@ void ex_function(exarg_T *eap) && (fp->uf_script_ctx.sc_sid != current_sctx.sc_sid || fp->uf_script_ctx.sc_seq == current_sctx.sc_seq)) { emsg_funcname(e_funcexts, name); - goto erret; + goto errret_keep; } if (fp->uf_calls > 0) { emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"), name); - goto erret; + goto errret_keep; } if (fp->uf_refcount > 1) { // This function is referenced somewhere, don't redefine it but @@ -2779,7 +2865,7 @@ void ex_function(exarg_T *eap) } } } else { - char numbuf[20]; + char numbuf[NUMBUFLEN]; fp = NULL; if (fudi.fd_newkey == NULL && !eap->forceit) { @@ -2799,8 +2885,8 @@ void ex_function(exarg_T *eap) // Give the function a sequential number. Can only be used with a // Funcref! xfree(name); - snprintf(numbuf, sizeof(numbuf), "%d", ++func_nr); - name = xstrdup(numbuf); + namelen = (size_t)snprintf(numbuf, sizeof(numbuf), "%d", ++func_nr); + name = xmemdupz(numbuf, namelen); } if (fp == NULL) { @@ -2824,7 +2910,10 @@ void ex_function(exarg_T *eap) } } - fp = xcalloc(1, offsetof(ufunc_T, uf_name) + strlen(name) + 1); + if (namelen == 0) { + namelen = strlen(name); + } + fp = alloc_ufunc(name, namelen); if (fudi.fd_dict != NULL) { if (fudi.fd_di == NULL) { @@ -2832,7 +2921,7 @@ void ex_function(exarg_T *eap) fudi.fd_di = tv_dict_item_alloc(fudi.fd_newkey); if (tv_dict_add(fudi.fd_dict, fudi.fd_di) == FAIL) { xfree(fudi.fd_di); - xfree(fp); + XFREE_CLEAR(fp); goto erret; } } else { @@ -2840,19 +2929,18 @@ void ex_function(exarg_T *eap) tv_clear(&fudi.fd_di->di_tv); } fudi.fd_di->di_tv.v_type = VAR_FUNC; - fudi.fd_di->di_tv.vval.v_string = xstrdup(name); + fudi.fd_di->di_tv.vval.v_string = xmemdupz(name, namelen); // behave like "dict" was used flags |= FC_DICT; } // insert the new function in the function list - set_ufunc_name(fp, name); if (overwrite) { hashitem_T *hi = hash_find(&func_hashtab, name); hi->hi_key = UF2HIKEY(fp); } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) { - xfree(fp); + free_fp = true; goto erret; } fp->uf_refcount = 1; @@ -2881,18 +2969,27 @@ void ex_function(exarg_T *eap) goto ret_free; erret: + if (fp != NULL) { + // these were set to "newargs" and "default_args", which are cleared below + ga_init(&fp->uf_args, (int)sizeof(char *), 1); + ga_init(&fp->uf_def_args, (int)sizeof(char *), 1); + } +errret_2: + if (fp != NULL) { + XFREE_CLEAR(fp->uf_name_exp); + } + if (free_fp) { + XFREE_CLEAR(fp); + } +errret_keep: ga_clear_strings(&newargs); ga_clear_strings(&default_args); -errret_2: ga_clear_strings(&newlines); ret_free: - xfree(skip_until); - xfree(heredoc_trimmed); xfree(line_to_free); xfree(fudi.fd_newkey); xfree(name); did_emsg |= saved_did_emsg; - need_wait_return |= saved_wait_return; if (show_block) { ui_ext_cmdline_block_leave(); } @@ -2979,15 +3076,16 @@ char *get_user_func_name(expand_T *xp, int idx) return ""; // don't show dict and lambda functions } - if (strlen(fp->uf_name) + 4 >= IOSIZE) { + if (fp->uf_namelen + 4 >= IOSIZE) { return fp->uf_name; // Prevent overflow. } - cat_func_name(IObuff, IOSIZE, fp); + int len = cat_func_name(IObuff, IOSIZE, fp); if (xp->xp_context != EXPAND_USER_FUNC) { - xstrlcat(IObuff, "(", IOSIZE); + xstrlcpy(IObuff + len, "(", IOSIZE - (size_t)len); if (!fp->uf_varargs && GA_EMPTY(&fp->uf_args)) { - xstrlcat(IObuff, ")", IOSIZE); + len++; + xstrlcpy(IObuff + len, ")", IOSIZE - (size_t)len); } } return IObuff; @@ -3589,21 +3687,26 @@ char *get_return_cmd(void *rettv) { char *s = NULL; char *tofree = NULL; + size_t slen = 0; if (rettv != NULL) { tofree = s = encode_tv2echo((typval_T *)rettv, NULL); } if (s == NULL) { s = ""; + } else { + slen = strlen(s); } xstrlcpy(IObuff, ":return ", IOSIZE); xstrlcpy(IObuff + 8, s, IOSIZE - 8); - if (strlen(s) + 8 >= IOSIZE) { + size_t IObufflen = 8 + slen; + if (IObufflen >= IOSIZE) { STRCPY(IObuff + IOSIZE - 4, "..."); + IObufflen = IOSIZE - 1; } xfree(tofree); - return xstrdup(IObuff); + return xstrnsave(IObuff, IObufflen); } /// Get next function line. @@ -4046,7 +4149,8 @@ bool set_ref_in_func(char *name, ufunc_T *fp_in, int copyID) char *register_luafunc(LuaRef ref) { char *name = get_lambda_name(); - ufunc_T *fp = xcalloc(1, offsetof(ufunc_T, uf_name) + strlen(name) + 1); + size_t namelen = get_lambda_name_len(); + ufunc_T *fp = alloc_ufunc(name, namelen); fp->uf_refcount = 1; fp->uf_varargs = true; @@ -4055,7 +4159,6 @@ char *register_luafunc(LuaRef ref) fp->uf_script_ctx = current_sctx; fp->uf_luaref = ref; - STRCPY(fp->uf_name, name); hash_add(&func_hashtab, UF2HIKEY(fp)); // coverity[leaked_storage] |