From b0fc6108c923a198325354ae36b71f90c4c68e19 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Thu, 5 Jan 2017 17:13:23 -0700 Subject: vim-patch:7.4.1727 Problem: Cannot detect a crash in tests when caused by garbagecollect(). Solution: Add garbagecollect_for_testing(). Do not free a job if is still useful. https://github.com/vim/vim/commit/ebf7dfa6f121c82f97d2adca3d45fbaba9ad8f7e --- src/nvim/eval.c | 54 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 9 deletions(-) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index bbb6565509..8692ddc334 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -389,6 +389,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), @@ -648,8 +649,8 @@ 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 */ free_all_functions(); @@ -5813,6 +5814,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,16 +5839,19 @@ 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. @@ -5954,6 +5961,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 +6021,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 *)_( @@ -7085,9 +7098,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); @@ -17306,6 +17334,14 @@ 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) { -- cgit From bb2afeb0266ffd410e2e226a376f7ddbac633491 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Wed, 14 Dec 2016 00:20:48 -0700 Subject: vim-patch:7.4.1989 Problem: filter() and map() only accept a string argument. Solution: Implement using a Funcref argument (Yasuhiro Matsumoto, Ken Takata) https://github.com/vim/vim/commit/b33c7eb5b813cb631b2b0ca5c4029e1788a09bde --- src/nvim/eval.c | 58 ++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 19 deletions(-) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 8692ddc334..e4afa18d10 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5285,7 +5285,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 @@ -9306,8 +9307,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; @@ -9339,16 +9339,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; @@ -9412,20 +9411,41 @@ 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; + typeval_T argv[3]; 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; + s = expr->vval.v_string; + if (expr->v_type == VAR_FUNC) { + if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, 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->pt_name; + if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, 0L, 0L, &dummy, + true, partial, NULL) == FAIL) { + goto theend; + } + } else { + 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 */ -- cgit From 6c423989fc5becb294dacedceaac0c2e878a3858 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Wed, 14 Dec 2016 00:26:52 -0700 Subject: vim-patch:7.4.2002 Problem: Crash when passing number to filter() or map(). Solution: Convert to a string. (Ozaki Kiichi) https://github.com/vim/vim/commit/a06ec8f345eabb66e5b7d7c0192cfebdde63115d --- src/nvim/eval.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index e4afa18d10..017bb92c75 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -9414,7 +9414,8 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) { typval_T rettv; - typeval_T argv[3]; + typval_T argv[3]; + char_u buf[NUMBUFLEN]; char_u *s; int retval = FAIL; int dummy; @@ -9424,6 +9425,7 @@ static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) argv[1] = vimvars[VV_VAL].vv_tv; s = expr->vval.v_string; if (expr->v_type == VAR_FUNC) { + s = expr->vval.v_string; if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, 0L, 0L, &dummy, true, NULL, NULL) == FAIL) { goto theend; @@ -9437,6 +9439,10 @@ static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) 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; -- cgit From bb7d0deb2f93bd7980a51dca05ae222e751ab632 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Wed, 14 Dec 2016 17:06:36 -0700 Subject: vim-patch:7.4.2044 Problem: filter() and map() either require a string or defining a function. Solution: Support lambda, a short way to define a function that evaluates an expression. (Yasuhiro Matsumoto, Ken Takata) https://github.com/vim/vim/commit/069c1e7fa9f45a665064f7f2c17da84d6a48f544 --- src/nvim/eval.c | 368 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 286 insertions(+), 82 deletions(-) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 017bb92c75..bf5ee7a530 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -4301,8 +4301,12 @@ 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 @@ -6871,6 +6875,202 @@ failret: return OK; } +/// Get function arguments. +static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, + int *varargs, int 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) { + 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; +} + +/// 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, int evaluate) +{ + garray_T newargs; + garray_T newlines; + ufunc_T *fp = NULL; + int varargs; + int ret; + char_u name[20]; + char_u *start = skipwhite(*arg + 1); + char_u *s, *e; + static int lambda_no = 0; + + // TODO(mike): What lengths should be used here? + ga_init(&newargs, (int)sizeof(char_u *), 80); + ga_init(&newlines, (int)sizeof(char_u *), 80); + + // 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. + *arg = skipwhite(*arg + 1); + ret = get_function_args(arg, '-', &newargs, &varargs, false); + if (ret == FAIL || **arg != '>') { + goto errret; + } + + // 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; + char_u *p; + + fp = (ufunc_T *)xmalloc((unsigned)(sizeof(ufunc_T) + 20)); + if (fp == NULL) { + goto errret; + } + + snprintf((char *)name, sizeof(name), "%d", lambda_no++); + + ga_init(&newlines, (int)sizeof(char_u *), 1); + ga_grow(&newlines, 1); + + // Add "return " before the expression. + // TODO(vim): Support multiple expressions. + len = 7 + e - s + 1; + p = (char_u *)xmalloc(len); + if (p == NULL) { + goto errret; + } + ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p; + STRCPY(p, "return "); + STRNCPY(p + 7, s, e - s); + p[7 + e - s] = NUL; + + fp->uf_refcount = 1; + STRCPY(fp->uf_name, name); + hash_add(&func_hashtab, UF2HIKEY(fp)); + fp->uf_args = newargs; + fp->uf_lines = newlines; + +#ifdef FEAT_PROFILE + 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); + } +#endif + fp->uf_varargs = true; + fp->uf_flags = 0; + fp->uf_calls = 0; + fp->uf_script_ID = current_SID; + + rettv->vval.v_string = vim_strsave(name); + rettv->v_type = VAR_FUNC; + } else { + ga_clear_strings(&newargs); + } + + return OK; + +errret: + ga_clear_strings(&newargs); + ga_clear_strings(&newlines); + xfree(fp); + return FAIL; +} + /// Convert the string to a floating point number /// /// This uses strtod(). setlocale(LC_NUMERIC, "C") has been used earlier to @@ -9448,7 +9648,7 @@ static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) goto theend; } - if (*s != NUL) { // check for trailing chars after expr + if (*s != NUL) { // check for trailing chars after expr EMSG2(_(e_invexpr2), s); goto theend; } @@ -20495,8 +20695,7 @@ 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; int indent; @@ -20679,59 +20878,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" and "abort" for (;; ) { p = skipwhite(p); if (STRNCMP(p, "range", 5) == 0) { @@ -21041,6 +21192,7 @@ void ex_function(exarg_T *eap) erret: ga_clear_strings(&newargs); +errret_2: ga_clear_strings(&newlines); ret_free: xfree(skip_until); @@ -21764,7 +21916,9 @@ void func_unref(char_u *name) { ufunc_T *fp; - if (name != NULL && isdigit(*name)) { + if (name == NULL) { + return; + } else if (isdigit(*name)) { fp = find_func(name); if (fp == NULL) { #ifdef EXITFREE @@ -21777,6 +21931,16 @@ void func_unref(char_u *name) } else { user_func_unref(fp); } + } else if (STRNCMP(name, "", 8) == 0) { + // fail silently, when lambda function isn't found + fp = find_func(name); + 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); + } + } } } @@ -21798,12 +21962,21 @@ void func_ref(char_u *name) { ufunc_T *fp; - if (name != NULL && isdigit(*name)) { + if (name == NULL) { + return; + } else if (isdigit(*name)) { fp = find_func(name); - if (fp == NULL) + if (fp == NULL) { EMSG2(_(e_intern2), "func_ref()"); - else - ++fp->uf_refcount; + } else { + (fp->uf_refcount)++; + } + } else if (STRNCMP(name, "", 8) == 0) { + // fail silently, when lambda function isn't found. + fp = find_func(name); + if (fp != NULL) { + (fp->uf_refcount)++; + } } } @@ -21830,6 +22003,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; @@ -21867,14 +22041,15 @@ 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. - */ + if (STRNCMP(fp->uf_name, "", 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 @@ -21916,31 +22091,49 @@ 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; + dictitem_T *v2; + 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) { v = &fc->fixvar[fixvar_idx++].var; v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + + if (addlocal) { + v2 = v; + } } else { v = xmalloc(sizeof(dictitem_T) + STRLEN(name)); v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC; + + if (addlocal) { + v2 = (dictitem_T *)xmalloc((unsigned)(sizeof(dictitem_T) + + STRLEN(name))); + if (v2 == NULL) { + xfree(v); + break; + } + v2->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)); @@ -21950,6 +22143,15 @@ call_user_func ( v->di_tv = argvars[i]; v->di_tv.v_lock = VAR_FIXED; + // Named arguments can be accessed without the "a:" prefix in lambda + // expressions. Add to the l: dict. + if (addlocal) { + STRCPY(v2->di_key, name); + copy_tv(&v->di_tv, &v2->di_tv); + v2->di_tv.v_lock = VAR_FIXED; + hash_add(&fc->l_vars.dv_hashtab, DI2HIKEY(v2)); + } + if (ai >= 0 && ai < MAX_FUNC_ARGS) { list_append(&fc->l_varlist, &fc->l_listitems[ai]); fc->l_listitems[ai].li_tv = argvars[i]; @@ -22167,7 +22369,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 && (isdigit(*fp->uf_name) + || STRNCMP(fp->uf_name, "", 8) == 0) + && fp->uf_refcount <= 0) { // Function was unreferenced while being used, free it now. func_free(fp); } -- cgit From fc46efd3f2d95920bf50832848515b14b12c0723 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Thu, 15 Dec 2016 13:53:14 -0700 Subject: vim-patch:7.4.2072 Problem: substitute() does not support a Funcref argument. Solution: Support a Funcref like it supports a string starting with "\=". https://github.com/vim/vim/commit/72ab729c3dcdea0fba44d8e676602c847e841bcd --- src/nvim/eval.c | 62 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 28 deletions(-) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index bf5ee7a530..709ea0e2e1 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -16997,19 +16997,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; @@ -23133,7 +23140,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); @@ -23169,12 +23176,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; @@ -23212,23 +23219,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) @@ -23245,11 +23250,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; } -- cgit From 7f4848aff47b7b2a85be5f83007846934ef9fd90 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Thu, 15 Dec 2016 13:27:32 -0700 Subject: vim-patch:7.4.2090 Problem: Using submatch() in a lambda passed to substitute() is verbose. Solution: Use a static list and pass it as an optional argument to the function. Fix memory leak. https://github.com/vim/vim/commit/df48fb456fb6bf63d94cad9b302ff01d8ee8d311 --- src/nvim/eval.c | 70 ++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 16 deletions(-) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 709ea0e2e1..e64d24baa0 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1230,8 +1230,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) { @@ -7388,6 +7388,11 @@ fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error) { } /// 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 @@ -7398,6 +7403,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 @@ -7484,15 +7490,19 @@ call_func( } if (fp != NULL) { - if (fp->uf_flags & FC_RANGE) - *doesrange = TRUE; - if (argcount < fp->uf_args.ga_len) + 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); @@ -8344,7 +8354,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); } @@ -9626,16 +9636,16 @@ static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) s = expr->vval.v_string; if (expr->v_type == VAR_FUNC) { s = expr->vval.v_string; - if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, 0L, 0L, &dummy, - true, NULL, NULL) == FAIL) { + 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->pt_name; - if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, 0L, 0L, &dummy, - true, partial, NULL) == FAIL) { + if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL, + 0L, 0L, &dummy, true, partial, NULL) == FAIL) { goto theend; } } else { @@ -16160,7 +16170,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]); @@ -17666,7 +17676,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); } @@ -18315,6 +18325,33 @@ static bool write_list(FILE *fd, list_T *list, bool binary) return ret; } +/// 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; + } + } +} + /// Saves a typval_T as a string. /// /// For lists, replaces NLs with NUL and separates items with NLs. @@ -19763,7 +19800,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) { @@ -23621,6 +23658,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, -- cgit From cbaa87a63912ea64a4c8027532b86114a6571a19 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Thu, 15 Dec 2016 12:48:41 -0700 Subject: vim-patch:7.4.2096 Problem: Lambda functions show up with completion. Solution: Don't show lambda functions. (Ken Takata) https://github.com/vim/vim/commit/b49edc11a1872fa99befa9a4a8ea6c8537868038 --- src/nvim/eval.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index e64d24baa0..0766fc8ee8 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -21839,8 +21839,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, "", 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 */ -- cgit From 6563d859904837790515d790ecadaa4f06064471 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Thu, 15 Dec 2016 22:30:05 -0700 Subject: vim-patch:7.4.2104 Problem: Code duplication when unreferencing a function. Solution: De-duplicate. https://github.com/vim/vim/commit/97baee80f0906ee2f651ee1215ec033e84f866ad --- src/nvim/eval.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 0766fc8ee8..c3812f8e48 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -21960,11 +21960,12 @@ static void func_free(ufunc_T *fp) */ void func_unref(char_u *name) { - ufunc_T *fp; + ufunc_T *fp = NULL; if (name == NULL) { return; - } else if (isdigit(*name)) { + } + if (isdigit(*name)) { fp = find_func(name); if (fp == NULL) { #ifdef EXITFREE @@ -21980,12 +21981,12 @@ void func_unref(char_u *name) } else if (STRNCMP(name, "", 8) == 0) { // fail silently, when lambda function isn't found fp = find_func(name); - 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); - } + } + 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); } } } -- cgit From 9f6f7fe26d7caa89083f5d5b00c55bf2046591ca Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Sat, 17 Dec 2016 13:07:37 -0700 Subject: vim-patch:7.4.2119 Problem: Closures are not supported. Solution: Capture variables in lambdas from the outer scope. (Yasuhiro Matsumoto, Ken Takata) https://github.com/vim/vim/commit/1e96d9bf98f9ab84d5af7f98d6a961d91b17364f --- src/nvim/eval.c | 378 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 292 insertions(+), 86 deletions(-) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index c3812f8e48..63af474030 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -223,14 +223,15 @@ 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; +// structure to hold info for a function that is currently being executed. struct funccall_S { ufunc_T *func; /* function being called */ @@ -241,18 +242,21 @@ struct funccall_S { 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 */ + 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 + int fc_refcount; + int fc_copyID; // for garbage collection + garray_T fc_funcs; // list of ufunc_T* which refer this }; /* @@ -4358,6 +4362,11 @@ static int eval7( } else { if (**arg == '(') { // recursive! partial_T *partial; + + if (!evaluate) { + check_vars(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); @@ -4387,6 +4396,7 @@ static int eval7( } else if (evaluate) { ret = get_var_tv(s, len, rettv, NULL, true, false); } else { + check_vars(s, len); ret = OK; } } @@ -6255,6 +6265,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 = abort || set_ref_in_func(pt->pt_name, copyID); if (pt->pt_dict != NULL) { typval_T dtv; @@ -6271,6 +6282,8 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, break; } case VAR_FUNC: + abort = abort || set_ref_in_func(tv->vval.v_string, copyID); + break; case VAR_UNKNOWN: case VAR_SPECIAL: case VAR_FLOAT: @@ -6282,6 +6295,39 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, return abort; } +/// 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. +int set_ref_in_func(char_u *name, int copyID) +{ + ufunc_T *fp; + funccall_T *fc; + int error; + char_u fname_buf[FLEN_FIXED + 1]; + char_u *tofree = NULL; + char_u *fname; + bool abort = false; + if (name == NULL) { + return false; + } + + 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) { + if (fc->fc_copyID != copyID) { + fc->fc_copyID = copyID; + abort = set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL); + abort = set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL); + } + } + } + xfree(tofree); + return abort; +} + /// Mark all lists and dicts referenced in given mark /// /// @returns true if setting references failed somehow. @@ -6971,6 +7017,7 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) { garray_T newargs; garray_T newlines; + garray_T *pnewargs; ufunc_T *fp = NULL; int varargs; int ret; @@ -6978,6 +7025,8 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) 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; // TODO(mike): What lengths should be used here? ga_init(&newargs, (int)sizeof(char_u *), 80); @@ -6990,12 +7039,22 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) } // Parse the arguments again. + if (evaluate) { + pnewargs = &newargs; + } else { + pnewargs = NULL; + } *arg = skipwhite(*arg + 1); - ret = get_function_args(arg, '-', &newargs, &varargs, false); + ret = get_function_args(arg, '-', pnewargs, &varargs, false); if (ret == FAIL || **arg != '>') { goto errret; } + // Set up dictionaries 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; @@ -7014,18 +7073,17 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) int len; char_u *p; - fp = (ufunc_T *)xmalloc((unsigned)(sizeof(ufunc_T) + 20)); + snprintf((char *)name, sizeof(name), "%d", lambda_no++); + + fp = (ufunc_T *)xmalloc((unsigned)(sizeof(ufunc_T) + STRLEN(name))); if (fp == NULL) { goto errret; } - snprintf((char *)name, sizeof(name), "%d", lambda_no++); - ga_init(&newlines, (int)sizeof(char_u *), 1); ga_grow(&newlines, 1); // Add "return " before the expression. - // TODO(vim): Support multiple expressions. len = 7 + e - s + 1; p = (char_u *)xmalloc(len); if (p == NULL) { @@ -7041,8 +7099,17 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) hash_add(&func_hashtab, UF2HIKEY(fp)); fp->uf_args = newargs; fp->uf_lines = newlines; + if (current_funccal != NULL && eval_lavars) { + 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; + func_ref(current_funccal->func->uf_name); + } else { + fp->uf_scoped = NULL; + } -#ifdef FEAT_PROFILE fp->uf_tml_count = NULL; fp->uf_tml_total = NULL; fp->uf_tml_self = NULL; @@ -7050,7 +7117,6 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) if (prof_def_func()) { func_do_profile(fp); } -#endif fp->uf_varargs = true; fp->uf_flags = 0; fp->uf_calls = 0; @@ -7058,17 +7124,16 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) rettv->vval.v_string = vim_strsave(name); rettv->v_type = VAR_FUNC; - } else { - ga_clear_strings(&newargs); - } - - return OK; + } + eval_lavars_used = old_eval_lavars; + return OK; errret: - ga_clear_strings(&newargs); - ga_clear_strings(&newlines); - xfree(fp); - return FAIL; + ga_clear_strings(&newargs); + ga_clear_strings(&newlines); + xfree(fp); + eval_lavars_used = old_eval_lavars; + return FAIL; } /// Convert the string to a floating point number @@ -19204,13 +19269,37 @@ get_var_tv ( 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 ( +/// 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(char_u *name, int len) +{ + int cc; + char_u *varname; + hashtab_T *ht; + + if (eval_lavars_used == NULL) { + return; + } + + // truncate the name, so that we can use strcmp() + cc = name[len]; + name[len] = NUL; + + ht = find_var_ht(name, &varname); + if (ht == get_funccal_local_ht() || ht == get_funccal_args_ht()) { + if (find_var(name, NULL, true) != NULL) { + *eval_lavars_used = true; + } + } + + name[len] = cc; +} + +/// 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, typval_T *rettv, int evaluate, /* do more than finding the end */ @@ -19845,19 +19934,29 @@ static dictitem_T *find_var(char_u *name, hashtab_T **htp, int no_autoload) { char_u *varname; hashtab_T *ht; + dictitem_T *ret = NULL; ht = find_var_ht(name, &varname); - if (htp != NULL) + 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); + } + ret = find_var_in_ht(ht, *name, varname, no_autoload || htp != NULL); + if (ret != NULL) { + return ret; + } + + // Search in parent scope for lambda + return find_var_in_scoped_ht(name, varname ? &varname : NULL, + 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) +dictitem_T *find_var_in_ht(hashtab_T *ht, int htname, + char_u *varname, bool no_autoload) { hashitem_T *hi; @@ -19916,6 +20015,26 @@ static funccall_T *get_funccal(void) return funccal; } +/// Return the hashtable used for argument in the current funccal. +/// Return NULL if there is no current funccal. +hashtab_T *get_funccal_args_ht(void) +{ + 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. +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 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) @@ -20198,7 +20317,12 @@ set_var ( EMSG2(_(e_illvar), name); return; } - v = find_var_in_ht(ht, 0, varname, TRUE); + v = find_var_in_ht(ht, 0, varname, true); + + // Search in parent scope which is possible to reference from lambda + if (v == NULL) { + v = find_var_in_scoped_ht(name, varname ? &varname : NULL, true); + } if ((tv->v_type == VAR_FUNC || tv->v_type == VAR_PARTIAL) && var_check_func_name(name, v == NULL)) { @@ -21222,6 +21346,7 @@ void ex_function(exarg_T *eap) fp->uf_refcount = 1; fp->uf_args = newargs; fp->uf_lines = newlines; + fp->uf_scoped = NULL; fp->uf_tml_count = NULL; fp->uf_tml_total = NULL; fp->uf_tml_self = NULL; @@ -21944,13 +22069,12 @@ static void func_free(ufunc_T *fp) xfree(fp->uf_tml_total); xfree(fp->uf_tml_self); - /* 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); - + // 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); + } + funccal_unref(fp->uf_scoped, fp); xfree(fp); } @@ -22088,6 +22212,12 @@ call_user_func ( fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0); fc->dbg_tick = debug_tick; + // Set up fields for closure. + fc->fc_refcount = 0; + fc->fc_copyID = 0; + ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1); + func_ref(fp->uf_name); + if (STRNCMP(fp->uf_name, "", 8) == 0) { islambda = true; } @@ -22147,7 +22277,6 @@ call_user_func ( (varnumber_T)lastline); for (int i = 0; i < argcount; i++) { bool addlocal = false; - dictitem_T *v2; ai = i - fp->uf_args.ga_len; if (ai < 0) { @@ -22164,39 +22293,24 @@ call_user_func ( if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) { v = &fc->fixvar[fixvar_idx++].var; v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - - if (addlocal) { - v2 = v; - } } else { v = xmalloc(sizeof(dictitem_T) + STRLEN(name)); v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC; - - if (addlocal) { - v2 = (dictitem_T *)xmalloc((unsigned)(sizeof(dictitem_T) - + STRLEN(name))); - if (v2 == NULL) { - xfree(v); - break; - } - v2->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; - // Named arguments can be accessed without the "a:" prefix in lambda - // expressions. Add to the l: dict. if (addlocal) { - STRCPY(v2->di_key, name); - copy_tv(&v->di_tv, &v2->di_tv); - v2->di_tv.v_lock = VAR_FIXED; - hash_add(&fc->l_vars.dv_hashtab, DI2HIKEY(v2)); + // 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) { @@ -22388,8 +22502,9 @@ call_user_func ( * 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; @@ -22429,15 +22544,53 @@ 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. If "fp" is not NULL, "fp" is detached from "fc". +static void funccal_unref(funccall_T *fc, ufunc_T *fp) +{ + funccall_T **pfc; + int i; + int freed = false; + + if (fc == NULL) { + return; + } + + if (--fc->fc_refcount <= 0) { + for (pfc = &previous_funccal; *pfc != NULL; ) { + if (fc == *pfc + && 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) { + *pfc = fc->caller; + free_funccal(fc, true); + freed = true; + } else { + pfc = &(*pfc)->caller; + } + } + if (!freed) { + func_unref(fc->func->uf_name); + + if (fp != NULL) { + 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; } /* @@ -22451,18 +22604,38 @@ 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]; + + if (fp != NULL) { + fp->uf_scoped = NULL; + } + } + + // 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); + } + } + + for (int i = 0; i < fc->fc_funcs.ga_len; i++) { + ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; + if (fp != NULL) { + func_unref(fc->func->uf_name); + } + } + ga_clear(&fc->fc_funcs); + + func_unref(fc->func->uf_name); xfree(fc); } @@ -22781,6 +22954,39 @@ static var_flavour_T var_flavour(char_u *varname) } } +/// Search variable in parent scope. +dictitem_T *find_var_in_scoped_ht(char_u *name, char_u **varname, + int no_autoload) +{ + dictitem_T *v = NULL; + funccall_T *old_current_funccal = current_funccal; + hashtab_T *ht; + + if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) { + return NULL; + } + + // Search in parent scope which is possible to reference from lambda + current_funccal = current_funccal->func->uf_scoped; + while (current_funccal) { + ht = find_var_ht(name, varname ? &(*varname) : NULL); + if (ht != NULL) { + v = find_var_in_ht(ht, *name, + varname ? *varname : NULL, 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 -- cgit From f59321e319ecfc6977f666fb04a52ac50bead09d Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Sat, 17 Dec 2016 16:01:42 -0700 Subject: vim-patch:7.4.2120 Problem: User defined functions can't be a closure. Solution: Add the "closure" argument. Allow using :unlet on a bound variable. (Yasuhiro Matsumoto, Ken Takata) https://github.com/vim/vim/commit/10ce39a0d52272a3dfff2feb8c631529f29e6740 --- src/nvim/eval.c | 90 ++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 13 deletions(-) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 63af474030..5ebd86a59d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -209,10 +209,13 @@ static int echo_attr = 0; /* attributes used for ":echo" */ #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" */ +// 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" +#define FC_CLOSURE 8 // closure, uses outer scope variables +#define FC_DELETED 16 // :delfunction used while uf_refcount > 0 +#define FC_REMOVED 32 // 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}; @@ -3070,7 +3073,10 @@ 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(name, &varname, &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) @@ -7070,7 +7076,7 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) (*arg)++; if (evaluate) { - int len; + int len, flags = 0; char_u *p; snprintf((char *)name, sizeof(name), "%d", lambda_no++); @@ -7100,6 +7106,7 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) fp->uf_args = newargs; fp->uf_lines = newlines; if (current_funccal != NULL && eval_lavars) { + flags |= FC_CLOSURE; fp->uf_scoped = current_funccal; current_funccal->fc_refcount++; ga_grow(¤t_funccal->fc_funcs, 1); @@ -7118,7 +7125,7 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) func_do_profile(fp); } fp->uf_varargs = true; - fp->uf_flags = 0; + fp->uf_flags = flags; fp->uf_calls = 0; fp->uf_script_ID = current_SID; @@ -21050,7 +21057,7 @@ void ex_function(exarg_T *eap) goto errret_2; } - // 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) { @@ -21062,8 +21069,12 @@ 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; + } else { break; + } } /* When there is a line break use what follows for the function body. @@ -21346,7 +21357,21 @@ void ex_function(exarg_T *eap) fp->uf_refcount = 1; fp->uf_args = newargs; fp->uf_lines = newlines; - fp->uf_scoped = NULL; + if ((flags & FC_CLOSURE) != 0) { + if (current_funccal == NULL) { + emsg_funcname(N_("E932 Closure function should not be at top level: %s"), + name); + goto erret; + } + 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; + func_ref(current_funccal->func->uf_name); + } else { + fp->uf_scoped = NULL; + } fp->uf_tml_count = NULL; fp->uf_tml_total = NULL; fp->uf_tml_self = NULL; @@ -21627,12 +21652,18 @@ static void list_func_head(ufunc_T *fp, int indent) MSG_PUTS("..."); } msg_putchar(')'); - if (fp->uf_flags & FC_ABORT) + if (fp->uf_flags & FC_ABORT) { MSG_PUTS(" abort"); - if (fp->uf_flags & FC_RANGE) + } + if (fp->uf_flags & FC_RANGE) { MSG_PUTS(" range"); - if (fp->uf_flags & FC_DICT) + } + 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); @@ -22954,6 +22985,39 @@ static var_flavour_T var_flavour(char_u *varname) } } +/// Search hashitem in parent scope. +hashitem_T *find_hi_in_scoped_ht(char_u *name, char_u **varname, + hashtab_T **pht) +{ + funccall_T *old_current_funccal = current_funccal; + hashtab_T *ht; + hashitem_T *hi = NULL; + + if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) { + return NULL; + } + + // Search in parent scope which is possible to reference from lambda + current_funccal = current_funccal->func->uf_scoped; + while (current_funccal) { + ht = find_var_ht(name, varname); + if (ht != NULL && **varname != NUL) { + hi = hash_find(ht, *varname); + 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(char_u *name, char_u **varname, int no_autoload) -- cgit From d7c798cd863611754c5ff1c92fbc0b9c12479a47 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Sat, 17 Dec 2016 16:20:47 -0700 Subject: vim-patch:7.4.2121 Problem: No easy way to check if lambda and closure are supported. Solution: Add the +lambda feature. https://github.com/vim/vim/commit/9532fe7fbe1b14531931e83bd9f8054efdcf7509 --- src/nvim/eval.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 5ebd86a59d..e84742abcf 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -11653,6 +11653,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) "insert_expand", "jumplist", "keymap", + "lambda", "langmap", "libcall", "linebreak", -- cgit From 1e3c0efa0f3d41563f91097d04b3616848d5eb62 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Sat, 17 Dec 2016 18:01:24 -0700 Subject: vim-patch:7.4.2134 Problem: No error for using function() badly. Solution: Check for passing wrong function name. (Ken Takata) https://github.com/vim/vim/commit/b54c3ff3174dbb5dfbfcabdf95200b047beaa644 --- src/nvim/eval.c | 95 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 54 insertions(+), 41 deletions(-) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index e84742abcf..d1b60d67b6 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -200,14 +200,15 @@ 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 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 +#define TFN_NO_DEREF 8 // do not dereference a Funcref -/* 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 */ +// 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 @@ -7018,7 +7019,8 @@ err_ret: } /// Parse a lambda expression and get a Funcref from "*arg". -/// Return OK or FAIL. Returns NOTDONE for dict or {expr}. +/// +/// @return OK or FAIL. Returns NOTDONE for dict or {expr}. static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) { garray_T newargs; @@ -7056,7 +7058,7 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) goto errret; } - // Set up dictionaries for checking local variables and arguments. + // Set up a flag for checking local variables and arguments. if (evaluate) { eval_lavars_used = &eval_lavars; } @@ -9242,12 +9244,13 @@ static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) 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(&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, false); } else if (*p == ':') { n = cmd_exists(p + 1); } else if (*p == '#') { @@ -10019,7 +10022,7 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) 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)) { + && !function_exists(s, true)) { // Don't check an autoload name for existence here. EMSG2(_("E700: Unknown function: %s"), s); } else { @@ -20457,10 +20460,10 @@ 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(name, false)) { EMSG2(_("E705: Variable name conflicts with existing function: %s"), name); return TRUE; @@ -21397,16 +21400,16 @@ ret_free: need_wait_return |= saved_wait_return; } -/* - * Get a function name, translating "" and "". - * 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). - */ +/// Get a function name, translating "" and "". +/// 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. static char_u * trans_function_name ( char_u **pp, @@ -21509,7 +21512,7 @@ trans_function_name ( 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); if (name == *pp) { @@ -21572,7 +21575,7 @@ trans_function_name ( 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) { @@ -21709,17 +21712,23 @@ int translated_function_exists(char_u *name) return find_func(name) != NULL; } -/* - * Return TRUE if a function "name" exists. - */ -static int function_exists(char_u *name) +/// Check if a function exists by name +/// +/// @param[in] name The name of the function to check. +/// @param[in] no_deref If true, do not dereference a Funcref. +/// +/// @return true if a function "name" exists. +static int function_exists(char_u *name, bool no_deref) { char_u *nm = name; char_u *p; - int n = FALSE; + int 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; + } + p = trans_function_name(&nm, false, flag, NULL, NULL); nm = skipwhite(nm); /* Only accept "funcname", "funcname ", "funcname (..." and @@ -21730,9 +21739,13 @@ static int function_exists(char_u *name) 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 The name of the builtin function to check. +/// @param[in] len The 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) { if (!ASCII_ISLOWER(name[0])) { -- cgit From 1f715ac1c1a1eee43360be911636020ed855e12c Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Sat, 17 Dec 2016 22:33:03 -0700 Subject: vim-patch:7.4.2136 Problem: Closure function fails. Solution: Don't reset uf_scoped when it points to another funccal. https://github.com/vim/vim/commit/580164481924ed8611eb79f0247a0eb1ca0b3b9a --- src/nvim/eval.c | 70 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index d1b60d67b6..192afa0708 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -7018,6 +7018,18 @@ err_ret: return FAIL; } +/// Register function "fp" as using "current_funccal" as its scope. +static int register_closure(ufunc_T *fp) { + funccal_unref(fp->uf_scoped, NULL); + 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; + func_ref(current_funccal->func->uf_name); + return OK; +} + /// Parse a lambda expression and get a Funcref from "*arg". /// /// @return OK or FAIL. Returns NOTDONE for dict or {expr}. @@ -7083,7 +7095,7 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) snprintf((char *)name, sizeof(name), "%d", lambda_no++); - fp = (ufunc_T *)xmalloc((unsigned)(sizeof(ufunc_T) + STRLEN(name))); + fp = (ufunc_T *)xcalloc(1, (unsigned)(sizeof(ufunc_T) + STRLEN(name))); if (fp == NULL) { goto errret; } @@ -7109,12 +7121,7 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) fp->uf_lines = newlines; if (current_funccal != NULL && eval_lavars) { flags |= FC_CLOSURE; - 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; - func_ref(current_funccal->func->uf_name); + register_closure(fp); } else { fp->uf_scoped = NULL; } @@ -21076,6 +21083,12 @@ void ex_function(exarg_T *eap) } 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; } @@ -21329,7 +21342,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) { @@ -21362,17 +21375,7 @@ void ex_function(exarg_T *eap) fp->uf_args = newargs; fp->uf_lines = newlines; if ((flags & FC_CLOSURE) != 0) { - if (current_funccal == NULL) { - emsg_funcname(N_("E932 Closure function should not be at top level: %s"), - name); - goto erret; - } - 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; - func_ref(current_funccal->func->uf_name); + register_closure(fp); } else { fp->uf_scoped = NULL; } @@ -22543,8 +22546,8 @@ 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 @@ -22555,9 +22558,9 @@ call_user_func ( 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; @@ -22653,9 +22656,15 @@ free_funccal ( ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; if (fp != NULL) { - fp->uf_scoped = NULL; + // Function may have been redefined and point to another + // funccall_T, don't clear it then. + if (fp->uf_scoped == fc) { + fp->uf_scoped = NULL; + } + func_unref(fc->func->uf_name); } } + ga_clear(&fc->fc_funcs): // The a: variables typevals may not have been allocated, only free the // allocated variables. @@ -22671,15 +22680,6 @@ free_funccal ( } } - for (int i = 0; i < fc->fc_funcs.ga_len; i++) { - ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; - - if (fp != NULL) { - func_unref(fc->func->uf_name); - } - } - ga_clear(&fc->fc_funcs); - func_unref(fc->func->uf_name); xfree(fc); } @@ -23013,7 +23013,7 @@ hashitem_T *find_hi_in_scoped_ht(char_u *name, char_u **varname, // Search in parent scope which is possible to reference from lambda current_funccal = current_funccal->func->uf_scoped; - while (current_funccal) { + while (current_funccal != NULL) { ht = find_var_ht(name, varname); if (ht != NULL && **varname != NUL) { hi = hash_find(ht, *varname); -- cgit From 53fad45115d3ee438dfb537d99ccf3b021ebc6b7 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Fri, 16 Dec 2016 14:51:49 -0700 Subject: vim-patch:7.4.2137 Problem: Using function() with a name will find another function when it is redefined. Solution: Add funcref(). Refer to lambda using a partial. Fix several reference counting issues. https://github.com/vim/vim/commit/437bafe4c8a83ed71ee006eda7f54b65a90f0d4c --- src/nvim/eval.c | 432 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 255 insertions(+), 177 deletions(-) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 192afa0708..2e0ac6034d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -218,7 +218,7 @@ static int echo_attr = 0; /* attributes used for ":echo" */ #define FC_DELETED 16 // :delfunction used while uf_refcount > 0 #define FC_REMOVED 32 // function redefined while uf_refcount > 0 -/* The names of packages that once were loaded are remembered. */ +// 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 @@ -232,37 +232,6 @@ static list_T *first_list = NULL; // list of all lists #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. - -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 - int fc_refcount; - int fc_copyID; // for garbage collection - garray_T fc_funcs; // list of ufunc_T* which refer this -}; - /* * Info used by a ":for" loop. */ @@ -273,15 +242,6 @@ typedef struct { list_T *fi_list; /* list being used */ } 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() */ @@ -4960,6 +4920,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++) { @@ -4967,8 +4936,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); } @@ -5232,12 +5205,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; } @@ -6272,7 +6245,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 = abort || set_ref_in_func(pt->pt_name, copyID); + abort = abort || set_ref_in_func(pt->pt_name, pt->pt_func, copyID); if (pt->pt_dict != NULL) { typval_T dtv; @@ -6289,7 +6262,7 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, break; } case VAR_FUNC: - abort = abort || set_ref_in_func(tv->vval.v_string, copyID); + abort = abort || set_ref_in_func(tv->vval.v_string, NULL, copyID); break; case VAR_UNKNOWN: case VAR_SPECIAL: @@ -6307,21 +6280,23 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, /// "ht_stack" is used to add hashtabs to be marked. Can be NULL. /// /// @return TRUE if setting references failed somehow. -int set_ref_in_func(char_u *name, int copyID) +int set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID) { - ufunc_T *fp; + ufunc_T *fp = fp_in; funccall_T *fc; int error; char_u fname_buf[FLEN_FIXED + 1]; char_u *tofree = NULL; char_u *fname; bool abort = false; - if (name == NULL) { + if (name == NULL && fp_in == NULL) { return false; } - fname = fname_trans_sid(name, fname_buf, &tofree, &error); - fp = find_func(fname); + 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) { if (fc->fc_copyID != copyID) { @@ -7026,7 +7001,7 @@ static int register_closure(ufunc_T *fp) { ga_grow(¤t_funccal->fc_funcs, 1); ((ufunc_T **)current_funccal->fc_funcs.ga_data) [current_funccal->fc_funcs.ga_len++] = fp; - func_ref(current_funccal->func->uf_name); + func_ptr_ref(current_funccal->func); return OK; } @@ -7041,7 +7016,6 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) ufunc_T *fp = NULL; int varargs; int ret; - char_u name[20]; char_u *start = skipwhite(*arg + 1); char_u *s, *e; static int lambda_no = 0; @@ -7092,6 +7066,8 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) if (evaluate) { int len, flags = 0; char_u *p; + char_u name[20]; + partial_T *pt; snprintf((char *)name, sizeof(name), "%d", lambda_no++); @@ -7099,6 +7075,11 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) if (fp == NULL) { goto errret; } + pt = (partial_T *)xcalloc(1, (unsigned)(sizeof(partial_T))); + if (pt == NULL) { + xfree(fp); + goto errret; + } ga_init(&newlines, (int)sizeof(char_u *), 1); ga_grow(&newlines, 1); @@ -7138,8 +7119,10 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) fp->uf_calls = 0; fp->uf_script_ID = current_SID; - rettv->vval.v_string = vim_strsave(name); - rettv->v_type = VAR_FUNC; + 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; @@ -7300,6 +7283,8 @@ static char_u *deref_func_name( { dictitem_T *v; int cc; + char_u *s; + if (partialp != NULL) { *partialp = NULL; } @@ -7313,8 +7298,9 @@ static char_u *deref_func_name( *lenp = 0; return (char_u *)""; /* just in case */ } - *lenp = (int)STRLEN(v->di_tv.vval.v_string); - return v->di_tv.vval.v_string; + s = v->di_tv.vval.v_string; + *lenp = (int)STRLEN(s); + return s; } if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) { @@ -7327,8 +7313,9 @@ static char_u *deref_func_name( if (partialp != NULL) { *partialp = pt; } - *lenp = (int)STRLEN(pt->pt_name); - return pt->pt_name; + s = partial_name(pt); + *lenp = (int)STRLEN(s); + return s; } return name; @@ -7421,6 +7408,7 @@ get_func_tv ( #define ERROR_NONE 5 #define ERROR_OTHER 6 #define ERROR_BOTH 7 +#define ERROR_DELETED 8 #define FLEN_FIXED 40 /// In a script change name() and s:name() to K_SNR 123_name(). @@ -7552,12 +7540,14 @@ call_func( error = ERROR_UNKNOWN; if (!builtin_function(rfname, -1)) { - /* - * User defined function. - */ - fp = find_func(rfname); + // 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()) { @@ -7570,7 +7560,9 @@ call_func( fp = find_func(rfname); } - if (fp != NULL) { + 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); } @@ -7629,6 +7621,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; @@ -8465,7 +8460,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]); } @@ -9725,7 +9720,7 @@ static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) } else if (expr->v_type == VAR_PARTIAL) { partial_T *partial = expr->vval.v_partial; - s = partial->pt_name; + s = partial_name(partial); if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL, 0L, 0L, &dummy, true, partial, NULL) == FAIL) { goto theend; @@ -10002,15 +9997,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) @@ -10019,17 +10013,28 @@ 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 (((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 != NULL) { + s = NULL; + } + } 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, true)) { + } else if (trans_name != NULL + && (is_funcref ? find_func(trans_name) == NULL + : !translated_function_exists(trans_name))) { // Don't check an autoload name for existence here. EMSG2(_("E700: Unknown function: %s"), s); } else { @@ -10071,7 +10076,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; @@ -10082,7 +10087,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) { @@ -10090,7 +10095,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 @@ -10103,18 +10108,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++]); } } } @@ -10136,8 +10140,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; @@ -10148,6 +10162,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 @@ -10201,11 +10227,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; @@ -16243,8 +16276,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]); @@ -17746,7 +17780,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: @@ -19346,7 +19380,7 @@ 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; } @@ -19406,19 +19440,22 @@ 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->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)) { @@ -19439,8 +19476,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); @@ -21286,11 +21328,19 @@ void ex_function(exarg_T *eap) 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 = 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]; @@ -21366,12 +21416,15 @@ 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_args = newargs; fp->uf_lines = newlines; if ((flags & FC_CLOSURE) != 0) { @@ -21413,8 +21466,8 @@ ret_free: /// Advances "pp" to just after the function name (if no error). /// /// @return the function name in allocated memory, or NULL for failure. -static char_u * -trans_function_name ( +char_u * +trans_function_name( char_u **pp, int skip, /* only find the end, don't evaluate */ int flags, @@ -21479,7 +21532,7 @@ trans_function_name ( fdp->fd_di = lv.ll_di; } if (lv.ll_tv->v_type == VAR_FUNC && lv.ll_tv->vval.v_string != NULL) { - name = vim_strsave(lv.ll_tv->vval.v_string); + name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial)); *pp = end; } else if (lv.ll_tv->v_type == VAR_PARTIAL && lv.ll_tv->vval.v_partial != NULL) { @@ -21676,11 +21729,9 @@ static void list_func_head(ufunc_T *fp, int indent) 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. +ufunc_T *find_func(char_u *name) { hashitem_T *hi; @@ -22098,8 +22149,34 @@ 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 { + // Normal functions (not numbered functions and lambdas) have a + // refcount of 1 for the entry in the hashtable. When deleting + // them and the refcount is more than one, it should be kept. + // Numbered functions and lambdas snould be kept if the refcount is + // one or more. + if (fp->uf_refcount > (isdigit(fp->uf_name[0]) + || fp->uf_name[0] == '<') ? 0 : 1) { + // Function is still referenced somewhere. Don't free it but + // do remove it from the hashtable. + func_remove(fp); + fp->uf_flags |= FC_DELETED; + fp->uf_refcount--; + } else { + func_free(fp); + } + } + } +} + +/// Remove the function from the function hashtable. If the function was +/// deleted while it still has references this was already done. +static void func_remove(ufunc_T *fp) +{ + hashitem_T *hi = hash_find(&func_hashtab, UF2HIKEY(fp)); + + if (!HASHITEM_EMPTY(hi)) { + hash_remove(&func_hashtab, hi); } } @@ -22108,9 +22185,7 @@ void ex_delfunction(exarg_T *eap) */ static void func_free(ufunc_T *fp) { - hashitem_T *hi; - - /* clear this function */ + // clear this function ga_clear_strings(&(fp->uf_args)); ga_clear_strings(&(fp->uf_lines)); xfree(fp->uf_tml_count); @@ -22123,12 +22198,13 @@ static void func_free(ufunc_T *fp) func_remove(fp); } funccal_unref(fp->uf_scoped, fp); + func_remove(fp); xfree(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) { @@ -22154,6 +22230,10 @@ void func_unref(char_u *name) // fail silently, when lambda function isn't found fp = find_func(name); } + if (fp == NULL && isdigit(*name)) { + EMSG2(_(e_intern2), "func_unref()"); + } + 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. @@ -22163,10 +22243,12 @@ void func_unref(char_u *name) } } -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); @@ -22174,36 +22256,35 @@ static void user_func_unref(ufunc_T *fp) } } -/* - * Count a reference to a Function. - */ +/// Count a reference to a Function. void func_ref(char_u *name) { ufunc_T *fp; if (name == NULL) { return; + } + fp = find_func(name); + if (fp != NULL) { + (fp->uf_refcount)++; } else if (isdigit(*name)) { - fp = find_func(name); - if (fp == NULL) { - EMSG2(_(e_intern2), "func_ref()"); - } else { - (fp->uf_refcount)++; - } - } else if (STRNCMP(name, "", 8) == 0) { - // fail silently, when lambda function isn't found. - fp = find_func(name); - if (fp != NULL) { - (fp->uf_refcount)++; - } + // 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 ( +/// 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 */ @@ -22264,7 +22345,7 @@ call_user_func ( fc->fc_refcount = 0; fc->fc_copyID = 0; ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1); - func_ref(fp->uf_name); + func_ptr_ref(fp); if (STRNCMP(fp->uf_name, "", 8) == 0) { islambda = true; @@ -22579,9 +22660,7 @@ call_user_func ( copy_tv(&li->li_tv, &li->li_tv); } - if (--fp->uf_calls <= 0 && (isdigit(*fp->uf_name) - || STRNCMP(fp->uf_name, "", 8) == 0) - && 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); } @@ -22605,33 +22684,32 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp) } if (--fc->fc_refcount <= 0) { - for (pfc = &previous_funccal; *pfc != NULL; ) { - if (fc == *pfc - && 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) { - *pfc = fc->caller; - free_funccal(fc, true); - freed = true; - } else { - pfc = &(*pfc)->caller; + for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) { + if (fc == *pfc) { + 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) { + *pfc = fc->caller; + free_funccal(fc, true); + freed = true; + } } } - if (!freed) { - func_unref(fc->func->uf_name); + } + if (!freed) { + func_ptr_unref(fc->func); - if (fp != NULL) { - for (i = 0; i < fc->fc_funcs.ga_len; i++) { - if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) { + if (fp != NULL) { + 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 +/// @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) { @@ -22661,10 +22739,10 @@ free_funccal ( if (fp->uf_scoped == fc) { fp->uf_scoped = NULL; } - func_unref(fc->func->uf_name); + func_ptr_unref(fc->func); } } - ga_clear(&fc->fc_funcs): + ga_clear(&fc->fc_funcs); // The a: variables typevals may not have been allocated, only free the // allocated variables. @@ -22680,7 +22758,7 @@ free_funccal ( } } - func_unref(fc->func->uf_name); + func_ptr_unref(fc->func); xfree(fc); } -- cgit From e71e9020eb31cfd606469e5d5ab97500232c65d6 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Fri, 16 Dec 2016 14:56:09 -0700 Subject: vim-patch:7.4.2139 Problem: :delfunction causes illegal memory access. Solution: Correct logic when deciding to free a function. https://github.com/vim/vim/commit/0588d4f9d2741f35a271400a37fddbdd72d84219 --- src/nvim/eval.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 2e0ac6034d..70c47b09a6 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -219,7 +219,7 @@ static int echo_attr = 0; /* attributes used for ":echo" */ #define FC_REMOVED 32 // 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}; +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, @@ -20926,6 +20926,7 @@ void ex_function(exarg_T *eap) int varargs = false; int flags = 0; ufunc_T *fp; + bool overwrite = false; int indent; int nesting; char_u *skip_until = NULL; @@ -21337,7 +21338,7 @@ void ex_function(exarg_T *eap) } else { // redefine existing function ga_clear_strings(&(fp->uf_args)); - ga_clear_strings(&(fp->uf_lines)) + ga_clear_strings(&(fp->uf_lines)); xfree(name); name = NULL; } @@ -22156,7 +22157,7 @@ void ex_delfunction(exarg_T *eap) // Numbered functions and lambdas snould be kept if the refcount is // one or more. if (fp->uf_refcount > (isdigit(fp->uf_name[0]) - || fp->uf_name[0] == '<') ? 0 : 1) { + || fp->uf_name[0] == '<' ? 0 : 1)) { // Function is still referenced somewhere. Don't free it but // do remove it from the hashtable. func_remove(fp); -- cgit From 42727ecf086b444ed6dd91798a0106a835536792 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Mon, 19 Dec 2016 17:06:20 -0700 Subject: vim-patch:7.4.2141 Problem: Coverity reports bogus NULL check. Solution: When checking for a variable in the funccal scope don't pass the varname. https://github.com/vim/vim/commit/ba96e9af388804364425185b47eed14988302865 --- src/nvim/eval.c | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 70c47b09a6..ba81afcdb2 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -3035,7 +3035,7 @@ int do_unlet(char_u *name, int forceit) } hi = hash_find(ht, varname); if (HASHITEM_EMPTY(hi)) { - hi = find_hi_in_scoped_ht(name, &varname, &ht); + hi = find_hi_in_scoped_ht(name, &ht); } if (hi != NULL && !HASHITEM_EMPTY(hi)) { di = HI2DI(hi); @@ -20009,8 +20009,7 @@ static dictitem_T *find_var(char_u *name, hashtab_T **htp, int no_autoload) } // Search in parent scope for lambda - return find_var_in_scoped_ht(name, varname ? &varname : NULL, - no_autoload || htp != NULL); + return find_var_in_scoped_ht(name, no_autoload || htp != NULL); } /// Find variable "varname" in hashtab "ht" with name "htname". @@ -20381,7 +20380,7 @@ set_var ( // Search in parent scope which is possible to reference from lambda if (v == NULL) { - v = find_var_in_scoped_ht(name, varname ? &varname : NULL, true); + v = find_var_in_scoped_ht(name, true); } if ((tv->v_type == VAR_FUNC || tv->v_type == VAR_PARTIAL) @@ -23079,12 +23078,12 @@ static var_flavour_T var_flavour(char_u *varname) } /// Search hashitem in parent scope. -hashitem_T *find_hi_in_scoped_ht(char_u *name, char_u **varname, - hashtab_T **pht) +hashitem_T *find_hi_in_scoped_ht(char_u *name, hashtab_T **pht) { funccall_T *old_current_funccal = current_funccal; hashtab_T *ht; hashitem_T *hi = NULL; + char_u *varname; if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) { return NULL; @@ -23093,9 +23092,9 @@ hashitem_T *find_hi_in_scoped_ht(char_u *name, char_u **varname, // Search in parent scope which is possible to reference from lambda current_funccal = current_funccal->func->uf_scoped; while (current_funccal != NULL) { - ht = find_var_ht(name, varname); - if (ht != NULL && **varname != NUL) { - hi = hash_find(ht, *varname); + ht = find_var_ht(name, &varname); + if (ht != NULL && *varname != NUL) { + hi = hash_find(ht, varname); if (!HASHITEM_EMPTY(hi)) { *pht = ht; break; @@ -23112,12 +23111,12 @@ hashitem_T *find_hi_in_scoped_ht(char_u *name, char_u **varname, } /// Search variable in parent scope. -dictitem_T *find_var_in_scoped_ht(char_u *name, char_u **varname, - int no_autoload) +dictitem_T *find_var_in_scoped_ht(char_u *name, int no_autoload) { dictitem_T *v = NULL; funccall_T *old_current_funccal = current_funccal; - hashtab_T *ht; + hashtab_T *ht; + char_u *varname; if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) { return NULL; @@ -23126,10 +23125,9 @@ dictitem_T *find_var_in_scoped_ht(char_u *name, char_u **varname, // Search in parent scope which is possible to reference from lambda current_funccal = current_funccal->func->uf_scoped; while (current_funccal) { - ht = find_var_ht(name, varname ? &(*varname) : NULL); - if (ht != NULL) { - v = find_var_in_ht(ht, *name, - varname ? *varname : NULL, no_autoload); + ht = find_var_ht(name, &varname); + if (ht != NULL && *varname != NUL) { + v = find_var_in_ht(ht, *name, varname, no_autoload); if (v != NULL) { break; } -- cgit From 00ac82eae276e358f54f5db657f2440701da7810 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Fri, 16 Dec 2016 15:07:03 -0700 Subject: vim-patch:7.4.2142 Problem: Leaking memory when redefining a function. Solution: Don't increment the function reference count when it's found by name. Don't remove the wrong function from the hashtab. More reference counting fixes. https://github.com/vim/vim/commit/8dd3a43d75550e9b5736066124c97697564f769e --- src/nvim/eval.c | 86 +++++++++++++++++++++++++++++++++------------------------ 1 file changed, 50 insertions(+), 36 deletions(-) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index ba81afcdb2..f301753996 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -211,12 +211,12 @@ static int echo_attr = 0; /* attributes used for ":echo" */ #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" -#define FC_CLOSURE 8 // closure, uses outer scope variables -#define FC_DELETED 16 // :delfunction used while uf_refcount > 0 -#define FC_REMOVED 32 // function redefined while uf_refcount > 0 +#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 }; @@ -6995,13 +6995,17 @@ err_ret: /// Register function "fp" as using "current_funccal" as its scope. static int register_closure(ufunc_T *fp) { - funccal_unref(fp->uf_scoped, NULL); + if (fp->uf_scoped == current_funccal) { + // no change + return OK; + } + funccal_unref(fp->uf_scoped, fp); fp->uf_scoped = current_funccal; current_funccal->fc_refcount++; + func_ptr_ref(current->funccal->func); ga_grow(¤t_funccal->fc_funcs, 1); ((ufunc_T **)current_funccal->fc_funcs.ga_data) [current_funccal->fc_funcs.ga_len++] = fp; - func_ptr_ref(current_funccal->func); return OK; } @@ -21332,6 +21336,7 @@ void ex_function(exarg_T *eap) // 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 { @@ -22097,9 +22102,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; @@ -22150,18 +22164,18 @@ void ex_delfunction(exarg_T *eap) * invoke func_unref() and possibly delete the function. */ dictitem_remove(fudi.fd_dict, fudi.fd_di); } else { - // Normal functions (not numbered functions and lambdas) have a + // A normal function (not a numbered function or lambda) has a // refcount of 1 for the entry in the hashtable. When deleting - // them and the refcount is more than one, it should be kept. - // Numbered functions and lambdas snould be kept if the refcount is + // it and the refcount is more than one, it should be kept. + // A numbered function or lambda snould be kept if the refcount is // one or more. - if (fp->uf_refcount > (isdigit(fp->uf_name[0]) - || fp->uf_name[0] == '<' ? 0 : 1)) { + 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. - func_remove(fp); + if (func_remove(fp)) { + fp->uf_refcount--; + } fp->uf_flags |= FC_DELETED; - fp->uf_refcount--; } else { func_free(fp); } @@ -22171,13 +22185,18 @@ void ex_delfunction(exarg_T *eap) /// Remove the function from the function hashtable. If the function was /// deleted while it still has references this was already done. -static void func_remove(ufunc_T *fp) +/// +/// @return true if the entry was deleted, false if it wasn't found. +static bool func_remove(ufunc_T *fp) { hashitem_T *hi = hash_find(&func_hashtab, UF2HIKEY(fp)); if (!HASHITEM_EMPTY(hi)) { hash_remove(&func_hashtab, hi); + return true; } + + return false; } /* @@ -22210,7 +22229,7 @@ void func_unref(char_u *name) { ufunc_T *fp = NULL; - if (name == NULL) { + if (name == NULL || !func_name_refcount(name)) { return; } if (isdigit(*name)) { @@ -22261,7 +22280,7 @@ void func_ref(char_u *name) { ufunc_T *fp; - if (name == NULL) { + if (name == NULL || !func_name_refcount(name)) { return; } fp = find_func(name); @@ -22672,7 +22691,7 @@ call_user_func( } /// Unreference "fc": decrement the reference count and free it when it -/// becomes zero. If "fp" is not NULL, "fp" is detached from "fc". +/// becomes zero. "fp" is detached from "fc". static void funccal_unref(funccall_T *fc, ufunc_T *fp) { funccall_T **pfc; @@ -22683,28 +22702,23 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp) return; } - if (--fc->fc_refcount <= 0) { + if (--fc->fc_refcount <= 0 + && 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) { - 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) { *pfc = fc->caller; free_funccal(fc, true); - freed = true; + return; } } } } - if (!freed) { - func_ptr_unref(fc->func); - - if (fp != NULL) { - for (i = 0; i < fc->fc_funcs.ga_len; i++) { - if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) { - ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL; - } - } + for (i = 0; i < fc->fc_funcs.ga_len; i++) { + if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) { + func_ptr_unref(fc->func); + ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL; } } } -- cgit From e2f76d190d5d7c6bf54fa1d8ee32d88d0a1169cb Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Mon, 19 Dec 2016 17:49:32 -0700 Subject: vim-patch:7.4.2143 Problem: A funccal is garbage collected while it can still be used. Solution: Set copyID in all referenced functions. Do not list lambda functions with ":function". https://github.com/vim/vim/commit/bc7ce675b2d1c9fb58c067eff3edd59abc30aba4 --- src/nvim/eval.c | 121 +++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 80 insertions(+), 41 deletions(-) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index f301753996..8ce9c7712a 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5938,6 +5938,9 @@ bool garbage_collect(bool testing) 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; @@ -6275,12 +6278,33 @@ 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 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. -int set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID) +/// @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; @@ -6299,11 +6323,7 @@ int set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID) } if (fp != NULL) { for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped) { - if (fc->fc_copyID != copyID) { - fc->fc_copyID = copyID; - abort = set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL); - abort = set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL); - } + abort = abort || set_ref_in_funccal(fc, copyID); } } xfree(tofree); @@ -6356,9 +6376,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) +{ + int 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)); @@ -6999,10 +7030,9 @@ static int register_closure(ufunc_T *fp) { // no change return OK; } - funccal_unref(fp->uf_scoped, fp); + funccal_unref(fp->uf_scoped, fp, false); fp->uf_scoped = current_funccal; current_funccal->fc_refcount++; - func_ptr_ref(current->funccal->func); ga_grow(¤t_funccal->fc_funcs, 1); ((ufunc_T **)current_funccal->fc_funcs.ga_data) [current_funccal->fc_funcs.ga_len++] = fp; @@ -20952,8 +20982,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); + } } } } @@ -21756,8 +21787,17 @@ void free_all_functions(void) while (func_hashtab.ht_used > 0) for (hi = func_hashtab.ht_array;; ++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, true); + skipped = 0; + break; + } } } @@ -22177,7 +22217,7 @@ void ex_delfunction(exarg_T *eap) } fp->uf_flags |= FC_DELETED; } else { - func_free(fp); + func_free(fp, false); } } } @@ -22199,10 +22239,10 @@ static bool func_remove(ufunc_T *fp) return false; } -/* - * Free a function and remove it from the list of functions. - */ -static void func_free(ufunc_T *fp) +/// Free a function and remove it from the list of functions. +/// +/// param[in] force When true, we are exiting. +static void func_free(ufunc_T *fp, bool force) { // clear this function ga_clear_strings(&(fp->uf_args)); @@ -22216,7 +22256,7 @@ static void func_free(ufunc_T *fp) if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0) { func_remove(fp); } - funccal_unref(fp->uf_scoped, fp); + funccal_unref(fp->uf_scoped, fp, force); func_remove(fp); xfree(fp); } @@ -22257,7 +22297,7 @@ void func_unref(char_u *name) // 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_free(fp, false); } } } @@ -22270,7 +22310,7 @@ void func_ptr_unref(ufunc_T *fp) // 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_free(fp, false); } } } @@ -22681,7 +22721,7 @@ call_user_func( if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) { // Function was unreferenced while being used, free it now. - func_free(fp); + func_free(fp, false); } // restore search patterns and redo buffer if (did_save_redo) { @@ -22692,26 +22732,26 @@ call_user_func( /// Unreference "fc": decrement the reference count and free it when it /// becomes zero. "fp" is detached from "fc". -static void funccal_unref(funccall_T *fc, ufunc_T *fp) +/// +/// @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; - int freed = false; if (fc == NULL) { return; } - if (--fc->fc_refcount <= 0 - && fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT + 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) { + && 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; - } + *pfc = fc->caller; + free_funccal(fc, true); + return; } } } @@ -22747,13 +22787,12 @@ free_funccal ( for (int i = 0; i < fc->fc_funcs.ga_len; i++) { ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; - if (fp != NULL) { - // Function may have been redefined and point to another - // funccall_T, don't clear it then. - if (fp->uf_scoped == fc) { - fp->uf_scoped = NULL; - } - func_ptr_unref(fc->func); + // 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); -- cgit From 8cae66b5e04467322b3dcd20d0a998d9f327efb9 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Fri, 16 Dec 2016 15:16:55 -0700 Subject: vim-patch:7.4.2197 Problem: All functions are freed on exit, which may hide leaks. Solution: Only free named functions, not reference counted ones. https://github.com/vim/vim/commit/c257487035f83aabe1c7e07f0552309e98f1bcb1 --- src/nvim/eval.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 8ce9c7712a..068a1b8ed4 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -21781,11 +21781,15 @@ ufunc_T *find_func(char_u *name) void free_all_functions(void) { hashitem_T *hi; - - /* 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) + ufunc_T *fp; + uint64_t skipped = 0; + uint64_t todo; + + // Need to start all over every time, because func_free() may change the + // hash table. + while (func_hashtab.ht_used > skipped) { + todo = func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0; hi++) { if (!HASHITEM_EMPTY(hi)) { todo--; // Only free functions that are not refcounted, those are @@ -21799,6 +21803,11 @@ void free_all_functions(void) break; } } + } + } + if (skipped == 0) { + hash_clear(&func_hashtab); + } } #endif -- cgit From effe760b134589f8024d6a5f1cb2c7b979054937 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Mon, 19 Dec 2016 17:21:50 -0700 Subject: vim-patch:7.4.2233 Problem: Crash when using funcref() with invalid name. (Dominique Pelle) Solution: Check for NULL translated name. https://github.com/vim/vim/commit/843b884461de1c79a1d2748549776fb13fc94360 --- src/nvim/eval.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 068a1b8ed4..5d10002846 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -10054,8 +10054,7 @@ static void common_function(typval_T *argvars, typval_T *rettv, use_string = true; } - if (((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) - || is_funcref)) { + 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 @@ -10064,7 +10063,8 @@ static void common_function(typval_T *argvars, typval_T *rettv, s = NULL; } } - if (s == NULL || *s == NUL || (use_string && ascii_isdigit(*s))) { + if (s == NULL || *s == NUL || (use_string && ascii_isdigit(*s)) + || (is_funcref && trans_name == NULL)) { EMSG2(_(e_invarg2), s); } else if (trans_name != NULL && (is_funcref ? find_func(trans_name) == NULL -- cgit From bae8a19c63381c3f6c860bae75af3580d68bf3b3 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Mon, 19 Dec 2016 17:24:21 -0700 Subject: vim-patch:7.4.2235 Problem: submatch() does not check for a valid argument. Solution: Give an error if the argument is out of range. (Dominique Pelle) https://github.com/vim/vim/commit/989f592f7ffcbafdc4ec35cee4dc65bd053e2077 --- src/nvim/eval.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 5d10002846..2e412781af 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -17129,12 +17129,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; } } -- cgit From ef8701610baa18ecf2568990eab4ecf02ca8f6c1 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Mon, 19 Dec 2016 20:09:07 -0700 Subject: Allow lambdas to be used with jobs, timers and dictwatchers. --- src/nvim/eval.c | 199 +++++++++++++++++++++++++------------------------------- 1 file changed, 89 insertions(+), 110 deletions(-) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 2e412781af..ac0e25ff2c 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -620,9 +620,8 @@ void eval_clear(void) // unreferenced lists and dicts (void)garbage_collect(false); - /* functions */ + // functions free_all_functions(); - hash_clear(&func_hashtab); } #endif @@ -5859,6 +5858,7 @@ bool garbage_collect(bool testing) // 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); } @@ -5934,6 +5934,7 @@ bool garbage_collect(bool testing) // 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); } @@ -5961,8 +5962,8 @@ bool garbage_collect(bool testing) // 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); + ABORTING(set_ref_in_item)(((typval_T **)funcargs.ga_data)[i], + copyID, NULL, NULL); } // v: vars @@ -6248,7 +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 = abort || set_ref_in_func(pt->pt_name, pt->pt_func, copyID); + abort = set_ref_in_func(pt->pt_name, pt->pt_func, copyID); if (pt->pt_dict != NULL) { typval_T dtv; @@ -6265,7 +6266,7 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, break; } case VAR_FUNC: - abort = abort || set_ref_in_func(tv->vval.v_string, NULL, copyID); + abort = set_ref_in_func(tv->vval.v_string, NULL, copyID); break; case VAR_UNKNOWN: case VAR_SPECIAL: @@ -6287,48 +6288,19 @@ bool set_ref_in_functions(int copyID) ufunc_T *fp; todo = (int)func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) { + 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); + abort = abort || set_ref_in_func(NULL, fp, copyID); } } } return abort; } -/// 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; - 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; -} /// Mark all lists and dicts referenced in given mark /// @@ -6378,7 +6350,7 @@ static inline bool set_ref_dict(dict_T *dict, int copyID) static bool set_ref_in_funccal(funccall_T *fc, int copyID) { - int abort = false; + bool abort = false; if (fc->fc_copyID != copyID) { fc->fc_copyID = copyID; @@ -6936,7 +6908,7 @@ failret: /// Get function arguments. static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, - int *varargs, int skip) + int *varargs, bool skip) { bool mustend = false; char_u *arg = *argp; @@ -6979,6 +6951,7 @@ static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, *p = NUL; arg = vim_strsave(arg); if (arg == NULL) { + *p = c; goto err_ret; } @@ -7025,10 +6998,11 @@ err_ret: } /// Register function "fp" as using "current_funccal" as its scope. -static int register_closure(ufunc_T *fp) { +static void register_closure(ufunc_T *fp) +{ if (fp->uf_scoped == current_funccal) { // no change - return OK; + return; } funccal_unref(fp->uf_scoped, fp, false); fp->uf_scoped = current_funccal; @@ -7036,16 +7010,14 @@ static int register_closure(ufunc_T *fp) { ga_grow(¤t_funccal->fc_funcs, 1); ((ufunc_T **)current_funccal->fc_funcs.ga_data) [current_funccal->fc_funcs.ga_len++] = fp; - return OK; } /// 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, int evaluate) +static int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) { - garray_T newargs; - garray_T newlines; + garray_T newargs = GA_EMPTY_INIT_VALUE; garray_T *pnewargs; ufunc_T *fp = NULL; int varargs; @@ -7056,10 +7028,6 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) int *old_eval_lavars = eval_lavars_used; int eval_lavars = false; - // TODO(mike): What lengths should be used here? - ga_init(&newargs, (int)sizeof(char_u *), 80); - ga_init(&newlines, (int)sizeof(char_u *), 80); - // First, check if this is a lambda expression. "->" must exists. ret = get_function_args(&start, '-', NULL, NULL, true); if (ret == FAIL || *start != '>') { @@ -7102,14 +7070,13 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) char_u *p; char_u name[20]; partial_T *pt; + garray_T newlines; - snprintf((char *)name, sizeof(name), "%d", lambda_no++); + lambda_no++; + snprintf((char *)name, sizeof(name), "%d", lambda_no); - fp = (ufunc_T *)xcalloc(1, (unsigned)(sizeof(ufunc_T) + STRLEN(name))); - if (fp == NULL) { - goto errret; - } - pt = (partial_T *)xcalloc(1, (unsigned)(sizeof(partial_T))); + 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; @@ -7121,13 +7088,9 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) // Add "return " before the expression. len = 7 + e - s + 1; p = (char_u *)xmalloc(len); - if (p == NULL) { - goto errret; - } ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p; STRCPY(p, "return "); - STRNCPY(p + 7, s, e - s); - p[7 + e - s] = NUL; + STRLCPY(p + 7, s, e - s + 1); fp->uf_refcount = 1; STRCPY(fp->uf_name, name); @@ -7158,12 +7121,12 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) rettv->vval.v_partial = pt; rettv->v_type = VAR_PARTIAL; } + eval_lavars_used = old_eval_lavars; return OK; errret: ga_clear_strings(&newargs); - ga_clear_strings(&newlines); xfree(fp); eval_lavars_used = old_eval_lavars; return FAIL; @@ -7291,9 +7254,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. @@ -7401,7 +7361,7 @@ get_func_tv ( ret = FAIL; if (ret == OK) { - int i = 0; + int i = 0; if (get_vim_var_nr(VV_TESTING)) { // Prepare for calling garbagecollect_for_testing(), need to know @@ -7413,7 +7373,7 @@ get_func_tv ( 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); @@ -7490,6 +7450,37 @@ fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error) { 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 @@ -7612,7 +7603,7 @@ call_func( } 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; } } @@ -8927,11 +8918,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); @@ -8943,6 +8929,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; } @@ -9744,7 +9731,6 @@ static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) copy_tv(tv, &vimvars[VV_VAL].vv_tv); argv[0] = vimvars[VV_KEY].vv_tv; argv[1] = vimvars[VV_VAL].vv_tv; - s = expr->vval.v_string; if (expr->v_type == VAR_FUNC) { s = expr->vval.v_string; if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL, @@ -10054,22 +10040,22 @@ static void common_function(typval_T *argvars, typval_T *rettv, use_string = true; } - if (((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) || is_funcref)) { + 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 != NULL) { + if (*name != NUL) { s = NULL; } } if (s == NULL || *s == NUL || (use_string && ascii_isdigit(*s)) || (is_funcref && trans_name == NULL)) { - EMSG2(_(e_invarg2), s); + 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(trans_name))) { - // Don't check an autoload name for existence here. EMSG2(_("E700: Unknown function: %s"), s); } else { int dict_idx = 0; @@ -17733,7 +17719,8 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) } // "test_garbagecollect_now()" function -static void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv, FunPtr fptr) +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. @@ -18481,7 +18468,8 @@ static bool write_list(FILE *fd, list_T *list, bool binary) } /// Initializes a static list with 10 items. -void init_static_list(staticList10_T *sl) { +void init_static_list(staticList10_T *sl) +{ list_T *l = &sl->sl_list; memset(sl, 0, sizeof(staticList10_T)); @@ -19487,7 +19475,8 @@ static void set_selfdict(typval_T *rettv, dict_T *selfdict) 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->vval.v_string + 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); @@ -21362,7 +21351,7 @@ 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; @@ -21463,7 +21452,7 @@ void ex_function(exarg_T *eap) xfree(fp); goto erret; } - fp->uf_refcount = 1; + fp->uf_refcount = 1; } fp->uf_args = newargs; fp->uf_lines = newlines; @@ -21572,11 +21561,11 @@ trans_function_name( fdp->fd_di = lv.ll_di; } if (lv.ll_tv->v_type == VAR_FUNC && lv.ll_tv->vval.v_string != NULL) { - name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial)); + name = vim_strsave(lv.ll_tv->vval.v_string); *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; @@ -22220,7 +22209,7 @@ void ex_delfunction(exarg_T *eap) // 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 snould be kept if the refcount is + // 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 @@ -22270,7 +22259,6 @@ static void func_free(ufunc_T *fp, bool force) func_remove(fp); } funccal_unref(fp->uf_scoped, fp, force); - func_remove(fp); xfree(fp); } @@ -22285,27 +22273,19 @@ void func_unref(char_u *name) if (name == NULL || !func_name_refcount(name)) { return; } - if (isdigit(*name)) { - fp = find_func(name); - if (fp == NULL) { + + 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); - } - } else if (STRNCMP(name, "", 8) == 0) { - // fail silently, when lambda function isn't found - fp = find_func(name); - } - if (fp == NULL && isdigit(*name)) { - EMSG2(_(e_intern2), "func_unref()"); } - 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. @@ -22357,13 +22337,13 @@ void func_ptr_ref(ufunc_T *fp) /// 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" */ + 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; @@ -22770,7 +22750,6 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force) } for (i = 0; i < fc->fc_funcs.ga_len; i++) { if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) { - func_ptr_unref(fc->func); ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL; } } -- cgit From 10c9ecc2117a69d2b83e983082f53c1779547035 Mon Sep 17 00:00:00 2001 From: Michael Ennen Date: Thu, 2 Feb 2017 16:09:09 -0700 Subject: vim-patch:8.0.0297 Problem: Double free on exit when using a closure. (James McCoy) Solution: Split free_al_functions in two parts. (closes #1428) https://github.com/vim/vim/commit/03ff9bcbc968f7d306e4a4e334e226fdde62ca82 --- src/nvim/eval.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 11 deletions(-) (limited to 'src/nvim/eval.c') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index ac0e25ff2c..49ce9f3144 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -21776,10 +21776,37 @@ void free_all_functions(void) hashitem_T *hi; ufunc_T *fp; uint64_t skipped = 0; - uint64_t todo; + uint64_t todo = 1; + uint64_t used; - // Need to start all over every time, because func_free() may change the - // hash table. + // 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--; + } + } + } + + // 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++) { @@ -21791,7 +21818,7 @@ void free_all_functions(void) if (func_name_refcount(fp->uf_name)) { skipped++; } else { - func_free(fp, true); + func_free(fp); skipped = 0; break; } @@ -22219,7 +22246,7 @@ void ex_delfunction(exarg_T *eap) } fp->uf_flags |= FC_DELETED; } else { - func_free(fp, false); + func_clear_free(fp, false); } } } @@ -22241,27 +22268,49 @@ static bool func_remove(ufunc_T *fp) return false; } -/// Free a function and remove it from the list of functions. +/// 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_free(ufunc_T *fp, bool force) +static void func_clear(ufunc_T *fp, bool force) { + if (fp->uf_cleared) { + return; + } + fp->uf_cleared = true; + // 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); +} +/// 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); } - funccal_unref(fp->uf_scoped, fp, force); 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. @@ -22290,7 +22339,7 @@ void func_unref(char_u *name) // 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, false); + func_clear_free(fp, false); } } } @@ -22303,7 +22352,7 @@ void func_ptr_unref(ufunc_T *fp) // 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, false); + func_clear_free(fp, false); } } } @@ -22714,7 +22763,7 @@ call_user_func( if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) { // Function was unreferenced while being used, free it now. - func_free(fp, false); + func_clear_free(fp, false); } // restore search patterns and redo buffer if (did_save_redo) { -- cgit