mirror of
https://github.com/neovim/neovim.git
synced 2025-10-05 09:26:30 +00:00
vim-patch:9.0.0419: the :defer command does not check the function arguments
Problem: The :defer command does not check the function argument count and
types.
Solution: Check the function arguments when adding a deferred function.
169003289f
Cherry-pick check_internal_func() from Vim, but use EvalFuncDef pointer
as first argument.
Co-authored-by: Bram Moolenaar <Bram@vim.org>
This commit is contained in:
@@ -228,6 +228,31 @@ const EvalFuncDef *find_internal_func(const char *const name)
|
|||||||
return index >= 0 ? &functions[index] : NULL;
|
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,
|
int call_internal_func(const char *const fname, const int argcount, typval_T *const argvars,
|
||||||
typval_T *const rettv)
|
typval_T *const rettv)
|
||||||
FUNC_ATTR_NONNULL_ALL
|
FUNC_ATTR_NONNULL_ALL
|
||||||
|
@@ -72,7 +72,7 @@ static funccall_T *current_funccal = NULL;
|
|||||||
// item in it is still being used.
|
// item in it is still being used.
|
||||||
static funccall_T *previous_funccal = NULL;
|
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_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_funcdict = N_("E717: Dictionary entry already exists");
|
||||||
static const char *e_funcref = N_("E718: Funcref required");
|
static const char *e_funcref = N_("E718: Funcref required");
|
||||||
@@ -1523,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.
|
/// Give an error message for the result of a function.
|
||||||
/// Nothing if "error" is FCERR_NONE.
|
/// Nothing if "error" is FCERR_NONE.
|
||||||
static void user_func_error(int error, const char *name, funcexe_T *funcexe)
|
static void user_func_error(int error, const char *name, funcexe_T *funcexe)
|
||||||
FUNC_ATTR_NONNULL_ALL
|
FUNC_ATTR_NONNULL_ARG(2)
|
||||||
{
|
{
|
||||||
switch (error) {
|
switch (error) {
|
||||||
case FCERR_UNKNOWN:
|
case FCERR_UNKNOWN:
|
||||||
if (funcexe->fe_found_var) {
|
if (funcexe->fe_found_var) {
|
||||||
semsg(_(e_not_callable_type_str), name);
|
semsg(_(e_not_callable_type_str), name);
|
||||||
} else {
|
} else {
|
||||||
emsg_funcname(e_unknownfunc, name);
|
emsg_funcname(e_unknown_function_str, name);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FCERR_NOTMETHOD:
|
case FCERR_NOTMETHOD:
|
||||||
@@ -1543,7 +1543,7 @@ static void user_func_error(int error, const char *name, funcexe_T *funcexe)
|
|||||||
emsg_funcname(_(e_toomanyarg), name);
|
emsg_funcname(_(e_toomanyarg), name);
|
||||||
break;
|
break;
|
||||||
case FCERR_TOOFEW:
|
case FCERR_TOOFEW:
|
||||||
emsg_funcname(N_("E119: Not enough arguments for function: %s"), name);
|
emsg_funcname(_(e_toofewarg), name);
|
||||||
break;
|
break;
|
||||||
case FCERR_SCRIPT:
|
case FCERR_SCRIPT:
|
||||||
emsg_funcname(N_("E120: Using <SID> not in a script context: %s"), name);
|
emsg_funcname(N_("E120: Using <SID> not in a script context: %s"), name);
|
||||||
@@ -3172,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);
|
int r = get_func_arguments(arg, evalarg, false, argvars + partial_argc, &argcount);
|
||||||
argcount += partial_argc;
|
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) {
|
if (r == FAIL) {
|
||||||
while (--argcount >= 0) {
|
while (--argcount >= 0) {
|
||||||
tv_clear(&argvars[argcount]);
|
tv_clear(&argvars[argcount]);
|
||||||
|
@@ -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_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_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_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_dictkey[] INIT(= N_("E716: Key not present in Dictionary: \"%s\""));
|
||||||
EXTERN const char e_listreq[] INIT(= N_("E714: List required"));
|
EXTERN const char e_listreq[] INIT(= N_("E714: List required"));
|
||||||
EXTERN const char e_listblobreq[] INIT(= N_("E897: List or Blob required"));
|
EXTERN const char e_listblobreq[] INIT(= N_("E897: List or Blob required"));
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
source check.vim
|
source check.vim
|
||||||
source shared.vim
|
source shared.vim
|
||||||
|
source vim9.vim
|
||||||
|
|
||||||
func Table(title, ...)
|
func Table(title, ...)
|
||||||
let ret = a:title
|
let ret = a:title
|
||||||
@@ -627,7 +628,7 @@ func Test_defer_quitall()
|
|||||||
call DeferLevelOne()
|
call DeferLevelOne()
|
||||||
END
|
END
|
||||||
call writefile(lines, 'XdeferQuitall', 'D')
|
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_equal(0, v:shell_error)
|
||||||
call assert_false(filereadable('XQuitallOne'))
|
call assert_false(filereadable('XQuitallOne'))
|
||||||
call assert_false(filereadable('XQuitallTwo'))
|
call assert_false(filereadable('XQuitallTwo'))
|
||||||
@@ -650,7 +651,7 @@ func Test_defer_quitall_in_expr_func()
|
|||||||
call Test_defer_in_funcref()
|
call Test_defer_in_funcref()
|
||||||
END
|
END
|
||||||
call writefile(lines, 'XdeferQuitallExpr', 'D')
|
call writefile(lines, 'XdeferQuitallExpr', 'D')
|
||||||
let res = system(GetVimCommandClean() .. ' -X -S XdeferQuitallExpr')
|
let res = system(GetVimCommand() .. ' -X -S XdeferQuitallExpr')
|
||||||
call assert_equal(0, v:shell_error)
|
call assert_equal(0, v:shell_error)
|
||||||
call assert_false(filereadable('Xentry0'))
|
call assert_false(filereadable('Xentry0'))
|
||||||
call assert_false(filereadable('Xentry1'))
|
call assert_false(filereadable('Xentry1'))
|
||||||
@@ -662,5 +663,61 @@ func FuncIndex(idx, val)
|
|||||||
return a:val == 'c'
|
return a:val == 'c'
|
||||||
endfunc
|
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
|
" vim: shiftwidth=2 sts=2 expandtab
|
||||||
|
Reference in New Issue
Block a user