Skip to content

Commit c150c09

Browse files
committed
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 #7827)
1 parent 31842cd commit c150c09

6 files changed

Lines changed: 190 additions & 19 deletions

File tree

src/testdir/test_vim9_disassemble.vim

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,6 +1111,63 @@ def Test_disassemble_for_loop_unpack()
11111111
instr)
11121112
enddef
11131113

1114+
def ForLoopContinue()
1115+
for nr in [1, 2]
1116+
try
1117+
echo "ok"
1118+
try
1119+
echo "deeper"
1120+
catch
1121+
continue
1122+
endtry
1123+
catch
1124+
echo "not ok"
1125+
endtry
1126+
endfor
1127+
enddef
1128+
1129+
def Test_disassemble_for_loop_continue()
1130+
var instr = execute('disassemble ForLoopContinue')
1131+
assert_match('ForLoopContinue\_s*' ..
1132+
'for nr in \[1, 2]\_s*' ..
1133+
'0 STORE -1 in $0\_s*' ..
1134+
'1 PUSHNR 1\_s*' ..
1135+
'2 PUSHNR 2\_s*' ..
1136+
'3 NEWLIST size 2\_s*' ..
1137+
'4 FOR $0 -> 22\_s*' ..
1138+
'5 STORE $1\_s*' ..
1139+
'try\_s*' ..
1140+
'6 TRY catch -> 17, end -> 20\_s*' ..
1141+
'echo "ok"\_s*' ..
1142+
'7 PUSHS "ok"\_s*' ..
1143+
'8 ECHO 1\_s*' ..
1144+
'try\_s*' ..
1145+
'9 TRY catch -> 13, end -> 15\_s*' ..
1146+
'echo "deeper"\_s*' ..
1147+
'10 PUSHS "deeper"\_s*' ..
1148+
'11 ECHO 1\_s*' ..
1149+
'catch\_s*' ..
1150+
'12 JUMP -> 15\_s*' ..
1151+
'13 CATCH\_s*' ..
1152+
'continue\_s*' ..
1153+
'14 TRY-CONTINUE 2 levels -> 4\_s*' ..
1154+
'endtry\_s*' ..
1155+
'15 ENDTRY\_s*' ..
1156+
'catch\_s*' ..
1157+
'16 JUMP -> 20\_s*' ..
1158+
'17 CATCH\_s*' ..
1159+
'echo "not ok"\_s*' ..
1160+
'18 PUSHS "not ok"\_s*' ..
1161+
'19 ECHO 1\_s*' ..
1162+
'endtry\_s*' ..
1163+
'20 ENDTRY\_s*' ..
1164+
'endfor\_s*' ..
1165+
'21 JUMP -> 4\_s*' ..
1166+
'\d\+ DROP\_s*' ..
1167+
'\d\+ RETURN 0',
1168+
instr)
1169+
enddef
1170+
11141171
let g:number = 42
11151172

11161173
def TypeCast()

src/testdir/test_vim9_script.vim

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2201,6 +2201,23 @@ def Test_for_loop_unpack()
22012201
CheckDefExecFailure(lines, 'E1017:', 1)
22022202
enddef
22032203

2204+
def Test_for_loop_with_try_continue()
2205+
var looped = 0
2206+
var cleanup = 0
2207+
for i in range(3)
2208+
looped += 1
2209+
try
2210+
eval [][0]
2211+
catch
2212+
continue
2213+
finally
2214+
cleanup += 1
2215+
endtry
2216+
endfor
2217+
assert_equal(3, looped)
2218+
assert_equal(3, cleanup)
2219+
enddef
2220+
22042221
def Test_while_loop()
22052222
var result = ''
22062223
var cnt = 0

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,8 @@ static char *(features[]) =
750750

