Skip to content

Commit 7e82c5f

Browse files
committed
patch 8.2.2539: Vim9: return from finally block causes a hang
Problem: Vim9: return from finally block causes a hang. Solution: Store both the finally and endtry indexes. (closes #7885)
1 parent 2157827 commit 7e82c5f

6 files changed

Lines changed: 89 additions & 38 deletions

File tree

src/testdir/test_vim9_disassemble.vim

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ def Test_disassemble_try()
422422
var res = execute('disass s:ScriptFuncTry')
423423
assert_match('<SNR>\d*_ScriptFuncTry\_s*' ..
424424
'try\_s*' ..
425-
'\d TRY catch -> \d\+, finally -> \d\+\_s*' ..
425+
'\d TRY catch -> \d\+, finally -> \d\+, endtry -> \d\+\_s*' ..
426426
'echo "yes"\_s*' ..
427427
'\d PUSHS "yes"\_s*' ..
428428
'\d ECHO 1\_s*' ..
@@ -437,6 +437,7 @@ def Test_disassemble_try()
437437
'\d\+ PUSHS "no"\_s*' ..
438438
'\d\+ ECHO 1\_s*' ..
439439
'finally\_s*' ..
440+
'\d\+ FINALLY\_s*' ..
440441
'throw "end"\_s*' ..
441442
'\d\+ PUSHS "end"\_s*' ..
442443
'\d\+ THROW\_s*' ..
@@ -1137,12 +1138,12 @@ def Test_disassemble_for_loop_continue()
11371138
'4 FOR $0 -> 22\_s*' ..
11381139
'5 STORE $1\_s*' ..
11391140
'try\_s*' ..
1140-
'6 TRY catch -> 17, end -> 20\_s*' ..
1141+
'6 TRY catch -> 17, endtry -> 20\_s*' ..
11411142
'echo "ok"\_s*' ..
11421143
'7 PUSHS "ok"\_s*' ..
11431144
'8 ECHO 1\_s*' ..
11441145
'try\_s*' ..
1145-
'9 TRY catch -> 13, end -> 15\_s*' ..
1146+
'9 TRY catch -> 13, endtry -> 15\_s*' ..
11461147
'echo "deeper"\_s*' ..
11471148
'10 PUSHS "deeper"\_s*' ..
11481149
'11 ECHO 1\_s*' ..

src/testdir/test_vim9_script.vim

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,16 @@ def Test_try_catch_throw()
577577
counter += 1
578578
endfor
579579
assert_equal(4, counter)
580+
581+
# return in finally after empty catch
582+
def ReturnInFinally(): number
583+
try
584+
finally
585+
return 4
586+
endtry
587+
return 2
588+
enddef
589+
assert_equal(4, ReturnInFinally())
580590
enddef
581591

582592
def Test_cnext_works_in_catch()

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+
2539,
753755
/**/
754756
2538,
755757
/**/

src/vim9.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ typedef enum {
100100
ISN_THROW, // pop value of stack, store in v:exception
101101
ISN_PUSHEXC, // push v:exception
102102
ISN_CATCH, // drop v:exception
103+
ISN_FINALLY, // start of :finally block
103104
ISN_ENDTRY, // take entry off from ec_trystack
104105
ISN_TRYCONT, // handle :continue inside a :try statement
105106

@@ -208,10 +209,16 @@ typedef struct {
208209
int for_end; // position to jump to after done
209210
} forloop_T;
210211

211-
// arguments to ISN_TRY
212+
// indirect arguments to ISN_TRY
212213
typedef struct {
213214
int try_catch; // position to jump to on throw
214215
int try_finally; // :finally or :endtry position to jump to
216+
int try_endtry; // :endtry position to jump to
217+
} tryref_T;
218+
219+
// arguments to ISN_TRY
220+
typedef struct {
221+
tryref_T *try_ref;
215222
} try_T;
216223

217224
// arguments to ISN_TRYCONT

src/vim9compile.c

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7518,10 +7518,17 @@ compile_try(char_u *arg, cctx_T *cctx)
75187518

75197519
if (cctx->ctx_skip != SKIP_YES)
75207520
{
7521-
// "catch" is set when the first ":catch" is found.
7522-
// "finally" is set when ":finally" or ":endtry" is found
7521+
isn_T *isn;
7522+
7523+
// "try_catch" is set when the first ":catch" is found or when no catch
7524+
// is found and ":finally" is found.
7525+
// "try_finally" is set when ":finally" is found
7526+
// "try_endtry" is set when ":endtry" is found
75237527
try_scope->se_u.se_try.ts_try_label = instr->ga_len;
7524-
if (generate_instr(cctx, ISN_TRY) == NULL)
7528+
if ((isn = generate_instr(cctx, ISN_TRY)) == NULL)
7529+
return NULL;
7530+
isn->isn_arg.try.try_ref = ALLOC_CLEAR_ONE(tryref_T);
7531+
if (isn->isn_arg.try.try_ref == NULL)
75257532
return NULL;
75267533
}
75277534

@@ -7577,8 +7584,8 @@ compile_catch(char_u *arg, cctx_T *cctx UNUSED)
75777584

75787585
// End :try or :catch scope: set value in ISN_TRY instruction
75797586
isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_try_label;
7580-
if (isn->isn_arg.try.try_catch == 0)
7581-
isn->isn_arg.try.try_catch = instr->ga_len;
7587+
if (isn->isn_arg.try.try_ref->try_catch == 0)
7588+
isn->isn_arg.try.try_ref->try_catch = instr->ga_len;
75827589
if (scope->se_u.se_try.ts_catch_label != 0)
75837590
{
75847591
// Previous catch without match jumps here
@@ -7670,7 +7677,7 @@ compile_finally(char_u *arg, cctx_T *cctx)
76707677

76717678
// End :catch or :finally scope: set value in ISN_TRY instruction
76727679
isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_try_label;
7673-
if (isn->isn_arg.try.try_finally != 0)
7680+
if (isn->isn_arg.try.try_ref->try_finally != 0)
76747681
{
76757682
emsg(_(e_finally_dup));
76767683
return NULL;
@@ -7688,14 +7695,19 @@ compile_finally(char_u *arg, cctx_T *cctx)
76887695
compile_fill_jump_to_end(&scope->se_u.se_try.ts_end_label,
76897696
this_instr, cctx);
76907697

7691-
isn->isn_arg.try.try_finally = this_instr;
7698+
// If there is no :catch then an exception jumps to :finally.
7699+
if (isn->isn_arg.try.try_ref->try_catch == 0)
7700+
isn->isn_arg.try.try_ref->try_catch = this_instr;
7701+
isn->isn_arg.try.try_ref->try_finally = this_instr;
76927702
if (scope->se_u.se_try.ts_catch_label != 0)
76937703
{
76947704
// Previous catch without match jumps here
76957705
isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_catch_label;
76967706
isn->isn_arg.jump.jump_where = this_instr;
76977707
scope->se_u.se_try.ts_catch_label = 0;
76987708
}
7709+
if (generate_instr(cctx, ISN_FINALLY) == NULL)
7710+
return NULL;
76997711

77007712
// TODO: set index in ts_finally_label jumps
77017713

@@ -7731,8 +7743,8 @@ compile_endtry(char_u *arg, cctx_T *cctx)
77317743
try_isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_try_label;
77327744
if (cctx->ctx_skip != SKIP_YES)
77337745
{
7734-
if (try_isn->isn_arg.try.try_catch == 0
7735-
&& try_isn->isn_arg.try.try_finally == 0)
7746+
if (try_isn->isn_arg.try.try_ref->try_catch == 0
7747+
&& try_isn->isn_arg.try.try_ref->try_finally == 0)
77367748
{
77377749
emsg(_(e_missing_catch_or_finally));
77387750
return NULL;
@@ -7751,12 +7763,6 @@ compile_endtry(char_u *arg, cctx_T *cctx)
77517763
compile_fill_jump_to_end(&scope->se_u.se_try.ts_end_label,
77527764
instr->ga_len, cctx);
77537765

7754-
// End :catch or :finally scope: set value in ISN_TRY instruction
7755-
if (try_isn->isn_arg.try.try_catch == 0)
7756-
try_isn->isn_arg.try.try_catch = instr->ga_len;
7757-
if (try_isn->isn_arg.try.try_finally == 0)
7758-
try_isn->isn_arg.try.try_finally = instr->ga_len;
7759-
77607766
if (scope->se_u.se_try.ts_catch_label != 0)
77617767
{
77627768
// Last catch without match jumps here
@@ -7770,11 +7776,9 @@ compile_endtry(char_u *arg, cctx_T *cctx)
77707776

77717777
if (cctx->ctx_skip != SKIP_YES)
77727778
{
7773-
if (try_isn->isn_arg.try.try_finally == 0)
7774-
// No :finally encountered, use the try_finaly field to point to
7775-
// ENDTRY, so that TRYCONT can jump there.
7776-
try_isn->isn_arg.try.try_finally = instr->ga_len;
7777-
7779+
// End :catch or :finally scope: set instruction index in ISN_TRY
7780+
// instruction
7781+
try_isn->isn_arg.try.try_ref->try_endtry = instr->ga_len;
77787782
if (cctx->ctx_skip != SKIP_YES
77797783
&& generate_instr(cctx, ISN_ENDTRY) == NULL)
77807784
return NULL;
@@ -8867,6 +8871,10 @@ delete_instr(isn_T *isn)
88678871
vim_free(isn->isn_arg.script.scriptref);
88688872
break;
88698873

8874+
case ISN_TRY:
8875+
vim_free(isn->isn_arg.try.try_ref);
8876+
break;
8877+
88708878
case ISN_2BOOL:
88718879
case ISN_2STRING:
88728880
case ISN_2STRING_ANY:
@@ -8899,6 +8907,7 @@ delete_instr(isn_T *isn)
88998907
case ISN_ENDTRY:
89008908
case ISN_EXECCONCAT:
89018909
case ISN_EXECUTE:
8910+
case ISN_FINALLY:
89028911
case ISN_FOR:
89038912
case ISN_GETITEM:
89048913
case ISN_JUMP:
@@ -8943,7 +8952,6 @@ delete_instr(isn_T *isn)
89438952
case ISN_STRINDEX:
89448953
case ISN_STRSLICE:
89458954
case ISN_THROW:
8946-
case ISN_TRY:
89478955
case ISN_TRYCONT:
89488956
case ISN_UNLETINDEX:
89498957
case ISN_UNLETRANGE:

src/vim9execute.c

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@
2626
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
29-
int tcd_catch_idx; // instruction of the first catch
30-
int tcd_finally_idx; // instruction of the finally block or :endtry
29+
int tcd_catch_idx; // instruction of the first :catch or :finally
30+
int tcd_finally_idx; // instruction of the :finally block or zero
31+
int tcd_endtry_idx; // instruction of the :endtry
3132
int tcd_caught; // catch block entered
3233
int tcd_cont; // :continue encountered, jump here
3334
int tcd_return; // when TRUE return from end of :finally
@@ -2517,10 +2518,9 @@ call_def_function(
25172518
+ trystack->ga_len - 1;
25182519
if (trycmd != NULL
25192520
&& trycmd->tcd_frame_idx == ectx.ec_frame_idx
2520-
&& ectx.ec_instr[trycmd->tcd_finally_idx]
2521-
.isn_type != ISN_ENDTRY)
2521+
&& trycmd->tcd_finally_idx != 0)
25222522
{
2523-
// jump to ":finally"
2523+
// jump to ":finally" once
25242524
ectx.ec_iidx = trycmd->tcd_finally_idx;
25252525
trycmd->tcd_return = TRUE;
25262526
}
@@ -2665,8 +2665,9 @@ call_def_function(
26652665
CLEAR_POINTER(trycmd);
26662666
trycmd->tcd_frame_idx = ectx.ec_frame_idx;
26672667
trycmd->tcd_stack_len = ectx.ec_stack.ga_len;
2668-
trycmd->tcd_catch_idx = iptr->isn_arg.try.try_catch;
2669-
trycmd->tcd_finally_idx = iptr->isn_arg.try.try_finally;
2668+
trycmd->tcd_catch_idx = iptr->isn_arg.try.try_ref->try_catch;
2669+
trycmd->tcd_finally_idx = iptr->isn_arg.try.try_ref->try_finally;
2670+
trycmd->tcd_endtry_idx = iptr->isn_arg.try.try_ref->try_endtry;
26702671
}
26712672
break;
26722673

@@ -2731,13 +2732,26 @@ call_def_function(
27312732
trycmd = ((trycmd_T *)trystack->ga_data)
27322733
+ trystack->ga_len - i;
27332734
trycmd->tcd_cont = iidx;
2734-
iidx = trycmd->tcd_finally_idx;
2735+
iidx = trycmd->tcd_finally_idx == 0
2736+
? trycmd->tcd_endtry_idx : trycmd->tcd_finally_idx;
27352737
}
27362738
// jump to :finally or :endtry of current try statement
27372739
ectx.ec_iidx = iidx;
27382740
}
27392741
break;
27402742

2743+
case ISN_FINALLY:
2744+
{
2745+
garray_T *trystack = &ectx.ec_trystack;
2746+
trycmd_T *trycmd = ((trycmd_T *)trystack->ga_data)
2747+
+ trystack->ga_len - 1;
2748+
2749+
// Reset the index to avoid a return statement jumps here
2750+
// again.
2751+
trycmd->tcd_finally_idx = 0;
2752+
break;
2753+
}
2754+
27412755
// end of ":try" block
27422756
case ISN_ENDTRY:
27432757
{
@@ -4348,11 +4362,17 @@ ex_disassemble(exarg_T *eap)
43484362
{
43494363
try_T *try = &iptr->isn_arg.try;
43504364

4351-
smsg("%4d TRY catch -> %d, %s -> %d", current,
4352-
try->try_catch,
4353-
instr[try->try_finally].isn_type == ISN_ENDTRY
4354-
? "end" : "finally",
4355-
try->try_finally);
4365+
if (try->try_ref->try_finally == 0)
4366+
smsg("%4d TRY catch -> %d, endtry -> %d",
4367+
current,
4368+
try->try_ref->try_catch,
4369+
try->try_ref->try_endtry);
4370+
else
4371+
smsg("%4d TRY catch -> %d, finally -> %d, endtry -> %d",
4372+
current,
4373+
try->try_ref->try_catch,
4374+
try->try_ref->try_finally,
4375+
try->try_ref->try_endtry);
43564376
}
43574377
break;
43584378
case ISN_CATCH:
@@ -4369,6 +4389,9 @@ ex_disassemble(exarg_T *eap)
43694389
trycont->tct_where);
43704390
}
43714391
break;
4392+
case ISN_FINALLY:
4393+
smsg("%4d FINALLY", current);
4394+
break;
43724395
case ISN_ENDTRY:
43734396
smsg("%4d ENDTRY", current);
43744397
break;

0 commit comments

Comments
 (0)