Patch 8.2.2506

5 views
Skip to first unread message

Bram Moolenaar

unread,
Feb 13, 2021, 9:03:19 AM2/13/21
to vim...@googlegroups.com

Patch 8.2.2506
Problem: Vim9: :continue does not work correctly in a :try block
Solution: Add the TRYCLEANUP instruction. (closes #7827)
Files: src/vim9compile.c, src/vim9execute.c, src/vim9.h,
src/testdir/test_vim9_script.vim,
src/testdir/test_vim9_disassemble.vim


*** ../vim-8.2.2505/src/vim9compile.c 2021-02-12 21:50:53.509801284 +0100
--- src/vim9compile.c 2021-02-13 14:11:21.597975056 +0100
***************
*** 1592,1597 ****
--- 1592,1614 ----

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,7319 ****
--- 7331,7338 ----
compile_continue(char_u *arg, cctx_T *cctx)
{
scope_T *scope = cctx->ctx_scope;
+ int try_scopes = 0;
+ int loop_label;

for (;;)
{
***************
*** 7322,7336 ****
emsg(_(e_continue));
return NULL;
}
! if (scope->se_type == FOR_SCOPE || scope->se_type == WHILE_SCOPE)
break;
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);
return arg;
}

--- 7341,7369 ----
emsg(_(e_continue));
return NULL;
}
! 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;
}

! 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,7631 ****
{
scope_T *scope = cctx->ctx_scope;
garray_T *instr = &cctx->ctx_instr;
! isn_T *isn;

// end block scope from :catch or :finally
if (scope != NULL && scope->se_type == BLOCK_SCOPE)
--- 7658,7664 ----
{
scope_T *scope = cctx->ctx_scope;
garray_T *instr = &cctx->ctx_instr;
! isn_T *try_isn;

// end block scope from :catch or :finally
if (scope != NULL && scope->se_type == BLOCK_SCOPE)
***************
*** 7646,7656 ****
return NULL;
}

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)
{
emsg(_(e_missing_catch_or_finally));
return NULL;
--- 7679,7689 ----
return NULL;
}

+ try_isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_try_label;
if (cctx->ctx_skip != SKIP_YES)
{
! 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,7690 ****
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 (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->isn_arg.jump.jump_where = instr->ga_len;
}
}

compile_endblock(cctx);

if (cctx->ctx_skip != SKIP_YES && generate_instr(cctx, ISN_ENDTRY) == NULL)
return NULL;
#ifdef FEAT_PROFILE
--- 7703,7729 ----
instr->ga_len, cctx);

// End :catch or :finally scope: set value in ISN_TRY instruction
! 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_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,8855 ****
--- 8889,8895 ----
case ISN_STRSLICE:
case ISN_THROW:
case ISN_TRY:
+ case ISN_TRYCONT:
case ISN_UNLETINDEX:
case ISN_UNPACK:
// nothing allocated
*** ../vim-8.2.2505/src/vim9execute.c 2021-02-12 21:32:42.600949557 +0100
--- src/vim9execute.c 2021-02-13 14:33:03.229753249 +0100
***************
*** 27,34 ****
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_caught; // catch block entered
int tcd_return; // when TRUE return from end of :finally
} trycmd_T;

--- 27,35 ----
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 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,2423 ****
+ trystack->ga_len - 1;
if (trycmd != NULL
&& trycmd->tcd_frame_idx == ectx.ec_frame_idx
! && trycmd->tcd_finally_idx != 0)
{
// jump to ":finally"
ectx.ec_iidx = trycmd->tcd_finally_idx;
--- 2418,2425 ----
+ trystack->ga_len - 1;
if (trycmd != NULL
&& trycmd->tcd_frame_idx == ectx.ec_frame_idx
! && ectx.ec_instr[trycmd->tcd_finally_idx]
! .isn_type != ISN_ENDTRY)
{
// jump to ":finally"
ectx.ec_iidx = trycmd->tcd_finally_idx;
***************
*** 2610,2615 ****
--- 2612,2645 ----
}
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,2645 ****
--- 2670,2679 ----
--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,4226 ****
{
try_T *try = &iptr->isn_arg.try;

! smsg("%4d TRY catch -> %d, finally -> %d", current,
! try->try_catch, try->try_finally);
}
break;
case ISN_CATCH:
// TODO
smsg("%4d CATCH", current);
break;
case ISN_ENDTRY:
smsg("%4d ENDTRY", current);
break;
--- 4247,4273 ----
{
try_T *try = &iptr->isn_arg.try;

! 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;
*** ../vim-8.2.2505/src/vim9.h 2021-01-25 23:02:35.240235395 +0100
--- src/vim9.h 2021-02-13 14:11:55.721858308 +0100
***************
*** 100,105 ****
--- 100,106 ----
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,217 ****
// arguments to ISN_TRY
typedef struct {
int try_catch; // position to jump to on throw
! int try_finally; // position to jump to for return
} try_T;

// arguments to ISN_ECHO
typedef struct {
int echo_with_white; // :echo instead of :echon
--- 210,224 ----
// arguments to ISN_TRY
typedef struct {
int try_catch; // position to jump to on throw
! 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,338 ****
--- 340,346 ----
jump_T jump;
forloop_T forloop;
try_T try;
+ trycont_T trycont;
cbfunc_T bfunc;
cdfunc_T dfunc;
cpfunc_T pfunc;
*** ../vim-8.2.2505/src/testdir/test_vim9_script.vim 2021-02-12 21:32:42.600949557 +0100
--- src/testdir/test_vim9_script.vim 2021-02-13 15:00:48.675885815 +0100
***************
*** 2201,2206 ****
--- 2201,2223 ----
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
*** ../vim-8.2.2505/src/testdir/test_vim9_disassemble.vim 2021-01-24 17:53:43.681840018 +0100
--- src/testdir/test_vim9_disassemble.vim 2021-02-13 14:58:05.796429051 +0100
***************
*** 1111,1116 ****
--- 1111,1173 ----
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()
*** ../vim-8.2.2505/src/version.c 2021-02-12 22:10:18.227311434 +0100
--- src/version.c 2021-02-13 15:01:32.707739349 +0100
***************
*** 752,753 ****
--- 752,755 ----
{ /* Add new patch number below this line */
+ /**/
+ 2506,
/**/

--
Seen on the back of a biker's vest: If you can read this, my wife fell off.

/// Bram Moolenaar -- Br...@Moolenaar.net -- http://www.Moolenaar.net \\\
/// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
\\\ an exciting new programming language -- http://www.Zimbu.org ///
\\\ help me help AIDS victims -- http://ICCF-Holland.org ///
Reply all
Reply to author
Forward
0 new messages