From e4633e657a338a39e381da397f02ee33c367d437 Mon Sep 17 00:00:00 2001 From: "Aaron L. Zeng" Date: Thu, 26 Mar 2026 16:45:14 -0400 Subject: [PATCH] Support Elisp debugging and backtraces in code called by modules * src/lisp.h (handlertype) : New enumeration value. * src/emacs-module.c (MODULE_HANDLE_NONLOCAL_EXIT): * src/eval.c (Fthrow, signal_or_quit): Use it. * test/src/emacs-module-tests.el (mod-test-non-local-exit-funcall-debug-on-error): New test. Bug#80714 Copyright-paperwork-exempt: yes. --- src/emacs-module.c | 4 +--- src/eval.c | 6 +++++- src/lisp.h | 2 ++ test/src/emacs-module-tests.el | 10 ++++++++++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/emacs-module.c b/src/emacs-module.c index 887c078f7d9..142ca675734 100644 --- a/src/emacs-module.c +++ b/src/emacs-module.c @@ -263,13 +263,11 @@ module_memory_buffer_too_small (ptrdiff_t actual, ptrdiff_t required) code after the macro may longjmp back into the macro, which means its local variable INTERNAL_CLEANUP must stay live in later code. */ -/* TODO: Make backtraces work if this macro is used. */ - #define MODULE_HANDLE_NONLOCAL_EXIT(retval) \ if (module_non_local_exit_check (env) != emacs_funcall_exit_return) \ return retval; \ struct handler *internal_handler = \ - push_handler_nosignal (Qt, CATCHER_ALL); \ + push_handler_nosignal (Qt, CATCHER_ALL_DEBUGGABLE); \ if (!internal_handler) \ { \ module_out_of_memory (env); \ diff --git a/src/eval.c b/src/eval.c index ff2ce1b01a5..b61bda4a024 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1453,7 +1453,7 @@ Both TAG and VALUE are evalled. */ if (!NILP (tag)) for (c = handlerlist; c; c = c->next) { - if (c->type == CATCHER_ALL) + if (c->type == CATCHER_ALL || c->type == CATCHER_ALL_DEBUGGABLE) unwind_to_catch (c, NONLOCAL_EXIT_THROW, Fcons (tag, value)); if (c->type == CATCHER && EQ (c->tag_or_ch, tag)) unwind_to_catch (c, NONLOCAL_EXIT_THROW, value); @@ -1981,6 +1981,9 @@ signal_or_quit (Lisp_Object error_symbol, Lisp_Object data, bool continuable) case CATCHER_ALL: clause = Qt; break; + case CATCHER_ALL_DEBUGGABLE: + clause = Qdebug; + break; case CATCHER: continue; case CONDITION_CASE: @@ -2024,6 +2027,7 @@ signal_or_quit (Lisp_Object error_symbol, Lisp_Object data, bool continuable) || NILP (clause) /* A `debug' symbol in the handler list disables the normal suppression of the debugger. */ + || EQ (clause, Qdebug) || (CONSP (clause) && !NILP (Fmemq (Qdebug, clause))) /* Special handler that means "print a message and run debugger if requested". */ diff --git a/src/lisp.h b/src/lisp.h index 223eb4df564..ee50cc777c6 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -3810,6 +3810,8 @@ enum handlertype { CATCHER_ALL, /* Wildcard that catches all throws and signals. 'tag_or_ch' is unused. 'val' holds the retval during longjmp. */ + CATCHER_ALL_DEBUGGABLE, /* Like CATCHER_ALL, but does not suppress the + debugger. */ HANDLER_BIND, /* Entry for 'handler-bind'. 'tag_or_ch' holds the list of conditions. 'val' holds the handler function. diff --git a/test/src/emacs-module-tests.el b/test/src/emacs-module-tests.el index d2ec127f878..5d3abb0d76b 100644 --- a/test/src/emacs-module-tests.el +++ b/test/src/emacs-module-tests.el @@ -146,6 +146,16 @@ changes." (should (equal (mod-test-non-local-exit-funcall (lambda () (throw 'tag 32))) '(throw tag 32)))) +(ert-deftest mod-test-non-local-exit-funcall-debug-on-error () + (let* ((debugger-entered nil) + (debugger (lambda (&rest _) (setq debugger-entered t)))) + (should (equal + (let ((debug-on-error t)) + (mod-test-non-local-exit-funcall + (lambda () (error "test error")))) + '(signal error ("test error")))) + (should debugger-entered))) + ;;; String tests (defun multiply-string (s n)