Patch 9.0.0577
Problem:    Buffer underflow with unexpected :finally.
Solution:   Check CSF_TRY can be found.
Files:      src/ex_eval.c, src/testdir/test_trycatch.vim


*** ../vim-9.0.0576/src/ex_eval.c       2022-09-21 18:59:10.671074961 +0100
--- src/ex_eval.c       2022-09-24 17:21:20.299859969 +0100
***************
*** 1935,2062 ****
      if (cmdmod_error(FALSE))
        return;
  
!     if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0)
        eap->errmsg = _(e_finally_without_try);
!     else
      {
!       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;
  
!       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))
!           {
!               // 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);
! 
!           if (cstack->cs_idx >= 0
!                              && (cstack->cs_flags[cstack->cs_idx] & CSF_TRY))
!           {
!               // Variables declared in the previous block can no longer be
!               // used.
!               leave_block(cstack);
!               enter_block(cstack);
!           }
  
!           /*
!            * 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;
        }
      }
  }
  
--- 1935,2061 ----
      if (cmdmod_error(FALSE))
        return;
  
!     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);
!       return;
!     }
! 
!     if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY))
      {
!       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)
!     {
!       // 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))
        {
!           // 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);
  
!       if (cstack->cs_idx >= 0
!                          && (cstack->cs_flags[cstack->cs_idx] & CSF_TRY))
        {
!           // Variables declared in the previous block can no longer be
!           // used.
!           leave_block(cstack);
!           enter_block(cstack);
!       }
  
!       /*
!        * 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;
      }
  }
  
***************
*** 2076,2260 ****
      if (cmdmod_error(FALSE))
        return;
  
!     if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0)
!       eap->errmsg = _(e_endtry_without_try);
!     else
      {
!       /*
!        * 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);
  
!           // 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;
  
!           /*
!            * 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;
  
!           // 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;
!           }
        }
  
        /*
!        * 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];
        }
  
!       /*
!        * 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 (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;
  
!               // 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;
!           }
        }
- 
-       if (rethrow)
-           // Rethrow the current exception (within this cstack).
-           do_throw(cstack);
      }
  }
  
  /*
--- 2075,2257 ----
      if (cmdmod_error(FALSE))
        return;
  
!     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_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);
  
!       // 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();
  
!       // 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()
                     && (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;
        }
+     }
  
!     /*
!      * 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];
!     }
  
!     /*
!      * 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 (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;
  
!           // 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;
        }
      }
+ 
+     if (rethrow)
+       // Rethrow the current exception (within this cstack).
+       do_throw(cstack);
  }
  
  /*
*** ../vim-9.0.0576/src/testdir/test_trycatch.vim       2022-05-27 
13:47:02.000000000 +0100
--- src/testdir/test_trycatch.vim       2022-09-24 17:22:06.711776571 +0100
***************
*** 3,8 ****
--- 3,9 ----
  
  source check.vim
  source shared.vim
+ import './vim9.vim' as v9
  
  
"-------------------------------------------------------------------------------
  " Test environment                                                        {{{1
***************
*** 2008,2013 ****
--- 2009,2035 ----
    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
*** ../vim-9.0.0576/src/version.c       2022-09-24 15:55:22.385626232 +0100
--- src/version.c       2022-09-24 17:03:39.749548167 +0100
***************
*** 701,702 ****
--- 701,704 ----
  {   /* Add new patch number below this line */
+ /**/
+     577,
  /**/

-- 
Microsoft says that MS-Windows is much better for you than Linux.
That's like the Pope saying that catholicism is much better for
you than protestantism.

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///                                                                      \\\
\\\        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/20220924162442.A459A1C0757%40moolenaar.net.

Raspunde prin e-mail lui