Support Elisp debugging and backtraces in code called by modules

* src/lisp.h (handlertype) <CATCHER_ALL_DEBUGGABLE>: 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.
This commit is contained in:
Aaron L. Zeng 2026-03-26 16:45:14 -04:00 committed by Eli Zaretskii
parent 1cfbad0188
commit e4633e657a
4 changed files with 18 additions and 4 deletions

View file

@ -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); \

View file

@ -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". */

View file

@ -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.

View file

@ -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)