]> granicus.if.org Git - vim/commitdiff
patch 8.2.2506: Vim9: :continue does not work correctly in a :try block v8.2.2506
authorBram Moolenaar <Bram@vim.org>
Sat, 13 Feb 2021 14:02:46 +0000 (15:02 +0100)
committerBram Moolenaar <Bram@vim.org>
Sat, 13 Feb 2021 14:02:46 +0000 (15:02 +0100)
Problem:    Vim9: :continue does not work correctly in a :try block
Solution:   Add the TRYCLEANUP instruction. (closes #7827)

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

index 1c57432485c7ef5215b45a3c4ca75bfcbd193b15..4d7bbf63e9f98f4144e93b27405cdaadec7ef53e 100644 (file)
@@ -1111,6 +1111,63 @@ def Test_disassemble_for_loop_unpack()
         instr)
 enddef
 
+def ForLoopContinue()
+  for nr in [1, 2]
+    try
+      echo "ok"
+      try
+        echo "deeper"
+      catch
+        continue
+      endtry
+    catch
+      echo "not ok"
+    endtry
+  endfor
+enddef
+
+def Test_disassemble_for_loop_continue()
+  var instr = execute('disassemble ForLoopContinue')
+  assert_match('ForLoopContinue\_s*' ..
+        'for nr in \[1, 2]\_s*' ..
+        '0 STORE -1 in $0\_s*' ..
+        '1 PUSHNR 1\_s*' ..
+        '2 PUSHNR 2\_s*' ..
+        '3 NEWLIST size 2\_s*' ..
+        '4 FOR $0 -> 22\_s*' ..
+        '5 STORE $1\_s*' ..
+        'try\_s*' ..
+        '6 TRY catch -> 17, end -> 20\_s*' ..
+        'echo "ok"\_s*' ..
+        '7 PUSHS "ok"\_s*' ..
+        '8 ECHO 1\_s*' ..
+        'try\_s*' ..
+        '9 TRY catch -> 13, end -> 15\_s*' ..
+        'echo "deeper"\_s*' ..
+        '10 PUSHS "deeper"\_s*' ..
+        '11 ECHO 1\_s*' ..
+        'catch\_s*' ..
+        '12 JUMP -> 15\_s*' ..
+        '13 CATCH\_s*' ..
+        'continue\_s*' ..
+        '14 TRY-CONTINUE 2 levels -> 4\_s*' ..
+        'endtry\_s*' ..
+        '15 ENDTRY\_s*' ..
+        'catch\_s*' ..
+        '16 JUMP -> 20\_s*' ..
+        '17 CATCH\_s*' ..
+        'echo "not ok"\_s*' ..
+        '18 PUSHS "not ok"\_s*' ..
+        '19 ECHO 1\_s*' ..
+        'endtry\_s*' ..
+        '20 ENDTRY\_s*' ..
+        'endfor\_s*' ..
+        '21 JUMP -> 4\_s*' ..
+        '\d\+ DROP\_s*' ..
+        '\d\+ RETURN 0',
+        instr)
+enddef
+
 let g:number = 42
 
 def TypeCast()
index 17b996f1768b150d816182da86818700c232be15..85eea13fdd7ba076f7ce9b7b6ceeea8769c97b06 100644 (file)
@@ -2201,6 +2201,23 @@ def Test_for_loop_unpack()
   CheckDefExecFailure(lines, 'E1017:', 1)
 enddef
 
+def Test_for_loop_with_try_continue()
+  var looped = 0
+  var cleanup = 0
+  for i in range(3)
+    looped += 1
+    try
+      eval [][0]
+    catch
+      continue
+    finally
+      cleanup += 1
+    endtry
+  endfor
+  assert_equal(3, looped)
+  assert_equal(3, cleanup)
+enddef
+
 def Test_while_loop()
   var result = ''
   var cnt = 0
index 6ec23a95aa92c8ddd22c3ad1508607a930bd1773..6d01fa8829b9077283992f5bd1b50d898990932d 100644 (file)
@@ -750,6 +750,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    2506,
 /**/
     2505,
 /**/
