]> git.eshelyaron.com Git - emacs.git/commitdiff
debug.el: Prevent re-entering the debugger for the same error
authorStefan Monnier <monnier@iro.umontreal.ca>
Sun, 17 Mar 2024 03:10:48 +0000 (23:10 -0400)
committerEshel Yaron <me@eshelyaron.com>
Mon, 18 Mar 2024 15:44:03 +0000 (16:44 +0100)
We can have several active `handler-bind`s that all want to invoke the
debugger, in which case we can have the following sequence:

- The more deeply nested handler calls the debugger.
- After a while the user invokes `debugger-continue`.
- `signal_or_quit` propagates the error up the stack to the
  second handler, which calls the debugger again.
- The user thus ends up right back at the same place, as if
  `debugger-continue` had not be processed.

Fix this by remembering the last processed error and skipping
the debugger if we bump into it again.

* lisp/emacs-lisp/debug.el (debugger--last-error): New var.
(debugger--duplicate-p): New function.
(debug): Use them.

(cherry picked from commit 445e2499baa1b8ef21e8edcc13692b5d78912922)

lisp/emacs-lisp/debug.el

index 60d14d11970ff5777696a6bc7d576a366e29742f..ec947c1215d02ba390879e81a990acff0454b7e8 100644 (file)
@@ -153,6 +153,12 @@ where CAUSE can be:
     (insert (debugger--buffer-state-content state)))
   (goto-char (debugger--buffer-state-pos state)))
 
+(defvar debugger--last-error nil)
+
+(defun debugger--duplicate-p (args)
+  (pcase args
+    (`(error ,err . ,_) (and (consp err) (eq err debugger--last-error)))))
+
 ;;;###autoload
 (setq debugger 'debug)
 ;;;###autoload
@@ -175,9 +181,14 @@ first will be printed into the backtrace buffer.
 If `inhibit-redisplay' is non-nil when this function is called,
 the debugger will not be entered."
   (interactive)
-  (if inhibit-redisplay
-      ;; Don't really try to enter debugger within an eval from redisplay.
+  (if (or inhibit-redisplay
+          (debugger--duplicate-p args))
+      ;; Don't really try to enter debugger within an eval from redisplay
+      ;; or if we already popper into the debugger for this error,
+      ;; which can happen when we have several nested `handler-bind's that
+      ;; want to invoke the debugger.
       debugger-value
+    (setq debugger--last-error nil)
     (let ((non-interactive-frame
            (or noninteractive           ;FIXME: Presumably redundant.
                ;; If we're in the initial-frame (where `message' just
@@ -318,6 +329,12 @@ the debugger will not be entered."
                   (backtrace-mode))))
            (with-timeout-unsuspend debugger-with-timeout-suspend)
            (set-match-data debugger-outer-match-data)))
+       (when (eq 'error (car-safe debugger-args))
+         ;; Remember the error we just debugged, to avoid re-entering
+          ;; the debugger if some higher-up `handler-bind' invokes us
+          ;; again, oblivious that the error was already debugged from
+          ;; a more deeply nested `handler-bind'.
+         (setq debugger--last-error (nth 1 debugger-args)))
         (setq debug-on-next-call debugger-step-after-exit)
         debugger-value))))
 \f