changeset 23927:5e5780e3f75d v8.2.2506

patch 8.2.2506: Vim9: :continue does not work correctly in a :try block Commit: https://github.com/vim/vim/commit/c150c09ec4f97636c6339f5687fdaa9f665095d2 Author: Bram Moolenaar <Bram@vim.org> Date: Sat Feb 13 15:02:46 2021 +0100 patch 8.2.2506: Vim9: :continue does not work correctly in a :try block Problem: Vim9: :continue does not work correctly in a :try block Solution: Add the TRYCLEANUP instruction. (closes https://github.com/vim/vim/issues/7827)
author Bram Moolenaar <Bram@vim.org>
date Sat, 13 Feb 2021 15:15:03 +0100
parents 1a41fde53816
children 45fb23517cc3
files src/testdir/test_vim9_disassemble.vim src/testdir/test_vim9_script.vim src/version.c src/vim9.h src/vim9compile.c src/vim9execute.c
diffstat 6 files changed, 190 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -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()
--- a/src/testdir/test_vim9_script.vim
+++ b/src/testdir/test_vim9_script.vim
@@ -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
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    2506,
+/**/
     2505,
 /**/
     2504,
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -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;
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -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 *cc
 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 *cc
 	    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
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -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;