Merge pull request #23144 from zeertzjq/vim-9.0.0419

vim-patch:9.0.{0406,0419}: the :defer command does not check the function arguments
This commit is contained in:
zeertzjq
2023-04-17 10:09:18 +08:00
committed by GitHub
4 changed files with 162 additions and 13 deletions

View File

@@ -228,6 +228,31 @@ const EvalFuncDef *find_internal_func(const char *const name)
return index >= 0 ? &functions[index] : NULL;
}
/// Check the argument count to use for internal function "fdef".
/// @return -1 for failure, 0 if no method base accepted, 1 if method base is
/// first argument, 2 if method base is second argument, etc.
int check_internal_func(const EvalFuncDef *const fdef, const int argcount)
FUNC_ATTR_NONNULL_ALL
{
int res;
if (argcount < fdef->min_argc) {
res = FCERR_TOOFEW;
} else if (argcount > fdef->max_argc) {
res = FCERR_TOOMANY;
} else {
return fdef->base_arg;
}
const char *const name = fdef->name;
if (res == FCERR_TOOMANY) {
semsg(_(e_toomanyarg), name);
} else {
semsg(_(e_toofewarg), name);
}
return -1;
}
int call_internal_func(const char *const fname, const int argcount, typval_T *const argvars,
typval_T *const rettv)
FUNC_ATTR_NONNULL_ALL

View File

