]> granicus.if.org Git - vim/commitdiff
patch 8.2.3029: Vim9: crash when using operator and list unpack assignment v8.2.3029
authorBram Moolenaar <Bram@vim.org>
Mon, 21 Jun 2021 17:44:11 +0000 (19:44 +0200)
committerBram Moolenaar <Bram@vim.org>
Mon, 21 Jun 2021 17:44:11 +0000 (19:44 +0200)
Problem:    Vim9: crash when using operator and list unpack assignment.
            (Naohiro Ono)
Solution:   Get variable value before operation. (closes #8416)

src/ex_docmd.c
src/testdir/test_vim9_assign.vim
src/testdir/test_vim9_disassemble.vim
src/version.c
src/vim9.h
src/vim9compile.c
src/vim9execute.c

index 2a9983f8a8e6751a9776e1506ea42f060a651954..027d139da4d36c1b52db963684e2a799f3fd8b63 100644 (file)
@@ -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;
index 1cbdcfb4661fffdb28ebb84da5602f66d60e1df2..1d3c20e9fee7145b3e458cc6fea7cf255a8e0657 100644 (file)
@@ -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)
 
index 9d2f697d8a50144a4195d67f86303969c9bedbd1..7938d91f85ca7d74bd1c790c09d1c5d0576e76b3 100644 (file)
@@ -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)
index db2433db9962491009fb1d1287a3f32253595a25..096235fc940f44737c69d0c7dcc7976a029bf5ca 100644 (file)
@@ -755,6 +755,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    3029,
 /**/
     3028,
 /**/
index 97e2132a3e2609e80bd141abbf1c24e90f2fe394..58d451cd11e1949510dcf626ef286df5fea3c440 100644 (file)
@@ -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;
 };
 
index e4656c99af5a018bb4a830dac2628285f013fbab..c5a2c2dcf28711778679dfdb5557054cdd4644cb 100644 (file)
@@ -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;
                }
 
index dfceb27d000863cd7fe8890644bb47cd69c02b8a..6d0da617977e1a792d7621a8cfe94c1d6decbb5b 100644 (file)
@@ -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;