index 82fdfe6340047fe2672fc134bb92ea553da64253..c2a3916fd742c4a0d0798cc2148ac7522cd78391 100644 (file)
@@ -100,6 +100,7 @@ typedef enum {
     ISN_PUSHEXC,    // push v:exception
     ISN_CATCH,     // drop v:exception
     ISN_ENDTRY,            // take entry off from ec_trystack
+    ISN_TRYCONT,    // handle :continue inside a :try statement
 
     // more expression operations
     ISN_ADDLIST,    // add two lists
@@ -209,9 +210,15 @@ typedef struct {
 // arguments to ISN_TRY
 typedef struct {
     int            try_catch;      // position to jump to on throw
-    int            try_finally;    // position to jump to for return
+    int            try_finally;    // :finally or :endtry position to jump to
 } try_T;
 
+// arguments to ISN_TRYCONT
+typedef struct {
+    int            tct_levels;     // number of nested try statements
+    int            tct_where;      // position to jump to, WHILE or FOR
+} trycont_T;
+
 // arguments to ISN_ECHO
 typedef struct {
     int            echo_with_white;    // :echo instead of :echon
@@ -333,6 +340,7 @@ struct isn_S {
        jump_T              jump;
        forloop_T           forloop;
        try_T               try;
+       trycont_T           trycont;
        cbfunc_T            bfunc;
        cdfunc_T            dfunc;
        cpfunc_T            pfunc;
index b4b9c28c1d8f0f0930ebb9c631cae15e1b699df8..5742123c06d33cbb3687439c6a49939931265647 100644 (file)
@@ -1592,6 +1592,23 @@ generate_FOR(cctx_T *cctx, int loop_idx)
 
     return OK;
 }
+/*
+ * Generate an ISN_TRYCONT instruction.
+ */
+    static int
+generate_TRYCONT(cctx_T *cctx, int levels, int where)
+{
+    isn_T      *isn;
+
+    RETURN_OK_IF_SKIP(cctx);
+    if ((isn = generate_instr(cctx, ISN_TRYCONT)) == NULL)
+       return FAIL;
+    isn->isn_arg.trycont.tct_levels = levels;
+    isn->isn_arg.trycont.tct_where = where;
+
+    return OK;
+}
+
 
 /*
  * Generate an ISN_BCALL instruction.
@@ -7314,6 +7331,8 @@ compile_endwhile(char_u *arg, cctx_T *cctx)
 compile_continue(char_u *arg, cctx_T *cctx)
 {
     scope_T    *scope = cctx->ctx_scope;
+    int                try_scopes = 0;
+    int                loop_label;
 
     for (;;)
     {
@@ -7322,15 +7341,29 @@ compile_continue(char_u *arg, cctx_T *cctx)
            emsg(_(e_continue));
            return NULL;
        }
-       if (scope->se_type == FOR_SCOPE || scope->se_type == WHILE_SCOPE)
+       if (scope->se_type == FOR_SCOPE)
+       {
+           loop_label = scope->se_u.se_for.fs_top_label;
+           break;
+       }
+       if (scope->se_type == WHILE_SCOPE)
+       {
+           loop_label = scope->se_u.se_while.ws_top_label;
            break;
+       }
+       if (scope->se_type == TRY_SCOPE)
+           ++try_scopes;
        scope = scope->se_outer;
     }
 
-    // Jump back to the FOR or WHILE instruction.
-    generate_JUMP(cctx, JUMP_ALWAYS,
-           scope->se_type == FOR_SCOPE ? scope->se_u.se_for.fs_top_label
-                                         : scope->se_u.se_while.ws_top_label);
+    if (try_scopes > 0)
+       // Inside one or more try/catch blocks we first need to jump to the
+       // "finally" or "endtry" to cleanup.
+       generate_TRYCONT(cctx, try_scopes, loop_label);
+    else
+       // Jump back to the FOR or WHILE instruction.
+       generate_JUMP(cctx, JUMP_ALWAYS, loop_label);
+
     return arg;
 }
 
@@ -7625,7 +7658,7 @@ compile_endtry(char_u *arg, cctx_T *cctx)
 {
     scope_T    *scope = cctx->ctx_scope;
     garray_T   *instr = &cctx->ctx_instr;
-    isn_T      *isn;
+    isn_T      *try_isn;
 
     // end block scope from :catch or :finally
     if (scope != NULL && scope->se_type == BLOCK_SCOPE)
@@ -7646,11 +7679,11 @@ compile_endtry(char_u *arg, cctx_T *cctx)
        return NULL;
     }
 
+    try_isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_try_label;
     if (cctx->ctx_skip != SKIP_YES)
     {
-       isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_try_label;
-       if (isn->isn_arg.try.try_catch == 0
-                                         && isn->isn_arg.try.try_finally == 0)
+       if (try_isn->isn_arg.try.try_catch == 0
+                                     && try_isn->isn_arg.try.try_finally == 0)
        {
            emsg(_(e_missing_catch_or_finally));
            return NULL;
@@ -7670,21 +7703,27 @@ compile_endtry(char_u *arg, cctx_T *cctx)
                                                          instr->ga_len, cctx);
 
        // End :catch or :finally scope: set value in ISN_TRY instruction
-       if (isn->isn_arg.try.try_catch == 0)
-           isn->isn_arg.try.try_catch = instr->ga_len;
-       if (isn->isn_arg.try.try_finally == 0)
-           isn->isn_arg.try.try_finally = instr->ga_len;
+       if (try_isn->isn_arg.try.try_catch == 0)
+           try_isn->isn_arg.try.try_catch = instr->ga_len;
+       if (try_isn->isn_arg.try.try_finally == 0)
+           try_isn->isn_arg.try.try_finally = instr->ga_len;
 
        if (scope->se_u.se_try.ts_catch_label != 0)
        {
            // Last catch without match jumps here
-           isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_catch_label;
+           isn_T *isn = ((isn_T *)instr->ga_data)
+                                          + scope->se_u.se_try.ts_catch_label;
            isn->isn_arg.jump.jump_where = instr->ga_len;
        }
     }
 
     compile_endblock(cctx);
 
+    if (try_isn->isn_arg.try.try_finally == 0)
+       // No :finally encountered, use the try_finaly field to point to
+       // ENDTRY, so that TRYCONT can jump there.
+       try_isn->isn_arg.try.try_finally = cctx->ctx_instr.ga_len;
+
     if (cctx->ctx_skip != SKIP_YES && generate_instr(cctx, ISN_ENDTRY) == NULL)
        return NULL;
 #ifdef FEAT_PROFILE
@@ -8850,6 +8889,7 @@ delete_instr(isn_T *isn)
        case ISN_STRSLICE:
        case ISN_THROW:
        case ISN_TRY:
+       case ISN_TRYCONT:
        case ISN_UNLETINDEX:
        case ISN_UNPACK:
            // nothing allocated
index 8febf3623aa8baefd4cbe5db924c26843e40ccbd..89a33b5c6af938365d60ec31245adef0430790b8 100644 (file)
@@ -27,8 +27,9 @@ typedef struct {
     int            tcd_frame_idx;      // ec_frame_idx at ISN_TRY
     int            tcd_stack_len;      // size of ectx.ec_stack at ISN_TRY
     int            tcd_catch_idx;      // instruction of the first catch
-    int            tcd_finally_idx;    // instruction of the finally block
+    int            tcd_finally_idx;    // instruction of the finally block or :endtry
     int            tcd_caught;         // catch block entered
+    int            tcd_cont;           // :continue encountered, jump here
     int            tcd_return;         // when TRUE return from end of :finally
 } trycmd_T;
 
@@ -2417,7 +2418,8 @@ call_def_function(
                                                        + trystack->ga_len - 1;
                    if (trycmd != NULL
                                  && trycmd->tcd_frame_idx == ectx.ec_frame_idx
-                           && trycmd->tcd_finally_idx != 0)
+                                 && ectx.ec_instr[trycmd->tcd_finally_idx]
+                                                      .isn_type != ISN_ENDTRY)
                    {
                        // jump to ":finally"
                        ectx.ec_iidx = trycmd->tcd_finally_idx;
@@ -2610,6 +2612,34 @@ call_def_function(
                }
                break;
 
+           case ISN_TRYCONT:
+               {
+                   garray_T    *trystack = &ectx.ec_trystack;
+                   trycont_T   *trycont = &iptr->isn_arg.trycont;
+                   int         i;
+                   trycmd_T    *trycmd;
+                   int         iidx = trycont->tct_where;
+
+                   if (trystack->ga_len < trycont->tct_levels)
+                   {
+                       siemsg("TRYCONT: expected %d levels, found %d",
+                                       trycont->tct_levels, trystack->ga_len);
+                       goto failed;
+                   }
+                   // Make :endtry jump to any outer try block and the last
+                   // :endtry inside the loop to the loop start.
+                   for (i = trycont->tct_levels; i > 0; --i)
+                   {
+                       trycmd = ((trycmd_T *)trystack->ga_data)
+                                                       + trystack->ga_len - i;
+                       trycmd->tcd_cont = iidx;
+                       iidx = trycmd->tcd_finally_idx;
+                   }
+                   // jump to :finally or :endtry of current try statement
+                   ectx.ec_iidx = iidx;
+               }
+               break;
+
            // end of ":try" block
            case ISN_ENDTRY:
                {
@@ -2640,6 +2670,10 @@ call_def_function(
                            --ectx.ec_stack.ga_len;
                            clear_tv(STACK_TV_BOT(0));
                        }
+                       if (trycmd->tcd_cont)
+                           // handling :continue: jump to outer try block or
+                           // start of the loop
+                           ectx.ec_iidx = trycmd->tcd_cont;
                    }
                }
                break;
@@ -4213,14 +4247,27 @@ ex_disassemble(exarg_T *eap)
                {
                    try_T *try = &iptr->isn_arg.try;
 
-                   smsg("%4d TRY catch -> %d, finally -> %d", current,
-                                            try->try_catch, try->try_finally);
+                   smsg("%4d TRY catch -> %d, %s -> %d", current,
+                                try->try_catch,
+                                instr[try->try_finally].isn_type == ISN_ENDTRY
+                                                          ? "end" : "finally",
+                                try->try_finally);
                }
                break;
            case ISN_CATCH:
                // TODO
                smsg("%4d CATCH", current);
                break;
+           case ISN_TRYCONT:
+               {
+                   trycont_T *trycont = &iptr->isn_arg.trycont;
+
+                   smsg("%4d TRY-CONTINUE %d level%s -> %d", current,
+                                     trycont->tct_levels,
+                                     trycont->tct_levels == 1 ? "" : "s",
+                                     trycont->tct_where);
+               }
+               break;
            case ISN_ENDTRY:
                smsg("%4d ENDTRY", current);
                break;