To: vim_dev@googlegroups.com Subject: Patch 8.2.2506 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ 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 -- Bram@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 ///