@@ -72,7 +72,7 @@ static funccall_T *current_funccal = NULL;
// item in it is still being used.
static funccall_T *previous_funccal = NULL;
static const char *e_unknownfunc = N_("E117: Unknown function: %s");
static const char *e_unknown_function_str = N_("E117: Unknown function: %s");
static const char *e_funcexts = N_("E122: Function %s already exists, add ! to replace it");
static const char *e_funcdict = N_("E717: Dictionary entry already exists");
static const char *e_funcref = N_("E718: Funcref required");
@@ -881,6 +881,27 @@ static void func_clear_free(ufunc_T *fp, bool force)
func_free(fp);
}
/// Allocate a funccall_T, link it in current_funccal and fill in "fp" and "rettv".
/// Must be followed by one call to remove_funccal() or cleanup_function_call().
funccall_T *create_funccal(ufunc_T *fp, typval_T *rettv)
{
funccall_T *fc = xcalloc(1, sizeof(funccall_T));
fc->fc_caller = current_funccal;
current_funccal = fc;
fc->fc_func = fp;
func_ptr_ref(fp);
fc->fc_rettv = rettv;
return fc;
}
/// Restore current_funccal.
void remove_funccal(void)
{
funccall_T *fc = current_funccal;
current_funccal = fc->fc_caller;
free_funccal(fc);
}
/// Call a user function
///
/// @param fp Function to call.
@@ -895,7 +916,6 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
FUNC_ATTR_NONNULL_ARG(1, 3, 4)
{
bool using_sandbox = false;
funccall_T *fc;
int save_did_emsg;
static int depth = 0;
dictitem_T *v;
@@ -930,19 +950,13 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
// check for CTRL-C hit
line_breakcheck();
// prepare the funccall_T structure
fc = xcalloc(1, sizeof(funccall_T));
fc->fc_caller = current_funccal;
current_funccal = fc;
fc->fc_func = fp;
fc->fc_rettv = rettv;
funccall_T *fc = create_funccal(fp, rettv);
fc->fc_level = ex_nesting_level;
// Check if this function has a breakpoint.
fc->fc_breakpoint = dbg_find_breakpoint(false, fp->uf_name, (linenr_T)0);
fc->fc_dbg_tick = debug_tick;
// Set up fields for closure.
ga_init(&fc->fc_ufuncs, sizeof(ufunc_T *), 1);
func_ptr_ref(fp);
if (strncmp(fp->uf_name, "<lambda>", 8) == 0) {
islambda = true;
@@ -1509,14 +1523,14 @@ varnumber_T callback_call_retnr(Callback *callback, int argcount, typval_T *argv
/// Give an error message for the result of a function.
/// Nothing if "error" is FCERR_NONE.
static void user_func_error(int error, const char *name, funcexe_T *funcexe)
FUNC_ATTR_NONNULL_ALL
FUNC_ATTR_NONNULL_ARG(2)
{
switch (error) {
case FCERR_UNKNOWN:
if (funcexe->fe_found_var) {
semsg(_(e_not_callable_type_str), name);
} else {
emsg_funcname(e_unknownfunc, name);
emsg_funcname(e_unknown_function_str, name);
}
break;
case FCERR_NOTMETHOD:
@@ -1529,7 +1543,7 @@ static void user_func_error(int error, const char *name, funcexe_T *funcexe)
emsg_funcname(_(e_toomanyarg), name);
break;
case FCERR_TOOFEW:
emsg_funcname(N_("E119: Not enough arguments for function: %s"), name);
emsg_funcname(_(e_toofewarg), name);
break;
case FCERR_SCRIPT:
emsg_funcname(N_("E120: Using <SID> not in a script context: %s"), name);
@@ -3158,6 +3172,29 @@ static int ex_defer_inner(char *name, char **arg, const partial_T *const partial
}
int r = get_func_arguments(arg, evalarg, false, argvars + partial_argc, &argcount);
argcount += partial_argc;
if (r == OK) {
if (builtin_function(name, -1)) {
const EvalFuncDef *const fdef = find_internal_func(name);
if (fdef == NULL) {
emsg_funcname(e_unknown_function_str, name);
r = FAIL;
} else if (check_internal_func(fdef, argcount) == -1) {
r = FAIL;
}
} else {
ufunc_T *ufunc = find_func(name);
// we tolerate an unknown function here, it might be defined later
if (ufunc != NULL) {
int error = check_user_func_argcount(ufunc, argcount);
if (error != FCERR_UNKNOWN) {
user_func_error(error, name, NULL);
r = FAIL;
}
}
}
}
if (r == FAIL) {
while (--argcount >= 0) {
tv_clear(&argvars[argcount]);

View File

@@ -953,6 +953,7 @@ EXTERN const char e_dictreq[] INIT(= N_("E715: Dictionary required"));
EXTERN const char e_blobidx[] INIT(= N_("E979: Blob index out of range: %" PRId64));
EXTERN const char e_invalblob[] INIT(= N_("E978: Invalid operation for Blob"));
EXTERN const char e_toomanyarg[] INIT(= N_("E118: Too many arguments for function: %s"));
EXTERN const char e_toofewarg[] INIT(= N_("E119: Not enough arguments for function: %s"));
EXTERN const char e_dictkey[] INIT(= N_("E716: Key not present in Dictionary: \"%s\""));
EXTERN const char e_listreq[] INIT(= N_("E714: List required"));
EXTERN const char e_listblobreq[] INIT(= N_("E897: List or Blob required"));

View File

@@ -5,6 +5,7 @@
source check.vim
source shared.vim
source vim9.vim
func Table(title, ...)
let ret = a:title
@@ -627,11 +628,96 @@ func Test_defer_quitall()
call DeferLevelOne()
END
call writefile(lines, 'XdeferQuitall', 'D')
let res = system(GetVimCommandClean() .. ' -X -S XdeferQuitall')
let res = system(GetVimCommand() .. ' -X -S XdeferQuitall')
call assert_equal(0, v:shell_error)
call assert_false(filereadable('XQuitallOne'))
call assert_false(filereadable('XQuitallTwo'))
endfunc
func Test_defer_quitall_in_expr_func()
throw 'Skipped: Vim9 script is N/A'
let lines =<< trim END
def DefIndex(idx: number, val: string): bool
call writefile([idx .. ': ' .. val], 'Xentry' .. idx, 'D')
if val == 'b'
qa!
endif
return val == 'c'
enddef
def Test_defer_in_funcref()
assert_equal(2, indexof(['a', 'b', 'c'], funcref('g:DefIndex')))
enddef
call Test_defer_in_funcref()
END
call writefile(lines, 'XdeferQuitallExpr', 'D')
let res = system(GetVimCommand() .. ' -X -S XdeferQuitallExpr')
call assert_equal(0, v:shell_error)
call assert_false(filereadable('Xentry0'))
call assert_false(filereadable('Xentry1'))
call assert_false(filereadable('Xentry2'))
endfunc
func FuncIndex(idx, val)
call writefile([a:idx .. ': ' .. a:val], 'Xentry' .. a:idx, 'D')
return a:val == 'c'
endfunc
func Test_defer_wrong_arguments()
call assert_fails('defer delete()', 'E119:')
call assert_fails('defer FuncIndex(1)', 'E119:')
call assert_fails('defer delete(1, 2, 3)', 'E118:')
call assert_fails('defer FuncIndex(1, 2, 3)', 'E118:')
throw 'Skipped: Vim9 script is N/A'
let lines =<< trim END
def DeferFunc0()
defer delete()
enddef
defcompile
END
call v9.CheckScriptFailure(lines, 'E119:')
let lines =<< trim END
def DeferFunc3()
defer delete(1, 2, 3)
enddef
defcompile
END
call v9.CheckScriptFailure(lines, 'E118:')
let lines =<< trim END
def DeferFunc2()
defer delete(1, 2)
enddef
defcompile
END
call v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected string but got number')
def g:FuncOneArg(arg: string)
echo arg
enddef
let lines =<< trim END
def DeferUserFunc0()
defer g:FuncOneArg()
enddef
defcompile
END
call v9.CheckScriptFailure(lines, 'E119:')
let lines =<< trim END
def DeferUserFunc2()
defer g:FuncOneArg(1, 2)
enddef
defcompile
END
call v9.CheckScriptFailure(lines, 'E118:')
let lines =<< trim END
def DeferUserFunc1()
defer g:FuncOneArg(1)
enddef
defcompile
END
call v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected string but got number')
endfunc
" vim: shiftwidth=2 sts=2 expandtab