aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/eval/userfunc.c
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2025-02-05 23:09:29 +0000
committerJosh Rahm <joshuarahm@gmail.com>2025-02-05 23:09:29 +0000
commitd5f194ce780c95821a855aca3c19426576d28ae0 (patch)
treed45f461b19f9118ad2bb1f440a7a08973ad18832 /src/nvim/eval/userfunc.c
parentc5d770d311841ea5230426cc4c868e8db27300a8 (diff)
parent44740e561fc93afe3ebecfd3618bda2d2abeafb0 (diff)
downloadrneovim-rahm.tar.gz
rneovim-rahm.tar.bz2
rneovim-rahm.zip
Merge remote-tracking branch 'upstream/master' into mix_20240309HEADrahm
Diffstat (limited to 'src/nvim/eval/userfunc.c')
-rw-r--r--src/nvim/eval/userfunc.c793
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]