summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2022-09-08 19:51:45 +0100
committerBram Moolenaar <Bram@vim.org>2022-09-08 19:51:45 +0100
commit169003289fb4b2ad18fd7f5807e0d05efff0be85 (patch)
tree50602045f6b4e7ed30d2498bef163412839c8776
parent45bbaef0382c5468d9fac511775bd99ea7bf5b84 (diff)
downloadvim-git-9.0.0419.tar.gz
patch 9.0.0419: the :defer command does not check the function argumentsv9.0.0419
Problem: The :defer command does not check the function argument count and types. Solution: Check the function arguments when adding a deferred function.
-rw-r--r--src/proto/vim9instr.pro3
-rw-r--r--src/testdir/test_user_func.vim60
-rw-r--r--src/userfunc.c41
-rw-r--r--src/version.c2
-rw-r--r--src/vim9cmds.c46
-rw-r--r--src/vim9instr.c169
6 files changed, 226 insertions, 95 deletions
diff --git a/src/proto/vim9instr.pro b/src/proto/vim9instr.pro
index b0f3aa476..1fea52b98 100644
--- a/src/proto/vim9instr.pro
+++ b/src/proto/vim9instr.pro
@@ -46,11 +46,14 @@ int generate_JUMP(cctx_T *cctx, jumpwhen_T when, int where);
int generate_JUMP_IF_ARG_SET(cctx_T *cctx, int arg_off);
int generate_FOR(cctx_T *cctx, int loop_idx);
int generate_TRYCONT(cctx_T *cctx, int levels, int where);
+int check_internal_func_args(cctx_T *cctx, int func_idx, int argcount, int method_call, type2_T **argtypes, type2_T *shuffled_argtypes);
int generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call);
int generate_LISTAPPEND(cctx_T *cctx);
int generate_BLOBAPPEND(cctx_T *cctx);
+int check_args_on_stack(cctx_T *cctx, ufunc_T *ufunc, int argcount);
int generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount);
int generate_UCALL(cctx_T *cctx, char_u *name, int argcount);
+int check_func_args_from_type(cctx_T *cctx, type_T *type, int argcount, int at_top, char_u *name);
int generate_PCALL(cctx_T *cctx, int argcount, char_u *name, type_T *type, int at_top);
int generate_DEFER(cctx_T *cctx, int var_idx, int argcount);
int generate_STRINGMEMBER(cctx_T *cctx, char_u *name, size_t len);
diff --git a/src/testdir/test_user_func.vim b/src/testdir/test_user_func.vim
index 6b1753440..4721175e7 100644
--- a/src/testdir/test_user_func.vim
+++ b/src/testdir/test_user_func.vim
@@ -5,6 +5,7 @@
source check.vim
source shared.vim
+import './vim9.vim' as v9
func Table(title, ...)
let ret = a:title
@@ -619,7 +620,7 @@ func Test_defer_quitall()
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'))
@@ -641,7 +642,7 @@ func Test_defer_quitall_in_expr_func()
call Test_defer_in_funcref()
END
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_false(filereadable('Xentry0'))
call assert_false(filereadable('Xentry1'))
@@ -695,5 +696,60 @@ def Test_defer_in_funcref()
assert_false(filereadable('Xentry2'))
enddef
+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:')
+
+ 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
diff --git a/src/userfunc.c b/src/userfunc.c
index 7b6034aaf..1412caa8e 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -5608,6 +5608,7 @@ ex_call_inner(
ex_defer_inner(
char_u *name,
char_u **arg,
+ type_T *type,
partial_T *partial,
evalarg_T *evalarg)
{
@@ -5640,6 +5641,44 @@ ex_defer_inner(
r = get_func_arguments(arg, evalarg, FALSE,
argvars + partial_argc, &argcount);
argcount += partial_argc;
+
+ if (r == OK)
+ {
+ if (type != NULL)
+ {
+ // Check that the arguments are OK for the types of the funcref.
+ r = check_argument_types(type, argvars, argcount, NULL, name);
+ }
+ else if (builtin_function(name, -1))
+ {
+ int idx = find_internal_func(name);
+
+ if (idx < 0)
+ {
+ emsg_funcname(e_unknown_function_str, name);
+ r = FAIL;
+ }
+ else if (check_internal_func(idx, argcount) == -1)
+ r = FAIL;
+ }
+ else
+ {
+ ufunc_T *ufunc = find_func(name, FALSE);
+
+ // 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)
@@ -5839,7 +5878,7 @@ ex_call(exarg_T *eap)
if (eap->cmdidx == CMD_defer)
{
arg = startarg;
- failed = ex_defer_inner(name, &arg, partial, &evalarg) == FAIL;
+ failed = ex_defer_inner(name, &arg, type, partial, &evalarg) == FAIL;
}
else
{
diff --git a/src/version.c b/src/version.c
index c3d1ad520..81de0b7d2 100644
--- a/src/version.c
+++ b/src/version.c
@@ -704,6 +704,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 419,
+/**/
418,
/**/
417,
diff --git a/src/vim9cmds.c b/src/vim9cmds.c
index c294d70a8..080674d2c 100644
--- a/src/vim9cmds.c
+++ b/src/vim9cmds.c
@@ -1685,33 +1685,12 @@ compile_eval(char_u *arg, cctx_T *cctx)
}
/*
- * Get the local variable index for deferred function calls.
- * Reserve it when not done already.
- * Returns zero for failure.
- */
- int
-get_defer_var_idx(cctx_T *cctx)
-{
- dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
- + cctx->ctx_ufunc->uf_dfunc_idx;
- if (dfunc->df_defer_var_idx == 0)
- {
- lvar_T *lvar = reserve_local(cctx, (char_u *)"@defer@", 7,
- TRUE, &t_list_any);
- if (lvar == NULL)
- return 0;
- dfunc->df_defer_var_idx = lvar->lv_idx + 1;
- }
- return dfunc->df_defer_var_idx;
-}
-
-/*
* Compile "defer func(arg)".
*/
char_u *
compile_defer(char_u *arg_start, cctx_T *cctx)
{
- char_u *p;
+ char_u *paren;
char_u *arg = arg_start;
int argcount = 0;
int defer_var_idx;
@@ -1720,13 +1699,13 @@ compile_defer(char_u *arg_start, cctx_T *cctx)
// Get a funcref for the function name.
// TODO: better way to find the "(".
- p = vim_strchr(arg, '(');
- if (p == NULL)
+ paren = vim_strchr(arg, '(');
+ if (paren == NULL)
{
semsg(_(e_missing_parenthesis_str), arg);
return NULL;
}
- *p = NUL;
+ *paren = NUL;
func_idx = find_internal_func(arg);
if (func_idx >= 0)
// TODO: better type
@@ -1734,7 +1713,7 @@ compile_defer(char_u *arg_start, cctx_T *cctx)
&t_func_any, FALSE);
else if (compile_expr0(&arg, cctx) == FAIL)
return NULL;
- *p = '(';
+ *paren = '(';
// check for function type
type = get_type_on_stack(cctx, 0);
@@ -1745,11 +1724,22 @@ compile_defer(char_u *arg_start, cctx_T *cctx)
}
// compile the arguments
- arg = skipwhite(p + 1);
+ arg = skipwhite(paren + 1);
if (compile_arguments(&arg, cctx, &argcount, CA_NOT_SPECIAL) == FAIL)
return NULL;
- // TODO: check argument count with "type"
+ if (func_idx >= 0)
+ {
+ type2_T *argtypes = NULL;
+ type2_T shuffled_argtypes[MAX_FUNC_ARGS];
+
+ if (check_internal_func_args(cctx, func_idx, argcount, FALSE,
+ &argtypes, shuffled_argtypes) == FAIL)
+ return NULL;
+ }
+ else if (check_func_args_from_type(cctx, type, argcount, TRUE,
+ arg_start) == FAIL)
+ return NULL;
defer_var_idx = get_defer_var_idx(cctx);
if (defer_var_idx == 0)
diff --git a/src/vim9instr.c b/src/vim9instr.c
index 34d4ae33f..6a387fa51 100644
--- a/src/vim9instr.c
+++ b/src/vim9instr.c
@@ -1329,33 +1329,31 @@ generate_TRYCONT(cctx_T *cctx, int levels, int where)
return OK;
}
-
/*
- * Generate an ISN_BCALL instruction.
- * "method_call" is TRUE for "value->method()"
- * Return FAIL if the number of arguments is wrong.
+ * Check "argount" arguments and their types on the type stack.
+ * Give an error and return FAIL if something is wrong.
+ * When "method_call" is NULL no code is generated.
*/
int
-generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call)
+check_internal_func_args(
+ cctx_T *cctx,
+ int func_idx,
+ int argcount,
+ int method_call,
+ type2_T **argtypes,
+ type2_T *shuffled_argtypes)
{
- isn_T *isn;
garray_T *stack = &cctx->ctx_type_stack;
- int argoff;
- type2_T *typep;
- type2_T *argtypes = NULL;
- type2_T shuffled_argtypes[MAX_FUNC_ARGS];
- type2_T *maptype = NULL;
- type_T *type;
- type_T *decl_type;
+ int argoff = check_internal_func(func_idx, argcount);
- RETURN_OK_IF_SKIP(cctx);
- argoff = check_internal_func(func_idx, argcount);
if (argoff < 0)
return FAIL;
if (method_call && argoff > 1)
{
- if ((isn = generate_instr(cctx, ISN_SHUFFLE)) == NULL)
+ isn_T *isn = generate_instr(cctx, ISN_SHUFFLE);
+
+ if (isn == NULL)
return FAIL;
isn->isn_arg.shuffle.shfl_item = argcount;
isn->isn_arg.shuffle.shfl_up = argoff - 1;
@@ -1363,17 +1361,18 @@ generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call)
if (argcount > 0)
{
+ type2_T *typep = ((type2_T *)stack->ga_data) + stack->ga_len - argcount;
+
// Check the types of the arguments.
- typep = ((type2_T *)stack->ga_data) + stack->ga_len - argcount;
if (method_call && argoff > 1)
{
int i;
for (i = 0; i < argcount; ++i)
shuffled_argtypes[i] = (i < argoff - 1)
- ? typep[i + 1]
- : (i == argoff - 1) ? typep[0] : typep[i];
- argtypes = shuffled_argtypes;
+ ? typep[i + 1]
+ : (i == argoff - 1) ? typep[0] : typep[i];
+ *argtypes = shuffled_argtypes;
}
else
{
@@ -1381,14 +1380,39 @@ generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call)
for (i = 0; i < argcount; ++i)
shuffled_argtypes[i] = typep[i];
- argtypes = shuffled_argtypes;
+ *argtypes = shuffled_argtypes;
}
- if (internal_func_check_arg_types(argtypes, func_idx, argcount,
+ if (internal_func_check_arg_types(*argtypes, func_idx, argcount,
cctx) == FAIL)
return FAIL;
- if (internal_func_is_map(func_idx))
- maptype = argtypes;
}
+ return OK;
+}
+
+/*
+ * Generate an ISN_BCALL instruction.
+ * "method_call" is TRUE for "value->method()"
+ * Return FAIL if the number of arguments is wrong.
+ */
+ int
+generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call)
+{
+ isn_T *isn;
+ garray_T *stack = &cctx->ctx_type_stack;
+ type2_T *argtypes = NULL;
+ type2_T shuffled_argtypes[MAX_FUNC_ARGS];
+ type2_T *maptype = NULL;
+ type_T *type;
+ type_T *decl_type;
+
+ RETURN_OK_IF_SKIP(cctx);
+
+ if (check_internal_func_args(cctx, func_idx, argcount, method_call,
+ &argtypes, shuffled_argtypes) == FAIL)
+ return FAIL;
+
+ if (internal_func_is_map(func_idx))
+ maptype = argtypes;
if ((isn = generate_instr(cctx, ISN_BCALL)) == NULL)
return FAIL;
@@ -1578,6 +1602,61 @@ generate_UCALL(cctx_T *cctx, char_u *name, int argcount)
}
/*
+ * Check the arguments of function "type" against the types on the stack.
+ * Returns OK or FAIL;
+ */
+ int
+check_func_args_from_type(
+ cctx_T *cctx,
+ type_T *type,
+ int argcount,
+ int at_top,
+ char_u *name)
+{
+ if (type->tt_argcount != -1)
+ {
+ int varargs = (type->tt_flags & TTFLAG_VARARGS) ? 1 : 0;
+
+ if (argcount < type->tt_min_argcount - varargs)
+ {
+ emsg_funcname(e_not_enough_arguments_for_function_str, name);
+ return FAIL;
+ }
+ if (!varargs && argcount > type->tt_argcount)
+ {
+ emsg_funcname(e_too_many_arguments_for_function_str, name);
+ return FAIL;
+ }
+ if (type->tt_args != NULL)
+ {
+ int i;
+
+ for (i = 0; i < argcount; ++i)
+ {
+ int offset = -argcount + i - (at_top ? 0 : 1);
+ type_T *actual = get_type_on_stack(cctx, -1 - offset);
+ type_T *expected;
+
+ if (varargs && i >= type->tt_argcount - 1)
+ expected = type->tt_args[type->tt_argcount - 1]->tt_member;
+ else if (i >= type->tt_min_argcount
+ && actual->tt_type == VAR_SPECIAL)
+ expected = &t_any;
+ else
+ expected = type->tt_args[i];
+ if (need_type(actual, expected, offset, i + 1,
+ cctx, TRUE, FALSE) == FAIL)
+ {
+ arg_type_mismatch(expected, actual, i + 1);
+ return FAIL;
+ }
+ }
+ }
+ }
+
+ return OK;
+}
+/*
* Generate an ISN_PCALL instruction.
* "type" is the type of the FuncRef.
*/
@@ -1598,47 +1677,9 @@ generate_PCALL(
ret_type = &t_any;
else if (type->tt_type == VAR_FUNC || type->tt_type == VAR_PARTIAL)
{
- if (type->tt_argcount != -1)
- {
- int varargs = (type->tt_flags & TTFLAG_VARARGS) ? 1 : 0;
-
- if (argcount < type->tt_min_argcount - varargs)
- {
- emsg_funcname(e_not_enough_arguments_for_function_str, name);
- return FAIL;
- }
- if (!varargs && argcount > type->tt_argcount)
- {
- emsg_funcname(e_too_many_arguments_for_function_str, name);
- return FAIL;
- }
- if (type->tt_args != NULL)
- {
- int i;
+ if (check_func_args_from_type(cctx, type, argcount, at_top, name) == FAIL)
+ return FAIL;
- for (i = 0; i < argcount; ++i)
- {
- int offset = -argcount + i - (at_top ? 0 : 1);
- type_T *actual = get_type_on_stack(cctx, -1 - offset);
- type_T *expected;
-
- if (varargs && i >= type->tt_argcount - 1)
- expected = type->tt_args[
- type->tt_argcount - 1]->tt_member;
- else if (i >= type->tt_min_argcount
- && actual->tt_type == VAR_SPECIAL)
- expected = &t_any;
- else
- expected = type->tt_args[i];
- if (need_type(actual, expected, offset, i + 1,
- cctx, TRUE, FALSE) == FAIL)
- {
- arg_type_mismatch(expected, actual, i + 1);
- return FAIL;
- }
- }
- }
- }
ret_type = type->tt_member;
if (ret_type == &t_unknown)
// return type not known yet, use a runtime check