summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2021-02-13 15:02:46 +0100
committerBram Moolenaar <Bram@vim.org>2021-02-13 15:02:46 +0100
commitc150c09ec4f97636c6339f5687fdaa9f665095d2 (patch)
tree6ad97a4c1eff027184c75fda74555d4a97a04992
parent31842cd0772b557eb9584a13740430db29de8a51 (diff)
downloadvim-git-c150c09ec4f97636c6339f5687fdaa9f665095d2.tar.gz
patch 8.2.2506: Vim9: :continue does not work correctly in a :try blockv8.2.2506
Problem: Vim9: :continue does not work correctly in a :try block Solution: Add the TRYCLEANUP instruction. (closes #7827)
-rw-r--r--src/testdir/test_vim9_disassemble.vim57
-rw-r--r--src/testdir/test_vim9_script.vim17
-rw-r--r--src/version.c2
-rw-r--r--src/vim9.h10
-rw-r--r--src/vim9compile.c68
-rw-r--r--src/vim9execute.c55
6 files changed, 190 insertions, 19 deletions
diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim
index 1c5743248..4d7bbf63e 100644
--- 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()
diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim
index 17b996f17..85eea13fd 100644
--- 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
diff --git a/src/version.c b/src/version.c
index 6ec23a95a..6d01fa882 100644
--- 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,
diff --git a/src/vim9.h b/src/vim9.h
index 82fdfe634..c2a3916fd 100644
--- 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;
diff --git a/src/vim9compile.c b/src/vim9compile.c
index b4b9c28c1..5742123c0 100644
--- 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 *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
diff --git a/src/vim9execute.c b/src/vim9execute.c
index 8febf3623..89a33b5c6 100644
--- 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;