summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2022-09-24 17:24:12 +0100
committerBram Moolenaar <Bram@vim.org>2022-09-24 17:24:12 +0100
commit96b9bf8f74af8abf1e30054f996708db7dc285be (patch)
treee1af54d138e88ecfcd69d43414dee1c55d857492
parentb2209f213e2931cf3313b24868a9165bbb717cc4 (diff)
patch 9.0.0577: buffer underflow with unexpected :finallyv9.0.0577
Problem: Buffer underflow with unexpected :finally. Solution: Check CSF_TRY can be found.
-rw-r--r--src/ex_eval.c523
-rw-r--r--src/testdir/test_trycatch.vim22
-rw-r--r--src/version.c2
3 files changed, 284 insertions, 263 deletions
diff --git a/src/ex_eval.c b/src/ex_eval.c
index 5721b766ee..77d6e8bb98 100644
--- a/src/ex_eval.c
+++ b/src/ex_eval.c
@@ -1935,128 +1935,127 @@ ex_finally(exarg_T *eap)
if (cmdmod_error(FALSE))
return;
- if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0)
+ for (idx = cstack->cs_idx; idx >= 0; --idx)
+ if (cstack->cs_flags[idx] & CSF_TRY)
+ break;
+ if (cstack->cs_trylevel <= 0 || idx < 0)
+ {
eap->errmsg = _(e_finally_without_try);
- else
+ return;
+ }
+
+ if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY))
{
- if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY))
- {
- eap->errmsg = get_end_emsg(cstack);
- for (idx = cstack->cs_idx - 1; idx > 0; --idx)
- if (cstack->cs_flags[idx] & CSF_TRY)
- break;
- // Make this error pending, so that the commands in the following
- // finally clause can be executed. This overrules also a pending
- // ":continue", ":break", ":return", or ":finish".
- pending = CSTP_ERROR;
- }
- else
- idx = cstack->cs_idx;
+ eap->errmsg = get_end_emsg(cstack);
+ // Make this error pending, so that the commands in the following
+ // finally clause can be executed. This overrules also a pending
+ // ":continue", ":break", ":return", or ":finish".
+ pending = CSTP_ERROR;
+ }
- if (cstack->cs_flags[idx] & CSF_FINALLY)
+ if (cstack->cs_flags[idx] & CSF_FINALLY)
+ {
+ // Give up for a multiple ":finally" and ignore it.
+ eap->errmsg = _(e_multiple_finally);
+ return;
+ }
+ rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR,
+ &cstack->cs_looplevel);
+
+ /*
+ * Don't do something when the corresponding try block never got active
+ * (because of an inactive surrounding conditional or after an error or
+ * interrupt or throw) or for a ":finally" without ":try" or a multiple
+ * ":finally". After every other error (did_emsg or the conditional
+ * errors detected above) or after an interrupt (got_int) or an
+ * exception (did_throw), the finally clause must be executed.
+ */
+ skip = !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE);
+
+ if (!skip)
+ {
+ // When debugging or a breakpoint was encountered, display the
+ // debug prompt (if not already done). The user then knows that the
+ // finally clause is executed.
+ if (dbg_check_skipped(eap))
{
- // Give up for a multiple ":finally" and ignore it.
- eap->errmsg = _(e_multiple_finally);
- return;
+ // Handle a ">quit" debug command as if an interrupt had
+ // occurred before the ":finally". That is, discard the
+ // original exception and replace it by an interrupt
+ // exception.
+ (void)do_intthrow(cstack);
}
- rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR,
- &cstack->cs_looplevel);
/*
- * Don't do something when the corresponding try block never got active
- * (because of an inactive surrounding conditional or after an error or
- * interrupt or throw) or for a ":finally" without ":try" or a multiple
- * ":finally". After every other error (did_emsg or the conditional
- * errors detected above) or after an interrupt (got_int) or an
- * exception (did_throw), the finally clause must be executed.
+ * If there is a preceding catch clause and it caught the exception,
+ * finish the exception now. This happens also after errors except
+ * when this is a multiple ":finally" or one not within a ":try".
+ * After an error or interrupt, this also discards a pending
+ * ":continue", ":break", ":finish", or ":return" from the preceding
+ * try block or catch clause.
*/
- skip = !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE);
+ cleanup_conditionals(cstack, CSF_TRY, FALSE);
- if (!skip)
+ if (cstack->cs_idx >= 0
+ && (cstack->cs_flags[cstack->cs_idx] & CSF_TRY))
{
- // When debugging or a breakpoint was encountered, display the
- // debug prompt (if not already done). The user then knows that the
- // finally clause is executed.
- if (dbg_check_skipped(eap))
- {
- // Handle a ">quit" debug command as if an interrupt had
- // occurred before the ":finally". That is, discard the
- // original exception and replace it by an interrupt
- // exception.
- (void)do_intthrow(cstack);
- }
-
- /*
- * If there is a preceding catch clause and it caught the exception,
- * finish the exception now. This happens also after errors except
- * when this is a multiple ":finally" or one not within a ":try".
- * After an error or interrupt, this also discards a pending
- * ":continue", ":break", ":finish", or ":return" from the preceding
- * try block or catch clause.
- */
- cleanup_conditionals(cstack, CSF_TRY, FALSE);
+ // Variables declared in the previous block can no longer be
+ // used.
+ leave_block(cstack);
+ enter_block(cstack);
+ }
- if (cstack->cs_idx >= 0
- && (cstack->cs_flags[cstack->cs_idx] & CSF_TRY))
+ /*
+ * Make did_emsg, got_int, did_throw pending. If set, they overrule
+ * a pending ":continue", ":break", ":return", or ":finish". Then
+ * we have particularly to discard a pending return value (as done
+ * by the call to cleanup_conditionals() above when did_emsg or
+ * got_int is set). The pending values are restored by the
+ * ":endtry", except if there is a new error, interrupt, exception,
+ * ":continue", ":break", ":return", or ":finish" in the following
+ * finally clause. A missing ":endwhile", ":endfor" or ":endif"
+ * detected here is treated as if did_emsg and did_throw had
+ * already been set, respectively in case that the error is not
+ * converted to an exception, did_throw had already been unset.
+ * We must not set did_emsg here since that would suppress the
+ * error message.
+ */
+ if (pending == CSTP_ERROR || did_emsg || got_int || did_throw)
+ {
+ if (cstack->cs_pending[cstack->cs_idx] == CSTP_RETURN)
{
- // Variables declared in the previous block can no longer be
- // used.
- leave_block(cstack);
- enter_block(cstack);
+ report_discard_pending(CSTP_RETURN,
+ cstack->cs_rettv[cstack->cs_idx]);
+ discard_pending_return(cstack->cs_rettv[cstack->cs_idx]);
}
-
- /*
- * Make did_emsg, got_int, did_throw pending. If set, they overrule
- * a pending ":continue", ":break", ":return", or ":finish". Then
- * we have particularly to discard a pending return value (as done
- * by the call to cleanup_conditionals() above when did_emsg or
- * got_int is set). The pending values are restored by the
- * ":endtry", except if there is a new error, interrupt, exception,
- * ":continue", ":break", ":return", or ":finish" in the following
- * finally clause. A missing ":endwhile", ":endfor" or ":endif"
- * detected here is treated as if did_emsg and did_throw had
- * already been set, respectively in case that the error is not
- * converted to an exception, did_throw had already been unset.
- * We must not set did_emsg here since that would suppress the
- * error message.
- */
- if (pending == CSTP_ERROR || did_emsg || got_int || did_throw)
- {
- if (cstack->cs_pending[cstack->cs_idx] == CSTP_RETURN)
- {
- report_discard_pending(CSTP_RETURN,
- cstack->cs_rettv[cstack->cs_idx]);
- discard_pending_return(cstack->cs_rettv[cstack->cs_idx]);
- }
- if (pending == CSTP_ERROR && !did_emsg)
- pending |= (THROW_ON_ERROR) ? CSTP_THROW : 0;
- else
- pending |= did_throw ? CSTP_THROW : 0;
- pending |= did_emsg ? CSTP_ERROR : 0;
- pending |= got_int ? CSTP_INTERRUPT : 0;
- cstack->cs_pending[cstack->cs_idx] = pending;
-
- // It's mandatory that the current exception is stored in the
- // cstack so that it can be rethrown at the ":endtry" or be
- // discarded if the finally clause is left by a ":continue",
- // ":break", ":return", ":finish", error, interrupt, or another
- // exception. When emsg() is called for a missing ":endif" or
- // a missing ":endwhile"/":endfor" detected here, the
- // exception will be discarded.
- if (did_throw && cstack->cs_exception[cstack->cs_idx]
- != current_exception)
- internal_error("ex_finally()");
- }
-
- /*
- * Set CSL_HAD_FINA, so do_cmdline() will reset did_emsg,
- * got_int, and did_throw and make the finally clause active.
- * This will happen after emsg() has been called for a missing
- * ":endif" or a missing ":endwhile"/":endfor" detected here, so
- * that the following finally clause will be executed even then.
- */
- cstack->cs_lflags |= CSL_HAD_FINA;
+ if (pending == CSTP_ERROR && !did_emsg)
+ pending |= (THROW_ON_ERROR) ? CSTP_THROW : 0;
+ else
+ pending |= did_throw ? CSTP_THROW : 0;
+ pending |= did_emsg ? CSTP_ERROR : 0;
+ pending |= got_int ? CSTP_INTERRUPT : 0;
+ cstack->cs_pending[cstack->cs_idx] = pending;
+
+ // It's mandatory that the current exception is stored in the
+ // cstack so that it can be rethrown at the ":endtry" or be
+ // discarded if the finally clause is left by a ":continue",
+ // ":break", ":return", ":finish", error, interrupt, or another
+ // exception. When emsg() is called for a missing ":endif" or
+ // a missing ":endwhile"/":endfor" detected here, the
+ // exception will be discarded.
+ if (did_throw && cstack->cs_exception[cstack->cs_idx]
+ != current_exception)
+ internal_error("ex_finally()");
}
+
+ /*
+ * Set CSL_HAD_FINA, so do_cmdline() will reset did_emsg,
+ * got_int, and did_throw and make the finally clause active.
+ * This will happen after emsg() has been called for a missing
+ * ":endif" or a missing ":endwhile"/":endfor" detected here, so
+ * that the following finally clause will be executed even then.
+ */
+ cstack->cs_lflags |= CSL_HAD_FINA;
}
}
@@ -2076,185 +2075,183 @@ ex_endtry(exarg_T *eap)
if (cmdmod_error(FALSE))
return;
- if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0)
- eap->errmsg = _(e_endtry_without_try);
- else
+ for (idx = cstack->cs_idx; idx >= 0; --idx)
+ if (cstack->cs_flags[idx] & CSF_TRY)
+ break;
+ if (cstack->cs_trylevel <= 0 || idx < 0)
{
- /*
- * Don't do something after an error, interrupt or throw in the try
- * block, catch clause, or finally clause preceding this ":endtry" or
- * when an error or interrupt occurred after a ":continue", ":break",
- * ":return", or ":finish" in a try block or catch clause preceding this
- * ":endtry" or when the try block never got active (because of an
- * inactive surrounding conditional or after an error or interrupt or
- * throw) or when there is a surrounding conditional and it has been
- * made inactive by a ":continue", ":break", ":return", or ":finish" in
- * the finally clause. The latter case need not be tested since then
- * anything pending has already been discarded. */
- skip = did_emsg || got_int || did_throw
+ eap->errmsg = _(e_endtry_without_try);
+ return;
+ }
+
+ /*
+ * Don't do something after an error, interrupt or throw in the try
+ * block, catch clause, or finally clause preceding this ":endtry" or
+ * when an error or interrupt occurred after a ":continue", ":break",
+ * ":return", or ":finish" in a try block or catch clause preceding this
+ * ":endtry" or when the try block never got active (because of an
+ * inactive surrounding conditional or after an error or interrupt or
+ * throw) or when there is a surrounding conditional and it has been
+ * made inactive by a ":continue", ":break", ":return", or ":finish" in
+ * the finally clause. The latter case need not be tested since then
+ * anything pending has already been discarded. */
+ skip = did_emsg || got_int || did_throw
|| !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE);
- if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY))
- {
- eap->errmsg = get_end_emsg(cstack);
+ if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY))
+ {
+ eap->errmsg = get_end_emsg(cstack);
- // Find the matching ":try" and report what's missing.
- idx = cstack->cs_idx;
- do
- --idx;
- while (idx > 0 && !(cstack->cs_flags[idx] & CSF_TRY));
- rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR,
- &cstack->cs_looplevel);
- skip = TRUE;
+ // Find the matching ":try" and report what's missing.
+ rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR,
+ &cstack->cs_looplevel);
+ skip = TRUE;
- /*
- * If an exception is being thrown, discard it to prevent it from
- * being rethrown at the end of this function. It would be
- * discarded by the error message, anyway. Resets did_throw.
- * This does not affect the script termination due to the error
- * since "trylevel" is decremented after emsg() has been called.
- */
- if (did_throw)
- discard_current_exception();
+ /*
+ * If an exception is being thrown, discard it to prevent it from
+ * being rethrown at the end of this function. It would be
+ * discarded by the error message, anyway. Resets did_throw.
+ * This does not affect the script termination due to the error
+ * since "trylevel" is decremented after emsg() has been called.
+ */
+ if (did_throw)
+ discard_current_exception();
- // report eap->errmsg, also when there already was an error
- did_emsg = FALSE;
- }
- else
- {
- idx = cstack->cs_idx;
+ // report eap->errmsg, also when there already was an error
+ did_emsg = FALSE;
+ }
+ else
+ {
+ idx = cstack->cs_idx;
- // Check the flags only when not in a skipped block.
- if (!skip && in_vim9script()
+ // Check the flags only when not in a skipped block.
+ if (!skip && in_vim9script()
&& (cstack->cs_flags[idx] & (CSF_CATCH|CSF_FINALLY)) == 0)
- {
- // try/endtry without any catch or finally: give an error and
- // continue.
- eap->errmsg = _(e_missing_catch_or_finally);
- }
-
- /*
- * If we stopped with the exception currently being thrown at this
- * try conditional since we didn't know that it doesn't have
- * a finally clause, we need to rethrow it after closing the try
- * conditional.
- */
- if (did_throw && (cstack->cs_flags[idx] & CSF_TRUE)
- && !(cstack->cs_flags[idx] & CSF_FINALLY))
- rethrow = TRUE;
- }
-
- // If there was no finally clause, show the user when debugging or
- // a breakpoint was encountered that the end of the try conditional has
- // been reached: display the debug prompt (if not already done). Do
- // this on normal control flow or when an exception was thrown, but not
- // on an interrupt or error not converted to an exception or when
- // a ":break", ":continue", ":return", or ":finish" is pending. These
- // actions are carried out immediately.
- if ((rethrow || (!skip
- && !(cstack->cs_flags[idx] & CSF_FINALLY)
- && !cstack->cs_pending[idx]))
- && dbg_check_skipped(eap))
{
- // Handle a ">quit" debug command as if an interrupt had occurred
- // before the ":endtry". That is, throw an interrupt exception and
- // set "skip" and "rethrow".
- if (got_int)
- {
- skip = TRUE;
- (void)do_intthrow(cstack);
- // The do_intthrow() call may have reset did_throw or
- // cstack->cs_pending[idx].
- rethrow = FALSE;
- if (did_throw && !(cstack->cs_flags[idx] & CSF_FINALLY))
- rethrow = TRUE;
- }
+ // try/endtry without any catch or finally: give an error and
+ // continue.
+ eap->errmsg = _(e_missing_catch_or_finally);
}
/*
- * If a ":return" is pending, we need to resume it after closing the
- * try conditional; remember the return value. If there was a finally
- * clause making an exception pending, we need to rethrow it. Make it
- * the exception currently being thrown.
+ * If we stopped with the exception currently being thrown at this
+ * try conditional since we didn't know that it doesn't have
+ * a finally clause, we need to rethrow it after closing the try
+ * conditional.
*/
- if (!skip)
+ if (did_throw && (cstack->cs_flags[idx] & CSF_TRUE)
+ && !(cstack->cs_flags[idx] & CSF_FINALLY))
+ rethrow = TRUE;
+ }
+
+ // If there was no finally clause, show the user when debugging or
+ // a breakpoint was encountered that the end of the try conditional has
+ // been reached: display the debug prompt (if not already done). Do
+ // this on normal control flow or when an exception was thrown, but not
+ // on an interrupt or error not converted to an exception or when
+ // a ":break", ":continue", ":return", or ":finish" is pending. These
+ // actions are carried out immediately.
+ if ((rethrow || (!skip && !(cstack->cs_flags[idx] & CSF_FINALLY)
+ && !cstack->cs_pending[idx]))
+ && dbg_check_skipped(eap))
+ {
+ // Handle a ">quit" debug command as if an interrupt had occurred
+ // before the ":endtry". That is, throw an interrupt exception and
+ // set "skip" and "rethrow".
+ if (got_int)
{
- pending = cstack->cs_pending[idx];
- cstack->cs_pending[idx] = CSTP_NONE;
- if (pending == CSTP_RETURN)
- rettv = cstack->cs_rettv[idx];
- else if (pending & CSTP_THROW)
- current_exception = cstack->cs_exception[idx];
+ skip = TRUE;
+ (void)do_intthrow(cstack);
+ // The do_intthrow() call may have reset did_throw or
+ // cstack->cs_pending[idx].
+ rethrow = FALSE;
+ if (did_throw && !(cstack->cs_flags[idx] & CSF_FINALLY))
+ rethrow = TRUE;
}
+ }
- /*
- * Discard anything pending on an error, interrupt, or throw in the
- * finally clause. If there was no ":finally", discard a pending
- * ":continue", ":break", ":return", or ":finish" if an error or
- * interrupt occurred afterwards, but before the ":endtry" was reached.
- * If an exception was caught by the last of the catch clauses and there
- * was no finally clause, finish the exception now. This happens also
- * after errors except when this ":endtry" is not within a ":try".
- * Restore "emsg_silent" if it has been reset by this try conditional.
- */
- (void)cleanup_conditionals(cstack, CSF_TRY | CSF_SILENT, TRUE);
+ /*
+ * If a ":return" is pending, we need to resume it after closing the
+ * try conditional; remember the return value. If there was a finally
+ * clause making an exception pending, we need to rethrow it. Make it
+ * the exception currently being thrown.
+ */
+ if (!skip)
+ {
+ pending = cstack->cs_pending[idx];
+ cstack->cs_pending[idx] = CSTP_NONE;
+ if (pending == CSTP_RETURN)
+ rettv = cstack->cs_rettv[idx];
+ else if (pending & CSTP_THROW)
+ current_exception = cstack->cs_exception[idx];
+ }
- if (cstack->cs_idx >= 0
- && (cstack->cs_flags[cstack->cs_idx] & CSF_TRY))
- leave_block(cstack);
- --cstack->cs_trylevel;
+ /*
+ * Discard anything pending on an error, interrupt, or throw in the
+ * finally clause. If there was no ":finally", discard a pending
+ * ":continue", ":break", ":return", or ":finish" if an error or
+ * interrupt occurred afterwards, but before the ":endtry" was reached.
+ * If an exception was caught by the last of the catch clauses and there
+ * was no finally clause, finish the exception now. This happens also
+ * after errors except when this ":endtry" is not within a ":try".
+ * Restore "emsg_silent" if it has been reset by this try conditional.
+ */
+ (void)cleanup_conditionals(cstack, CSF_TRY | CSF_SILENT, TRUE);
- if (!skip)
- {
- report_resume_pending(pending,
+ if (cstack->cs_idx >= 0 && (cstack->cs_flags[cstack->cs_idx] & CSF_TRY))
+ leave_block(cstack);
+ --cstack->cs_trylevel;
+
+ if (!skip)
+ {
+ report_resume_pending(pending,
(pending == CSTP_RETURN) ? rettv :
(pending & CSTP_THROW) ? (void *)current_exception : NULL);
- switch (pending)
- {
- case CSTP_NONE:
- break;
+ switch (pending)
+ {
+ case CSTP_NONE:
+ break;
- // Reactivate a pending ":continue", ":break", ":return",
- // ":finish" from the try block or a catch clause of this try
- // conditional. This is skipped, if there was an error in an
- // (unskipped) conditional command or an interrupt afterwards
- // or if the finally clause is present and executed a new error,
- // interrupt, throw, ":continue", ":break", ":return", or
- // ":finish".
- case CSTP_CONTINUE:
- ex_continue(eap);
- break;
- case CSTP_BREAK:
- ex_break(eap);
- break;
- case CSTP_RETURN:
- do_return(eap, FALSE, FALSE, rettv);
- break;
- case CSTP_FINISH:
- do_finish(eap, FALSE);
- break;
+ // Reactivate a pending ":continue", ":break", ":return",
+ // ":finish" from the try block or a catch clause of this try
+ // conditional. This is skipped, if there was an error in an
+ // (unskipped) conditional command or an interrupt afterwards
+ // or if the finally clause is present and executed a new error,
+ // interrupt, throw, ":continue", ":break", ":return", or
+ // ":finish".
+ case CSTP_CONTINUE:
+ ex_continue(eap);
+ break;
+ case CSTP_BREAK:
+ ex_break(eap);
+ break;
+ case CSTP_RETURN:
+ do_return(eap, FALSE, FALSE, rettv);
+ break;
+ case CSTP_FINISH:
+ do_finish(eap, FALSE);
+ break;
- // When the finally clause was entered due to an error,
- // interrupt or throw (as opposed to a ":continue", ":break",
- // ":return", or ":finish"), restore the pending values of
- // did_emsg, got_int, and did_throw. This is skipped, if there
- // was a new error, interrupt, throw, ":continue", ":break",
- // ":return", or ":finish". in the finally clause.
- default:
- if (pending & CSTP_ERROR)
- did_emsg = TRUE;
- if (pending & CSTP_INTERRUPT)
- got_int = TRUE;
- if (pending & CSTP_THROW)
- rethrow = TRUE;
- break;
- }
+ // When the finally clause was entered due to an error,
+ // interrupt or throw (as opposed to a ":continue", ":break",
+ // ":return", or ":finish"), restore the pending values of
+ // did_emsg, got_int, and did_throw. This is skipped, if there
+ // was a new error, interrupt, throw, ":continue", ":break",
+ // ":return", or ":finish". in the finally clause.
+ default:
+ if (pending & CSTP_ERROR)
+ did_emsg = TRUE;
+ if (pending & CSTP_INTERRUPT)
+ got_int = TRUE;
+ if (pending & CSTP_THROW)
+ rethrow = TRUE;
+ break;
}
-
- if (rethrow)
- // Rethrow the current exception (within this cstack).
- do_throw(cstack);
}
+
+ if (rethrow)
+ // Rethrow the current exception (within this cstack).
+ do_throw(cstack);
}
/*
diff --git a/src/testdir/test_trycatch.vim b/src/testdir/test_trycatch.vim
index aa42205a1b..28cd39f048 100644
--- a/src/testdir/test_trycatch.vim
+++ b/src/testdir/test_trycatch.vim
@@ -3,6 +3,7 @@
source check.vim
source shared.vim
+import './vim9.vim' as v9
"-------------------------------------------------------------------------------
" Test environment {{{1
@@ -2008,6 +2009,27 @@ func Test_try_catch_errors()
call assert_fails('try | for i in range(5) | endif | endtry', 'E580:')
call assert_fails('try | while v:true | endtry', 'E170:')
call assert_fails('try | if v:true | endtry', 'E171:')
+
+ " this was using a negative index in cstack[]
+ let lines =<< trim END
+ try
+ for
+ if
+ endwhile
+ if
+ finally
+ END
+ call v9.CheckScriptFailure(lines, 'E690:')
+
+ let lines =<< trim END
+ try
+ for
+ if
+ endwhile
+ if
+ endtry
+ END
+ call v9.CheckScriptFailure(lines, 'E690:')
endfunc
" Test for verbose messages with :try :catch, and :finally {{{1
diff --git a/src/version.c b/src/version.c
index b3efbbe063..942a783ba0 100644
--- a/src/version.c
+++ b/src/version.c
@@ -700,6 +700,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 577,
+/**/
576,
/**/
575,