summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2021-06-21 19:44:11 +0200
committerBram Moolenaar <Bram@vim.org>2021-06-21 19:44:11 +0200
commit035bd1c99f2a8eda5ee886adde4f97ea71fb167f (patch)
tree8f666e256d60b0d5d490ada1074743ff975fa75a
parentf1e7449d567c630601aa0cec6c663b791785a668 (diff)
downloadvim-git-035bd1c99f2a8eda5ee886adde4f97ea71fb167f.tar.gz
patch 8.2.3029: Vim9: crash when using operator and list unpack assignmentv8.2.3029
Problem: Vim9: crash when using operator and list unpack assignment. (Naohiro Ono) Solution: Get variable value before operation. (closes #8416)
-rw-r--r--src/ex_docmd.c13
-rw-r--r--src/testdir/test_vim9_assign.vim23
-rw-r--r--src/testdir/test_vim9_disassemble.vim31
-rw-r--r--src/version.c2
-rw-r--r--src/vim9.h7
-rw-r--r--src/vim9compile.c26
-rw-r--r--src/vim9execute.c14
7 files changed, 96 insertions, 20 deletions
diff --git a/src/ex_docmd.c b/src/ex_docmd.c
index 2a9983f8a..027d139da 100644
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -3485,6 +3485,8 @@ find_ex_command(
// can't be an assignment.
if (*eap->cmd == '[')
{
+ char_u *eq;
+
p = to_name_const_end(eap->cmd);
if (p == eap->cmd && *p == '[')
{
@@ -3493,12 +3495,19 @@ find_ex_command(
p = skip_var_list(eap->cmd, TRUE, &count, &semicolon, TRUE);
}
- if (p == NULL || p == eap->cmd || *skipwhite(p) != '=')
+ eq = p;
+ if (eq != NULL)
+ {
+ eq = skipwhite(eq);
+ if (vim_strchr((char_u *)"+-*/%", *eq) != NULL)
+ ++eq;
+ }
+ if (p == NULL || p == eap->cmd || *eq != '=')
{
eap->cmdidx = CMD_eval;
return eap->cmd;
}
- if (p > eap->cmd && *skipwhite(p) == '=')
+ if (p > eap->cmd && *eq == '=')
{
eap->cmdidx = CMD_var;
return eap->cmd;
diff --git a/src/testdir/test_vim9_assign.vim b/src/testdir/test_vim9_assign.vim
index 1cbdcfb46..1d3c20e9f 100644
--- a/src/testdir/test_vim9_assign.vim
+++ b/src/testdir/test_vim9_assign.vim
@@ -283,6 +283,29 @@ def Test_assign_unpack()
[v1, v2; _] = [1, 2, 3, 4, 5]
assert_equal(1, v1)
assert_equal(2, v2)
+
+ var a = 1
+ var b = 3
+ [a, b] += [2, 4]
+ assert_equal(3, a)
+ assert_equal(7, b)
+
+ [a, b] -= [1, 2]
+ assert_equal(2, a)
+ assert_equal(5, b)
+
+ [a, b] *= [3, 2]
+ assert_equal(6, a)
+ assert_equal(10, b)
+
+ [a, b] /= [2, 4]
+ assert_equal(3, a)
+ assert_equal(2, b)
+
+ [a, b] = [17, 15]
+ [a, b] %= [5, 3]
+ assert_equal(2, a)
+ assert_equal(0, b)
END
CheckDefAndScriptSuccess(lines)
diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim
index 9d2f697d8..7938d91f8 100644
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -452,6 +452,37 @@ def Test_disassemble_list_assign()
res)
enddef
+def s:ListAssignWithOp()
+ var a = 2
+ var b = 3
+ [a, b] += [4, 5]
+enddef
+
+def Test_disassemble_list_assign_with_op()
+ var res = execute('disass s:ListAssignWithOp')
+ assert_match('<SNR>\d*_ListAssignWithOp\_s*' ..
+ 'var a = 2\_s*' ..
+ '\d STORE 2 in $0\_s*' ..
+ 'var b = 3\_s*' ..
+ '\d STORE 3 in $1\_s*' ..
+ '\[a, b\] += \[4, 5\]\_s*' ..
+ '\d\+ PUSHNR 4\_s*' ..
+ '\d\+ PUSHNR 5\_s*' ..
+ '\d\+ NEWLIST size 2\_s*' ..
+ '\d\+ CHECKLEN 2\_s*' ..
+ '\d\+ LOAD $0\_s*' ..
+ '\d\+ ITEM 0 with op\_s*' ..
+ '\d\+ OPNR +\_s*' ..
+ '\d\+ STORE $0\_s*' ..
+ '\d\+ LOAD $1\_s*' ..
+ '\d\+ ITEM 1 with op\_s*' ..
+ '\d\+ OPNR +\_s*' ..
+ '\d\+ STORE $1\_s*' ..
+ '\d\+ DROP\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
def s:ListAdd()
var l: list<number> = []
add(l, 123)
diff --git a/src/version.c b/src/version.c
index db2433db9..096235fc9 100644
--- a/src/version.c
+++ b/src/version.c
@@ -756,6 +756,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 3029,
+/**/
3028,
/**/
3027,
diff --git a/src/vim9.h b/src/vim9.h
index 97e2132a3..58d451cd1 100644
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -209,6 +209,12 @@ typedef struct {
int cuf_argcount; // number of arguments on top of stack
} cufunc_T;
+// arguments to ISN_GETITEM
+typedef struct {
+ varnumber_T gi_index;
+ int gi_with_op;
+} getitem_T;
+
typedef enum {
JUMP_ALWAYS,
JUMP_IF_FALSE, // pop and jump if false
@@ -432,6 +438,7 @@ struct isn_S {
isn_T *instr;
tostring_T tostring;
tobool_T tobool;
+ getitem_T getitem;
} isn_arg;
};
diff --git a/src/vim9compile.c b/src/vim9compile.c
index e4656c99a..c5a2c2dcf 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -1240,13 +1240,16 @@ generate_PUSHFUNC(cctx_T *cctx, char_u *name, type_T *type)
/*
* Generate an ISN_GETITEM instruction with "index".
+ * "with_op" is TRUE for "+=" and other operators, the stack has the current
+ * value below the list with values.
*/
static int
-generate_GETITEM(cctx_T *cctx, int index)
+generate_GETITEM(cctx_T *cctx, int index, int with_op)
{
isn_T *isn;
garray_T *stack = &cctx->ctx_type_stack;
- type_T *type = ((type_T **)stack->ga_data)[stack->ga_len - 1];
+ type_T *type = ((type_T **)stack->ga_data)[stack->ga_len
+ - (with_op ? 2 : 1)];
type_T *item_type = &t_any;
RETURN_OK_IF_SKIP(cctx);
@@ -1260,7 +1263,8 @@ generate_GETITEM(cctx_T *cctx, int index)
item_type = type->tt_member;
if ((isn = generate_instr(cctx, ISN_GETITEM)) == NULL)
return FAIL;
- isn->isn_arg.number = index;
+ isn->isn_arg.getitem.gi_index = index;
+ isn->isn_arg.getitem.gi_with_op = with_op;
// add the item type to the type stack
if (ga_grow(stack, 1) == FAIL)
@@ -6746,19 +6750,17 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
int is_const = FALSE;
char_u *wp;
+ // for "+=", "*=", "..=" etc. first load the current value
+ if (*op != '='
+ && compile_load_lhs_with_index(&lhs, var_start,
+ cctx) == FAIL)
+ goto theend;
+
// For "var = expr" evaluate the expression.
if (var_count == 0)
{
int r;
- // for "+=", "*=", "..=" etc. first load the current value
- if (*op != '=')
- {
- if (compile_load_lhs_with_index(&lhs, var_start,
- cctx) == FAIL)
- goto theend;
- }
-
// Compile the expression.
instr_count = instr->ga_len;
if (incdec)
@@ -6795,7 +6797,7 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
{
// For "[var, var] = expr" get the "var_idx" item from the
// list.
- if (generate_GETITEM(cctx, var_idx) == FAIL)
+ if (generate_GETITEM(cctx, var_idx, *op != '=') == FAIL)
goto theend;
}
diff --git a/src/vim9execute.c b/src/vim9execute.c
index dfceb27d0..6d0da6179 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -3832,12 +3832,12 @@ exec_instructions(ectx_T *ectx)
case ISN_GETITEM:
{
listitem_T *li;
- int index = iptr->isn_arg.number;
+ getitem_T *gi = &iptr->isn_arg.getitem;
// Get list item: list is at stack-1, push item.
// List type and length is checked for when compiling.
- tv = STACK_TV_BOT(-1);
- li = list_find(tv->vval.v_list, index);
+ tv = STACK_TV_BOT(-1 - gi->gi_with_op);
+ li = list_find(tv->vval.v_list, gi->gi_index);
if (GA_GROW(&ectx->ec_stack, 1) == FAIL)
goto theend;
@@ -3846,7 +3846,7 @@ exec_instructions(ectx_T *ectx)
// Useful when used in unpack assignment. Reset at
// ISN_DROP.
- ectx->ec_where.wt_index = index + 1;
+ ectx->ec_where.wt_index = gi->gi_index + 1;
ectx->ec_where.wt_variable = TRUE;
}
break;
@@ -5376,8 +5376,10 @@ list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc)
case ISN_ANYSLICE: smsg("%s%4d ANYSLICE", pfx, current); break;
case ISN_SLICE: smsg("%s%4d SLICE %lld",
pfx, current, iptr->isn_arg.number); break;
- case ISN_GETITEM: smsg("%s%4d ITEM %lld",
- pfx, current, iptr->isn_arg.number); break;
+ case ISN_GETITEM: smsg("%s%4d ITEM %lld%s", pfx, current,
+ iptr->isn_arg.getitem.gi_index,
+ iptr->isn_arg.getitem.gi_with_op ?
+ " with op" : ""); break;
case ISN_MEMBER: smsg("%s%4d MEMBER", pfx, current); break;
case ISN_STRINGMEMBER: smsg("%s%4d MEMBER %s", pfx, current,
iptr->isn_arg.string); break;