]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.0.0577: buffer underflow with unexpected :finally v9.0.0577
authorBram Moolenaar <Bram@vim.org>
Sat, 24 Sep 2022 16:24:12 +0000 (17:24 +0100)
committerBram Moolenaar <Bram@vim.org>
Sat, 24 Sep 2022 16:24:12 +0000 (17:24 +0100)
Problem:    Buffer underflow with unexpected :finally.
Solution:   Check CSF_TRY can be found.

src/ex_eval.c
src/testdir/test_trycatch.vim
src/version.c

index 5721b766ee541d43e6d2a75fb795440b2eb00e6a..77d6e8bb981179465a8c080b2dc8d94eaa0bcbeb 100644 (file)
@@ -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);
 }
 
 /*
index aa42205a1b0bc125553c5a1605c9709b52bbca7a..28cd39f048249ed0cf5408abc5c7fd58ec2a48be 100644 (file)
@@ -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
index b3efbbe063c19e35604ea85f2716fac9019e0d8b..942a783ba0eeb6999c93383ab2a194d6fb4dc36a 100644 (file)
@@ -699,6 +699,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    577,
 /**/
     576,
 /**/