diff options
| author | Alan Mackenzie | 2022-08-11 19:31:09 +0000 |
|---|---|---|
| committer | Alan Mackenzie | 2022-08-11 19:31:09 +0000 |
| commit | 48215c41d16fadb69e85121b3baca0dfca82cc44 (patch) | |
| tree | 1c410ce8b1b63d20cc02810078acb337af29a2b1 | |
| parent | e7f1d4f6e106576f3d8de4074290dc4e8c7c544f (diff) | |
| download | emacs-48215c41d16fadb69e85121b3baca0dfca82cc44.tar.gz emacs-48215c41d16fadb69e85121b3baca0dfca82cc44.zip | |
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.
| -rw-r--r-- | doc/lispref/debugging.texi | 44 | ||||
| -rw-r--r-- | etc/NEWS | 7 | ||||
| -rw-r--r-- | src/eval.c | 59 | ||||
| -rw-r--r-- | src/keyboard.c | 14 | ||||
| -rw-r--r-- | src/lisp.h | 2 | ||||
| -rw-r--r-- | src/xdisp.c | 4 |
6 files changed, 126 insertions, 4 deletions
diff --git a/doc/lispref/debugging.texi b/doc/lispref/debugging.texi index 058c9319544..9ae40949d1e 100644 --- a/doc/lispref/debugging.texi +++ b/doc/lispref/debugging.texi | |||
| @@ -77,6 +77,7 @@ debugger recursively. @xref{Recursive Editing}. | |||
| 77 | 77 | ||
| 78 | @menu | 78 | @menu |
| 79 | * Error Debugging:: Entering the debugger when an error happens. | 79 | * Error Debugging:: Entering the debugger when an error happens. |
| 80 | * Debugging Redisplay:: Getting backtraces from redisplay errors. | ||
| 80 | * Infinite Loops:: Stopping and debugging a program that doesn't exit. | 81 | * Infinite Loops:: Stopping and debugging a program that doesn't exit. |
| 81 | * Function Debugging:: Entering it when a certain function is called. | 82 | * Function Debugging:: Entering it when a certain function is called. |
| 82 | * Variable Debugging:: Entering it when a variable is modified. | 83 | * 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}. | |||
| 105 | (The command @code{toggle-debug-on-error} provides an easy way to do | 106 | (The command @code{toggle-debug-on-error} provides an easy way to do |
| 106 | this.) | 107 | this.) |
| 107 | 108 | ||
| 109 | Note that, for technical reasons, you cannot use the facilities | ||
| 110 | defined in this subsection to debug errors in Lisp that the redisplay | ||
| 111 | code has invoked. @xref{Debugging Redisplay}, for help with these. | ||
| 112 | |||
| 108 | @defopt debug-on-error | 113 | @defopt debug-on-error |
| 109 | This variable determines whether the debugger is called when an error | 114 | This variable determines whether the debugger is called when an error |
| 110 | is signaled and not handled. If @code{debug-on-error} is @code{t}, | 115 | 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 | |||
| 213 | bypasses the @code{condition-case} which normally catches errors in the | 218 | bypasses the @code{condition-case} which normally catches errors in the |
| 214 | init file. | 219 | init file. |
| 215 | 220 | ||
| 221 | @node Debugging Redisplay | ||
| 222 | @subsection Debugging Redisplay Errors | ||
| 223 | @cindex redisplay errors | ||
| 224 | @cindex debugging redisplay errors | ||
| 225 | |||
| 226 | When an error occurs in Lisp code which redisplay has invoked, Emacs's | ||
| 227 | usual debugging mechanisms are unusable, for technical reasons. This | ||
| 228 | subsection describes how to get a backtrace from such an error, which | ||
| 229 | should be helpful in debugging it. | ||
| 230 | |||
| 231 | These directions apply to Lisp forms used, for example, in | ||
| 232 | @code{:eval} mode line constructs (@pxref{Mode Line Data}), and in all | ||
| 233 | hooks invoked from redisplay, such as: | ||
| 234 | |||
| 235 | @itemize | ||
| 236 | @item | ||
| 237 | @code{fontification-functions} (@pxref{Auto Faces}). | ||
| 238 | @item | ||
| 239 | @code{window-scroll-functions} (@pxref{Window Hooks}). | ||
| 240 | @end itemize | ||
| 241 | |||
| 242 | Note that if you have had an error in a hook function called from | ||
| 243 | redisplay, the error handling might have removed this function from | ||
| 244 | the hook. You will thus need to reinitialize that hook somehow, | ||
| 245 | perhaps with @code{add-hook}, to be able to replay the bug. | ||
| 246 | |||
| 247 | To generate a backtrace in these circumstances, set the variable | ||
| 248 | @code{backtrace-on-redisplay-error} to non-@code{nil}. When the error | ||
| 249 | occurs, Emacs will dump the backtrace to the buffer | ||
| 250 | @file{*Redisplay-trace*}, but won't automatically display it in a | ||
| 251 | window. This is to avoid needlessly corrupting the redisplay you are | ||
| 252 | debugging. You will thus need to display the buffer yourself, with a | ||
| 253 | command such as @code{switch-to-buffer-other-frame} @key{C-x 5 b}. | ||
| 254 | |||
| 255 | @defvar backtrace-on-redisplay-error | ||
| 256 | Set this variable to non-@code{nil} to enable the generation of a | ||
| 257 | backtrace when an error occurs in any Lisp called from redisplay. | ||
| 258 | @end defvar | ||
| 259 | |||
| 216 | @node Infinite Loops | 260 | @node Infinite Loops |
| 217 | @subsection Debugging Infinite Loops | 261 | @subsection Debugging Infinite Loops |
| 218 | @cindex infinite loops | 262 | @cindex infinite loops |
| @@ -1339,6 +1339,13 @@ When invoked with a non-zero prefix argument, as in 'C-u C-x C-e', | |||
| 1339 | this command will pop up a new buffer and show the full pretty-printed | 1339 | this command will pop up a new buffer and show the full pretty-printed |
| 1340 | value there. | 1340 | value there. |
| 1341 | 1341 | ||
| 1342 | +++ | ||
| 1343 | *** You can now generate a backtrace from Lisp errors in redisplay. | ||
| 1344 | To do this, set the new variable 'backtrace-on-redisplay-error' to a | ||
| 1345 | non-nil value. The backtrace will be written to buffer | ||
| 1346 | *Redisplay-trace*. This buffer will not be automatically displayed in | ||
| 1347 | a window. | ||
| 1348 | |||
| 1342 | ** Compile | 1349 | ** Compile |
| 1343 | 1350 | ||
| 1344 | +++ | 1351 | +++ |
diff --git a/src/eval.c b/src/eval.c index d82d05797b2..56b42966623 100644 --- a/src/eval.c +++ b/src/eval.c | |||
| @@ -57,6 +57,12 @@ Lisp_Object Vrun_hooks; | |||
| 57 | /* FIXME: We should probably get rid of this! */ | 57 | /* FIXME: We should probably get rid of this! */ |
| 58 | Lisp_Object Vsignaling_function; | 58 | Lisp_Object Vsignaling_function; |
| 59 | 59 | ||
| 60 | /* The handler structure which will catch errors in Lisp hooks called | ||
| 61 | from redisplay. We do not use it for this; we compare it with the | ||
| 62 | handler which is about to be used in signal_or_quit, and if it | ||
| 63 | matches, cause a backtrace to be generated. */ | ||
| 64 | static struct handler *redisplay_deep_handler; | ||
| 65 | |||
| 60 | /* These would ordinarily be static, but they need to be visible to GDB. */ | 66 | /* These would ordinarily be static, but they need to be visible to GDB. */ |
| 61 | bool backtrace_p (union specbinding *) EXTERNALLY_VISIBLE; | 67 | bool backtrace_p (union specbinding *) EXTERNALLY_VISIBLE; |
| 62 | Lisp_Object *backtrace_args (union specbinding *) EXTERNALLY_VISIBLE; | 68 | Lisp_Object *backtrace_args (union specbinding *) EXTERNALLY_VISIBLE; |
| @@ -246,6 +252,7 @@ init_eval (void) | |||
| 246 | lisp_eval_depth = 0; | 252 | lisp_eval_depth = 0; |
| 247 | /* This is less than the initial value of num_nonmacro_input_events. */ | 253 | /* This is less than the initial value of num_nonmacro_input_events. */ |
| 248 | when_entered_debugger = -1; | 254 | when_entered_debugger = -1; |
| 255 | redisplay_deep_handler = NULL; | ||
| 249 | } | 256 | } |
| 250 | 257 | ||
| 251 | /* Ensure that *M is at least A + B if possible, or is its maximum | 258 | /* Ensure that *M is at least A + B if possible, or is its maximum |
| @@ -333,7 +340,8 @@ call_debugger (Lisp_Object arg) | |||
| 333 | /* Interrupting redisplay and resuming it later is not safe under | 340 | /* Interrupting redisplay and resuming it later is not safe under |
| 334 | all circumstances. So, when the debugger returns, abort the | 341 | all circumstances. So, when the debugger returns, abort the |
| 335 | interrupted redisplay by going back to the top-level. */ | 342 | interrupted redisplay by going back to the top-level. */ |
| 336 | if (debug_while_redisplaying) | 343 | if (debug_while_redisplaying |
| 344 | && !EQ (Vdebugger, Qdebug_early)) | ||
| 337 | Ftop_level (); | 345 | Ftop_level (); |
| 338 | 346 | ||
| 339 | return unbind_to (count, val); | 347 | return unbind_to (count, val); |
| @@ -1556,12 +1564,16 @@ internal_condition_case_n (Lisp_Object (*bfun) (ptrdiff_t, Lisp_Object *), | |||
| 1556 | ptrdiff_t nargs, | 1564 | ptrdiff_t nargs, |
| 1557 | Lisp_Object *args)) | 1565 | Lisp_Object *args)) |
| 1558 | { | 1566 | { |
| 1567 | struct handler *old_deep = redisplay_deep_handler; | ||
| 1559 | struct handler *c = push_handler (handlers, CONDITION_CASE); | 1568 | struct handler *c = push_handler (handlers, CONDITION_CASE); |
| 1569 | if (redisplaying_p) | ||
| 1570 | redisplay_deep_handler = c; | ||
| 1560 | if (sys_setjmp (c->jmp)) | 1571 | if (sys_setjmp (c->jmp)) |
| 1561 | { | 1572 | { |
| 1562 | Lisp_Object val = handlerlist->val; | 1573 | Lisp_Object val = handlerlist->val; |
| 1563 | clobbered_eassert (handlerlist == c); | 1574 | clobbered_eassert (handlerlist == c); |
| 1564 | handlerlist = handlerlist->next; | 1575 | handlerlist = handlerlist->next; |
| 1576 | redisplay_deep_handler = old_deep; | ||
| 1565 | return hfun (val, nargs, args); | 1577 | return hfun (val, nargs, args); |
| 1566 | } | 1578 | } |
| 1567 | else | 1579 | else |
| @@ -1569,6 +1581,7 @@ internal_condition_case_n (Lisp_Object (*bfun) (ptrdiff_t, Lisp_Object *), | |||
| 1569 | Lisp_Object val = bfun (nargs, args); | 1581 | Lisp_Object val = bfun (nargs, args); |
| 1570 | eassert (handlerlist == c); | 1582 | eassert (handlerlist == c); |
| 1571 | handlerlist = c->next; | 1583 | handlerlist = c->next; |
| 1584 | redisplay_deep_handler = old_deep; | ||
| 1572 | return val; | 1585 | return val; |
| 1573 | } | 1586 | } |
| 1574 | } | 1587 | } |
| @@ -1701,6 +1714,11 @@ quit (void) | |||
| 1701 | return signal_or_quit (Qquit, Qnil, true); | 1714 | return signal_or_quit (Qquit, Qnil, true); |
| 1702 | } | 1715 | } |
| 1703 | 1716 | ||
| 1717 | /* Has an error in redisplay giving rise to a backtrace occurred as | ||
| 1718 | yet in the current command? This gets reset in the command | ||
| 1719 | loop. */ | ||
| 1720 | bool backtrace_yet = false; | ||
| 1721 | |||
| 1704 | /* Signal an error, or quit. ERROR_SYMBOL and DATA are as with Fsignal. | 1722 | /* Signal an error, or quit. ERROR_SYMBOL and DATA are as with Fsignal. |
| 1705 | If KEYBOARD_QUIT, this is a quit; ERROR_SYMBOL should be | 1723 | If KEYBOARD_QUIT, this is a quit; ERROR_SYMBOL should be |
| 1706 | Qquit and DATA should be Qnil, and this function may return. | 1724 | 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) | |||
| 1816 | unbind_to (count, Qnil); | 1834 | unbind_to (count, Qnil); |
| 1817 | } | 1835 | } |
| 1818 | 1836 | ||
| 1837 | /* If an error is signalled during a Lisp hook in redisplay, write a | ||
| 1838 | backtrace into the buffer *Redisplay-trace*. */ | ||
| 1839 | if (!debugger_called && !NILP (error_symbol) | ||
| 1840 | && backtrace_on_redisplay_error | ||
| 1841 | && (NILP (clause) || h == redisplay_deep_handler) | ||
| 1842 | && NILP (Vinhibit_debugger) | ||
| 1843 | && !NILP (Ffboundp (Qdebug_early))) | ||
| 1844 | { | ||
| 1845 | max_ensure_room (&max_lisp_eval_depth, lisp_eval_depth, 100); | ||
| 1846 | specpdl_ref count = SPECPDL_INDEX (); | ||
| 1847 | ptrdiff_t counti = specpdl_ref_to_count (count); | ||
| 1848 | AUTO_STRING (redisplay_trace, "*Redisplay_trace*"); | ||
| 1849 | Lisp_Object redisplay_trace_buffer; | ||
| 1850 | AUTO_STRING (gap, "\n\n\n\n"); /* Separates things in *Redisplay-trace* */ | ||
| 1851 | Lisp_Object delayed_warning; | ||
| 1852 | max_ensure_room (&max_specpdl_size, counti, 200); | ||
| 1853 | redisplay_trace_buffer = Fget_buffer_create (redisplay_trace, Qnil); | ||
| 1854 | current_buffer = XBUFFER (redisplay_trace_buffer); | ||
| 1855 | if (!backtrace_yet) /* Are we on the first backtrace of the command? */ | ||
| 1856 | Ferase_buffer (); | ||
| 1857 | else | ||
| 1858 | Finsert (1, &gap); | ||
| 1859 | backtrace_yet = true; | ||
| 1860 | specbind (Qstandard_output, redisplay_trace_buffer); | ||
| 1861 | specbind (Qdebugger, Qdebug_early); | ||
| 1862 | call_debugger (list2 (Qerror, Fcons (error_symbol, data))); | ||
| 1863 | unbind_to (count, Qnil); | ||
| 1864 | delayed_warning = make_string | ||
| 1865 | ("Error in a redisplay Lisp hook. See buffer *Redisplay_trace*", 61); | ||
| 1866 | |||
| 1867 | Vdelayed_warnings_list = Fcons (list2 (Qerror, delayed_warning), | ||
| 1868 | Vdelayed_warnings_list); | ||
| 1869 | } | ||
| 1870 | |||
| 1819 | if (!NILP (clause)) | 1871 | if (!NILP (clause)) |
| 1820 | { | 1872 | { |
| 1821 | Lisp_Object unwind_data | 1873 | Lisp_Object unwind_data |
| @@ -4278,6 +4330,11 @@ Does not apply if quit is handled by a `condition-case'. */); | |||
| 4278 | DEFVAR_BOOL ("debug-on-next-call", debug_on_next_call, | 4330 | DEFVAR_BOOL ("debug-on-next-call", debug_on_next_call, |
| 4279 | doc: /* Non-nil means enter debugger before next `eval', `apply' or `funcall'. */); | 4331 | doc: /* Non-nil means enter debugger before next `eval', `apply' or `funcall'. */); |
| 4280 | 4332 | ||
| 4333 | DEFVAR_BOOL ("backtrace-on-redisplay-error", backtrace_on_redisplay_error, | ||
| 4334 | doc: /* Non-nil means create a backtrace if a lisp error occurs in redisplay. | ||
| 4335 | The backtrace is written to buffer *Redisplay-trace*. */); | ||
| 4336 | backtrace_on_redisplay_error = false; | ||
| 4337 | |||
| 4281 | DEFVAR_BOOL ("debugger-may-continue", debugger_may_continue, | 4338 | DEFVAR_BOOL ("debugger-may-continue", debugger_may_continue, |
| 4282 | doc: /* Non-nil means debugger may continue execution. | 4339 | doc: /* Non-nil means debugger may continue execution. |
| 4283 | This is nil when the debugger is called under circumstances where it | 4340 | This is nil when the debugger is called under circumstances where it |
diff --git a/src/keyboard.c b/src/keyboard.c index 4ad6e4e6bd1..719226caedc 100644 --- a/src/keyboard.c +++ b/src/keyboard.c | |||
| @@ -1331,6 +1331,7 @@ command_loop_1 (void) | |||
| 1331 | display_malloc_warning (); | 1331 | display_malloc_warning (); |
| 1332 | 1332 | ||
| 1333 | Vdeactivate_mark = Qnil; | 1333 | Vdeactivate_mark = Qnil; |
| 1334 | backtrace_yet = false; | ||
| 1334 | 1335 | ||
| 1335 | /* Don't ignore mouse movements for more than a single command | 1336 | /* Don't ignore mouse movements for more than a single command |
| 1336 | loop. (This flag is set in xdisp.c whenever the tool bar is | 1337 | 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) | |||
| 1841 | static Lisp_Object | 1842 | static Lisp_Object |
| 1842 | safe_run_hooks_error (Lisp_Object error, ptrdiff_t nargs, Lisp_Object *args) | 1843 | safe_run_hooks_error (Lisp_Object error, ptrdiff_t nargs, Lisp_Object *args) |
| 1843 | { | 1844 | { |
| 1844 | eassert (nargs == 2); | 1845 | eassert (nargs >= 2 && nargs <= 4); |
| 1845 | AUTO_STRING (format, "Error in %s (%S): %S"); | 1846 | AUTO_STRING (format, "Error in %s (%S): %S"); |
| 1846 | Lisp_Object hook = args[0]; | 1847 | Lisp_Object hook = args[0]; |
| 1847 | Lisp_Object fun = args[1]; | 1848 | Lisp_Object fun = args[1]; |
| @@ -1915,6 +1916,17 @@ safe_run_hooks_maybe_narrowed (Lisp_Object hook, struct window *w) | |||
| 1915 | unbind_to (count, Qnil); | 1916 | unbind_to (count, Qnil); |
| 1916 | } | 1917 | } |
| 1917 | 1918 | ||
| 1919 | void | ||
| 1920 | safe_run_hooks_2 (Lisp_Object hook, Lisp_Object arg1, Lisp_Object arg2) | ||
| 1921 | { | ||
| 1922 | specpdl_ref count = SPECPDL_INDEX (); | ||
| 1923 | |||
| 1924 | specbind (Qinhibit_quit, Qt); | ||
| 1925 | run_hook_with_args (4, ((Lisp_Object []) {hook, hook, arg1, arg2}), | ||
| 1926 | safe_run_hook_funcall); | ||
| 1927 | unbind_to (count, Qnil); | ||
| 1928 | } | ||
| 1929 | |||
| 1918 | 1930 | ||
| 1919 | /* Nonzero means polling for input is temporarily suppressed. */ | 1931 | /* Nonzero means polling for input is temporarily suppressed. */ |
| 1920 | 1932 | ||
diff --git a/src/lisp.h b/src/lisp.h index fe6e98843d1..2f73ba4c617 100644 --- a/src/lisp.h +++ b/src/lisp.h | |||
| @@ -4530,6 +4530,7 @@ extern Lisp_Object Vrun_hooks; | |||
| 4530 | extern Lisp_Object Vsignaling_function; | 4530 | extern Lisp_Object Vsignaling_function; |
| 4531 | extern Lisp_Object inhibit_lisp_code; | 4531 | extern Lisp_Object inhibit_lisp_code; |
| 4532 | extern bool signal_quit_p (Lisp_Object); | 4532 | extern bool signal_quit_p (Lisp_Object); |
| 4533 | extern bool backtrace_yet; | ||
| 4533 | 4534 | ||
| 4534 | /* To run a normal hook, use the appropriate function from the list below. | 4535 | /* To run a normal hook, use the appropriate function from the list below. |
| 4535 | The calling convention: | 4536 | The calling convention: |
| @@ -4831,6 +4832,7 @@ extern bool detect_input_pending_ignore_squeezables (void); | |||
| 4831 | extern bool detect_input_pending_run_timers (bool); | 4832 | extern bool detect_input_pending_run_timers (bool); |
| 4832 | extern void safe_run_hooks (Lisp_Object); | 4833 | extern void safe_run_hooks (Lisp_Object); |
| 4833 | extern void safe_run_hooks_maybe_narrowed (Lisp_Object, struct window *); | 4834 | extern void safe_run_hooks_maybe_narrowed (Lisp_Object, struct window *); |
| 4835 | extern void safe_run_hooks_2 (Lisp_Object, Lisp_Object, Lisp_Object); | ||
| 4834 | extern void cmd_error_internal (Lisp_Object, const char *); | 4836 | extern void cmd_error_internal (Lisp_Object, const char *); |
| 4835 | extern Lisp_Object command_loop_2 (Lisp_Object); | 4837 | extern Lisp_Object command_loop_2 (Lisp_Object); |
| 4836 | extern Lisp_Object read_menu_command (void); | 4838 | extern Lisp_Object read_menu_command (void); |
diff --git a/src/xdisp.c b/src/xdisp.c index 855f48f2bde..5268c359ecd 100644 --- a/src/xdisp.c +++ b/src/xdisp.c | |||
| @@ -18133,8 +18133,8 @@ run_window_scroll_functions (Lisp_Object window, struct text_pos startp) | |||
| 18133 | { | 18133 | { |
| 18134 | specpdl_ref count = SPECPDL_INDEX (); | 18134 | specpdl_ref count = SPECPDL_INDEX (); |
| 18135 | specbind (Qinhibit_quit, Qt); | 18135 | specbind (Qinhibit_quit, Qt); |
| 18136 | run_hook_with_args_2 (Qwindow_scroll_functions, window, | 18136 | safe_run_hooks_2 |
| 18137 | make_fixnum (CHARPOS (startp))); | 18137 | (Qwindow_scroll_functions, window, make_fixnum (CHARPOS (startp))); |
| 18138 | unbind_to (count, Qnil); | 18138 | unbind_to (count, Qnil); |
| 18139 | SET_TEXT_POS_FROM_MARKER (startp, w->start); | 18139 | SET_TEXT_POS_FROM_MARKER (startp, w->start); |
| 18140 | /* In case the hook functions switch buffers. */ | 18140 | /* In case the hook functions switch buffers. */ |