751751
static int included_patches[] =
752752
{ /* Add new patch number below this line */
753+
/**/
754+
2506,
753755
/**/
754756
2505,
755757
/**/

src/vim9.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ typedef enum {
100100
ISN_PUSHEXC, // push v:exception
101101
ISN_CATCH, // drop v:exception
102102
ISN_ENDTRY, // take entry off from ec_trystack
103+
ISN_TRYCONT, // handle :continue inside a :try statement
103104

104105
// more expression operations
105106
ISN_ADDLIST, // add two lists
@@ -209,9 +210,15 @@ typedef struct {
209210
// arguments to ISN_TRY
210211
typedef struct {
211212
int try_catch; // position to jump to on throw
212-
int try_finally; // position to jump to for return
213+
int try_finally; // :finally or :endtry position to jump to
213214
} try_T;
214215

216+
// arguments to ISN_TRYCONT
217+
typedef struct {
218+
int tct_levels; // number of nested try statements
219+
int tct_where; // position to jump to, WHILE or FOR
220+
} trycont_T;
221+
215222
// arguments to ISN_ECHO
216223
typedef struct {
217224
int echo_with_white; // :echo instead of :echon
@@ -333,6 +340,7 @@ struct isn_S {
333340
jump_T jump;
334341
forloop_T forloop;
335342
try_T try;
343+
trycont_T trycont;
336344
cbfunc_T bfunc;
337345
cdfunc_T dfunc;
338346
cpfunc_T pfunc;

src/vim9compile.c

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1592,6 +1592,23 @@ generate_FOR(cctx_T *cctx, int loop_idx)
15921592

15931593
return OK;
15941594
}
1595+
/*
1596+
* Generate an ISN_TRYCONT instruction.
1597+
*/
1598+
static int
1599+
generate_TRYCONT(cctx_T *cctx, int levels, int where)
1600+
{
1601+
isn_T *isn;
1602+
1603+
RETURN_OK_IF_SKIP(cctx);
1604+
if ((isn = generate_instr(cctx, ISN_TRYCONT)) == NULL)
1605+
return FAIL;
1606+
isn->isn_arg.trycont.tct_levels = levels;
1607+
isn->isn_arg.trycont.tct_where = where;
1608+
1609+
return OK;
1610+
}
1611+
15951612

15961613
/*
15971614
* Generate an ISN_BCALL instruction.
@@ -7314,6 +7331,8 @@ compile_endwhile(char_u *arg, cctx_T *cctx)
73147331
compile_continue(char_u *arg, cctx_T *cctx)
73157332
{
73167333
scope_T *scope = cctx->ctx_scope;
7334+
int try_scopes = 0;
7335+
int loop_label;
73177336

73187337
for (;;)
73197338
{
@@ -7322,15 +7341,29 @@ compile_continue(char_u *arg, cctx_T *cctx)
73227341
emsg(_(e_continue));
73237342
return NULL;
73247343
}
7325-
if (scope->se_type == FOR_SCOPE || scope->se_type == WHILE_SCOPE)
7344+
if (scope->se_type == FOR_SCOPE)
7345+
{
7346+
loop_label = scope->se_u.se_for.fs_top_label;
7347+
break;
7348+
}
7349+
if (scope->se_type == WHILE_SCOPE)
7350+
{
7351+
loop_label = scope->se_u.se_while.ws_top_label;
73267352
break;
7353+
}
7354+
if (scope->se_type == TRY_SCOPE)
7355+
++try_scopes;
73277356
scope = scope->se_outer;
73287357
}
73297358

7330-
// Jump back to the FOR or WHILE instruction.
7331-
generate_JUMP(cctx, JUMP_ALWAYS,
7332-
scope->se_type == FOR_SCOPE ? scope->se_u.se_for.fs_top_label
7333-
: scope->se_u.se_while.ws_top_label);
7359+
if (try_scopes > 0)
7360+
// Inside one or more try/catch blocks we first need to jump to the
7361+
// "finally" or "endtry" to cleanup.
7362+
generate_TRYCONT(cctx, try_scopes, loop_label);
7363+
else
7364+
// Jump back to the FOR or WHILE instruction.
7365+
generate_JUMP(cctx, JUMP_ALWAYS, loop_label);
7366+
73347367
return arg;
73357368
}
73367369

@@ -7625,7 +7658,7 @@ compile_endtry(char_u *arg, cctx_T *cctx)
76257658
{
76267659
scope_T *scope = cctx->ctx_scope;
76277660
garray_T *instr = &cctx->ctx_instr;
7628-
isn_T *isn;
7661+
isn_T *try_isn;
76297662

76307663
// end block scope from :catch or :finally
76317664
if (scope != NULL && scope->se_type == BLOCK_SCOPE)
@@ -7646,11 +7679,11 @@ compile_endtry(char_u *arg, cctx_T *cctx)
76467679
return NULL;
76477680
}
76487681

7682+
try_isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_try_label;
76497683
if (cctx->ctx_skip != SKIP_YES)
76507684
{
7651-
isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_try_label;
7652-
if (isn->isn_arg.try.try_catch == 0
7653-
&& isn->isn_arg.try.try_finally == 0)
7685+
if (try_isn->isn_arg.try.try_catch == 0
7686+
&& try_isn->isn_arg.try.try_finally == 0)
76547687
{
76557688
emsg(_(e_missing_catch_or_finally));
76567689
return NULL;
@@ -7670,21 +7703,27 @@ compile_endtry(char_u *arg, cctx_T *cctx)
76707703
instr->ga_len, cctx);
76717704

76727705
// End :catch or :finally scope: set value in ISN_TRY instruction
7673-
if (isn->isn_arg.try.try_catch == 0)
7674-
isn->isn_arg.try.try_catch = instr->ga_len;
7675-
if (isn->isn_arg.try.try_finally == 0)
7676-
isn->isn_arg.try.try_finally = instr->ga_len;
7706+
if (try_isn->isn_arg.try.try_catch == 0)
7707+
try_isn->isn_arg.try.try_catch = instr->ga_len;
7708+
if (try_isn->isn_arg.try.try_finally == 0)
7709+
try_isn->isn_arg.try.try_finally = instr->ga_len;
76777710

76787711
if (scope->se_u.se_try.ts_catch_label != 0)
76797712
{
76807713
// Last catch without match jumps here
7681-
isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_catch_label;
7714+
isn_T *isn = ((isn_T *)instr->ga_data)
7715+
+ scope->se_u.se_try.ts_catch_label;
76827716
isn->isn_arg.jump.jump_where = instr->ga_len;
76837717
}
76847718
}
76857719

76867720
compile_endblock(cctx);
76877721

7722+
if (try_isn->isn_arg.try.try_finally == 0)
7723+
// No :finally encountered, use the try_finaly field to point to
7724+
// ENDTRY, so that TRYCONT can jump there.
7725+
try_isn->isn_arg.try.try_finally = cctx->ctx_instr.ga_len;
7726+
76887727
if (cctx->ctx_skip != SKIP_YES && generate_instr(cctx, ISN_ENDTRY) == NULL)
76897728
return NULL;
76907729
#ifdef FEAT_PROFILE
@@ -8850,6 +8889,7 @@ delete_instr(isn_T *isn)
88508889
case ISN_STRSLICE:
88518890
case ISN_THROW:
88528891
case ISN_TRY:
8892+
case ISN_TRYCONT:
88538893
case ISN_UNLETINDEX:
88548894
case ISN_UNPACK:
88558895
// nothing allocated

src/vim9execute.c

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ typedef struct {
2727
int tcd_frame_idx; // ec_frame_idx at ISN_TRY
2828
int tcd_stack_len; // size of ectx.ec_stack at ISN_TRY
2929
int tcd_catch_idx; // instruction of the first catch
30-
int tcd_finally_idx; // instruction of the finally block
30+
int tcd_finally_idx; // instruction of the finally block or :endtry
3131
int tcd_caught; // catch block entered
32+
int tcd_cont; // :continue encountered, jump here
3233
int tcd_return; // when TRUE return from end of :finally
3334
} trycmd_T;
3435

@@ -2417,7 +2418,8 @@ call_def_function(
24172418
+ trystack->ga_len - 1;
24182419
if (trycmd != NULL
24192420
&& trycmd->tcd_frame_idx == ectx.ec_frame_idx
2420-
&& trycmd->tcd_finally_idx != 0)
2421+
&& ectx.ec_instr[trycmd->tcd_finally_idx]
2422+
.isn_type != ISN_ENDTRY)
24212423
{
24222424
// jump to ":finally"
24232425
ectx.ec_iidx = trycmd->tcd_finally_idx;
@@ -2610,6 +2612,34 @@ call_def_function(
26102612
}
26112613
break;
26122614

2615+
case ISN_TRYCONT:
2616+
{
2617+
garray_T *trystack = &ectx.ec_trystack;
2618+
trycont_T *trycont = &iptr->isn_arg.trycont;
2619+
int i;
2620+
trycmd_T *trycmd;
2621+
int iidx = trycont->tct_where;
2622+
2623+
if (trystack->ga_len < trycont->tct_levels)
2624+
{
2625+
siemsg("TRYCONT: expected %d levels, found %d",
2626+
trycont->tct_levels, trystack->ga_len);
2627+
goto failed;
2628+
}
2629+
// Make :endtry jump to any outer try block and the last
2630+
// :endtry inside the loop to the loop start.
2631+
for (i = trycont->tct_levels; i > 0; --i)
2632+
{
2633+
trycmd = ((trycmd_T *)trystack->ga_data)
2634+
+ trystack->ga_len - i;
2635+
trycmd->tcd_cont = iidx;
2636+
iidx = trycmd->tcd_finally_idx;
2637+
}
2638+
// jump to :finally or :endtry of current try statement
2639+
ectx.ec_iidx = iidx;
2640+
}
2641+
break;
2642+
26132643
// end of ":try" block
26142644
case ISN_ENDTRY:
26152645
{
@@ -2640,6 +2670,10 @@ call_def_function(
26402670
--ectx.ec_stack.ga_len;
26412671
clear_tv(STACK_TV_BOT(0));
26422672
}
2673+
if (trycmd->tcd_cont)
2674+
// handling :continue: jump to outer try block or
2675+
// start of the loop
2676+
ectx.ec_iidx = trycmd->tcd_cont;
26432677
}
26442678
}
26452679
break;
@@ -4213,14 +4247,27 @@ ex_disassemble(exarg_T *eap)
42134247
{
42144248
try_T *try = &iptr->isn_arg.try;
42154249

4216-
smsg("%4d TRY catch -> %d, finally -> %d", current,
4217-
try->try_catch, try->try_finally);
4250+
smsg("%4d TRY catch -> %d, %s -> %d", current,
4251+
try->try_catch,
4252+
instr[try->try_finally].isn_type == ISN_ENDTRY
4253+
? "end" : "finally",
4254+
try->try_finally);
42184255
}
42194256
break;
42204257
case ISN_CATCH:
42214258
// TODO
42224259
smsg("%4d CATCH", current);
42234260
break;
4261+
case ISN_TRYCONT:
4262+
{
4263+
trycont_T *trycont = &iptr->isn_arg.trycont;
4264+
4265+
smsg("%4d TRY-CONTINUE %d level%s -> %d", current,
4266+
trycont->tct_levels,
4267+
trycont->tct_levels == 1 ? "" : "s",
4268+
trycont->tct_where);
4269+
}
4270+
break;
42244271
case ISN_ENDTRY:
42254272
smsg("%4d ENDTRY", current);
42264273
break;

0 commit comments

Comments
 (0)