# HG changeset patch # User Bram Moolenaar # Date 1611518404 -3600 # Node ID f98692ae09a058d2480caf697c96ab9ee65a934f # Parent 85e6fa8b86f7019553ecbdf7d2f2420d241d3eb3 patch 8.2.2404: Vim9: profiling try/catch not correct Commit: https://github.com/vim/vim/commit/107e9cecf72928b4aaaf08b73bf2d191924ab695 Author: Bram Moolenaar Date: Sun Jan 24 20:52:00 2021 +0100 patch 8.2.2404: Vim9: profiling try/catch not correct Problem: Vim9: profiling try/catch not correct. Solution: Add profile instructions. Fix that "entry" did not rethrow an excpetion. diff --git a/src/testdir/test_profile.vim b/src/testdir/test_profile.vim --- a/src/testdir/test_profile.vim +++ b/src/testdir/test_profile.vim @@ -100,11 +100,11 @@ func RunProfileFunc(command, declare, as endfunc func Test_profile_func_with_ifelse() - call Run_profile_func_with_ifelse('func', 'let', 'let') - call Run_profile_func_with_ifelse('def', 'var', '') + call Run_profile_func_with_ifelse('func', 'let') + call Run_profile_func_with_ifelse('def', 'var') endfunc -func Run_profile_func_with_ifelse(command, declare, assign) +func Run_profile_func_with_ifelse(command, declare) let lines =<< trim [CODE] XXX Foo1() if 1 @@ -140,7 +140,6 @@ func Run_profile_func_with_ifelse(comman call map(lines, {k, v -> substitute(v, 'XXX', a:command, '') }) call map(lines, {k, v -> substitute(v, 'DDD', a:declare, '') }) - call map(lines, {k, v -> substitute(v, 'AAA', a:assign, '') }) call writefile(lines, 'Xprofile_func.vim') call system(GetVimCommand() @@ -219,42 +218,56 @@ func Run_profile_func_with_ifelse(comman endfunc func Test_profile_func_with_trycatch() + call Run_profile_func_with_trycatch('func', 'let') + call Run_profile_func_with_trycatch('def', 'var') +endfunc + +func Run_profile_func_with_trycatch(command, declare) let lines =<< trim [CODE] - func! Foo1() + XXX Foo1() try - let x = 0 + DDD x = 0 catch - let x = 1 + DDD x = 1 finally - let x = 2 + DDD x = 2 endtry - endfunc - func! Foo2() + endXXX + XXX Foo2() try throw 0 catch - let x = 1 + DDD x = 1 finally - let x = 2 + DDD x = 2 endtry - endfunc - func! Foo3() + endXXX + XXX Foo3() try throw 0 catch throw 1 finally - let x = 2 + DDD x = 2 endtry - endfunc + endXXX call Foo1() call Foo2() + let rethrown = 0 try call Foo3() catch + let rethrown = 1 endtry + if rethrown != 1 + " call Foo1 again so that the test fails + call Foo1() + endif [CODE] + call map(lines, {k, v -> substitute(v, 'XXX', a:command, '') }) + call map(lines, {k, v -> substitute(v, 'DDD', a:declare, '') }) + call writefile(lines, 'Xprofile_func.vim') call system(GetVimCommand() \ . ' -es -i NONE --noplugin' @@ -279,11 +292,11 @@ func Test_profile_func_with_trycatch() call assert_equal('', lines[5]) call assert_equal('count total (s) self (s)', lines[6]) call assert_match('^\s*1\s\+.*\stry$', lines[7]) - call assert_match('^\s*1\s\+.*\s let x = 0$', lines[8]) + call assert_match('^\s*1\s\+.*\s \(let\|var\) x = 0$', lines[8]) call assert_match( '^\s\+catch$', lines[9]) - call assert_match( '^\s\+let x = 1$', lines[10]) + call assert_match( '^\s\+\(let\|var\) x = 1$', lines[10]) call assert_match('^\s*1\s\+.*\sfinally$', lines[11]) - call assert_match('^\s*1\s\+.*\s let x = 2$', lines[12]) + call assert_match('^\s*1\s\+.*\s \(let\|var\) x = 2$', lines[12]) call assert_match('^\s*1\s\+.*\sendtry$', lines[13]) call assert_equal('', lines[14]) call assert_equal('FUNCTION Foo2()', lines[15]) @@ -295,9 +308,9 @@ func Test_profile_func_with_trycatch() call assert_match('^\s*1\s\+.*\stry$', lines[22]) call assert_match('^\s*1\s\+.*\s throw 0$', lines[23]) call assert_match('^\s*1\s\+.*\scatch$', lines[24]) - call assert_match('^\s*1\s\+.*\s let x = 1$', lines[25]) + call assert_match('^\s*1\s\+.*\s \(let\|var\) x = 1$', lines[25]) call assert_match('^\s*1\s\+.*\sfinally$', lines[26]) - call assert_match('^\s*1\s\+.*\s let x = 2$', lines[27]) + call assert_match('^\s*1\s\+.*\s \(let\|var\) x = 2$', lines[27]) call assert_match('^\s*1\s\+.*\sendtry$', lines[28]) call assert_equal('', lines[29]) call assert_equal('FUNCTION Foo3()', lines[30]) @@ -311,7 +324,7 @@ func Test_profile_func_with_trycatch() call assert_match('^\s*1\s\+.*\scatch$', lines[39]) call assert_match('^\s*1\s\+.*\s throw 1$', lines[40]) call assert_match('^\s*1\s\+.*\sfinally$', lines[41]) - call assert_match('^\s*1\s\+.*\s let x = 2$', lines[42]) + call assert_match('^\s*1\s\+.*\s \(let\|var\) x = 2$', lines[42]) call assert_match( '^\s\+endtry$', lines[43]) call assert_equal('', lines[44]) call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[45]) diff --git a/src/version.c b/src/version.c --- 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 */ /**/ + 2404, +/**/ 2403, /**/ 2402, diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -6566,7 +6566,7 @@ compile_jump_to_end(endlabel_T **el, jum } static void -compile_fill_jump_to_end(endlabel_T **el, cctx_T *cctx) +compile_fill_jump_to_end(endlabel_T **el, int jump_where, cctx_T *cctx) { garray_T *instr = &cctx->ctx_instr; @@ -6576,7 +6576,7 @@ compile_fill_jump_to_end(endlabel_T **el isn_T *isn; isn = ((isn_T *)instr->ga_data) + cur->el_end_label; - isn->isn_arg.jump.jump_where = instr->ga_len; + isn->isn_arg.jump.jump_where = jump_where; *el = cur->el_next; vim_free(cur); } @@ -6939,7 +6939,7 @@ compile_endif(char_u *arg, cctx_T *cctx) isn->isn_arg.jump.jump_where = instr->ga_len; } // Fill in the "end" label in jumps at the end of the blocks. - compile_fill_jump_to_end(&ifscope->is_end_label, cctx); + compile_fill_jump_to_end(&ifscope->is_end_label, instr->ga_len, cctx); #ifdef FEAT_PROFILE // even when skipping we count the endif as executed, unless the block it's @@ -7182,7 +7182,7 @@ compile_endfor(char_u *arg, cctx_T *cctx isn->isn_arg.forloop.for_end = instr->ga_len; // Fill in the "end" label any BREAK statements - compile_fill_jump_to_end(&forscope->fs_end_label, cctx); + compile_fill_jump_to_end(&forscope->fs_end_label, instr->ga_len, cctx); // Below the ":for" scope drop the "expr" list from the stack. if (generate_instr_drop(cctx, ISN_DROP, 1) == NULL) @@ -7245,6 +7245,7 @@ compile_while(char_u *arg, cctx_T *cctx) compile_endwhile(char_u *arg, cctx_T *cctx) { scope_T *scope = cctx->ctx_scope; + garray_T *instr = &cctx->ctx_instr; if (scope == NULL || scope->se_type != WHILE_SCOPE) { @@ -7264,7 +7265,8 @@ compile_endwhile(char_u *arg, cctx_T *cc // Fill in the "end" label in the WHILE statement so it can jump here. // And in any jumps for ":break" - compile_fill_jump_to_end(&scope->se_u.se_while.ws_end_label, cctx); + compile_fill_jump_to_end(&scope->se_u.se_while.ws_end_label, + instr->ga_len, cctx); vim_free(scope); @@ -7446,6 +7448,12 @@ compile_catch(char_u *arg, cctx_T *cctx if (cctx->ctx_skip != SKIP_YES) { +#ifdef FEAT_PROFILE + // the profile-start should be after the jump + if (cctx->ctx_profiling && ((isn_T *)instr->ga_data)[instr->ga_len - 1] + .isn_type == ISN_PROF_START) + --instr->ga_len; +#endif // Jump from end of previous block to :finally or :endtry if (compile_jump_to_end(&scope->se_u.se_try.ts_end_label, JUMP_ALWAYS, cctx) == FAIL) @@ -7461,6 +7469,15 @@ compile_catch(char_u *arg, cctx_T *cctx isn = ((isn_T *)instr->ga_data) + scope->se_u.se_try.ts_catch_label; isn->isn_arg.jump.jump_where = instr->ga_len; } +#ifdef FEAT_PROFILE + if (cctx->ctx_profiling) + { + // a "throw" that jumps here needs to be counted + generate_instr(cctx, ISN_PROF_END); + // the "catch" is also counted + generate_instr(cctx, ISN_PROF_START); + } +#endif } p = skipwhite(arg); @@ -7521,6 +7538,7 @@ compile_finally(char_u *arg, cctx_T *cct scope_T *scope = cctx->ctx_scope; garray_T *instr = &cctx->ctx_instr; isn_T *isn; + int this_instr; // end block scope from :try or :catch if (scope != NULL && scope->se_type == BLOCK_SCOPE) @@ -7542,15 +7560,24 @@ compile_finally(char_u *arg, cctx_T *cct return NULL; } + this_instr = instr->ga_len; +#ifdef FEAT_PROFILE + if (cctx->ctx_profiling && ((isn_T *)instr->ga_data)[instr->ga_len - 1] + .isn_type == ISN_PROF_START) + // jump to the profile start of the "finally" + --this_instr; +#endif + // Fill in the "end" label in jumps at the end of the blocks. - compile_fill_jump_to_end(&scope->se_u.se_try.ts_end_label, cctx); - - isn->isn_arg.try.try_finally = instr->ga_len; + compile_fill_jump_to_end(&scope->se_u.se_try.ts_end_label, + this_instr, cctx); + + isn->isn_arg.try.try_finally = this_instr; if (scope->se_u.se_try.ts_catch_label != 0) { // Previous 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; + isn->isn_arg.jump.jump_where = this_instr; scope->se_u.se_try.ts_catch_label = 0; } @@ -7595,9 +7622,18 @@ compile_endtry(char_u *arg, cctx_T *cctx return NULL; } +#ifdef FEAT_PROFILE + if (cctx->ctx_profiling && ((isn_T *)instr->ga_data)[instr->ga_len - 1] + .isn_type == ISN_PROF_START) + // move the profile start after "endtry" so that it's not counted when + // the exception is rethrown. + --instr->ga_len; +#endif + // Fill in the "end" label in jumps at the end of the blocks, if not // done by ":finally". - compile_fill_jump_to_end(&scope->se_u.se_try.ts_end_label, cctx); + compile_fill_jump_to_end(&scope->se_u.se_try.ts_end_label, + instr->ga_len, cctx); // End :catch or :finally scope: set value in ISN_TRY instruction if (isn->isn_arg.try.try_catch == 0) @@ -7617,6 +7653,10 @@ compile_endtry(char_u *arg, cctx_T *cctx if (cctx->ctx_skip != SKIP_YES && generate_instr(cctx, ISN_ENDTRY) == NULL) return NULL; +#ifdef FEAT_PROFILE + if (cctx->ctx_profiling) + generate_instr(cctx, ISN_PROF_START); +#endif return arg; } diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -2613,7 +2613,7 @@ call_def_function( if (trystack->ga_len > 0) { - trycmd_T *trycmd = NULL; + trycmd_T *trycmd; --trystack->ga_len; --trylevel; @@ -2635,34 +2635,54 @@ call_def_function( break; case ISN_THROW: - if (ectx.ec_trystack.ga_len == 0 && trylevel == 0 - && emsg_silent) { - // throwing an exception while using "silent!" causes the - // function to abort but not display an error. - tv = STACK_TV_BOT(-1); - clear_tv(tv); - tv->v_type = VAR_NUMBER; - tv->vval.v_number = 0; - goto done; + garray_T *trystack = &ectx.ec_trystack; + + if (trystack->ga_len == 0 && trylevel == 0 && emsg_silent) + { + // throwing an exception while using "silent!" causes + // the function to abort but not display an error. + tv = STACK_TV_BOT(-1); + clear_tv(tv); + tv->v_type = VAR_NUMBER; + tv->vval.v_number = 0; + goto done; + } + --ectx.ec_stack.ga_len; + tv = STACK_TV_BOT(0); + if (tv->vval.v_string == NULL + || *skipwhite(tv->vval.v_string) == NUL) + { + vim_free(tv->vval.v_string); + SOURCING_LNUM = iptr->isn_lnum; + emsg(_(e_throw_with_empty_string)); + goto failed; + } + + // Inside a "catch" we need to first discard the caught + // exception. + if (trystack->ga_len > 0) + { + trycmd_T *trycmd = ((trycmd_T *)trystack->ga_data) + + trystack->ga_len - 1; + if (trycmd->tcd_caught && current_exception != NULL) + { + // discard the exception + if (caught_stack == current_exception) + caught_stack = caught_stack->caught; + discard_current_exception(); + trycmd->tcd_caught = FALSE; + } + } + + if (throw_exception(tv->vval.v_string, ET_USER, NULL) + == FAIL) + { + vim_free(tv->vval.v_string); + goto failed; + } + did_throw = TRUE; } - --ectx.ec_stack.ga_len; - tv = STACK_TV_BOT(0); - if (tv->vval.v_string == NULL - || *skipwhite(tv->vval.v_string) == NUL) - { - vim_free(tv->vval.v_string); - SOURCING_LNUM = iptr->isn_lnum; - emsg(_(e_throw_with_empty_string)); - goto failed; - } - - if (throw_exception(tv->vval.v_string, ET_USER, NULL) == FAIL) - { - vim_free(tv->vval.v_string); - goto failed; - } - did_throw = TRUE; break; // compare with special values