New debugging facility: backtraces from errors in Lisp called from redisplay
Setting backtrace-on-redisplay-error to non-nil enables the generation of a Lisp backtrace in buffer *Redisplay-trace* following an error in Lisp called from redisplay. * doc/lispref/debugging.texi (Debugging Redisplay): New subsection. (Error Debugging): Reference to the new subsection. * etc/NEWS: New entry for the new facility. * src/eval.c (redisplay_deep_handler): New variable. (init_eval): Initialize redisplay_deep_handler. (call_debugger): Don't throw to top-level after calling debug-early (internal_condition_case_n): "Bind" redisplay_deep_handler to the current handler. (backtrace_yet): New boolean variable. (signal_or_quit): New code section to handle Lisp errors occurring in redisplay. (syms_of_eval): New DEFVAR_BOOL backtrace-on-redisplay-error. * src/keyboard.c (command_loop_1): Set backtrace_yet to false each time around the loop. (safe_run_hooks_error): Allow args to be up to four Lisp_Objects long. (safe_run_hooks_2): New function. * src/lisp.h (top level): declare as externs backtrace_yet and safe_run_hooks_2. * src/xdisp.c (run_window_scroll_functions): Replace a call to run_hook_with_args_2 with one to safe_run_hooks_2.
This commit is contained in:
parent
e7f1d4f6e1
commit
48215c41d1
6 changed files with 126 additions and 4 deletions
|
@ -77,6 +77,7 @@ debugger recursively. @xref{Recursive Editing}.
|
|||
|
||||
@menu
|
||||
* Error Debugging:: Entering the debugger when an error happens.
|
||||
* Debugging Redisplay:: Getting backtraces from redisplay errors.
|
||||
* Infinite Loops:: Stopping and debugging a program that doesn't exit.
|
||||
* Function Debugging:: Entering it when a certain function is called.
|
||||
* Variable Debugging:: Entering it when a variable is modified.
|
||||
|
@ -105,6 +106,10 @@ debugger, set the variable @code{debug-on-error} to non-@code{nil}.
|
|||
(The command @code{toggle-debug-on-error} provides an easy way to do
|
||||
this.)
|
||||
|
||||
Note that, for technical reasons, you cannot use the facilities
|
||||
defined in this subsection to debug errors in Lisp that the redisplay
|
||||
code has invoked. @xref{Debugging Redisplay}, for help with these.
|
||||
|
||||
@defopt debug-on-error
|
||||
This variable determines whether the debugger is called when an error
|
||||
is signaled and not handled. If @code{debug-on-error} is @code{t},
|
||||
|
@ -213,6 +218,45 @@ file, use the option @samp{--debug-init}. This binds
|
|||
bypasses the @code{condition-case} which normally catches errors in the
|
||||
init file.
|
||||
|
||||
@node Debugging Redisplay
|
||||
@subsection Debugging Redisplay Errors
|
||||
@cindex redisplay errors
|
||||
@cindex debugging redisplay errors
|
||||
|
||||
When an error occurs in Lisp code which redisplay has invoked, Emacs's
|
||||
usual debugging mechanisms are unusable, for technical reasons. This
|
||||
subsection describes how to get a backtrace from such an error, which
|
||||
should be helpful in debugging it.
|
||||
|
||||
These directions apply to Lisp forms used, for example, in
|
||||
@code{:eval} mode line constructs (@pxref{Mode Line Data}), and in all
|
||||
hooks invoked from redisplay, such as:
|
||||
|
||||
@itemize
|
||||
@item
|
||||
@code{fontification-functions} (@pxref{Auto Faces}).
|
||||
@item
|
||||
@code{window-scroll-functions} (@pxref{Window Hooks}).
|
||||
@end itemize
|
||||
|
||||
Note that if you have had an error in a hook function called from
|
||||
redisplay, the error handling might have removed this function from
|
||||
the hook. You will thus need to reinitialize that hook somehow,
|
||||
perhaps with @code{add-hook}, to be able to replay the bug.
|
||||
|
||||
To generate a backtrace in these circumstances, set the variable
|
||||
@code{backtrace-on-redisplay-error} to non-@code{nil}. When the error
|
||||
occurs, Emacs will dump the backtrace to the buffer
|
||||
@file{*Redisplay-trace*}, but won't automatically display it in a
|
||||
window. This is to avoid needlessly corrupting the redisplay you are
|
||||
debugging. You will thus need to display the buffer yourself, with a
|
||||
command such as @code{switch-to-buffer-other-frame} @key{C-x 5 b}.
|
||||
|
||||
@defvar backtrace-on-redisplay-error
|
||||
Set this variable to non-@code{nil} to enable the generation of a
|
||||
backtrace when an error occurs in any Lisp called from redisplay.
|
||||
@end defvar
|
||||
|
||||
@node Infinite Loops
|
||||
@subsection Debugging Infinite Loops
|
||||
@cindex infinite loops
|
||||
|
|
7
etc/NEWS
7
etc/NEWS
|
@ -1339,6 +1339,13 @@ When invoked with a non-zero prefix argument, as in 'C-u C-x C-e',
|
|||
this command will pop up a new buffer and show the full pretty-printed
|
||||
value there.
|
||||
|
||||
+++
|
||||
*** You can now generate a backtrace from Lisp errors in redisplay.
|
||||
To do this, set the new variable 'backtrace-on-redisplay-error' to a
|
||||
non-nil value. The backtrace will be written to buffer
|
||||
*Redisplay-trace*. This buffer will not be automatically displayed in
|
||||
a window.
|
||||
|
||||
** Compile
|
||||
|
||||
+++
|
||||
|
|
59
src/eval.c
59
src/eval.c
|
@ -57,6 +57,12 @@ Lisp_Object Vrun_hooks;
|
|||
/* FIXME: We should probably get rid of this! */
|
||||
Lisp_Object Vsignaling_function;
|
||||
|
||||
/* The handler structure which will catch errors in Lisp hooks called
|
||||
from redisplay. We do not use it for this; we compare it with the
|
||||
handler which is about to be used in signal_or_quit, and if it
|
||||
matches, cause a backtrace to be generated. */
|
||||
static struct handler *redisplay_deep_handler;
|
||||
|
||||
/* These would ordinarily be static, but they need to be visible to GDB. */
|
||||
bool backtrace_p (union specbinding *) EXTERNALLY_VISIBLE;
|
||||
Lisp_Object *backtrace_args (union specbinding *) EXTERNALLY_VISIBLE;
|
||||
|
@ -246,6 +252,7 @@ init_eval (void)
|
|||
lisp_eval_depth = 0;
|
||||
/* This is less than the initial value of num_nonmacro_input_events. */
|
||||
when_entered_debugger = -1;
|
||||
redisplay_deep_handler = NULL;
|
||||
}
|
||||
|
||||
/* Ensure that *M is at least A + B if possible, or is its maximum
|
||||
|
@ -333,7 +340,8 @@ call_debugger (Lisp_Object arg)
|
|||
/* Interrupting redisplay and resuming it later is not safe under
|
||||
all circumstances. So, when the debugger returns, abort the
|
||||
interrupted redisplay by going back to the top-level. */
|
||||
if (debug_while_redisplaying)
|
||||
if (debug_while_redisplaying
|
||||
&& !EQ (Vdebugger, Qdebug_early))
|
||||
Ftop_level ();
|
||||
|
||||
return unbind_to (count, val);
|
||||
|
@ -1556,12 +1564,16 @@ internal_condition_case_n (Lisp_Object (*bfun) (ptrdiff_t, Lisp_Object *),
|
|||
ptrdiff_t nargs,
|
||||
Lisp_Object *args))
|
||||
{
|
||||
struct handler *old_deep = redisplay_deep_handler;
|
||||
struct handler *c = push_handler (handlers, CONDITION_CASE);
|
||||
if (redisplaying_p)
|
||||
redisplay_deep_handler = c;
|
||||
if (sys_setjmp (c->jmp))
|
||||
{
|
||||
Lisp_Object val = handlerlist->val;
|
||||
clobbered_eassert (handlerlist == c);
|
||||
handlerlist = handlerlist->next;
|
||||
redisplay_deep_handler = old_deep;
|
||||
return hfun (val, nargs, args);
|
||||
}
|
||||
else
|
||||
|
@ -1569,6 +1581,7 @@ internal_condition_case_n (Lisp_Object (*bfun) (ptrdiff_t, Lisp_Object *),
|
|||
Lisp_Object val = bfun (nargs, args);
|
||||
eassert (handlerlist == c);
|
||||
handlerlist = c->next;
|
||||
redisplay_deep_handler = old_deep;
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
@ -1701,6 +1714,11 @@ quit (void)
|
|||
return signal_or_quit (Qquit, Qnil, true);
|
||||
}
|
||||
|
||||
/* Has an error in redisplay giving rise to a backtrace occurred as
|
||||
yet in the current command? This gets reset in the command
|
||||
loop. */
|
||||
bool backtrace_yet = false;
|
||||
|
||||
/* Signal an error, or quit. ERROR_SYMBOL and DATA are as with Fsignal.
|
||||
If KEYBOARD_QUIT, this is a quit; ERROR_SYMBOL should be
|
||||
Qquit and DATA should be Qnil, and this function may return.
|
||||
|
@ -1816,6 +1834,40 @@ signal_or_quit (Lisp_Object error_symbol, Lisp_Object data, bool keyboard_quit)
|
|||
unbind_to (count, Qnil);
|
||||
}
|
||||
|
||||
/* If an error is signalled during a Lisp hook in redisplay, write a
|
||||
backtrace into the buffer *Redisplay-trace*. */
|
||||
if (!debugger_called && !NILP (error_symbol)
|
||||
&& backtrace_on_redisplay_error
|
||||
&& (NILP (clause) || h == redisplay_deep_handler)
|
||||
&& NILP (Vinhibit_debugger)
|
||||
&& !NILP (Ffboundp (Qdebug_early)))
|
||||
{
|
||||
max_ensure_room (&max_lisp_eval_depth, lisp_eval_depth, 100);
|
||||
specpdl_ref count = SPECPDL_INDEX ();
|
||||
ptrdiff_t counti = specpdl_ref_to_count (count);
|
||||
AUTO_STRING (redisplay_trace, "*Redisplay_trace*");
|
||||
Lisp_Object redisplay_trace_buffer;
|
||||
AUTO_STRING (gap, "\n\n\n\n"); /* Separates things in *Redisplay-trace* */
|
||||
Lisp_Object delayed_warning;
|
||||
max_ensure_room (&max_specpdl_size, counti, 200);
|
||||
redisplay_trace_buffer = Fget_buffer_create (redisplay_trace, Qnil);
|
||||
current_buffer = XBUFFER (redisplay_trace_buffer);
|
||||
if (!backtrace_yet) /* Are we on the first backtrace of the command? */
|
||||
Ferase_buffer ();
|
||||
else
|
||||
Finsert (1, &gap);
|
||||
backtrace_yet = true;
|
||||
specbind (Qstandard_output, redisplay_trace_buffer);
|
||||
specbind (Qdebugger, Qdebug_early);
|
||||
call_debugger (list2 (Qerror, Fcons (error_symbol, data)));
|
||||
unbind_to (count, Qnil);
|
||||
delayed_warning = make_string
|
||||
("Error in a redisplay Lisp hook. See buffer *Redisplay_trace*", 61);
|
||||
|
||||
Vdelayed_warnings_list = Fcons (list2 (Qerror, delayed_warning),
|
||||
Vdelayed_warnings_list);
|
||||
}
|
||||
|
||||
if (!NILP (clause))
|
||||
{
|
||||
Lisp_Object unwind_data
|
||||
|
@ -4278,6 +4330,11 @@ Does not apply if quit is handled by a `condition-case'. */);
|
|||
DEFVAR_BOOL ("debug-on-next-call", debug_on_next_call,
|
||||
doc: /* Non-nil means enter debugger before next `eval', `apply' or `funcall'. */);
|
||||
|
||||
DEFVAR_BOOL ("backtrace-on-redisplay-error", backtrace_on_redisplay_error,
|
||||
doc: /* Non-nil means create a backtrace if a lisp error occurs in redisplay.
|
||||
The backtrace is written to buffer *Redisplay-trace*. */);
|
||||
backtrace_on_redisplay_error = false;
|
||||
|
||||
DEFVAR_BOOL ("debugger-may-continue", debugger_may_continue,
|
||||
doc: /* Non-nil means debugger may continue execution.
|
||||
This is nil when the debugger is called under circumstances where it
|
||||
|
|
|
@ -1331,6 +1331,7 @@ command_loop_1 (void)
|
|||
display_malloc_warning ();
|
||||
|
||||
Vdeactivate_mark = Qnil;
|
||||
backtrace_yet = false;
|
||||
|
||||
/* Don't ignore mouse movements for more than a single command
|
||||
loop. (This flag is set in xdisp.c whenever the tool bar is
|
||||
|
@ -1841,7 +1842,7 @@ safe_run_hooks_1 (ptrdiff_t nargs, Lisp_Object *args)
|
|||
static Lisp_Object
|
||||
safe_run_hooks_error (Lisp_Object error, ptrdiff_t nargs, Lisp_Object *args)
|
||||
{
|
||||
eassert (nargs == 2);
|
||||
eassert (nargs >= 2 && nargs <= 4);
|
||||
AUTO_STRING (format, "Error in %s (%S): %S");
|
||||
Lisp_Object hook = args[0];
|
||||
Lisp_Object fun = args[1];
|
||||
|
@ -1915,6 +1916,17 @@ safe_run_hooks_maybe_narrowed (Lisp_Object hook, struct window *w)
|
|||
unbind_to (count, Qnil);
|
||||
}
|
||||
|
||||
void
|
||||
safe_run_hooks_2 (Lisp_Object hook, Lisp_Object arg1, Lisp_Object arg2)
|
||||
{
|
||||
specpdl_ref count = SPECPDL_INDEX ();
|
||||
|
||||
specbind (Qinhibit_quit, Qt);
|
||||
run_hook_with_args (4, ((Lisp_Object []) {hook, hook, arg1, arg2}),
|
||||
safe_run_hook_funcall);
|
||||
unbind_to (count, Qnil);
|
||||
}
|
||||
|
||||
|
||||
/* Nonzero means polling for input is temporarily suppressed. */
|
||||
|
||||
|
|
|
@ -4530,6 +4530,7 @@ extern Lisp_Object Vrun_hooks;
|
|||
extern Lisp_Object Vsignaling_function;
|
||||
extern Lisp_Object inhibit_lisp_code;
|
||||
extern bool signal_quit_p (Lisp_Object);
|
||||
extern bool backtrace_yet;
|
||||
|
||||
/* To run a normal hook, use the appropriate function from the list below.
|
||||
The calling convention:
|
||||
|
@ -4831,6 +4832,7 @@ extern bool detect_input_pending_ignore_squeezables (void);
|
|||
extern bool detect_input_pending_run_timers (bool);
|
||||
extern void safe_run_hooks (Lisp_Object);
|
||||
extern void safe_run_hooks_maybe_narrowed (Lisp_Object, struct window *);
|
||||
extern void safe_run_hooks_2 (Lisp_Object, Lisp_Object, Lisp_Object);
|
||||
extern void cmd_error_internal (Lisp_Object, const char *);
|
||||
extern Lisp_Object command_loop_2 (Lisp_Object);
|
||||
extern Lisp_Object read_menu_command (void);
|
||||
|
|
|
@ -18133,8 +18133,8 @@ run_window_scroll_functions (Lisp_Object window, struct text_pos startp)
|
|||
{
|
||||
specpdl_ref count = SPECPDL_INDEX ();
|
||||
specbind (Qinhibit_quit, Qt);
|
||||
run_hook_with_args_2 (Qwindow_scroll_functions, window,
|
||||
make_fixnum (CHARPOS (startp)));
|
||||
safe_run_hooks_2
|
||||
(Qwindow_scroll_functions, window, make_fixnum (CHARPOS (startp)));
|
||||
unbind_to (count, Qnil);
|
||||
SET_TEXT_POS_FROM_MARKER (startp, w->start);
|
||||
/* In case the hook functions switch buffers. */
|
||||
|
|
Loading…
Add table
Reference in a new issue