# HG changeset patch # User Bram Moolenaar # Date 1624297503 -7200 # Node ID 71b1e2ef00691262c2db0230e51c290334bca9e3 # Parent 94f7f4cd68b7e86e233c282b01ef2517eb3af81f patch 8.2.3029: Vim9: crash when using operator and list unpack assignment Commit: https://github.com/vim/vim/commit/035bd1c99f2a8eda5ee886adde4f97ea71fb167f Author: Bram Moolenaar Date: Mon Jun 21 19:44:11 2021 +0200 patch 8.2.3029: Vim9: crash when using operator and list unpack assignment Problem: Vim9: crash when using operator and list unpack assignment. (Naohiro Ono) Solution: Get variable value before operation. (closes #8416) diff --git a/src/ex_docmd.c b/src/ex_docmd.c --- 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 --- 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 --- 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('\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 = [] add(l, 123) diff --git a/src/version.c b/src/version.c --- 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 --- 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 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -1240,13 +1240,16 @@ generate_PUSHFUNC(cctx_T *cctx, char_u * /* * Generate an ISN_GETITEM instruction with "index". - */ - static int -generate_GETITEM(cctx_T *cctx, int 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, 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 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 { // 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 --- 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 *inst 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;