unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* Why is it so difficult to get a Lisp backtrace?
@ 2022-06-25 13:42 Alan Mackenzie
  2022-06-25 15:26 ` Alan Mackenzie
  2022-06-26  8:45 ` Why is it so difficult to get a Lisp backtrace? Stefan Monnier
  0 siblings, 2 replies; 54+ messages in thread
From: Alan Mackenzie @ 2022-06-25 13:42 UTC (permalink / raw)
  To: emacs-devel

Hello, Emacs.

(setq debug-on-error t) ought to be useful to get backtraces.  It is,
sometimes, but far from always.

There seem to be three use cases for debug-on-error:
(i) The vast bulk of users, who are barely, if at all, aware of its
  existence.  They will enable it on request from a maintainer to help
  debug a bug.
(ii) Hard-core maintainers, who run with it enabled constantly.  Here
  debug-ignored-errors helps suppress certain unhelpful errors which
  would otherwise occur too often and irritate.
(iii) Those maintainers who run with it disabled, but when an error
  occurs, want to be able to repeat that error and get a backtrace.

This third group of users is poorly catered for by the current collection
of mechanisms.  See also bug #56201, with thanks to Andreas and Lars who
helped me get to the bottom of it.  To be reasonably sure of getting a
backtrace, it seems one needs to do all of the following:
(i) (setq debug-on-error t).
(ii) (setq debug-on-signal t).
(iii) Bind debug-ignored-errors to nil.
(iv) Pray.
(v) Execute the erring command again.

I put it to people that we need a new command or variable not called
something like `backtrace-next-and-I-really-mean-it' which would allow
step (v) to generate the desired backtrace without having to go through
steps (i) to (iv) individually.  The lack of such a command/variable is
making (at least) my Emacs development less pleasant than it might
otherwise be.

-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Why is it so difficult to get a Lisp backtrace?
  2022-06-25 13:42 Why is it so difficult to get a Lisp backtrace? Alan Mackenzie
@ 2022-06-25 15:26 ` Alan Mackenzie
  2022-06-25 15:30   ` Stefan Kangas
  2022-06-25 15:35   ` Lars Ingebrigtsen
  2022-06-26  8:45 ` Why is it so difficult to get a Lisp backtrace? Stefan Monnier
  1 sibling, 2 replies; 54+ messages in thread
From: Alan Mackenzie @ 2022-06-25 15:26 UTC (permalink / raw)
  To: emacs-devel

Hello, again.

On Sat, Jun 25, 2022 at 13:42:04 +0000, Alan Mackenzie wrote:
> (setq debug-on-error t) ought to be useful to get backtraces.  It is,
> sometimes, but far from always.

> There seem to be three use cases for debug-on-error:
> (i) The vast bulk of users, who are barely, if at all, aware of its
>   existence.  They will enable it on request from a maintainer to help
>   debug a bug.
> (ii) Hard-core maintainers, who run with it enabled constantly.  Here
>   debug-ignored-errors helps suppress certain unhelpful errors which
>   would otherwise occur too often and irritate.
> (iii) Those maintainers who run with it disabled, but when an error
>   occurs, want to be able to repeat that error and get a backtrace.

> This third group of users is poorly catered for by the current collection
> of mechanisms.  See also bug #56201, with thanks to Andreas and Lars who
> helped me get to the bottom of it.  To be reasonably sure of getting a
> backtrace, it seems one needs to do all of the following:
> (i) (setq debug-on-error t).
> (ii) (setq debug-on-signal t).
> (iii) Bind debug-ignored-errors to nil.
> (iv) Pray.
> (v) Execute the erring command again.

> I put it to people that we need a new command or variable not called
> something like `backtrace-next-and-I-really-mean-it' which would allow
> step (v) to generate the desired backtrace without having to go through
> steps (i) to (iv) individually.  The lack of such a command/variable is
> making (at least) my Emacs development less pleasant than it might
> otherwise be.

I mean something like the following, which to a first approximation,
works.  To use it, do M-x debug-next-command and the execute the command
expected to give errors:


;; -*- lexical-binding: t -*-

(defvar debug-next-command-save-debug-on-error nil)
(defvar debug-next-command-save-debug-on-signal nil)
(defvar debug-next-command-save-debug-on-quit nil)
(defvar debug-next-command-save-debug-ignored-errors nil)

(defvar debug-next-command-stay-on-post-command nil)

(defun debug-next-command-end ()
  "Undo the effects of debug-next-command."
  (if debug-next-command-stay-on-post-command
      (setq debug-next-command-stay-on-post-command nil)
    (setq debug-on-error debug-next-command-save-debug-on-error
	  debug-on-signal debug-next-command-save-debug-on-signal
	  debug-on-quit debug-next-command-save-debug-on-quit
	  debug-ignored-errors debug-next-command-save-debug-ignored-errors)
    (remove-hook 'post-command-hook #'debug-next-command-end)))

(defun debug-next-command ()
  "Produce a bactrace in the next command should an error occur."
  (interactive)
  (setq debug-next-command-save-debug-on-error debug-on-error
	debug-next-command-save-debug-on-signal debug-on-signal
	debug-next-command-save-debug-on-quit debug-on-quit
	debug-next-command-save-debug-ignored-errors debug-ignored-errors)
  (setq debug-on-error t
	debug-on-signal t
	debug-on-quit t
	debug-ignored-errors nil)
  (setq debug-next-command-stay-on-post-command t)
  (add-hook 'post-command-hook #'debug-next-command-end))

-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Why is it so difficult to get a Lisp backtrace?
  2022-06-25 15:26 ` Alan Mackenzie
@ 2022-06-25 15:30   ` Stefan Kangas
  2022-06-25 15:52     ` Alan Mackenzie
  2022-06-25 15:35   ` Lars Ingebrigtsen
  1 sibling, 1 reply; 54+ messages in thread
From: Stefan Kangas @ 2022-06-25 15:30 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: Emacs developers

Alan Mackenzie <acm@muc.de> writes:

> I mean something like the following, which to a first approximation,
> works.  To use it, do M-x debug-next-command and the execute the command
> expected to give errors:

Good idea, but how about just making it into a manual toggle?  I might
need to run more than one command to reproduce a bug.



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Why is it so difficult to get a Lisp backtrace?
  2022-06-25 15:26 ` Alan Mackenzie
  2022-06-25 15:30   ` Stefan Kangas
@ 2022-06-25 15:35   ` Lars Ingebrigtsen
  2022-06-25 15:59     ` Alan Mackenzie
  1 sibling, 1 reply; 54+ messages in thread
From: Lars Ingebrigtsen @ 2022-06-25 15:35 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: emacs-devel

Alan Mackenzie <acm@muc.de> writes:

>> This third group of users is poorly catered for by the current collection
>> of mechanisms.  See also bug #56201, with thanks to Andreas and Lars who
>> helped me get to the bottom of it.  To be reasonably sure of getting a
>> backtrace, it seems one needs to do all of the following:
>> (i) (setq debug-on-error t).
>> (ii) (setq debug-on-signal t).
>> (iii) Bind debug-ignored-errors to nil.
>> (iv) Pray.

You probably also need to ensure that inhibit-redisplay is nil at the
point of the error, I think?  (At least I vaguely recall having to
remove some of those bindings to get a backtrace...)

> I mean something like the following, which to a first approximation,
> works.  To use it, do M-x debug-next-command and the execute the command
> expected to give errors:

I like it, but it may not be useful in all contexts -- errors are used
as a programming mechanism here and there (i.e., throwing an error
symbol to be caught by the caller), and you'll get backtraces from these
non-error errors, too, unfortunately.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Why is it so difficult to get a Lisp backtrace?
  2022-06-25 15:30   ` Stefan Kangas
@ 2022-06-25 15:52     ` Alan Mackenzie
  2022-06-25 16:27       ` Stefan Kangas
  0 siblings, 1 reply; 54+ messages in thread
From: Alan Mackenzie @ 2022-06-25 15:52 UTC (permalink / raw)
  To: Stefan Kangas; +Cc: Emacs developers

Hello, Stefan.

On Sat, Jun 25, 2022 at 17:30:30 +0200, Stefan Kangas wrote:
> Alan Mackenzie <acm@muc.de> writes:

> > I mean something like the following, which to a first approximation,
> > works.  To use it, do M-x debug-next-command and the execute the
> > command expected to give errors:

> Good idea, but how about just making it into a manual toggle?  I might
> need to run more than one command to reproduce a bug.

That's another idea.  It has snags, like for example, you set the toggle,
use it, and forget to cancel it.  You then amend one of the controlling
variables (like debug-on-quit) manually, then cancel the toggle, at which
point confusion and irritation happen.

One idea I had was to have an optional numerical argument, saying how
many commands to leave the thing in place for.  Yet another, conflicting,
idea would be to have an optional boolean argument to exclude
debug-on-signal from the mix, so as not to get too many "expected"
backtraces.

-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Why is it so difficult to get a Lisp backtrace?
  2022-06-25 15:35   ` Lars Ingebrigtsen
@ 2022-06-25 15:59     ` Alan Mackenzie
  2022-06-25 16:51       ` Eli Zaretskii
  0 siblings, 1 reply; 54+ messages in thread
From: Alan Mackenzie @ 2022-06-25 15:59 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: emacs-devel

Hello, Lars.

On Sat, Jun 25, 2022 at 17:35:02 +0200, Lars Ingebrigtsen wrote:
> Alan Mackenzie <acm@muc.de> writes:

> >> This third group of users is poorly catered for by the current
> >> collection of mechanisms.  See also bug #56201, with thanks to
> >> Andreas and Lars who helped me get to the bottom of it.  To be
> >> reasonably sure of getting a backtrace, it seems one needs to do
> >> all of the following:
> >> (i) (setq debug-on-error t).
> >> (ii) (setq debug-on-signal t).
> >> (iii) Bind debug-ignored-errors to nil.
> >> (iv) Pray.

> You probably also need to ensure that inhibit-redisplay is nil at the
> point of the error, I think?  (At least I vaguely recall having to
> remove some of those bindings to get a backtrace...)

For example, by testing a flag in the backtrace function, and binding
inhibit-redisplay to nil if that flag is set.  This could get quite
complicate quite quickly.

Another thing I'd like to be able to do is get a backtrace when there's
an error in font-locking during redisplay.  That's a whole order of
magnitude difference in complexity.

> > I mean something like the following, which to a first approximation,
> > works.  To use it, do M-x debug-next-command and the execute the
> > command expected to give errors:

> I like it, but it may not be useful in all contexts -- errors are used
> as a programming mechanism here and there (i.e., throwing an error
> symbol to be caught by the caller), and you'll get backtraces from
> these non-error errors, too, unfortunately.

As I suggested in my reply to Stefan, maybe a C-u argument to the
function could be used to exclude (or include) debug-on-signal from the
mix.  Or something like that.

> -- 
> (domestic pets only, the antidote for overdose, milk.)
>    bloggy blog: http://lars.ingebrigtsen.no

-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Why is it so difficult to get a Lisp backtrace?
  2022-06-25 15:52     ` Alan Mackenzie
@ 2022-06-25 16:27       ` Stefan Kangas
  0 siblings, 0 replies; 54+ messages in thread
From: Stefan Kangas @ 2022-06-25 16:27 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: Emacs developers

Alan Mackenzie <acm@muc.de> writes:

> One idea I had was to have an optional numerical argument, saying how
> many commands to leave the thing in place for.  Yet another, conflicting,
> idea would be to have an optional boolean argument to exclude
> debug-on-signal from the mix, so as not to get too many "expected"
> backtraces.

In my personal use, I'd probably put this behind a timer so that it is
disabled again automatically after 30 seconds (or something).  I do
that for `menu-bar-mode', for example, since I also only ever enable
that temporarily for testing.  But that might be too hacky for general
use.



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Why is it so difficult to get a Lisp backtrace?
  2022-06-25 15:59     ` Alan Mackenzie
@ 2022-06-25 16:51       ` Eli Zaretskii
  2022-06-25 19:45         ` Alan Mackenzie
  0 siblings, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2022-06-25 16:51 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: larsi, emacs-devel

> Date: Sat, 25 Jun 2022 15:59:52 +0000
> Cc: emacs-devel@gnu.org
> From: Alan Mackenzie <acm@muc.de>
> 
> Another thing I'd like to be able to do is get a backtrace when there's
> an error in font-locking during redisplay.  That's a whole order of
> magnitude difference in complexity.

The only two ways of having that, AFAIK, are:

  . emit the backtrace int *Messages*
  . add the backtrace to delayed-warnings-list

You _cannot_ have a *backtrace* buffer popping up when the error comes
from some Lisp called by redisplay, because we explicitly disable
that, and for a good reason.



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Why is it so difficult to get a Lisp backtrace?
  2022-06-25 16:51       ` Eli Zaretskii
@ 2022-06-25 19:45         ` Alan Mackenzie
  2022-06-26  5:20           ` Eli Zaretskii
  0 siblings, 1 reply; 54+ messages in thread
From: Alan Mackenzie @ 2022-06-25 19:45 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: larsi, emacs-devel

Hello, Eli.

On Sat, Jun 25, 2022 at 19:51:59 +0300, Eli Zaretskii wrote:
> > Date: Sat, 25 Jun 2022 15:59:52 +0000
> > Cc: emacs-devel@gnu.org
> > From: Alan Mackenzie <acm@muc.de>

> > Another thing I'd like to be able to do is get a backtrace when
> > there's an error in font-locking during redisplay.  That's a whole
> > order of magnitude difference in complexity.

> The only two ways of having that, AFAIK, are:

>   . emit the backtrace int *Messages*
>   . add the backtrace to delayed-warnings-list

Why can't the backtrace be generated and stored, and only displayed at a
subsequent redisplay (probably the next one)?

> You _cannot_ have a *backtrace* buffer popping up when the error comes
> from some Lisp called by redisplay, because we explicitly disable
> that, and for a good reason.

It would increase the complexity of redisplay, making it fragile, for no
good reason, yes.

-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Why is it so difficult to get a Lisp backtrace?
  2022-06-25 19:45         ` Alan Mackenzie
@ 2022-06-26  5:20           ` Eli Zaretskii
  2022-06-27 19:48             ` Fontification error backtrace [Was: Why is it so difficult to get a Lisp backtrace?] Alan Mackenzie
  0 siblings, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2022-06-26  5:20 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: larsi, emacs-devel

> Date: Sat, 25 Jun 2022 19:45:49 +0000
> Cc: larsi@gnus.org, emacs-devel@gnu.org
> From: Alan Mackenzie <acm@muc.de>
> 
> > The only two ways of having that, AFAIK, are:
> 
> >   . emit the backtrace int *Messages*
> >   . add the backtrace to delayed-warnings-list
> 
> Why can't the backtrace be generated and stored, and only displayed at a
> subsequent redisplay (probably the next one)?

That's what the second alternative above already does.



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Why is it so difficult to get a Lisp backtrace?
  2022-06-25 13:42 Why is it so difficult to get a Lisp backtrace? Alan Mackenzie
  2022-06-25 15:26 ` Alan Mackenzie
@ 2022-06-26  8:45 ` Stefan Monnier
  1 sibling, 0 replies; 54+ messages in thread
From: Stefan Monnier @ 2022-06-26  8:45 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: emacs-devel

> This third group of users is poorly catered for by the current collection
> of mechanisms.  See also bug #56201, with thanks to Andreas and Lars who
> helped me get to the bottom of it.  To be reasonably sure of getting a
> backtrace, it seems one needs to do all of the following:
> (i) (setq debug-on-error t).
> (ii) (setq debug-on-signal t).
> (iii) Bind debug-ignored-errors to nil.
> (iv) Pray.
> (v) Execute the erring command again.

As discussed further in the thread another related problem is those
errors which happen in contexts where we can't popup the debugger.

I've been thinking recently that every time an error is raised, we
should stash the backtrace into some fixed-size table.

This way, when an error happens, you don't need to do steps i..v at all,
you just go and consult the recent backtraces.

I'm not sure I'd do that for all those signals which are caught (just like
I'm not sure step ii should be there in your recipe), but I'd definitely
do that for those errors caught by `with-demoted-errors`.


        Stefan




^ permalink raw reply	[flat|nested] 54+ messages in thread

* Fontification error backtrace [Was: Why is it so difficult to get a Lisp backtrace?]
  2022-06-26  5:20           ` Eli Zaretskii
@ 2022-06-27 19:48             ` Alan Mackenzie
  2022-06-28 11:43               ` Lars Ingebrigtsen
  2022-06-28 11:57               ` Eli Zaretskii
  0 siblings, 2 replies; 54+ messages in thread
From: Alan Mackenzie @ 2022-06-27 19:48 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: larsi, emacs-devel

Hello, Eli.

On Sun, Jun 26, 2022 at 08:20:47 +0300, Eli Zaretskii wrote:
> > Date: Sat, 25 Jun 2022 19:45:49 +0000
> > Cc: larsi@gnus.org, emacs-devel@gnu.org
> > From: Alan Mackenzie <acm@muc.de>

> > > The only two ways of having that, AFAIK, are:

> > >   . emit the backtrace int *Messages*
> > >   . add the backtrace to delayed-warnings-list

> > Why can't the backtrace be generated and stored, and only displayed at a
> > subsequent redisplay (probably the next one)?

> That's what the second alternative above already does.

OK, I've got some code.  :-)

What it does, on a fontification error during redisplay, is to write a
backtrace into the *Backtrace* buffer, attempts a pop-to-buffer (which
isn't working, not surprisingly), and aborts the redisplay.

The basic idea is to note the condition-case handler which is scheduled
to handle any fontification error.  In signal_or_quit, if that is the
handler which is about to handle an error, generate a backtrace instead.

To try it out, set the new variable backtrace-on-fontification-error to
non-nil, and tweak some code to generate an error during fontification.

As already noted, the *Backtrace* buffer doesn't appear on the screen,
yet.  Any hints on how to achieve this would be most welcome.  It seems I
could really do with after-redisplay-hook, in which I could call
pop-to-buffer, then cause redisplay to be restarted.

Maybe this code could go into Emacs 29, once it's working properly.



diff --git a/src/dispextern.h b/src/dispextern.h
index c7399ca299..4bf6fb843f 100644
--- a/src/dispextern.h
+++ b/src/dispextern.h
@@ -3406,6 +3406,7 @@ void move_it_in_display_line (struct it *it,
 int partial_line_height (struct it *it_origin);
 bool in_display_vector_p (struct it *);
 int frame_mode_line_height (struct frame *);
+extern struct handler *redisplay_deep_handler;
 extern bool redisplaying_p;
 extern bool help_echo_showing_p;
 extern Lisp_Object help_echo_string, help_echo_window;
diff --git a/src/eval.c b/src/eval.c
index 346dff8bdc..ad5eb9dc7e 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -1550,9 +1550,12 @@ internal_condition_case_n (Lisp_Object (*bfun) (ptrdiff_t, Lisp_Object *),
 			   Lisp_Object handlers,
 			   Lisp_Object (*hfun) (Lisp_Object err,
 						ptrdiff_t nargs,
-						Lisp_Object *args))
+						Lisp_Object *args),
+			   struct handler **handler)
 {
   struct handler *c = push_handler (handlers, CONDITION_CASE);
+  if (handler)
+    *handler = c;
   if (sys_setjmp (c->jmp))
     {
       Lisp_Object val = handlerlist->val;
@@ -1812,6 +1815,32 @@ signal_or_quit (Lisp_Object error_symbol, Lisp_Object data, bool keyboard_quit)
       unbind_to (count, Qnil);
     }
 
+  /* If an error is signalled during fontification in redisplay, print
+     a backtrace into the buffer *Backtrace*.  */
+  if (!debugger_called && !NILP (error_symbol)
+      && redisplay_fontifying
+      && backtrace_on_fontification_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);
+      Lisp_Object backtrace_buffer;
+      max_ensure_room (&max_specpdl_size, counti, 200);
+      backtrace_buffer = Fget_buffer_create (make_string ("*Backtrace*", 11),
+					     Qnil);
+      current_buffer = XBUFFER (backtrace_buffer);
+      Ferase_buffer ();
+      specbind (Qstandard_output, backtrace_buffer);
+      specbind (Qdebugger, Qdebug_early);
+      call_debugger (list2 (Qerror, Fcons (error_symbol, data)));
+      unbind_to (count, Qnil);
+      call1 (intern ("pop-to-buffer"), backtrace_buffer);
+      Fthrow (Qtop_level, Qt);	/* This might not be needed. */
+    }
+
   if (!NILP (clause))
     {
       Lisp_Object unwind_data
@@ -4269,6 +4298,12 @@ 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-fontification-error", backtrace_on_fontification_error,
+	       doc: /* Non-nil means show a backtrace if a fontification error occurs in redisplay.
+When this happens, it causes the current redisplay to be aborted and a new one
+including the *Backtrace* buffer to be performed.  */);
+  backtrace_on_fontification_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
diff --git a/src/keyboard.c b/src/keyboard.c
index e62b2e56d3..fdce343d6f 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -1878,7 +1878,7 @@ safe_run_hook_funcall (ptrdiff_t nargs, Lisp_Object *args)
   /* Yes, run_hook_with_args works with args in the other order.  */
   internal_condition_case_n (safe_run_hooks_1,
 			     2, ((Lisp_Object []) {args[1], args[0]}),
-			     Qt, safe_run_hooks_error);
+			     Qt, safe_run_hooks_error, NULL);
   return Qnil;
 }
 
diff --git a/src/lisp.h b/src/lisp.h
index 05b0754ff6..16830c7d8e 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -4558,7 +4558,7 @@ extern Lisp_Object internal_condition_case_1 (Lisp_Object (*) (Lisp_Object), Lis
 extern Lisp_Object internal_condition_case_2 (Lisp_Object (*) (Lisp_Object, Lisp_Object), Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object (*) (Lisp_Object));
 extern Lisp_Object internal_condition_case_n
     (Lisp_Object (*) (ptrdiff_t, Lisp_Object *), ptrdiff_t, Lisp_Object *,
-     Lisp_Object, Lisp_Object (*) (Lisp_Object, ptrdiff_t, Lisp_Object *));
+     Lisp_Object, Lisp_Object (*) (Lisp_Object, ptrdiff_t, Lisp_Object *), struct handler **);
 extern Lisp_Object internal_catch_all (Lisp_Object (*) (void *), void *, Lisp_Object (*) (enum nonlocal_exit, Lisp_Object));
 extern struct handler *push_handler (Lisp_Object, enum handlertype)
   ATTRIBUTE_RETURNS_NONNULL;
diff --git a/src/xdisp.c b/src/xdisp.c
index 90809ac3ab..6b2d09efb2 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -615,6 +615,9 @@ fill_column_indicator_column (struct it *it, int char_width)
 
 bool noninteractive_need_newline;
 
+/* The handler structure which will catch errors in fontification code.  */
+struct handler *redisplay_deep_handler;
+
 /* True means print newline to message log before next message.  */
 
 static bool message_log_need_newline;
@@ -3013,7 +3016,8 @@ safe__call (bool inhibit_quit, ptrdiff_t nargs, Lisp_Object func, va_list ap)
       /* Use Qt to ensure debugger does not run,
 	 so there is no possibility of wanting to redisplay.  */
       val = internal_condition_case_n (Ffuncall, nargs, args, Qt,
-				       safe_eval_handler);
+				       safe_eval_handler,
+				       &redisplay_deep_handler);
       val = SAFE_FREE_UNBIND_TO (count, val);
     }
 
@@ -4320,6 +4324,7 @@ handle_fontified_prop (struct it *it)
 
       val = Vfontification_functions;
       specbind (Qfontification_functions, Qnil);
+      specbind (Qredisplay_fontifying, Qt);
 
       eassert (it->end_charpos == ZV);
 
@@ -26431,7 +26436,7 @@ display_mode_element (struct it *it, int depth, int field_width, int precision,
 						   Flength (elt),
 						   props,
 						   elt}),
-					       Qt, safe_eval_handler);
+					       Qt, safe_eval_handler, NULL);
 		    /* Add this item to mode_line_proptrans_alist.  */
 		    mode_line_proptrans_alist
 		      = Fcons (Fcons (elt, props),
@@ -35704,6 +35709,7 @@ be let-bound around code that needs to disable messages temporarily. */);
   DEFSYM (QCfile, ":file");
   DEFSYM (Qfontified, "fontified");
   DEFSYM (Qfontification_functions, "fontification-functions");
+  DEFSYM (Qredisplay_fontifying, "redisplay-fontifying");
 
   /* Name of the symbol which disables Lisp evaluation in 'display'
      properties.  This is used by enriched.el.  */
@@ -36221,6 +36227,10 @@ fontified regions the property `fontified'.  */);
   Vfontification_functions = Qnil;
   Fmake_variable_buffer_local (Qfontification_functions);
 
+  DEFVAR_BOOL ("redisplay-fontifying", redisplay_fontifying,
+    doc: /* Non-nil when fontification invoked from redisplay is in progress.  */);
+  redisplay_fontifying = false;
+
   DEFVAR_BOOL ("unibyte-display-via-language-environment",
                unibyte_display_via_language_environment,
     doc: /* Non-nil means display unibyte text according to language environment.
diff --git a/src/xfns.c b/src/xfns.c
index 1372809da6..dc18cfb4ae 100644
--- a/src/xfns.c
+++ b/src/xfns.c
@@ -3405,7 +3405,7 @@ x_xim_text_to_utf8_unix (XIMText *text, ptrdiff_t *length)
   waiting_for_input = false;
   arg = make_mint_ptr (&data);
   internal_condition_case_n (x_xim_text_to_utf8_unix_1, 1, &arg,
-			     Qt, x_xim_text_to_utf8_unix_2);
+			     Qt, x_xim_text_to_utf8_unix_2, NULL);
   waiting_for_input = was_waiting_for_input_p;
 
   *length = coding.produced;


-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* Re: Fontification error backtrace  [Was: Why is it so difficult to get a Lisp backtrace?]
  2022-06-27 19:48             ` Fontification error backtrace [Was: Why is it so difficult to get a Lisp backtrace?] Alan Mackenzie
@ 2022-06-28 11:43               ` Lars Ingebrigtsen
  2022-06-28 11:57               ` Eli Zaretskii
  1 sibling, 0 replies; 54+ messages in thread
From: Lars Ingebrigtsen @ 2022-06-28 11:43 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: Eli Zaretskii, emacs-devel

Alan Mackenzie <acm@muc.de> writes:

> What it does, on a fontification error during redisplay, is to write a
> backtrace into the *Backtrace* buffer, attempts a pop-to-buffer (which
> isn't working, not surprisingly), and aborts the redisplay.

Sounds promising -- it's very difficult to debug fontification errors
currently, so this would be very welcome.

> As already noted, the *Backtrace* buffer doesn't appear on the screen,
> yet.  Any hints on how to achieve this would be most welcome.

Is `inhibit-redisplay' non-nil at this point?  In that case, `debug'
will just exit immediately...

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Fontification error backtrace [Was: Why is it so difficult to get a Lisp backtrace?]
  2022-06-27 19:48             ` Fontification error backtrace [Was: Why is it so difficult to get a Lisp backtrace?] Alan Mackenzie
  2022-06-28 11:43               ` Lars Ingebrigtsen
@ 2022-06-28 11:57               ` Eli Zaretskii
  2022-06-28 17:28                 ` Alan Mackenzie
  1 sibling, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2022-06-28 11:57 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: larsi, emacs-devel

> Date: Mon, 27 Jun 2022 19:48:45 +0000
> Cc: larsi@gnus.org, emacs-devel@gnu.org
> From: Alan Mackenzie <acm@muc.de>
> 
> > > > The only two ways of having that, AFAIK, are:
> 
> > > >   . emit the backtrace int *Messages*
> > > >   . add the backtrace to delayed-warnings-list
> 
> > > Why can't the backtrace be generated and stored, and only displayed at a
> > > subsequent redisplay (probably the next one)?
> 
> > That's what the second alternative above already does.
> 
> OK, I've got some code.  :-)

Sigh.  I suggested two alternatives, but you effectively
ignored/rejected both of them.  Why?

> What it does, on a fontification error during redisplay, is to write a
> backtrace into the *Backtrace* buffer, attempts a pop-to-buffer (which
> isn't working, not surprisingly), and aborts the redisplay.

I don't understand why you needed to do it this way.

First, what's wrong with the two ways I suggested?  They are simple to
implement, use existing infrastructure, don't require any changes in
the APIs, and are safe.  Why didn't you use one of them?

Second, if you _must_ roll out something new (but please explain why),
why do you need a special handler? why not slightly modify the
existing handler, safe_eval_handler?  Doing that would have avoided
the need to change existing APIs.

Third, why are you singling out the fontifications? they are not the
only source of Lisp errors in code called by the display engine.
Moreover, handle_fontified_prop is not the only way of calling
expensive Lisp code that deals with faces.

Forth, why on Earth do you want to abort redisplay??  Once you
collected the backtrace, there should be no need in such drastic
measures.  I've just finished adding a feature that _really_ needs to
abort redisplay, and I can tell you: it's NOT safe!  It can easily
crash Emacs within a few commands, and your patch didn't do what I
needed to do to avoid that.  You just throw to top-level from the
bowels of the display code, assuming that it's okay.  Well, it isn't,
not if handle_fontified_prop is called from redisplay_internal and its
subroutines!

So basically, I'm extremely confused, to say the least, by the
proposed changes and by your rationale, and am not happy at all with
installing them.

> As already noted, the *Backtrace* buffer doesn't appear on the screen,
> yet.  Any hints on how to achieve this would be most welcome.

Any reasons you'd want that?  Once the buffer exists, why do you need
to actually pop it up (and thus potentially disrupt the very
window/frame configuration which you are trying to debug)?

> It seems I could really do with after-redisplay-hook, in which I
> could call pop-to-buffer, then cause redisplay to be restarted.

I think you misunderstand what such a hook could do, and why it could
be useful, if at all, in the first place.  If all you want is to cause
the main loop perform some command after redisplay cycle ends, we
already have several mechanisms in place for doing that.

But all that is secondary.  We should first agree on the correct
design and implementation of the main part, which is how to handle
these errors for collecting Lisp backtrace information.



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Fontification error backtrace  [Was: Why is it so difficult to get a Lisp backtrace?]
  2022-06-28 11:57               ` Eli Zaretskii
@ 2022-06-28 17:28                 ` Alan Mackenzie
  2022-06-28 18:17                   ` Eli Zaretskii
  0 siblings, 1 reply; 54+ messages in thread
From: Alan Mackenzie @ 2022-06-28 17:28 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: larsi, emacs-devel

Hello, Eli.

On Tue, Jun 28, 2022 at 14:57:44 +0300, Eli Zaretskii wrote:
> > Date: Mon, 27 Jun 2022 19:48:45 +0000
> > Cc: larsi@gnus.org, emacs-devel@gnu.org
> > From: Alan Mackenzie <acm@muc.de>

> > > > > The only two ways of having that, AFAIK, are:

> > > > >   . emit the backtrace int *Messages*
> > > > >   . add the backtrace to delayed-warnings-list

> > > > Why can't the backtrace be generated and stored, and only displayed at a
> > > > subsequent redisplay (probably the next one)?

> > > That's what the second alternative above already does.

> > OK, I've got some code.  :-)

> Sigh.  I suggested two alternatives, but you effectively
> ignored/rejected both of them.  Why?

*Messages* just doesn't feel the right place to put a backtrace.

delayed-warnings-list consists of a list of structured lists, rather than
the vanilla text output by debug-early.  So the backtrace would need some
massaging to fit into delayed-warnings-list.  Also these delayed warnings
get displayed between post-command-hook and redisplay, which is not an
optimal time to display a backtrace generated in redisplay.

However, the display of the backtrace was not the difficult bit in
writing this code - that was triggering the backtrace when there was an
error signalled, and only then.

> > What it does, on a fontification error during redisplay, is to write a
> > backtrace into the *Backtrace* buffer, attempts a pop-to-buffer (which
> > isn't working, not surprisingly), and aborts the redisplay.

> I don't understand why you needed to do it this way.

I copied it from the debug-on-error backtrace.  It's just my first try at
getting the thing displayed.  I'm not at all experienced in creating and
displaying windows.

> First, what's wrong with the two ways I suggested?  They are simple to
> implement, use existing infrastructure, don't require any changes in
> the APIs, and are safe.  Why didn't you use one of them?

See above.

> Second, if you _must_ roll out something new (but please explain why),
> why do you need a special handler? why not slightly modify the
> existing handler, safe_eval_handler?  Doing that would have avoided
> the need to change existing APIs.

That way hadn't occurred to me.  It would surely be better than
yesterday's patch.

> Third, why are you singling out the fontifications? they are not the
> only source of Lisp errors in code called by the display engine.
> Moreover, handle_fontified_prop is not the only way of calling
> expensive Lisp code that deals with faces.

All that's true, yet fontification errors are an important source of
difficult to debug errors.  We could certainly extend the new backtrace
mechanism to handle these other types of bug, too.

> Forth, why on Earth do you want to abort redisplay??  Once you
> collected the backtrace, there should be no need in such drastic
> measures.

There's really no need for this, thinking about it.  Given that the
condition-case has handled the error, there's nothing stopping the
redisplay from continuing successfully on the other visible windows.

> I've just finished adding a feature that _really_ needs to abort
> redisplay, and I can tell you: it's NOT safe!  It can easily crash
> Emacs within a few commands, and your patch didn't do what I needed to
> do to avoid that.  You just throw to top-level from the bowels of the
> display code, assuming that it's okay.  Well, it isn't, not if
> handle_fontified_prop is called from redisplay_internal and its
> subroutines!

OK.

> So basically, I'm extremely confused, to say the least, by the
> proposed changes and by your rationale, and am not happy at all with
> installing them.

My patch was more a proof of concept, a demonstration that a backtrace
from within redisplay is possible.  It wasn't intended to be finished
code, and clearly isn't in that state, yet.

> > As already noted, the *Backtrace* buffer doesn't appear on the screen,
> > yet.  Any hints on how to achieve this would be most welcome.

> Any reasons you'd want that?  Once the buffer exists, why do you need
> to actually pop it up (and thus potentially disrupt the very
> window/frame configuration which you are trying to debug)?

A debug-on-error backtrace gets displayed immediately, so it would help
consistency.  If the window where the error occurred isn't occupying its
entire frame, there should be room to put the backtrace somewhere else, I
think.  I'm not at all experienced with things like pop-to-buffer, but
they're supposed to be very flexible.

> > It seems I could really do with after-redisplay-hook, in which I
> > could call pop-to-buffer, then cause redisplay to be restarted.

> I think you misunderstand what such a hook could do, and why it could
> be useful, if at all, in the first place.  If all you want is to cause
> the main loop perform some command after redisplay cycle ends, we
> already have several mechanisms in place for doing that.

That's what I want, yes, but couldn't find any such mechanism anywhere
when I looked.

> But all that is secondary.  We should first agree on the correct
> design and implementation of the main part, which is how to handle
> these errors for collecting Lisp backtrace information.

Yes.  How about what you suggested above, namely creating the backtrace
in save_eval_handler rather than signal_or_quit, at least as a second
attempt.  Though I don't think this would handle errors in other Lisp
called from redisplay.

Thanks for such a full review and helpful criticism.

-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Fontification error backtrace  [Was: Why is it so difficult to get a Lisp backtrace?]
  2022-06-28 17:28                 ` Alan Mackenzie
@ 2022-06-28 18:17                   ` Eli Zaretskii
  2022-06-28 19:31                     ` Alan Mackenzie
  0 siblings, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2022-06-28 18:17 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: larsi, emacs-devel

> Date: Tue, 28 Jun 2022 17:28:38 +0000
> Cc: larsi@gnus.org, emacs-devel@gnu.org
> From: Alan Mackenzie <acm@muc.de>
> 
> > > > > >   . emit the backtrace int *Messages*
> > > > > >   . add the backtrace to delayed-warnings-list
> 
> > > > > Why can't the backtrace be generated and stored, and only displayed at a
> > > > > subsequent redisplay (probably the next one)?
> 
> > > > That's what the second alternative above already does.
> 
> > > OK, I've got some code.  :-)
> 
> > Sigh.  I suggested two alternatives, but you effectively
> > ignored/rejected both of them.  Why?
> 
> *Messages* just doesn't feel the right place to put a backtrace.

That's where we currently log all the errors during redisplay, since
Emacs 21.1.  So why this one is different?

But you _really_ dislike *Messages*, then put the stuff into another
buffer, like *backtrace-in-redisplay* or somesuch.

> delayed-warnings-list consists of a list of structured lists, rather than
> the vanilla text output by debug-early.

Why is it a problem?  You could again stash the backtrace somewhere
and only show the name of that buffer in the warning text.  Using
delayed-warnings-list makes sure the problem is not missed.

> Also these delayed warnings get displayed between post-command-hook
> and redisplay, which is not an optimal time to display a backtrace
> generated in redisplay.

The buffer with warnings pops up very soon after redisplay, which is
more-or-less exactly what you wanted?

> > Third, why are you singling out the fontifications? they are not the
> > only source of Lisp errors in code called by the display engine.
> > Moreover, handle_fontified_prop is not the only way of calling
> > expensive Lisp code that deals with faces.
> 
> All that's true, yet fontification errors are an important source of
> difficult to debug errors.  We could certainly extend the new backtrace
> mechanism to handle these other types of bug, too.

It makes very little sense to me to create infrastructure that caters
only to your current and immediate problem.  If we introduce such a
feature, it should work with any Lisp called from redisplay.

> > > As already noted, the *Backtrace* buffer doesn't appear on the screen,
> > > yet.  Any hints on how to achieve this would be most welcome.
> 
> > Any reasons you'd want that?  Once the buffer exists, why do you need
> > to actually pop it up (and thus potentially disrupt the very
> > window/frame configuration which you are trying to debug)?
> 
> A debug-on-error backtrace gets displayed immediately, so it would help
> consistency.  If the window where the error occurred isn't occupying its
> entire frame, there should be room to put the backtrace somewhere else, I
> think.  I'm not at all experienced with things like pop-to-buffer, but
> they're supposed to be very flexible.

I think showing a delayed-warning with the text that mentions the
buffer with the backtrace is completely adequate.  It stops short of
popping the window with a backtrace itself, but that could be added
later, if necessary, perhaps as a feature of delayed-warning.

> > > It seems I could really do with after-redisplay-hook, in which I
> > > could call pop-to-buffer, then cause redisplay to be restarted.
> 
> > I think you misunderstand what such a hook could do, and why it could
> > be useful, if at all, in the first place.  If all you want is to cause
> > the main loop perform some command after redisplay cycle ends, we
> > already have several mechanisms in place for doing that.
> 
> That's what I want, yes, but couldn't find any such mechanism anywhere
> when I looked.

One thing that comes to mind is a timer.  Another thing is pushing
something onto unread-command-events.  I'm sure there are more.

> Yes.  How about what you suggested above, namely creating the backtrace
> in save_eval_handler rather than signal_or_quit, at least as a second
> attempt.  Though I don't think this would handle errors in other Lisp
> called from redisplay.

It will, because any Lisp we call goes though safe_eval.  The only
reason why your original proposal didn't was because it bound the
variable which enables this only in handle_fontified_prop.  But we
don't need this flag if the logic to decide what to do with the error
will be in save_eval_handler.

And you need to consider one more factor: code from display engine,
including fontification via handle_fontified_prop, is called also from
user commands, not via redisplay_internal.  For example, commands like
C-n and C-v invoke display code.  Errors signaled there _can_ be left
to go to top-level and probably could call the debugger.  To test
whether any given code was called from redisplay_internal, test the
value of the variable redisplaying_p.



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Fontification error backtrace  [Was: Why is it so difficult to get a Lisp backtrace?]
  2022-06-28 18:17                   ` Eli Zaretskii
@ 2022-06-28 19:31                     ` Alan Mackenzie
  2022-06-29 19:00                       ` Eli Zaretskii
  2022-06-30 20:34                       ` Fontification error backtrace [Was: Why is it so difficult to get a Lisp backtrace?] Stefan Monnier
  0 siblings, 2 replies; 54+ messages in thread
From: Alan Mackenzie @ 2022-06-28 19:31 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: larsi, emacs-devel

Hello, Eli.

On Tue, Jun 28, 2022 at 21:17:04 +0300, Eli Zaretskii wrote:
> > Date: Tue, 28 Jun 2022 17:28:38 +0000
> > Cc: larsi@gnus.org, emacs-devel@gnu.org
> > From: Alan Mackenzie <acm@muc.de>

> > > > > > >   . emit the backtrace int *Messages*
> > > > > > >   . add the backtrace to delayed-warnings-list

> > > > > > Why can't the backtrace be generated and stored, and only displayed at a
> > > > > > subsequent redisplay (probably the next one)?

> > > > > That's what the second alternative above already does.

> > > > OK, I've got some code.  :-)

> > > Sigh.  I suggested two alternatives, but you effectively
> > > ignored/rejected both of them.  Why?

> > *Messages* just doesn't feel the right place to put a backtrace.

> That's where we currently log all the errors during redisplay, since
> Emacs 21.1.  So why this one is different?

It's not a single line, or a small number of lines.  It could potentially
be a stack overflow error, generating a large backtrace, which might have
more lines than message-log-max.

> But you _really_ dislike *Messages*, then put the stuff into another
> buffer, like *backtrace-in-redisplay* or somesuch.

I'm currently using the buffer *Backtrace*.  Is there anything wrong with
that?

> > delayed-warnings-list consists of a list of structured lists, rather than
> > the vanilla text output by debug-early.

> Why is it a problem?  You could again stash the backtrace somewhere
> and only show the name of that buffer in the warning text.  Using
> delayed-warnings-list makes sure the problem is not missed.

> > Also these delayed warnings get displayed between post-command-hook
> > and redisplay, which is not an optimal time to display a backtrace
> > generated in redisplay.

> The buffer with warnings pops up very soon after redisplay, which is
> more-or-less exactly what you wanted?

OK, I've never used it.  On reading the documentation, I expected it to
wait until after the next command.

> > > Third, why are you singling out the fontifications? they are not the
> > > only source of Lisp errors in code called by the display engine.
> > > Moreover, handle_fontified_prop is not the only way of calling
> > > expensive Lisp code that deals with faces.

> > All that's true, yet fontification errors are an important source of
> > difficult to debug errors.  We could certainly extend the new backtrace
> > mechanism to handle these other types of bug, too.

> It makes very little sense to me to create infrastructure that caters
> only to your current and immediate problem.  If we introduce such a
> feature, it should work with any Lisp called from redisplay.

OK.  It shouldn't actually be much more difficult to keep the new
facility generic.

> > > > As already noted, the *Backtrace* buffer doesn't appear on the screen,
> > > > yet.  Any hints on how to achieve this would be most welcome.

> > > Any reasons you'd want that?  Once the buffer exists, why do you need
> > > to actually pop it up (and thus potentially disrupt the very
> > > window/frame configuration which you are trying to debug)?

> > A debug-on-error backtrace gets displayed immediately, so it would help
> > consistency.  If the window where the error occurred isn't occupying its
> > entire frame, there should be room to put the backtrace somewhere else, I
> > think.  I'm not at all experienced with things like pop-to-buffer, but
> > they're supposed to be very flexible.

> I think showing a delayed-warning with the text that mentions the
> buffer with the backtrace is completely adequate.  It stops short of
> popping the window with a backtrace itself, but that could be added
> later, if necessary, perhaps as a feature of delayed-warning.

I think that would be a good compromise.

> > > > It seems I could really do with after-redisplay-hook, in which I
> > > > could call pop-to-buffer, then cause redisplay to be restarted.

> > > I think you misunderstand what such a hook could do, and why it could
> > > be useful, if at all, in the first place.  If all you want is to cause
> > > the main loop perform some command after redisplay cycle ends, we
> > > already have several mechanisms in place for doing that.

> > That's what I want, yes, but couldn't find any such mechanism anywhere
> > when I looked.

> One thing that comes to mind is a timer.  Another thing is pushing
> something onto unread-command-events.  I'm sure there are more.

OK, yes, maybe a timer would be the thing.

> > Yes.  How about what you suggested above, namely creating the backtrace
> > in save_eval_handler rather than signal_or_quit, at least as a second
> > attempt.  Though I don't think this would handle errors in other Lisp
> > called from redisplay.

Now I remember why I created the backtrace in signal_or_quit - it needs
to be done before the stack gets unwound, which happens later in
signal_or_quit.  On return to save_eval_handler is just too late for
this.

> It will, because any Lisp we call goes though safe_eval.  The only
> reason why your original proposal didn't was because it bound the
> variable which enables this only in handle_fontified_prop.  But we
> don't need this flag if the logic to decide what to do with the error
> will be in save_eval_handler.

Then the mechanism I've implemented, namely to set redisplay_deep_handler
to the handler which would handle an error, could be made to serve for
other sorts of Lisp code.  Probably I'll need to rename some variables
whose name contains "fontification" and suchlike.

> And you need to consider one more factor: code from display engine,
> including fontification via handle_fontified_prop, is called also from
> user commands, not via redisplay_internal.  For example, commands like
> C-n and C-v invoke display code.  Errors signaled there _can_ be left
> to go to top-level and probably could call the debugger.  To test
> whether any given code was called from redisplay_internal, test the
> value of the variable redisplaying_p.

OK, this is a further complication.  But it would be good to get to the
debugger if possible, rather than just having a "historical" backtrace.

-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Fontification error backtrace  [Was: Why is it so difficult to get a Lisp backtrace?]
  2022-06-28 19:31                     ` Alan Mackenzie
@ 2022-06-29 19:00                       ` Eli Zaretskii
  2022-07-12 19:48                         ` Redisplay hook error bactraces [Was: Fontification error backtrace] Alan Mackenzie
  2022-06-30 20:34                       ` Fontification error backtrace [Was: Why is it so difficult to get a Lisp backtrace?] Stefan Monnier
  1 sibling, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2022-06-29 19:00 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: larsi, emacs-devel

> Date: Tue, 28 Jun 2022 19:31:59 +0000
> Cc: larsi@gnus.org, emacs-devel@gnu.org
> From: Alan Mackenzie <acm@muc.de>
> 
> > > *Messages* just doesn't feel the right place to put a backtrace.
> 
> > That's where we currently log all the errors during redisplay, since
> > Emacs 21.1.  So why this one is different?
> 
> It's not a single line, or a small number of lines.  It could potentially
> be a stack overflow error, generating a large backtrace, which might have
> more lines than message-log-max.

We could temporarily bind message-log-max to a larger value, if that's
the problem.  But I really doubt that something like this could be an
issue in practice.

> > But you _really_ dislike *Messages*, then put the stuff into another
> > buffer, like *backtrace-in-redisplay* or somesuch.
> 
> I'm currently using the buffer *Backtrace*.  Is there anything wrong with
> that?

Not that I see, no.

> > The buffer with warnings pops up very soon after redisplay, which is
> > more-or-less exactly what you wanted?
> 
> OK, I've never used it.  On reading the documentation, I expected it to
> wait until after the next command.

That's because the ELisp reference manual describes it from the POV of
setting up the warning as part of some command.  And even then, it
says:

     The value of this variable is a list of warnings to be displayed
     after the current command has finished.

"Current command", not "next command".

If you add a warning to the list inside redisplay, it will pop up
after redisplay returns.

> Now I remember why I created the backtrace in signal_or_quit - it needs
> to be done before the stack gets unwound, which happens later in
> signal_or_quit.  On return to save_eval_handler is just too late for
> this.

That's orthogonal, I think?  You can collect the data inside
signal_or_quit, and the signal handler then only needs to handle the
error gracefully after it adds the warning.

> > It will, because any Lisp we call goes though safe_eval.  The only
> > reason why your original proposal didn't was because it bound the
> > variable which enables this only in handle_fontified_prop.  But we
> > don't need this flag if the logic to decide what to do with the error
> > will be in save_eval_handler.
> 
> Then the mechanism I've implemented, namely to set redisplay_deep_handler
> to the handler which would handle an error, could be made to serve for
> other sorts of Lisp code.

It could, but I see no reason for having a new handler.

> > And you need to consider one more factor: code from display engine,
> > including fontification via handle_fontified_prop, is called also from
> > user commands, not via redisplay_internal.  For example, commands like
> > C-n and C-v invoke display code.  Errors signaled there _can_ be left
> > to go to top-level and probably could call the debugger.  To test
> > whether any given code was called from redisplay_internal, test the
> > value of the variable redisplaying_p.
> 
> OK, this is a further complication.  But it would be good to get to the
> debugger if possible, rather than just having a "historical" backtrace.

If the display code is called from "normal" commands, you can.



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Fontification error backtrace  [Was: Why is it so difficult to get a Lisp backtrace?]
  2022-06-28 19:31                     ` Alan Mackenzie
  2022-06-29 19:00                       ` Eli Zaretskii
@ 2022-06-30 20:34                       ` Stefan Monnier
  2022-07-01  5:39                         ` Eli Zaretskii
  2022-07-02 20:27                         ` Alan Mackenzie
  1 sibling, 2 replies; 54+ messages in thread
From: Stefan Monnier @ 2022-06-30 20:34 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: Eli Zaretskii, larsi, emacs-devel

>> That's where we currently log all the errors during redisplay, since
>> Emacs 21.1.  So why this one is different?
>
> It's not a single line, or a small number of lines.  It could potentially
> be a stack overflow error, generating a large backtrace, which might have
> more lines than message-log-max.

How 'bout stashing the backtrace data in a var (without turning it into
text yet; that should always be quick, safe, and painless) and then
adding a button in the *Messages* (or *Warnings*) next to the error
itself such that pressing the buttong shows you the actual backtrace?

We could even try and get fancy: instead of only stashing the
`backtrace-frames`, we could also stash a copy of the specpdl so we
could bring up a *Backtrace* buffer which isn't quite "live" but can
still be used to some extent to see the values of local vars (and
`current-buffer`) in the different activation frames.

BTW, in order to debug fontification errors, we also have
`jit-locak-debug-mode` which postpones the jit/font-lock execution from
within redisplay to "just a bit later" such that it can use the
debugger.  IIRC it still has some rough edges in some cases, but in
theory it should be possible to make this work such that you can (for
example) Edebug `font-lock.el` itself.


        Stefan




^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Fontification error backtrace  [Was: Why is it so difficult to get a Lisp backtrace?]
  2022-06-30 20:34                       ` Fontification error backtrace [Was: Why is it so difficult to get a Lisp backtrace?] Stefan Monnier
@ 2022-07-01  5:39                         ` Eli Zaretskii
  2022-07-01 15:34                           ` Juri Linkov
  2022-07-01 19:26                           ` Stefan Monnier
  2022-07-02 20:27                         ` Alan Mackenzie
  1 sibling, 2 replies; 54+ messages in thread
From: Eli Zaretskii @ 2022-07-01  5:39 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: acm, larsi, emacs-devel

> From: Stefan Monnier <monnier@iro.umontreal.ca>
> Cc: Eli Zaretskii <eliz@gnu.org>,  larsi@gnus.org,  emacs-devel@gnu.org
> Date: Thu, 30 Jun 2022 16:34:23 -0400
> 
> BTW, in order to debug fontification errors, we also have
> `jit-locak-debug-mode` which postpones the jit/font-lock execution from
> within redisplay to "just a bit later" such that it can use the
> debugger.  IIRC it still has some rough edges in some cases, but in
> theory it should be possible to make this work such that you can (for
> example) Edebug `font-lock.el` itself.

It would be nice to have this documented in the ELisp manual.  I can
easily add the jit-lock-debug-mode variable there, but from what you
write, there seems to be more wisdom to go with its usage.



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Fontification error backtrace  [Was: Why is it so difficult to get a Lisp backtrace?]
  2022-07-01  5:39                         ` Eli Zaretskii
@ 2022-07-01 15:34                           ` Juri Linkov
  2022-07-01 16:04                             ` Eli Zaretskii
  2022-07-01 19:26                           ` Stefan Monnier
  1 sibling, 1 reply; 54+ messages in thread
From: Juri Linkov @ 2022-07-01 15:34 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Stefan Monnier, acm, larsi, emacs-devel

>> BTW, in order to debug fontification errors, we also have
>> `jit-locak-debug-mode` which postpones the jit/font-lock execution from
>> within redisplay to "just a bit later" such that it can use the
>> debugger.  IIRC it still has some rough edges in some cases, but in
>> theory it should be possible to make this work such that you can (for
>> example) Edebug `font-lock.el` itself.
>
> It would be nice to have this documented in the ELisp manual.  I can
> easily add the jit-lock-debug-mode variable there, but from what you
> write, there seems to be more wisdom to go with its usage.

Also would be nice to introduce a "log level" with possible values
Fatal, Error, Warn, Info, Debug, Trace.  Then depending on the
customized value decide whether print the complete backtrace to
the *Messages* buffer, or only the error message, or nothing at all.



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Fontification error backtrace  [Was: Why is it so difficult to get a Lisp backtrace?]
  2022-07-01 15:34                           ` Juri Linkov
@ 2022-07-01 16:04                             ` Eli Zaretskii
  2022-07-01 19:14                               ` Juri Linkov
  0 siblings, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2022-07-01 16:04 UTC (permalink / raw)
  To: Juri Linkov; +Cc: monnier, acm, larsi, emacs-devel

> From: Juri Linkov <juri@linkov.net>
> Cc: Stefan Monnier <monnier@iro.umontreal.ca>,  acm@muc.de,  larsi@gnus.org,
>  emacs-devel@gnu.org
> Date: Fri, 01 Jul 2022 18:34:01 +0300
> 
> >> BTW, in order to debug fontification errors, we also have
> >> `jit-locak-debug-mode` which postpones the jit/font-lock execution from
> >> within redisplay to "just a bit later" such that it can use the
> >> debugger.  IIRC it still has some rough edges in some cases, but in
> >> theory it should be possible to make this work such that you can (for
> >> example) Edebug `font-lock.el` itself.
> >
> > It would be nice to have this documented in the ELisp manual.  I can
> > easily add the jit-lock-debug-mode variable there, but from what you
> > write, there seems to be more wisdom to go with its usage.
> 
> Also would be nice to introduce a "log level" with possible values
> Fatal, Error, Warn, Info, Debug, Trace.  Then depending on the
> customized value decide whether print the complete backtrace to
> the *Messages* buffer, or only the error message, or nothing at all.

I don't necessarily object, but I'm asking myself who'd want less than
the complete information.  And I don't find any good answer; it seems
to me that whenever one gets the less detailed report, one will
immediately want the most detailed one.



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Fontification error backtrace  [Was: Why is it so difficult to get a Lisp backtrace?]
  2022-07-01 16:04                             ` Eli Zaretskii
@ 2022-07-01 19:14                               ` Juri Linkov
  0 siblings, 0 replies; 54+ messages in thread
From: Juri Linkov @ 2022-07-01 19:14 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: monnier, acm, larsi, emacs-devel

>> Also would be nice to introduce a "log level" with possible values
>> Fatal, Error, Warn, Info, Debug, Trace.  Then depending on the
>> customized value decide whether print the complete backtrace to
>> the *Messages* buffer, or only the error message, or nothing at all.
>
> I don't necessarily object, but I'm asking myself who'd want less than
> the complete information.  And I don't find any good answer; it seems
> to me that whenever one gets the less detailed report, one will
> immediately want the most detailed one.

More information is better than less, indeed.  Then in case
someone worries about flooding the Messages buffer, maybe at least
hide the complete information, then provide a button to unhide it.



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Fontification error backtrace  [Was: Why is it so difficult to get a Lisp backtrace?]
  2022-07-01  5:39                         ` Eli Zaretskii
  2022-07-01 15:34                           ` Juri Linkov
@ 2022-07-01 19:26                           ` Stefan Monnier
  2022-07-02  5:53                             ` Eli Zaretskii
  1 sibling, 1 reply; 54+ messages in thread
From: Stefan Monnier @ 2022-07-01 19:26 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: acm, larsi, emacs-devel

>> BTW, in order to debug fontification errors, we also have
>> `jit-locak-debug-mode` which postpones the jit/font-lock execution from
>> within redisplay to "just a bit later" such that it can use the
>> debugger.  IIRC it still has some rough edges in some cases, but in
>> theory it should be possible to make this work such that you can (for
>> example) Edebug `font-lock.el` itself.
>
> It would be nice to have this documented in the ELisp manual.  I can
> easily add the jit-lock-debug-mode variable there, but from what you
> write, there seems to be more wisdom to go with its usage.

No, there's no extra wisdom.  It's just that it hasn't been used much,
so it probably needs refinement.  Bug reports would be quite welcome to
figure out what needs to be improved.


        Stefan




^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Fontification error backtrace  [Was: Why is it so difficult to get a Lisp backtrace?]
  2022-07-01 19:26                           ` Stefan Monnier
@ 2022-07-02  5:53                             ` Eli Zaretskii
  2022-07-02 21:15                               ` Stefan Monnier
  0 siblings, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2022-07-02  5:53 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: acm, larsi, emacs-devel

> From: Stefan Monnier <monnier@iro.umontreal.ca>
> Cc: acm@muc.de,  larsi@gnus.org,  emacs-devel@gnu.org
> Date: Fri, 01 Jul 2022 15:26:26 -0400
> 
> >> BTW, in order to debug fontification errors, we also have
> >> `jit-locak-debug-mode` which postpones the jit/font-lock execution from
> >> within redisplay to "just a bit later" such that it can use the
> >> debugger.  IIRC it still has some rough edges in some cases, but in
> >> theory it should be possible to make this work such that you can (for
> >> example) Edebug `font-lock.el` itself.
> >
> > It would be nice to have this documented in the ELisp manual.  I can
> > easily add the jit-lock-debug-mode variable there, but from what you
> > write, there seems to be more wisdom to go with its usage.
> 
> No, there's no extra wisdom.

Then what did you mean by "some rough edges in some cases"?



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Fontification error backtrace  [Was: Why is it so difficult to get a Lisp backtrace?]
  2022-06-30 20:34                       ` Fontification error backtrace [Was: Why is it so difficult to get a Lisp backtrace?] Stefan Monnier
  2022-07-01  5:39                         ` Eli Zaretskii
@ 2022-07-02 20:27                         ` Alan Mackenzie
  2022-07-02 21:12                           ` Stefan Monnier
  1 sibling, 1 reply; 54+ messages in thread
From: Alan Mackenzie @ 2022-07-02 20:27 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Eli Zaretskii, larsi, emacs-devel

Hello, Stefan.

On Thu, Jun 30, 2022 at 16:34:23 -0400, Stefan Monnier wrote:
> >> That's where we currently log all the errors during redisplay, since
> >> Emacs 21.1.  So why this one is different?

> > It's not a single line, or a small number of lines.  It could potentially
> > be a stack overflow error, generating a large backtrace, which might have
> > more lines than message-log-max.

> How 'bout stashing the backtrace data in a var (without turning it into
> text yet; that .....

Does "that" mean the stashing or the turning into text?

> .... should always be quick, safe, and painless) and then adding a
> button in the *Messages* (or *Warnings*) next to the error itself such
> that pressing the buttong shows you the actual backtrace?

I'm not sure the stashing of the backtrace data is a good idea.  It's
likely to be BIG (see above), and it's fragile - the only place to
record it is in signal_or_quit, before it gets discarded by a call to
unwind_to_catch.  The need to know its precise format is obviated by the
C function mapbacktrace, so we might as well just dump out the backtrace
as text in *Backtrace* at the first opportunity.  There's nothing time
critical about the said dumping.

> We could even try and get fancy: instead of only stashing the
> `backtrace-frames`, we could also stash a copy of the specpdl so we
> could bring up a *Backtrace* buffer which isn't quite "live" but can
> still be used to some extent to see the values of local vars (and
> `current-buffer`) in the different activation frames.

I think that's very ambitious.  Surely the time for that is when the
basic backtrace functionality is working, and the need for something
more sopisticated is perceived.

> BTW, in order to debug fontification errors, we also have
> `jit-locak-debug-mode` which postpones the jit/font-lock execution from
> within redisplay to "just a bit later" such that it can use the
> debugger.  IIRC it still has some rough edges in some cases, but in
> theory it should be possible to make this work such that you can (for
> example) Edebug `font-lock.el` itself.

It does work, though, doesn't it?

>         Stefan

-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Fontification error backtrace  [Was: Why is it so difficult to get a Lisp backtrace?]
  2022-07-02 20:27                         ` Alan Mackenzie
@ 2022-07-02 21:12                           ` Stefan Monnier
  0 siblings, 0 replies; 54+ messages in thread
From: Stefan Monnier @ 2022-07-02 21:12 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: Eli Zaretskii, larsi, emacs-devel

Alan Mackenzie [2022-07-02 20:27:32] wrote:
> Hello, Stefan.
> On Thu, Jun 30, 2022 at 16:34:23 -0400, Stefan Monnier wrote:
>> >> That's where we currently log all the errors during redisplay, since
>> >> Emacs 21.1.  So why this one is different?
>
>> > It's not a single line, or a small number of lines.  It could potentially
>> > be a stack overflow error, generating a large backtrace, which might have
>> > more lines than message-log-max.
>> How 'bout stashing the backtrace data in a var (without turning it into
>> text yet; that .....
> Does "that" mean the stashing or the turning into text?

It meant "stashing".

>> .... should always be quick, safe, and painless) and then adding a
>> button in the *Messages* (or *Warnings*) next to the error itself such
>> that pressing the buttong shows you the actual backtrace?
> I'm not sure the stashing of the backtrace data is a good idea.  It's
> likely to be BIG (see above), and it's fragile

I don't see the "above" that explains why you think it's going to
be BIG.  Neither do I understand why you think it would be fragile?

> - the only place to record it is in signal_or_quit, before it gets
> discarded by a call to unwind_to_catch.  The need to know its precise
> format is obviated by the C function mapbacktrace, so we might as well
> just dump out the backtrace as text in *Backtrace* at the first
> opportunity.  There's nothing time critical about the said dumping.

As I mentioned in another message, I think it would make sense to
"always" stash backtraces when we get an unhandled error, and then to
offer a way to investigate these "post mortem".  Think of it as Emacs's
equivalent of Unix's core dumps.

>> We could even try and get fancy: instead of only stashing the
>> `backtrace-frames`, we could also stash a copy of the specpdl so we
>> could bring up a *Backtrace* buffer which isn't quite "live" but can
>> still be used to some extent to see the values of local vars (and
>> `current-buffer`) in the different activation frames.
> I think that's very ambitious.

I don't think it's should be terribly hard.  We already support multiple
specpdl and switching between them for the purpose of threads, so most
of the delicate part of the code is already written.

>> BTW, in order to debug fontification errors, we also have
>> `jit-locak-debug-mode` which postpones the jit/font-lock execution from
>> within redisplay to "just a bit later" such that it can use the
>> debugger.  IIRC it still has some rough edges in some cases, but in
>> theory it should be possible to make this work such that you can (for
>> example) Edebug `font-lock.el` itself.
>
> It does work, though, doesn't it?

Yes.


        Stefan




^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Fontification error backtrace  [Was: Why is it so difficult to get a Lisp backtrace?]
  2022-07-02  5:53                             ` Eli Zaretskii
@ 2022-07-02 21:15                               ` Stefan Monnier
  0 siblings, 0 replies; 54+ messages in thread
From: Stefan Monnier @ 2022-07-02 21:15 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: acm, larsi, emacs-devel

> Then what did you mean by "some rough edges in some cases"?

I have a vague recollection of bumping into problems where
font-lock would keep being invoked while we're inside the debugger,
making it hard to see what happens in what order.


        Stefan




^ permalink raw reply	[flat|nested] 54+ messages in thread

* Redisplay hook error bactraces [Was: Fontification error backtrace]
  2022-06-29 19:00                       ` Eli Zaretskii
@ 2022-07-12 19:48                         ` Alan Mackenzie
  2022-07-13 12:33                           ` Eli Zaretskii
  2022-07-13 19:13                           ` Redisplay hook error bactraces [Was: Fontification error backtrace] Stefan Monnier
  0 siblings, 2 replies; 54+ messages in thread
From: Alan Mackenzie @ 2022-07-12 19:48 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: larsi, Stefan Monnier, emacs-devel, acm

Hello, Eli.

I've amended the patch I previously submitted considerably.  In
particular, it will generate a backtrace for a Lisp error in (almost) any
hook called from redisplay.  The exception is
redisplay-end-trigger-functions, which is obsolete, being used
(hopefully) only by the obsolete lazy-lock.

To use it, set the variable backtrace-on-redisplay-error to t.
Optionally, configure redisplay-last-error, which determines which
backtrace(s) to keep, should a command cause more than one.

In detail....

On Wed, Jun 29, 2022 at 22:00:54 +0300, Eli Zaretskii wrote:
> > Date: Tue, 28 Jun 2022 19:31:59 +0000
> > Cc: larsi@gnus.org, emacs-devel@gnu.org
> > From: Alan Mackenzie <acm@muc.de>

[ .... ]

> > I'm currently using the buffer *Backtrace*.  Is there anything wrong
> > with that?

> Not that I see, no.

OK, *Backtrace* it is.

> > > The buffer with warnings pops up very soon after redisplay, which is
> > > more-or-less exactly what you wanted?

> > OK, I've never used it.  On reading the documentation, I expected it to
> > wait until after the next command.

> That's because the ELisp reference manual describes it from the POV of
> setting up the warning as part of some command.  And even then, it
> says:

>      The value of this variable is a list of warnings to be displayed
>      after the current command has finished.

> "Current command", not "next command".

> If you add a warning to the list inside redisplay, it will pop up
> after redisplay returns.

I've tried to use it, but the list of warnings does indeed wait until
after the next command before being displayed.  I think the command loop
calls the functions for it between post-command-hook and redisplay.
Maybe the command loop could call it after redisplay as well.

> > Now I remember why I created the backtrace in signal_or_quit - it needs
> > to be done before the stack gets unwound, which happens later in
> > signal_or_quit.  On return to save_eval_handler is just too late for
> > this.

> That's orthogonal, I think?  You can collect the data inside
> signal_or_quit, and the signal handler then only needs to handle the
> error gracefully after it adds the warning.

The "handling" of the error is to allow the redisplay to continue, just
as though no backtrace were generated.  Since there's no possibility of
user interaction or a recursive redisplay, that should be OK.

> > > It will, because any Lisp we call goes though safe_eval.  The only
> > > reason why your original proposal didn't was because it bound the
> > > variable which enables this only in handle_fontified_prop.  But we
> > > don't need this flag if the logic to decide what to do with the error
> > > will be in save_eval_handler.

> > Then the mechanism I've implemented, namely to set redisplay_deep_handler
> > to the handler which would handle an error, could be made to serve for
> > other sorts of Lisp code.

> It could, but I see no reason for having a new handler.

Sorry, I didn't express that well.  That variable redisplay_deep_handler
merely records the handler that a condition-case might use.  In
signal_or_quit, if the handler about to be used matches
redisplay_deep_handler, then a backtrace gets generated.

> > > And you need to consider one more factor: code from display engine,
> > > including fontification via handle_fontified_prop, is called also from
> > > user commands, not via redisplay_internal.  For example, commands like
> > > C-n and C-v invoke display code.  Errors signaled there _can_ be left
> > > to go to top-level and probably could call the debugger.  To test
> > > whether any given code was called from redisplay_internal, test the
> > > value of the variable redisplaying_p.

> > OK, this is a further complication.  But it would be good to get to the
> > debugger if possible, rather than just having a "historical" backtrace.

> If the display code is called from "normal" commands, you can.

I haven't attended to this possibility, yet, but I don't foresee it as
being particularly difficult or time-consuming.

I also still need to write documentation for the new code.

Anyhow, here's the current state of the patch, which still clearly isn't
finished:



diff --git a/lisp/cus-start.el b/lisp/cus-start.el
index df919fd715..f5f52a2076 100644
--- a/lisp/cus-start.el
+++ b/lisp/cus-start.el
@@ -265,6 +265,10 @@ minibuffer-prompt-properties--setter
 	     (debug-ignored-errors debug (repeat (choice symbol regexp)))
 	     (debug-on-quit debug boolean)
 	     (debug-on-signal debug boolean)
+             (redisplay-last-error debug
+                                   (choice (const :tag "First backtrace only" nil)
+                                           (const :tag "Last backtrace only" t)
+                                           (const :tag "All backtraces" 'many)))
              (debugger-stack-frame-as-list debugger boolean "26.1")
 	     ;; fileio.c
 	     (delete-by-moving-to-trash auto-save boolean "23.1")
diff --git a/src/eval.c b/src/eval.c
index 141d2546f0..50982980cb 100644
--- a/src/eval.c
+++ b/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;
@@ -333,7 +339,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);
@@ -1550,14 +1557,18 @@ internal_condition_case_n (Lisp_Object (*bfun) (ptrdiff_t, Lisp_Object *),
 			   Lisp_Object handlers,
 			   Lisp_Object (*hfun) (Lisp_Object err,
 						ptrdiff_t nargs,
-						Lisp_Object *args))
+						Lisp_Object *args),
+			   bool note_handler)
 {
   struct handler *c = push_handler (handlers, CONDITION_CASE);
+  if (note_handler)
+    redisplay_deep_handler = c;
   if (sys_setjmp (c->jmp))
     {
       Lisp_Object val = handlerlist->val;
       clobbered_eassert (handlerlist == c);
       handlerlist = handlerlist->next;
+      redisplay_deep_handler = NULL;
       return hfun (val, nargs, args);
     }
   else
@@ -1565,6 +1576,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 = NULL;
       return val;
     }
 }
@@ -1697,6 +1709,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.
@@ -1812,6 +1829,42 @@ 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 *Backtrace*.  */
+  if (!debugger_called && !NILP (error_symbol)
+      && redisplay_lisping
+      && backtrace_on_redisplay_lisp_error
+      && (!backtrace_yet || !NILP (Vredisplay_last_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 (backtrace, "*Backtrace*");
+      Lisp_Object backtrace_buffer;
+      AUTO_STRING (gap, "\n\n\n\n"); /* Separates backtraces in *Backtrace* */
+      Lisp_Object delayed_warning;
+      max_ensure_room (&max_specpdl_size, counti, 200);
+      backtrace_buffer = Fget_buffer_create (backtrace, Qnil);
+      current_buffer = XBUFFER (backtrace_buffer);
+      if (!backtrace_yet
+	  || EQ (Vredisplay_last_error, Qt))
+	Ferase_buffer ();
+      else if (!EQ (Vredisplay_last_error, Qt))
+	Finsert (1, &gap);
+      backtrace_yet = true;
+      specbind (Qstandard_output, backtrace_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 *Backtrace*", 55);
+
+      Vdelayed_warnings_list = Fcons (list2 (Qerror, delayed_warning),
+				      Vdelayed_warnings_list);
+    }
+
   if (!NILP (clause))
     {
       Lisp_Object unwind_data
@@ -4274,6 +4327,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-lisp-error", backtrace_on_redisplay_lisp_error,
+	       doc: /* Non-nil means create a backtrace if a lisp error occurs in a redisplay hook.
+The backtrace is written to buffer *Backtrace*.  */);
+  backtrace_on_redisplay_lisp_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
@@ -4314,6 +4372,13 @@ message upon encountering an unhandled error, without showing
 the Lisp backtrace.  */);
   backtrace_on_error_noninteractive = true;
 
+  DEFVAR_LISP ("redisplay-last-error", Vredisplay_last_error,
+	       doc: /* Handling of *Backtrace* when several redisplay errors occur in one command.
+If nil (default), we write only the backtrace from the command's first error.
+If t, we save only the backtrace from the last error in the command.
+If any other value, we write backtraces for all errors in the command.  */);
+  Vredisplay_last_error = Qnil;
+
   /* The value of num_nonmacro_input_events as of the last time we
    started to enter the debugger.  If we decide to enter the debugger
    again when this is still equal to num_nonmacro_input_events, then we
diff --git a/src/keyboard.c b/src/keyboard.c
index c729d5dfb3..d95dc404c8 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -1330,6 +1330,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
@@ -1823,12 +1824,18 @@ adjust_point_for_property (ptrdiff_t last_pt, bool modified)
 }
 
 /* Subroutine for safe_run_hooks: run the hook, which is ARGS[1].  */
-
 static Lisp_Object
 safe_run_hooks_1 (ptrdiff_t nargs, Lisp_Object *args)
 {
-  eassert (nargs == 2);
-  return call0 (args[1]);
+  eassert (nargs >= 2 && nargs <= 4);
+  switch (nargs) {
+  case 2:
+    return call0 (args[1]);
+  case 3:
+    return call1 (args[1], args[2]);
+  default:
+    return call2 (args[1], args[2], args[3]);
+  }
 }
 
 /* Subroutine for safe_run_hooks: handle an error by clearing out the function
@@ -1837,7 +1844,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];
@@ -1877,7 +1884,7 @@ safe_run_hook_funcall (ptrdiff_t nargs, Lisp_Object *args)
   /* Yes, run_hook_with_args works with args in the other order.  */
   internal_condition_case_n (safe_run_hooks_1,
 			     2, ((Lisp_Object []) {args[1], args[0]}),
-			     Qt, safe_run_hooks_error);
+			     Qt, safe_run_hooks_error, false);
   return Qnil;
 }
 
@@ -1895,6 +1902,49 @@ safe_run_hooks (Lisp_Object hook)
   unbind_to (count, Qnil);
 }
 
+static Lisp_Object
+safe_run_hook_with_backtrace_funcall (ptrdiff_t nargs, Lisp_Object *args)
+{
+  Lisp_Object args_out [4];
+
+  eassert (nargs >= 2 && nargs <= 4);
+  args_out [0] = args[1];
+  args_out [1] = args[0];
+  if (nargs >= 3) args_out [2] = args [2];
+  if (nargs == 4) args_out [3] = args [3];
+  /* Yes, run_hook_with_args works with args in the other order.  */
+  internal_condition_case_n (safe_run_hooks_1,
+			     nargs, args_out,
+			     Qt, safe_run_hooks_error, true);
+  return Qnil;
+}
+
+/* Like safe_run_hooks, but in the event of a lisp error in a hook,
+   output a backtrace, should Emacs currently be configured for
+   this (backtrace-on-redisplay-lisp-error is non-nil).  */
+void
+safe_run_hooks_with_backtrace (Lisp_Object hook)
+{
+  specpdl_ref count = SPECPDL_INDEX ();
+
+  specbind (Qinhibit_quit, Qt);
+  run_hook_with_args (2, ((Lisp_Object []) {hook, hook}),
+		      safe_run_hook_with_backtrace_funcall);
+  unbind_to (count, Qnil);
+}
+
+void
+safe_run_hooks_2_with_backtrace (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_with_backtrace_funcall);
+  unbind_to (count, Qnil);
+}
+
 \f
 /* Nonzero means polling for input is temporarily suppressed.  */
 
diff --git a/src/lisp.h b/src/lisp.h
index dc496cc165..2227349d04 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -4526,6 +4526,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:
@@ -4562,7 +4563,7 @@ extern Lisp_Object internal_condition_case_1 (Lisp_Object (*) (Lisp_Object), Lis
 extern Lisp_Object internal_condition_case_2 (Lisp_Object (*) (Lisp_Object, Lisp_Object), Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object (*) (Lisp_Object));
 extern Lisp_Object internal_condition_case_n
     (Lisp_Object (*) (ptrdiff_t, Lisp_Object *), ptrdiff_t, Lisp_Object *,
-     Lisp_Object, Lisp_Object (*) (Lisp_Object, ptrdiff_t, Lisp_Object *));
+     Lisp_Object, Lisp_Object (*) (Lisp_Object, ptrdiff_t, Lisp_Object *), bool);
 extern Lisp_Object internal_catch_all (Lisp_Object (*) (void *), void *, Lisp_Object (*) (enum nonlocal_exit, Lisp_Object));
 extern struct handler *push_handler (Lisp_Object, enum handlertype)
   ATTRIBUTE_RETURNS_NONNULL;
@@ -4823,6 +4824,9 @@ extern bool detect_input_pending (void);
 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_with_backtrace (Lisp_Object);
+extern void safe_run_hooks_2_with_backtrace (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);
diff --git a/src/xdisp.c b/src/xdisp.c
index f205327cc3..5c4afa4a8d 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -3022,7 +3022,7 @@ safe__call (bool inhibit_quit, ptrdiff_t nargs, Lisp_Object func, va_list ap)
       /* Use Qt to ensure debugger does not run,
 	 so there is no possibility of wanting to redisplay.  */
       val = internal_condition_case_n (Ffuncall, nargs, args, Qt,
-				       safe_eval_handler);
+				       safe_eval_handler, true);
       val = SAFE_FREE_UNBIND_TO (count, val);
     }
 
@@ -4332,6 +4332,7 @@ handle_fontified_prop (struct it *it)
 
       val = Vfontification_functions;
       specbind (Qfontification_functions, Qnil);
+      specbind (Qredisplay_lisping, Qt);
 
       eassert (it->end_charpos == ZV);
 
@@ -12628,6 +12629,7 @@ set_message (Lisp_Object string)
     {
       specpdl_ref count = SPECPDL_INDEX ();
       specbind (Qinhibit_quit, Qt);
+      specbind (Qredisplay_lisping, Qt);
       message = safe_call1 (Vset_message_function, string);
       unbind_to (count, Qnil);
 
@@ -12705,7 +12707,8 @@ clear_message (bool current_p, bool last_displayed_p)
         {
           specpdl_ref count = SPECPDL_INDEX ();
           specbind (Qinhibit_quit, Qt);
-          preserve = safe_call (1, Vclear_message_function);
+	  specbind (Qredisplay_lisping, Qt);
+	  preserve = safe_call (1, Vclear_message_function);
           unbind_to (count, Qnil);
         }
 
@@ -13289,6 +13292,7 @@ prepare_menu_bars (void)
 {
   bool all_windows = windows_or_buffers_changed || update_mode_lines;
   bool some_windows = REDISPLAY_SOME_P ();
+  specpdl_ref count = SPECPDL_INDEX ();
 
   if (FUNCTIONP (Vpre_redisplay_function))
     {
@@ -13306,7 +13310,9 @@ prepare_menu_bars (void)
 		windows = Fcons (this, windows);
 	    }
 	}
+      specbind (Qredisplay_lisping, Qt);
       safe__call1 (true, Vpre_redisplay_function, windows);
+      unbind_to (count, Qnil);
     }
 
   /* Update all frame titles based on their buffer names, etc.  We do
@@ -13463,12 +13469,11 @@ update_menu_bar (struct frame *f, bool save_match_data, bool hooks_run)
 
 	  if (!hooks_run)
 	    {
-	      /* Run the Lucid hook.  */
-	      safe_run_hooks (Qactivate_menubar_hook);
+	      specbind (Qredisplay_lisping, Qt);
 
 	      /* If it has changed current-menubar from previous value,
 		 really recompute the menu-bar from the value.  */
-	      safe_run_hooks (Qmenu_bar_update_hook);
+	      safe_run_hooks_with_backtrace (Qmenu_bar_update_hook);
 
 	      hooks_run = true;
 	    }
@@ -17915,8 +17920,9 @@ 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)));
+      specbind (Qredisplay_lisping, Qt);
+      safe_run_hooks_2_with_backtrace
+	(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.  */
@@ -19419,6 +19425,7 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
 	 now actually do it.  */
       if (new_vpos >= 0)
 	{
+	  specpdl_ref count = SPECPDL_INDEX ();
 	  struct glyph_row *row;
 
 	  row = MATRIX_FIRST_TEXT_ROW (w->desired_matrix);
@@ -19444,7 +19451,9 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
 	     propagated its info to `w' anyway.  */
 	  w->redisplay = false;
 	  XBUFFER (w->contents)->text->redisplay = false;
+	  specbind (Qredisplay_lisping, Qt);
 	  safe__call1 (true, Vpre_redisplay_function, Fcons (window, Qnil));
+	  unbind_to (count, Qnil);
 
 	  if (w->redisplay || XBUFFER (w->contents)->text->redisplay
 	      || ((EQ (Vdisplay_line_numbers, Qrelative)
@@ -26544,7 +26553,7 @@ display_mode_element (struct it *it, int depth, int field_width, int precision,
 						   Flength (elt),
 						   props,
 						   elt}),
-					       Qt, safe_eval_handler);
+					       Qt, safe_eval_handler, false);
 		    /* Add this item to mode_line_proptrans_alist.  */
 		    mode_line_proptrans_alist
 		      = Fcons (Fcons (elt, props),
@@ -35848,6 +35857,7 @@ be let-bound around code that needs to disable messages temporarily. */);
   DEFSYM (QCfile, ":file");
   DEFSYM (Qfontified, "fontified");
   DEFSYM (Qfontification_functions, "fontification-functions");
+  DEFSYM (Qredisplay_lisping, "redisplay-lisping");
 
   /* Name of the symbol which disables Lisp evaluation in 'display'
      properties.  This is used by enriched.el.  */
@@ -36365,6 +36375,10 @@ fontified regions the property `fontified'.  */);
   Vfontification_functions = Qnil;
   Fmake_variable_buffer_local (Qfontification_functions);
 
+  DEFVAR_BOOL ("redisplay-lisping", redisplay_lisping,
+    doc: /* Non-nil when a Lisp hook call from redisplay is in progress.  */);
+  redisplay_lisping = false;
+
   DEFVAR_BOOL ("unibyte-display-via-language-environment",
                unibyte_display_via_language_environment,
     doc: /* Non-nil means display unibyte text according to language environment.
diff --git a/src/xfns.c b/src/xfns.c
index 331f22763e..b026605053 100644
--- a/src/xfns.c
+++ b/src/xfns.c
@@ -3405,7 +3405,7 @@ x_xim_text_to_utf8_unix (XIMText *text, ptrdiff_t *length)
   waiting_for_input = false;
   arg = make_mint_ptr (&data);
   internal_condition_case_n (x_xim_text_to_utf8_unix_1, 1, &arg,
-			     Qt, x_xim_text_to_utf8_unix_2);
+			     Qt, x_xim_text_to_utf8_unix_2, false);
   waiting_for_input = was_waiting_for_input_p;
 
   *length = coding.produced;


-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error bactraces [Was: Fontification error backtrace]
  2022-07-12 19:48                         ` Redisplay hook error bactraces [Was: Fontification error backtrace] Alan Mackenzie
@ 2022-07-13 12:33                           ` Eli Zaretskii
  2022-07-13 18:41                             ` Redisplay hook error backtraces Alan Mackenzie
  2022-07-13 19:13                           ` Redisplay hook error bactraces [Was: Fontification error backtrace] Stefan Monnier
  1 sibling, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2022-07-13 12:33 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: larsi, monnier, emacs-devel

> Date: Tue, 12 Jul 2022 19:48:49 +0000
> Cc: larsi@gnus.org, Stefan Monnier <monnier@iro.umontreal.ca>,
>   emacs-devel@gnu.org, acm@muc.de
> From: Alan Mackenzie <acm@muc.de>
> 
> To use it, set the variable backtrace-on-redisplay-error to t.

There's no such variable in the patch.

> Optionally, configure redisplay-last-error, which determines which
> backtrace(s) to keep, should a command cause more than one.

Why do we need two variables for that? why not a single variable that
is not a boolean?

> > > OK, I've never used it.  On reading the documentation, I expected it to
> > > wait until after the next command.
> 
> > That's because the ELisp reference manual describes it from the POV of
> > setting up the warning as part of some command.  And even then, it
> > says:
> 
> >      The value of this variable is a list of warnings to be displayed
> >      after the current command has finished.
> 
> > "Current command", not "next command".
> 
> > If you add a warning to the list inside redisplay, it will pop up
> > after redisplay returns.
> 
> I've tried to use it, but the list of warnings does indeed wait until
> after the next command before being displayed.

I think it's a terminology issue: how do you define "next command" in
this context?

I'm telling you: we have code in the display engine that uses delayed
warnings, and the warnings pop up right away, not after one more
command.

> > > Now I remember why I created the backtrace in signal_or_quit - it needs
> > > to be done before the stack gets unwound, which happens later in
> > > signal_or_quit.  On return to save_eval_handler is just too late for
> > > this.
> 
> > That's orthogonal, I think?  You can collect the data inside
> > signal_or_quit, and the signal handler then only needs to handle the
> > error gracefully after it adds the warning.
> 
> The "handling" of the error is to allow the redisplay to continue, just
> as though no backtrace were generated.  Since there's no possibility of
> user interaction or a recursive redisplay, that should be OK.

I don't understand this response.  It seems to be unrelated to what I
wrote.

> > > Then the mechanism I've implemented, namely to set redisplay_deep_handler
> > > to the handler which would handle an error, could be made to serve for
> > > other sorts of Lisp code.
> 
> > It could, but I see no reason for having a new handler.
> 
> Sorry, I didn't express that well.  That variable redisplay_deep_handler
> merely records the handler that a condition-case might use.  In
> signal_or_quit, if the handler about to be used matches
> redisplay_deep_handler, then a backtrace gets generated.

??? You use a handler as a flag?  Then why not just use a simple flag
variable?

> +  /* If an error is signalled during a Lisp hook in redisplay, write a
> +     backtrace into the buffer *Backtrace*.  */
> +  if (!debugger_called && !NILP (error_symbol)
> +      && redisplay_lisping
> +      && backtrace_on_redisplay_lisp_error
> +      && (!backtrace_yet || !NILP (Vredisplay_last_error))
> +      && (NILP (clause) || h == redisplay_deep_handler)
> +      && NILP (Vinhibit_debugger)
> +      && !NILP (Ffboundp (Qdebug_early)))

Why do we need so many conditions and flags?  Why do we need a new
variable redisplay_lisping, when we already have redisplaying_p?  Why
do you need to compare both redisplay_deep_handler and
backtrace_on_redisplay_lisp_error?  And what is the test of
backtrace_yet about?

I still hope this could be done more elegantly and with fewer changes
to infrastructure.

Thanks.



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error backtraces
  2022-07-13 12:33                           ` Eli Zaretskii
@ 2022-07-13 18:41                             ` Alan Mackenzie
  2022-07-13 19:00                               ` Eli Zaretskii
  0 siblings, 1 reply; 54+ messages in thread
From: Alan Mackenzie @ 2022-07-13 18:41 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: larsi, monnier, emacs-devel

Hello, Eli.

On Wed, Jul 13, 2022 at 15:33:07 +0300, Eli Zaretskii wrote:
> > Date: Tue, 12 Jul 2022 19:48:49 +0000
> > Cc: larsi@gnus.org, Stefan Monnier <monnier@iro.umontreal.ca>,
> >   emacs-devel@gnu.org, acm@muc.de
> > From: Alan Mackenzie <acm@muc.de>

> > To use it, set the variable backtrace-on-redisplay-error to t.

> There's no such variable in the patch.

Apologies.  I meant backtrace-on-redisplay-lisp-error.

> > Optionally, configure redisplay-last-error, which determines which
> > backtrace(s) to keep, should a command cause more than one.

> Why do we need two variables for that? why not a single variable that
> is not a boolean?

I was anticipating setting backtrace-on-redisplay-lisp-error
"dynamically" in debug-next-command, the command I introduced right at
the start of this thread ~3 weeks ago, and which would set all possible
flags to maximise the chance of getting a backtrace on the next command.
Such flags really need to be just nil/t.  Additionally,
redisplay-last-error allows a bit of configuration.

But I suppose if this is too many variables, we could cut one of them
out.

[ .... ]

> > > If you add a warning to the list inside redisplay, it will pop up
> > > after redisplay returns.

> > I've tried to use it, but the list of warnings does indeed wait until
> > after the next command before being displayed.

> I think it's a terminology issue: how do you define "next command" in
> this context?

I set up jit-lock-functions to give an error when a C-Mode buffer is
scrolled.  I enable the new mechanism.  I scroll the buffer.  I then see
the expected error in the echo area.  I type a random key.  This now
opens a window with *Warnings* in it, the sole line there being the error
I've just generated.  This is on a Linux console.

> I'm telling you: we have code in the display engine that uses delayed
> warnings, and the warnings pop up right away, not after one more
> command.

There is something different between what I am doing and what the
existing code in the display engine does.

But this doesn't seem the most pressing of problems with my patch.  Not
yet.

> > > > Now I remember why I created the backtrace in signal_or_quit - it
> > > > needs to be done before the stack gets unwound, which happens
> > > > later in signal_or_quit.  On return to save_eval_handler is just
> > > > too late for this.

> > > That's orthogonal, I think?  You can collect the data inside
> > > signal_or_quit, and the signal handler then only needs to handle
> > > the error gracefully after it adds the warning.

> > The "handling" of the error is to allow the redisplay to continue,
> > just as though no backtrace were generated.  Since there's no
> > possibility of user interaction or a recursive redisplay, that should
> > be OK.

> I don't understand this response.  It seems to be unrelated to what I
> wrote.

The backtrace is generated directly from signal_or_quit, so there is no
further collection of data to be done here; it's written to the buffer
*Backtrace*.  There is then no further handling to be done[*], except to
issue a delayed warning.  Maybe this could be better done in
internal_condition_case_n, thus taking some code lines out of
signal_or_quit.

[*] There's still the condition-case handling to be done, which would
have been needed regardless of the backtrace.

> > > > Then the mechanism I've implemented, namely to set
> > > > redisplay_deep_handler to the handler which would handle an
> > > > error, could be made to serve for other sorts of Lisp code.

> > > It could, but I see no reason for having a new handler.

> > Sorry, I didn't express that well.  That variable redisplay_deep_handler
> > merely records the handler that a condition-case might use.  In
> > signal_or_quit, if the handler about to be used matches
> > redisplay_deep_handler, then a backtrace gets generated.

> ??? You use a handler as a flag?  Then why not just use a simple flag
> variable?

The problem is that other signals go through the same C code in
signal_and_quit, for example "smaller" condition-cases.  We do not want
to generate backtraces for these routine condition-cases which simply get
handled by their Lisp code.  redisplay_deep_handler records the "deepest"
pending condition-case, and only if the variable `h' matches that deepest
condition case do we have an error that we want to output a backtrace for.

> > +  /* If an error is signalled during a Lisp hook in redisplay, write a
> > +     backtrace into the buffer *Backtrace*.  */
> > +  if (!debugger_called && !NILP (error_symbol)
> > +      && redisplay_lisping
> > +      && backtrace_on_redisplay_lisp_error
> > +      && (!backtrace_yet || !NILP (Vredisplay_last_error))
> > +      && (NILP (clause) || h == redisplay_deep_handler)
> > +      && NILP (Vinhibit_debugger)
> > +      && !NILP (Ffboundp (Qdebug_early)))

> Why do we need so many conditions and flags?  Why do we need a new
> variable redisplay_lisping, when we already have redisplaying_p?

Thanks for asking that.  redisplay_lisping is actually redundant, since
the state it records (being in a running Lisp hook) is trivially
reflected in other variables.  So I've removed it from the code.

> Why do you need to compare both redisplay_deep_handler and
> backtrace_on_redisplay_lisp_error?

r_d_h checks whether we've actually got a pertinent Lisp error (see
above).  b_o_r_l_e is a toggle set by the user to enable the feature.

> And what is the test of backtrace_yet about?

When a backtrace is being generated, it first erases *Backtrace*.  We
only want to do that for the first backtrace generated by the current
command.  So backtrace_yet records whether we've already generated a BT
in this command.  It is reset to false in the command loop.

This ensures that the user sees that first backtrace, which is
likely to be the most interesting one.  Unless she has configured
backtrace-last-error to do something different.

As an alternative to this complicated configuration, perhaps we could
just erase *Backtrace* for the first BT, then write any further BTs to
*Backtrace*, too.

> I still hope this could be done more elegantly and with fewer changes
> to infrastructure.

You mean, all the changes in eval.c and keyboard.c?  I think the changes
to internal_condition_case_n are essential to the patch, and I honestly
don't think it can be done much more elegantly, but I'm open to
suggestions.

Thanks for reviewing the patch (again) so thoroughly.

> Thanks.

-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error backtraces
  2022-07-13 18:41                             ` Redisplay hook error backtraces Alan Mackenzie
@ 2022-07-13 19:00                               ` Eli Zaretskii
  2022-07-13 20:12                                 ` Alan Mackenzie
  0 siblings, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2022-07-13 19:00 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: larsi, monnier, emacs-devel

> Date: Wed, 13 Jul 2022 18:41:01 +0000
> Cc: larsi@gnus.org, monnier@iro.umontreal.ca, emacs-devel@gnu.org
> From: Alan Mackenzie <acm@muc.de>
> 
> The problem is that other signals go through the same C code in
> signal_and_quit, for example "smaller" condition-cases.  We do not want
> to generate backtraces for these routine condition-cases which simply get
> handled by their Lisp code.  redisplay_deep_handler records the "deepest"
> pending condition-case, and only if the variable `h' matches that deepest
> condition case do we have an error that we want to output a backtrace for.

I still don't think I understand why testing redisplaying_p and the
new optional variable would not be enough.

> > And what is the test of backtrace_yet about?
> 
> When a backtrace is being generated, it first erases *Backtrace*.  We
> only want to do that for the first backtrace generated by the current
> command.  So backtrace_yet records whether we've already generated a BT
> in this command.  It is reset to false in the command loop.
> 
> This ensures that the user sees that first backtrace, which is
> likely to be the most interesting one.  Unless she has configured
> backtrace-last-error to do something different.
> 
> As an alternative to this complicated configuration, perhaps we could
> just erase *Backtrace* for the first BT, then write any further BTs to
> *Backtrace*, too.

Either that, or erase it every time.  I think anything more complex is
over-engineered.

> > I still hope this could be done more elegantly and with fewer changes
> > to infrastructure.
> 
> You mean, all the changes in eval.c and keyboard.c?  I think the changes
> to internal_condition_case_n are essential to the patch, and I honestly
> don't think it can be done much more elegantly, but I'm open to
> suggestions.

Can we discuss how to implement it without introducing a special
handler and without adding new safe_run_hooks_* functions?



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error bactraces [Was: Fontification error backtrace]
  2022-07-12 19:48                         ` Redisplay hook error bactraces [Was: Fontification error backtrace] Alan Mackenzie
  2022-07-13 12:33                           ` Eli Zaretskii
@ 2022-07-13 19:13                           ` Stefan Monnier
  2022-07-13 19:24                             ` Eli Zaretskii
  2022-07-13 19:58                             ` Alan Mackenzie
  1 sibling, 2 replies; 54+ messages in thread
From: Stefan Monnier @ 2022-07-13 19:13 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: Eli Zaretskii, larsi, emacs-devel

> +  /* If an error is signalled during a Lisp hook in redisplay, write a
> +     backtrace into the buffer *Backtrace*.  */
> +  if (!debugger_called && !NILP (error_symbol)
> +      && redisplay_lisping
> +      && backtrace_on_redisplay_lisp_error
> +      && (!backtrace_yet || !NILP (Vredisplay_last_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 (backtrace, "*Backtrace*");
> +      Lisp_Object backtrace_buffer;
> +      AUTO_STRING (gap, "\n\n\n\n"); /* Separates backtraces in *Backtrace* */

I really think that printing the backtrace right when the error is
signaled is a bad idea.  Better just copy the whole specpdl (or just the
backtrace part of it) and leave the buffer manipulation and printing for
later when we're back in a normal&sane context.

It would be worthwhile doing all this work in the context of the error
if there was an upside such as the ability to do the debugging "live",
but since this is for the case where we'll be doing a "post-mortem
debugging" anyway, we're much better off doing as little as possible in
the context of the error: too many things can go wrong when running in
the context of the error, it's asking for trouble.


        Stefan




^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error bactraces [Was: Fontification error backtrace]
  2022-07-13 19:13                           ` Redisplay hook error bactraces [Was: Fontification error backtrace] Stefan Monnier
@ 2022-07-13 19:24                             ` Eli Zaretskii
  2022-07-13 19:58                             ` Alan Mackenzie
  1 sibling, 0 replies; 54+ messages in thread
From: Eli Zaretskii @ 2022-07-13 19:24 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: acm, larsi, emacs-devel

> From: Stefan Monnier <monnier@iro.umontreal.ca>
> Cc: Eli Zaretskii <eliz@gnu.org>,  larsi@gnus.org,  emacs-devel@gnu.org
> Date: Wed, 13 Jul 2022 15:13:41 -0400
> 
> I really think that printing the backtrace right when the error is
> signaled is a bad idea.  Better just copy the whole specpdl (or just the
> backtrace part of it) and leave the buffer manipulation and printing for
> later when we're back in a normal&sane context.

I agree.



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error bactraces [Was: Fontification error backtrace]
  2022-07-13 19:13                           ` Redisplay hook error bactraces [Was: Fontification error backtrace] Stefan Monnier
  2022-07-13 19:24                             ` Eli Zaretskii
@ 2022-07-13 19:58                             ` Alan Mackenzie
  2022-07-13 20:45                               ` Stefan Monnier
  1 sibling, 1 reply; 54+ messages in thread
From: Alan Mackenzie @ 2022-07-13 19:58 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Eli Zaretskii, larsi, emacs-devel

Hello, Stefan.

On Wed, Jul 13, 2022 at 15:13:41 -0400, Stefan Monnier wrote:
> > +  /* If an error is signalled during a Lisp hook in redisplay, write a
> > +     backtrace into the buffer *Backtrace*.  */
> > +  if (!debugger_called && !NILP (error_symbol)
> > +      && redisplay_lisping
> > +      && backtrace_on_redisplay_lisp_error
> > +      && (!backtrace_yet || !NILP (Vredisplay_last_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 (backtrace, "*Backtrace*");
> > +      Lisp_Object backtrace_buffer;
> > +      AUTO_STRING (gap, "\n\n\n\n"); /* Separates backtraces in *Backtrace* */

> I really think that printing the backtrace right when the error is
> signaled is a bad idea.  Better just copy the whole specpdl (or just the
> backtrace part of it) and leave the buffer manipulation and printing for
> later when we're back in a normal&sane context.

What you suggest would imply a _lot_ of coding to make it happen.  Does
there exist anywhere any support for copyging a specpdl, and somehow
making a lisp object out of it?  Why do you think that copying the
specpdl would be less work or less risky than directly generating a
backtrace, as the current code does?

The advantage of directly generating the backtrace is that all the
infrastructure is already there.  We've got mapbacktrace which
encapsulates the formats of the specpdl.  And debug-early.el, whose two
functions total 60 lines (including doc strings and comments) and don't
use any other Lisp whatsoever.

> It would be worthwhile doing all this work in the context of the error
> if there was an upside such as the ability to do the debugging "live",
> but since this is for the case where we'll be doing a "post-mortem
> debugging" anyway, we're much better off doing as little as possible in
> the context of the error: too many things can go wrong when running in
> the context of the error, it's asking for trouble.

Again, why is copying the pdl less risky than generating the backtrace?

>         Stefan

-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error backtraces
  2022-07-13 19:00                               ` Eli Zaretskii
@ 2022-07-13 20:12                                 ` Alan Mackenzie
  2022-07-13 20:49                                   ` Stefan Monnier
  2022-07-14  5:12                                   ` Eli Zaretskii
  0 siblings, 2 replies; 54+ messages in thread
From: Alan Mackenzie @ 2022-07-13 20:12 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: larsi, monnier, emacs-devel

Hello, Eli.

On Wed, Jul 13, 2022 at 22:00:14 +0300, Eli Zaretskii wrote:
> > Date: Wed, 13 Jul 2022 18:41:01 +0000
> > Cc: larsi@gnus.org, monnier@iro.umontreal.ca, emacs-devel@gnu.org
> > From: Alan Mackenzie <acm@muc.de>

> > The problem is that other signals go through the same C code in
> > signal_and_quit, for example "smaller" condition-cases.  We do not want
> > to generate backtraces for these routine condition-cases which simply get
> > handled by their Lisp code.  redisplay_deep_handler records the "deepest"
> > pending condition-case, and only if the variable `h' matches that deepest
> > condition case do we have an error that we want to output a backtrace for.

> I still don't think I understand why testing redisplaying_p and the
> new optional variable would not be enough.

We've got to distinguish between the signals we want to generate
backtraces for and those we don't.  redisplaying_p is not relevant to
that, I think.  For example, we don't want to generate a backtrace for a
"failed" evaluation of the code generated by `c-safe'.

> > > And what is the test of backtrace_yet about?

> > When a backtrace is being generated, it first erases *Backtrace*.  We
> > only want to do that for the first backtrace generated by the current
> > command.  So backtrace_yet records whether we've already generated a BT
> > in this command.  It is reset to false in the command loop.

> > This ensures that the user sees that first backtrace, which is
> > likely to be the most interesting one.  Unless she has configured
> > backtrace-last-error to do something different.

> > As an alternative to this complicated configuration, perhaps we could
> > just erase *Backtrace* for the first BT, then write any further BTs to
> > *Backtrace*, too.

> Either that, or erase it every time.  I think anything more complex is
> over-engineered.

OK, I'll get rid of the config variable, and just dump every backtrace to
*Backtrace*.  That will get rid of quite a few lines of code.

> > > I still hope this could be done more elegantly and with fewer changes
> > > to infrastructure.

> > You mean, all the changes in eval.c and keyboard.c?  I think the changes
> > to internal_condition_case_n are essential to the patch, and I honestly
> > don't think it can be done much more elegantly, but I'm open to
> > suggestions.

> Can we discuss how to implement it without introducing a special
> handler and without adding new safe_run_hooks_* functions?

OK.  Perhaps with extra optional arguments, that kind of thing?

-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error bactraces [Was: Fontification error backtrace]
  2022-07-13 19:58                             ` Alan Mackenzie
@ 2022-07-13 20:45                               ` Stefan Monnier
  0 siblings, 0 replies; 54+ messages in thread
From: Stefan Monnier @ 2022-07-13 20:45 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: Eli Zaretskii, larsi, emacs-devel

> What you suggest would imply a _lot_ of coding to make it happen.  Does
> there exist anywhere any support for copyging a specpdl, and somehow
> making a lisp object out of it?  Why do you think that copying the
> specpdl would be less work or less risky than directly generating a
> backtrace, as the current code does?

I don't think it needs to be much coding.

Stashing the full specpdl would be more work, admittedly, but turning
the specpdl into a list via something like `backtrace-frames` is trivial
(we could even easily move `backtrace-frames` to C if we wanted to be
paranoid about running ELisp at that point).

> The advantage of directly generating the backtrace is that all the
> infrastructure is already there.  We've got mapbacktrace which
> encapsulates the formats of the specpdl.  And debug-early.el, whose two
> functions total 60 lines (including doc strings and comments) and don't
> use any other Lisp whatsoever.

That potentially runs a crapload of code via various hooks, many of
which can be influenced by variables dynamically bound in the context of
the error.

Moving the bulk of the work to a normal context means we can be much
more relaxed about using "fully featured" code, so there'll be much less
resistance to over-engineering :-)


        Stefan




^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error backtraces
  2022-07-13 20:12                                 ` Alan Mackenzie
@ 2022-07-13 20:49                                   ` Stefan Monnier
  2022-07-14  5:12                                   ` Eli Zaretskii
  1 sibling, 0 replies; 54+ messages in thread
From: Stefan Monnier @ 2022-07-13 20:49 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: Eli Zaretskii, larsi, emacs-devel

>> I still don't think I understand why testing redisplaying_p and the
>> new optional variable would not be enough.
>
> We've got to distinguish between the signals we want to generate
> backtraces for and those we don't.  redisplaying_p is not relevant to
> that, I think.  For example, we don't want to generate a backtrace for a
> "failed" evaluation of the code generated by `c-safe'.

BTW, `c-safe` is called `ignore-errors` in standard ELisp since
Emacs-23 (before that it was provided by `cl`).


        Stefan




^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error backtraces
  2022-07-13 20:12                                 ` Alan Mackenzie
  2022-07-13 20:49                                   ` Stefan Monnier
@ 2022-07-14  5:12                                   ` Eli Zaretskii
  2022-07-14  9:01                                     ` Alan Mackenzie
  1 sibling, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2022-07-14  5:12 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: larsi, monnier, emacs-devel

> Date: Wed, 13 Jul 2022 20:12:42 +0000
> Cc: larsi@gnus.org, monnier@iro.umontreal.ca, emacs-devel@gnu.org
> From: Alan Mackenzie <acm@muc.de>
> 
> > I still don't think I understand why testing redisplaying_p and the
> > new optional variable would not be enough.
> 
> We've got to distinguish between the signals we want to generate
> backtraces for and those we don't.  redisplaying_p is not relevant to
> that, I think.  For example, we don't want to generate a backtrace for a
> "failed" evaluation of the code generated by `c-safe'.

You want to distinguish errors inside condition-case?  If they are not
already marked in some way that allows you to do so, I think it's
better to do it the other way around: bind some variable in
internal_lisp_condition_case before invoking the body.

> > > You mean, all the changes in eval.c and keyboard.c?  I think the changes
> > > to internal_condition_case_n are essential to the patch, and I honestly
> > > don't think it can be done much more elegantly, but I'm open to
> > > suggestions.
> 
> > Can we discuss how to implement it without introducing a special
> > handler and without adding new safe_run_hooks_* functions?
> 
> OK.  Perhaps with extra optional arguments, that kind of thing?

Maybe, I don't know.  I still think the job of signal_or_quit and
safe_run_hooks is almost the same when you want to collect backtrace,
so too many differences strike me as unexpected.



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error backtraces
  2022-07-14  5:12                                   ` Eli Zaretskii
@ 2022-07-14  9:01                                     ` Alan Mackenzie
  2022-07-14  9:10                                       ` Eli Zaretskii
  0 siblings, 1 reply; 54+ messages in thread
From: Alan Mackenzie @ 2022-07-14  9:01 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: larsi, monnier, emacs-devel

Hello, Eli.

On Thu, Jul 14, 2022 at 08:12:51 +0300, Eli Zaretskii wrote:
> > Date: Wed, 13 Jul 2022 20:12:42 +0000
> > Cc: larsi@gnus.org, monnier@iro.umontreal.ca, emacs-devel@gnu.org
> > From: Alan Mackenzie <acm@muc.de>

> > > I still don't think I understand why testing redisplaying_p and the
> > > new optional variable would not be enough.

> > We've got to distinguish between the signals we want to generate
> > backtraces for and those we don't.  redisplaying_p is not relevant to
> > that, I think.  For example, we don't want to generate a backtrace
> > for a "failed" evaluation of the code generated by `c-safe'.

> You want to distinguish errors inside condition-case?

More, distinguish the different condition-cases in which errors might
occur.

> If they are not already marked in some way that allows you to do so, I
> think it's better to do it the other way around: bind some variable in
> internal_lisp_condition_case before invoking the body.

OK, I think I see what you're getting at.  But this would mean binding
that variable at _every_ condition_case, and needing some way of deciding
which way to bind it (an extra argument in every use of
internal_lisp_condition_case, perhaps?).  Why would this be better than
the way I have already implemented?

> > > > You mean, all the changes in eval.c and keyboard.c?  I think the
> > > > changes to internal_condition_case_n are essential to the patch,
> > > > and I honestly don't think it can be done much more elegantly,
> > > > but I'm open to suggestions.

> > > Can we discuss how to implement it without introducing a special
> > > handler and without adding new safe_run_hooks_* functions?

I think we need the new function safe_run_hooks_2_with_backtrace (see
below), since there is currently no "safe" function for hooks with two
arguments.  But some of the other ones could disappear (see below).

> > OK.  Perhaps with extra optional arguments, that kind of thing?

> Maybe, I don't know.  I still think the job of signal_or_quit and
> safe_run_hooks is almost the same when you want to collect backtrace,
> so too many differences strike me as unexpected.

OK, I have an idea.  I restore the variable redisplay_lisping back into
the code (I took it out last night), binding it to true (or Qt?) at every
place in xdisp.c where redisplay calls a Lisp hook.  I then test that
variable in internal_condition_case_n in place of having the extra bool
argument to that function.

That would then get rid of the new functions
safe_run_hooks_with_backtrace_funcall and safe_run_hooks_with_backtrace.
We could also rename safe_run_hooks_2_with_backtrace by removing
"_with_backtrace" from the name.

What do you think?

-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error backtraces
  2022-07-14  9:01                                     ` Alan Mackenzie
@ 2022-07-14  9:10                                       ` Eli Zaretskii
  2022-07-14 13:42                                         ` Alan Mackenzie
  0 siblings, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2022-07-14  9:10 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: larsi, monnier, emacs-devel

> Date: Thu, 14 Jul 2022 09:01:16 +0000
> Cc: larsi@gnus.org, monnier@iro.umontreal.ca, emacs-devel@gnu.org
> From: Alan Mackenzie <acm@muc.de>
> 
> > You want to distinguish errors inside condition-case?
> 
> More, distinguish the different condition-cases in which errors might
> occur.

What else is exposed to Lisp?  We are talking about catching Lisp
errors only, right?

> > > > Can we discuss how to implement it without introducing a special
> > > > handler and without adding new safe_run_hooks_* functions?
> 
> I think we need the new function safe_run_hooks_2_with_backtrace (see
> below), since there is currently no "safe" function for hooks with two
> arguments.  But some of the other ones could disappear (see below).

What is the second argument, and why do we need it?

> OK, I have an idea.  I restore the variable redisplay_lisping back into
> the code (I took it out last night), binding it to true (or Qt?) at every
> place in xdisp.c where redisplay calls a Lisp hook.

These all go through a single function, so there's just one place to
do that.

> I then test that variable in internal_condition_case_n in place of
> having the extra bool argument to that function.
> 
> That would then get rid of the new functions
> safe_run_hooks_with_backtrace_funcall and safe_run_hooks_with_backtrace.
> We could also rename safe_run_hooks_2_with_backtrace by removing
> "_with_backtrace" from the name.
> 
> What do you think?

Sounds good, but let's see the code.

And I still would like you to explore Stefan's suggestion, since doing
too much non-trivial Lisp stuff in signal_or_quit is better avoided.

Thanks.



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error backtraces
  2022-07-14  9:10                                       ` Eli Zaretskii
@ 2022-07-14 13:42                                         ` Alan Mackenzie
  2022-07-14 13:59                                           ` Eli Zaretskii
  0 siblings, 1 reply; 54+ messages in thread
From: Alan Mackenzie @ 2022-07-14 13:42 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: larsi, monnier, emacs-devel

Hello, Eli.

On Thu, Jul 14, 2022 at 12:10:13 +0300, Eli Zaretskii wrote:
> > Date: Thu, 14 Jul 2022 09:01:16 +0000
> > Cc: larsi@gnus.org, monnier@iro.umontreal.ca, emacs-devel@gnu.org
> > From: Alan Mackenzie <acm@muc.de>

> > > You want to distinguish errors inside condition-case?

> > More, distinguish the different condition-cases in which errors might
> > occur.

> What else is exposed to Lisp?

I don't understand this question.

> We are talking about catching Lisp errors only, right?

Yes.

> > > > > Can we discuss how to implement it without introducing a
> > > > > special handler and without adding new safe_run_hooks_*
> > > > > functions?

> > I think we need the new function safe_run_hooks_2_with_backtrace (see
> > below), since there is currently no "safe" function for hooks with
> > two arguments.  But some of the other ones could disappear (see
> > below).

> What is the second argument, and why do we need it?

There are three arguments, the non-standard hook, and the two arguments
which will be fed to that hook.

Currently, that hook (window-scroll-functions) is called with
run_hook_with_args_2, which doesn't have a "safe" in its functionality,
which we need.  My change is to use safe_run_hooks_2 (a new function)
instead.

> > OK, I have an idea.  I restore the variable redisplay_lisping back
> > into the code (I took it out last night), binding it to true (or Qt?)
> > at every place in xdisp.c where redisplay calls a Lisp hook.

> These all go through a single function, so there's just one place to
> do that.

I disagree.  There are seven places, for the seven different Lisp hooks
currently called from redisplay.

> > I then test that variable in internal_condition_case_n in place of
> > having the extra bool argument to that function.

Done.

> > That would then get rid of the new functions
> > safe_run_hooks_with_backtrace_funcall and safe_run_hooks_with_backtrace.
> > We could also rename safe_run_hooks_2_with_backtrace by removing
> > "_with_backtrace" from the name.

Also done.

> > What do you think?

> Sounds good, but let's see the code.

See below.

> And I still would like you to explore Stefan's suggestion, since doing
> too much non-trivial Lisp stuff in signal_or_quit is better avoided.

I still don't understand this.  The Lisp in debug-early.el is about as
trivial as you can get whilst still doing anything at all.  It doesn't
use any other Lisp libraries, only the C primitives.  (At least, that was
the case, and could and should be restored.)

But can we talk about this in the other branch of this thread which
already goes into some detail about this, please?

Here's the current version of the patch:



diff --git a/src/dispextern.h b/src/dispextern.h
index ca7834dec5..af490f574e 100644
--- a/src/dispextern.h
+++ b/src/dispextern.h
@@ -3410,6 +3410,7 @@ int partial_line_height (struct it *it_origin);
 bool in_display_vector_p (struct it *);
 int frame_mode_line_height (struct frame *);
 extern bool redisplaying_p;
+extern bool redisplay_lisping;
 extern bool display_working_on_window_p;
 extern void unwind_display_working_on_window (void);
 extern bool help_echo_showing_p;
diff --git a/src/eval.c b/src/eval.c
index 141d2546f0..edadf64434 100644
--- a/src/eval.c
+++ b/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;
@@ -333,7 +339,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);
@@ -1553,11 +1560,14 @@ internal_condition_case_n (Lisp_Object (*bfun) (ptrdiff_t, Lisp_Object *),
 						Lisp_Object *args))
 {
   struct handler *c = push_handler (handlers, CONDITION_CASE);
+  if (redisplay_lisping)
+    redisplay_deep_handler = c;
   if (sys_setjmp (c->jmp))
     {
       Lisp_Object val = handlerlist->val;
       clobbered_eassert (handlerlist == c);
       handlerlist = handlerlist->next;
+      redisplay_deep_handler = NULL;
       return hfun (val, nargs, args);
     }
   else
@@ -1565,6 +1575,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 = NULL;
       return val;
     }
 }
@@ -1697,6 +1708,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.
@@ -1812,6 +1828,39 @@ 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 *Backtrace*.  */
+  if (!debugger_called && !NILP (error_symbol)
+      && backtrace_on_redisplay_lisp_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 (backtrace, "*Backtrace*");
+      Lisp_Object backtrace_buffer;
+      AUTO_STRING (gap, "\n\n\n\n"); /* Separates backtraces in *Backtrace* */
+      Lisp_Object delayed_warning;
+      max_ensure_room (&max_specpdl_size, counti, 200);
+      backtrace_buffer = Fget_buffer_create (backtrace, Qnil);
+      current_buffer = XBUFFER (backtrace_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, backtrace_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 *Backtrace*", 55);
+
+      Vdelayed_warnings_list = Fcons (list2 (Qerror, delayed_warning),
+				      Vdelayed_warnings_list);
+    }
+
   if (!NILP (clause))
     {
       Lisp_Object unwind_data
@@ -4274,6 +4323,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-lisp-error", backtrace_on_redisplay_lisp_error,
+	       doc: /* Non-nil means create a backtrace if a lisp error occurs in a redisplay hook.
+The backtrace is written to buffer *Backtrace*.  */);
+  backtrace_on_redisplay_lisp_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
diff --git a/src/keyboard.c b/src/keyboard.c
index c729d5dfb3..e886f571ce 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -1330,6 +1330,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
@@ -1823,12 +1824,18 @@ adjust_point_for_property (ptrdiff_t last_pt, bool modified)
 }
 
 /* Subroutine for safe_run_hooks: run the hook, which is ARGS[1].  */
-
 static Lisp_Object
 safe_run_hooks_1 (ptrdiff_t nargs, Lisp_Object *args)
 {
-  eassert (nargs == 2);
-  return call0 (args[1]);
+  eassert (nargs >= 2 && nargs <= 4);
+  switch (nargs) {
+  case 2:
+    return call0 (args[1]);
+  case 3:
+    return call1 (args[1], args[2]);
+  default:
+    return call2 (args[1], args[2], args[3]);
+  }
 }
 
 /* Subroutine for safe_run_hooks: handle an error by clearing out the function
@@ -1837,7 +1844,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];
@@ -1895,6 +1902,17 @@ safe_run_hooks (Lisp_Object hook)
   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);
+}
+
 \f
 /* Nonzero means polling for input is temporarily suppressed.  */
 
diff --git a/src/lisp.h b/src/lisp.h
index dc496cc165..7caf5e2745 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -4526,6 +4526,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:
@@ -4823,6 +4824,7 @@ extern bool detect_input_pending (void);
 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_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);
diff --git a/src/xdisp.c b/src/xdisp.c
index f205327cc3..092b1f2be2 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -1030,6 +1030,11 @@ static struct glyph_slice null_glyph_slice = { 0, 0, 0, 0 };
 
 bool redisplaying_p;
 
+/* True if a call to a Lisp hook from redisplay is currently in
+   progress.  */
+
+bool redisplay_lisping = false;
+
 /* True while some display-engine code is working on layout of some
    window.
 
@@ -3137,6 +3142,12 @@ CHECK_WINDOW_END (struct window *w)
 #endif
 }
 
+static void
+unwind_redisplay_lisping (int arg)
+{
+  redisplay_lisping = arg;
+}
+
 /***********************************************************************
 		       Iterator initialization
  ***********************************************************************/
@@ -4332,6 +4343,8 @@ handle_fontified_prop (struct it *it)
 
       val = Vfontification_functions;
       specbind (Qfontification_functions, Qnil);
+      record_unwind_protect_int (unwind_redisplay_lisping, redisplay_lisping);
+      redisplay_lisping = true;
 
       eassert (it->end_charpos == ZV);
 
@@ -12628,6 +12641,8 @@ set_message (Lisp_Object string)
     {
       specpdl_ref count = SPECPDL_INDEX ();
       specbind (Qinhibit_quit, Qt);
+      record_unwind_protect_int (unwind_redisplay_lisping, redisplay_lisping);
+      redisplay_lisping = true;
       message = safe_call1 (Vset_message_function, string);
       unbind_to (count, Qnil);
 
@@ -12705,6 +12720,8 @@ clear_message (bool current_p, bool last_displayed_p)
         {
           specpdl_ref count = SPECPDL_INDEX ();
           specbind (Qinhibit_quit, Qt);
+	  record_unwind_protect_int (unwind_redisplay_lisping, redisplay_lisping);
+	  redisplay_lisping = true;
           preserve = safe_call (1, Vclear_message_function);
           unbind_to (count, Qnil);
         }
@@ -13289,6 +13306,7 @@ prepare_menu_bars (void)
 {
   bool all_windows = windows_or_buffers_changed || update_mode_lines;
   bool some_windows = REDISPLAY_SOME_P ();
+  specpdl_ref count = SPECPDL_INDEX ();
 
   if (FUNCTIONP (Vpre_redisplay_function))
     {
@@ -13306,7 +13324,10 @@ prepare_menu_bars (void)
 		windows = Fcons (this, windows);
 	    }
 	}
+      record_unwind_protect_int (unwind_redisplay_lisping, redisplay_lisping);
+      redisplay_lisping = true;
       safe__call1 (true, Vpre_redisplay_function, windows);
+      unbind_to (count, Qnil);
     }
 
   /* Update all frame titles based on their buffer names, etc.  We do
@@ -13463,8 +13484,8 @@ update_menu_bar (struct frame *f, bool save_match_data, bool hooks_run)
 
 	  if (!hooks_run)
 	    {
-	      /* Run the Lucid hook.  */
-	      safe_run_hooks (Qactivate_menubar_hook);
+	      record_unwind_protect_int (unwind_redisplay_lisping, redisplay_lisping);
+	      redisplay_lisping = true;
 
 	      /* If it has changed current-menubar from previous value,
 		 really recompute the menu-bar from the value.  */
@@ -17915,8 +17936,10 @@ 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)));
+      record_unwind_protect_int (unwind_redisplay_lisping, redisplay_lisping);
+      redisplay_lisping = true;
+      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.  */
@@ -19419,6 +19442,7 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
 	 now actually do it.  */
       if (new_vpos >= 0)
 	{
+	  specpdl_ref count = SPECPDL_INDEX ();
 	  struct glyph_row *row;
 
 	  row = MATRIX_FIRST_TEXT_ROW (w->desired_matrix);
@@ -19444,7 +19468,10 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
 	     propagated its info to `w' anyway.  */
 	  w->redisplay = false;
 	  XBUFFER (w->contents)->text->redisplay = false;
+	  record_unwind_protect_int (unwind_redisplay_lisping, redisplay_lisping);
+	  redisplay_lisping = true;
 	  safe__call1 (true, Vpre_redisplay_function, Fcons (window, Qnil));
+	  unbind_to (count, Qnil);
 
 	  if (w->redisplay || XBUFFER (w->contents)->text->redisplay
 	      || ((EQ (Vdisplay_line_numbers, Qrelative)
@@ -36882,6 +36909,7 @@ init_xdisp (void)
   }
 
   help_echo_showing_p = false;
+  redisplay_lisping = false;
 }
 
 #ifdef HAVE_WINDOW_SYSTEM




> Thanks.

-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error backtraces
  2022-07-14 13:42                                         ` Alan Mackenzie
@ 2022-07-14 13:59                                           ` Eli Zaretskii
  2022-07-14 16:07                                             ` Alan Mackenzie
  0 siblings, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2022-07-14 13:59 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: larsi, monnier, emacs-devel

> Date: Thu, 14 Jul 2022 13:42:18 +0000
> Cc: larsi@gnus.org, monnier@iro.umontreal.ca, emacs-devel@gnu.org
> From: Alan Mackenzie <acm@muc.de>
> 
> > > > You want to distinguish errors inside condition-case?
> 
> > > More, distinguish the different condition-cases in which errors might
> > > occur.
> 
> > What else is exposed to Lisp?
> 
> I don't understand this question.

There's just one condition-case available to Lisp code, AFAICT, so
why isn't it enough to distinguish condition-case from any other
callers of internal_condition_case* ?

> > > I think we need the new function safe_run_hooks_2_with_backtrace (see
> > > below), since there is currently no "safe" function for hooks with
> > > two arguments.  But some of the other ones could disappear (see
> > > below).
> 
> > What is the second argument, and why do we need it?
> 
> There are three arguments, the non-standard hook, and the two arguments
> which will be fed to that hook.
> 
> Currently, that hook (window-scroll-functions) is called with
> run_hook_with_args_2, which doesn't have a "safe" in its functionality,
> which we need.  My change is to use safe_run_hooks_2 (a new function)
> instead.

OK, sounds good.

> > > OK, I have an idea.  I restore the variable redisplay_lisping back
> > > into the code (I took it out last night), binding it to true (or Qt?)
> > > at every place in xdisp.c where redisplay calls a Lisp hook.
> 
> > These all go through a single function, so there's just one place to
> > do that.
> 
> I disagree.  There are seven places, for the seven different Lisp hooks
> currently called from redisplay.

Aren't they all go through safe_call?  Which seven places are you
talking about?



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error backtraces
  2022-07-14 13:59                                           ` Eli Zaretskii
@ 2022-07-14 16:07                                             ` Alan Mackenzie
  2022-07-14 16:18                                               ` Stefan Monnier
  2022-07-14 17:09                                               ` Eli Zaretskii
  0 siblings, 2 replies; 54+ messages in thread
From: Alan Mackenzie @ 2022-07-14 16:07 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: larsi, monnier, emacs-devel

Hello, Eli.

On Thu, Jul 14, 2022 at 16:59:57 +0300, Eli Zaretskii wrote:
> > Date: Thu, 14 Jul 2022 13:42:18 +0000
> > Cc: larsi@gnus.org, monnier@iro.umontreal.ca, emacs-devel@gnu.org
> > From: Alan Mackenzie <acm@muc.de>

> > > > > You want to distinguish errors inside condition-case?

> > > > More, distinguish the different condition-cases in which errors
> > > > might occur.

> > > What else is exposed to Lisp?

> > I don't understand this question.

> There's just one condition-case available to Lisp code, AFAICT, so why
> isn't it enough to distinguish condition-case from any other callers of
> internal_condition_case* ?

I think I understand what you're saying, now.  That a condition-case
called from Lisp will not use any of the internal_condition_case*
functions.  So we could just assume that if i_c_case* triggers, it must be
one of the hooks we're interested in that called it.

I don't think that's right.  It might well be that Lisp code calls a C
primitive function that itself uses an internal_condition_case.  In this
scenario, we would not want to generate a backtrace for that nested
internal_condition_case.

[ .... ]

> > > > OK, I have an idea.  I restore the variable redisplay_lisping
> > > > back into the code (I took it out last night), binding it to true
> > > > (or Qt?) at every place in xdisp.c where redisplay calls a Lisp
> > > > hook.

> > > These all go through a single function, so there's just one place to
> > > do that.

> > I disagree.  There are seven places, for the seven different Lisp hooks
> > currently called from redisplay.

> Aren't they all go through safe_call?

They do, yes, but so do other things that we don't want to engage the
backtrace mechanism for.

> Which seven places are you talking about?

1. handle_fontified_prop;
2. set_message;
3. clear_message;
4. prepare_menu_bars (near the top);
5. update_menu_bar (line ~54);
6. run_window_scroll_functions;
7. redisplay_window (line ~415).

-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error backtraces
  2022-07-14 16:07                                             ` Alan Mackenzie
@ 2022-07-14 16:18                                               ` Stefan Monnier
  2022-07-14 19:47                                                 ` Alan Mackenzie
  2022-07-14 17:09                                               ` Eli Zaretskii
  1 sibling, 1 reply; 54+ messages in thread
From: Stefan Monnier @ 2022-07-14 16:18 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: Eli Zaretskii, larsi, emacs-devel

>> Aren't they all go through safe_call?
>
> They do, yes, but so do other things that we don't want to engage the
> backtrace mechanism for.

Actually, I'm not sure that's the case.  Basically the question is
whether `safe_call` is used because we don't care about any errors that
might be thrown (akin to ignore-errors`) or whether we do care but don't
want to let them escape and ruin our lunch (akin to
`with-demoted-errors`).

Being in redisplay is usually a good reason why we don't want to let
errors escape, even we do care about those errors (and hence we need
to use a mechanism like the one you're implementing in order to debug
those problems), but there can be other such cases that also use
`safe_call` and where we would also want to use your new code.


        Stefan




^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error backtraces
  2022-07-14 16:07                                             ` Alan Mackenzie
  2022-07-14 16:18                                               ` Stefan Monnier
@ 2022-07-14 17:09                                               ` Eli Zaretskii
  2022-07-14 19:33                                                 ` Alan Mackenzie
  1 sibling, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2022-07-14 17:09 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: larsi, monnier, emacs-devel

> Date: Thu, 14 Jul 2022 16:07:33 +0000
> Cc: larsi@gnus.org, monnier@iro.umontreal.ca, emacs-devel@gnu.org
> From: Alan Mackenzie <acm@muc.de>
> 
> > There's just one condition-case available to Lisp code, AFAICT, so why
> > isn't it enough to distinguish condition-case from any other callers of
> > internal_condition_case* ?
> 
> I think I understand what you're saying, now.  That a condition-case
> called from Lisp will not use any of the internal_condition_case*
> functions.  So we could just assume that if i_c_case* triggers, it must be
> one of the hooks we're interested in that called it.
> 
> I don't think that's right.  It might well be that Lisp code calls a C
> primitive function that itself uses an internal_condition_case.  In this
> scenario, we would not want to generate a backtrace for that nested
> internal_condition_case.

And we won't, because redisplaying_p won't be set.

> > > I disagree.  There are seven places, for the seven different Lisp hooks
> > > currently called from redisplay.
> 
> > Aren't they all go through safe_call?
> 
> They do, yes, but so do other things that we don't want to engage the
> backtrace mechanism for.
> 
> > Which seven places are you talking about?
> 
> 1. handle_fontified_prop;
> 2. set_message;
> 3. clear_message;
> 4. prepare_menu_bars (near the top);
> 5. update_menu_bar (line ~54);
> 6. run_window_scroll_functions;
> 7. redisplay_window (line ~415).

These either go through safe__call, or through run_hook_with_args.  I
think the latter group should be converted to call safe__call and its
friends.  And then you will have what I described: all those calls go
through a single gate.



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error backtraces
  2022-07-14 17:09                                               ` Eli Zaretskii
@ 2022-07-14 19:33                                                 ` Alan Mackenzie
  2022-07-16  6:12                                                   ` Eli Zaretskii
  0 siblings, 1 reply; 54+ messages in thread
From: Alan Mackenzie @ 2022-07-14 19:33 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: larsi, monnier, emacs-devel

Hello, Eli.

On Thu, Jul 14, 2022 at 20:09:02 +0300, Eli Zaretskii wrote:
> > Date: Thu, 14 Jul 2022 16:07:33 +0000
> > Cc: larsi@gnus.org, monnier@iro.umontreal.ca, emacs-devel@gnu.org
> > From: Alan Mackenzie <acm@muc.de>

> > > There's just one condition-case available to Lisp code, AFAICT, so why
> > > isn't it enough to distinguish condition-case from any other callers of
> > > internal_condition_case* ?

> > I think I understand what you're saying, now.  That a condition-case
> > called from Lisp will not use any of the internal_condition_case*
> > functions.  So we could just assume that if i_c_case* triggers, it must be
> > one of the hooks we're interested in that called it.

> > I don't think that's right.  It might well be that Lisp code calls a C
> > primitive function that itself uses an internal_condition_case.  In this
> > scenario, we would not want to generate a backtrace for that nested
> > internal_condition_case.

> And we won't, because redisplaying_p won't be set.

I don't understand.  If redisplay is called normally, it will go through
redisplay_internal which sets redisplaying_p to true.  And it will stay
true till the end of redisplay_internal, unless one of two special things
happen: (i) We enter the debugger; (ii) there's a recursive edit.

I don't see how the new backtrace facility can get any useful information
out of redisplaying_p; if there were a nested internal_condition_case*,
redisplaying_p would still be true, surely?

What am I missing?

> > > > I disagree.  There are seven places, for the seven different Lisp hooks
> > > > currently called from redisplay.

> > > Aren't they all go through safe_call?

> > They do, yes, but so do other things that we don't want to engage the
> > backtrace mechanism for.

> > > Which seven places are you talking about?

> > 1. handle_fontified_prop;
> > 2. set_message;
> > 3. clear_message;
> > 4. prepare_menu_bars (near the top);
> > 5. update_menu_bar (line ~54);
> > 6. run_window_scroll_functions;
> > 7. redisplay_window (line ~415).

> These either go through safe__call, or through run_hook_with_args.  I
> think the latter group should be converted to call safe__call and its
> friends.  And then you will have what I described: all those calls go
> through a single gate.

Maybe tomorrow!

-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error backtraces
  2022-07-14 16:18                                               ` Stefan Monnier
@ 2022-07-14 19:47                                                 ` Alan Mackenzie
  2022-07-14 20:48                                                   ` Stefan Monnier
  2022-07-15  5:50                                                   ` Eli Zaretskii
  0 siblings, 2 replies; 54+ messages in thread
From: Alan Mackenzie @ 2022-07-14 19:47 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Eli Zaretskii, larsi, emacs-devel

Hello, Stefan.

On Thu, Jul 14, 2022 at 12:18:51 -0400, Stefan Monnier wrote:
> >> Aren't they all go through safe_call?

> > They do, yes, but so do other things that we don't want to engage the
> > backtrace mechanism for.

> Actually, I'm not sure that's the case.  Basically the question is
> whether `safe_call` is used because we don't care about any errors that
> might be thrown (akin to ignore-errors`) or whether we do care but don't
> want to let them escape and ruin our lunch (akin to
> `with-demoted-errors`).

> Being in redisplay is usually a good reason why we don't want to let
> errors escape, even we do care about those errors (and hence we need
> to use a mechanism like the one you're implementing in order to debug
> those problems), but there can be other such cases that also use
> `safe_call` and where we would also want to use your new code.

There are approximately 38 calls to something like safe_call in the C
sources, about half of which are in xdisp.c.  That's a lot of scope for
irritation if non-events continually swamp the real errors in redisplay's
Lisp hooks.

It feels like we would want some mechanism to filter out "some" safe_call
calls, and things are getting complicated fast.  The scope of the
exercise has already expanded considerably, from just
fontification-functions to all redisplay hooks.

>         Stefan

-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error backtraces
  2022-07-14 19:47                                                 ` Alan Mackenzie
@ 2022-07-14 20:48                                                   ` Stefan Monnier
  2022-07-15  5:50                                                   ` Eli Zaretskii
  1 sibling, 0 replies; 54+ messages in thread
From: Stefan Monnier @ 2022-07-14 20:48 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: Eli Zaretskii, larsi, emacs-devel

> It feels like we would want some mechanism to filter out "some"
> safe_call calls, and things are getting complicated fast.  The scope
> of the exercise has already expanded considerably, from just
> fontification-functions to all redisplay hooks.

Remember what I'm pushing for: a trace of recent silenced backtraces.


        Stefan




^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error backtraces
  2022-07-14 19:47                                                 ` Alan Mackenzie
  2022-07-14 20:48                                                   ` Stefan Monnier
@ 2022-07-15  5:50                                                   ` Eli Zaretskii
  2022-07-15 18:18                                                     ` Alan Mackenzie
  1 sibling, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2022-07-15  5:50 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: monnier, larsi, emacs-devel

> Date: Thu, 14 Jul 2022 19:47:25 +0000
> Cc: Eli Zaretskii <eliz@gnu.org>, larsi@gnus.org, emacs-devel@gnu.org
> From: Alan Mackenzie <acm@muc.de>
> 
> > Being in redisplay is usually a good reason why we don't want to let
> > errors escape, even we do care about those errors (and hence we need
> > to use a mechanism like the one you're implementing in order to debug
> > those problems), but there can be other such cases that also use
> > `safe_call` and where we would also want to use your new code.
> 
> There are approximately 38 calls to something like safe_call in the C
> sources, about half of which are in xdisp.c.  That's a lot of scope for
> irritation if non-events continually swamp the real errors in redisplay's
> Lisp hooks.
> 
> It feels like we would want some mechanism to filter out "some" safe_call
> calls, and things are getting complicated fast.  The scope of the
> exercise has already expanded considerably, from just
> fontification-functions to all redisplay hooks.

Alan, you are again over-engineering things.  Assuming that safe__call
is the only interesting gateway for Lisp errors during redisplay
should be good enough, if you in addition test redisplaying_p.



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error backtraces
  2022-07-15  5:50                                                   ` Eli Zaretskii
@ 2022-07-15 18:18                                                     ` Alan Mackenzie
  2022-07-16  6:03                                                       ` Eli Zaretskii
  0 siblings, 1 reply; 54+ messages in thread
From: Alan Mackenzie @ 2022-07-15 18:18 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: monnier, larsi, emacs-devel

Hello, everybody.

I know that there are technical issues in the code still to be thrashed
out, but the new backtrace facility needs documentation.  I've written
some and a proposed patch (for debugging.texi and NEWS) is below.

I've come to see that `backtrace-on-redisplay-lisp-error', although
accurate, is too much of a mouthful, and I think it needs shortening.
Would anybody object to me just calling this variable `debug-redisplay'?

Also, I think that writing the backtrace into *Backtrace* is a bad idea,
now.  Users expect such a buffer to have active facilities, whereas the
new backtrace is just a dead dump.  Is there any objection to me instead
calling the buffer something like "*Redisplay-trace*"?



diff --git a/doc/lispref/debugging.texi b/doc/lispref/debugging.texi
index 058c931954..3f9c9fea50 100644
--- a/doc/lispref/debugging.texi
+++ b/doc/lispref/debugging.texi
@@ -77,6 +77,7 @@ Debugger
 
 @menu
 * Error Debugging::       Entering the debugger when an error happens.
+* Debugging Redisplay::   Getting backtraces from redisplay hook 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 @@ Error Debugging
 (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 hooks 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,52 @@ Error Debugging
 bypasses the @code{condition-case} which normally catches errors in the
 init file.
 
+@node Debugging Redisplay
+@subsection Debugging Redisplay Hooks
+@cindex redisplay hooks
+@cindex debugging redisplay hooks
+
+When a Lisp error occurs in a hook function which redisplay has
+invoked, you cannot use Emacs's usual debugging mechanisms, for
+technical reasons.  This subsection describes how to get a backtrace
+from such an error, information which should be helpful in debugging
+it.
+
+Note that if you have had such an error, the error handling will
+likely have removed the function causing it from its hook.  You will
+thus need to reinitialize that hook somehow, perhaps with
+@code{add-hook}, to be able to replay the bug.
+
+The hooks which these directions apply to are the following:
+@enumerate
+@item
+@code{fontification-functions} (@pxref{Auto Faces}).
+@item
+@code{set-message-function} (@pxref{Displaying Messages}).
+@item
+@code{clear-message-function} (@pxref{Displaying Messages}).
+@item
+@code{pre-redisplay-function} (@pxref{Forcing Redisplay}).
+@item
+@code{pre-redisplay-functions} (@pxref{Forcing Redisplay}).
+@item
+@code{menu-bar-update-hook} (@pxref{Menu Bar}).
+@item
+@code{window-scroll-functions} (@pxref{Window Hooks}).
+@end enumerate
+
+To generate a backtrace in these circumstances, set the variable
+@code{backtrace-on-redisplay-lisp-error} to non-@code{nil}.  When the
+error occurs, Emacs will dump the backtrace to the buffer
+@file{*Backtrace*}.  This buffer is not automatically displayed in a
+window; you will need to display it yourself, with a command such as
+@code{switch-to-buffer-other-frame} (@key{C-x 5 b}).
+
+@defvar backtrace-on-redisplay-lisp-error
+Set this variable to non-@code{nil} to enable the generation of a
+backtrace when errors occur in any of the above hooks.
+@end defvar
+
 @node Infinite Loops
 @subsection Debugging Infinite Loops
 @cindex infinite loops
diff --git a/etc/NEWS b/etc/NEWS
index c2900b0bc4..0196d55306 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1227,6 +1227,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.
 
++++
+*** It is now possible to generate a backtrace from errors in redisplay
+hooks.  To do this, set the new variable
+'backtrace-on-redisplay-lisp-error' to a non-nil value.  The backtrace
+will be written to buffer *Backtrace*, but this buffer will not be
+automatically displayed in a window.
+
 ** Compile
 
 +++


-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply related	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error backtraces
  2022-07-15 18:18                                                     ` Alan Mackenzie
@ 2022-07-16  6:03                                                       ` Eli Zaretskii
  0 siblings, 0 replies; 54+ messages in thread
From: Eli Zaretskii @ 2022-07-16  6:03 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: monnier, larsi, emacs-devel

> Date: Fri, 15 Jul 2022 18:18:14 +0000
> Cc: monnier@iro.umontreal.ca, larsi@gnus.org, emacs-devel@gnu.org
> From: Alan Mackenzie <acm@muc.de>
> 
> I've come to see that `backtrace-on-redisplay-lisp-error', although
> accurate, is too much of a mouthful, and I think it needs shortening.
> Would anybody object to me just calling this variable `debug-redisplay'?

Too general, IMO.  How about backtrace-on-redisplay-errors?

> Also, I think that writing the backtrace into *Backtrace* is a bad idea,
> now.  Users expect such a buffer to have active facilities, whereas the
> new backtrace is just a dead dump.  Is there any objection to me instead
> calling the buffer something like "*Redisplay-trace*"?

No objections, but I think you are again over-engineering things.

> +Note that, for technical reasons, you cannot use the facilities
> +defined in this subsection to debug errors in hooks the redisplay code
> +has invoked.  @xref{Debugging Redisplay} for help with these.
                                           ^
A comma missing there.

> +@node Debugging Redisplay
> +@subsection Debugging Redisplay Hooks
> +@cindex redisplay hooks

This @cindex should be "redisplay hooks, debugging", because it
doesn't talk about the hooks in general.

> +When a Lisp error occurs in a hook function which redisplay has
> +invoked, you cannot use Emacs's usual debugging mechanisms, for
> +technical reasons.  This subsection describes how to get a backtrace
> +from such an error, information which should be helpful in debugging
> +it.

This talks about hooks, which restricts the scope of the facility: we
want to be able to collect backtraces from any Lisp called by the
display code.  For example, :eval forms in the mode line, Lisp code
called by fontification-functions, etc.  So the text ebove needs to be
revised with this in mind.

> +The hooks which these directions apply to are the following:

I don't want us to have a list that looks like it's an exhaustive one.
maintaining such a list is a maintenance burden we are better without.
Please give a couple of examples and change the text to the effect
that these are just examples.

> +*** It is now possible to generate a backtrace from errors in redisplay
> +hooks.  To do this, set the new variable

The heading line should be a full sentence.

Thanks.



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error backtraces
  2022-07-14 19:33                                                 ` Alan Mackenzie
@ 2022-07-16  6:12                                                   ` Eli Zaretskii
  2022-07-16 15:45                                                     ` Alan Mackenzie
  0 siblings, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2022-07-16  6:12 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: larsi, monnier, emacs-devel

> Date: Thu, 14 Jul 2022 19:33:09 +0000
> Cc: larsi@gnus.org, monnier@iro.umontreal.ca, emacs-devel@gnu.org
> From: Alan Mackenzie <acm@muc.de>
> 
> > > I think I understand what you're saying, now.  That a condition-case
> > > called from Lisp will not use any of the internal_condition_case*
> > > functions.  So we could just assume that if i_c_case* triggers, it must be
> > > one of the hooks we're interested in that called it.
> 
> > > I don't think that's right.  It might well be that Lisp code calls a C
> > > primitive function that itself uses an internal_condition_case.  In this
> > > scenario, we would not want to generate a backtrace for that nested
> > > internal_condition_case.
> 
> > And we won't, because redisplaying_p won't be set.
> 
> I don't understand.  If redisplay is called normally, it will go through
> redisplay_internal which sets redisplaying_p to true.  And it will stay
> true till the end of redisplay_internal, unless one of two special things
> happen: (i) We enter the debugger; (ii) there's a recursive edit.
> 
> I don't see how the new backtrace facility can get any useful information
> out of redisplaying_p; if there were a nested internal_condition_case*,
> redisplaying_p would still be true, surely?
> 
> What am I missing?

My point is that redisplaying_p tells you the code which runs was
invoked from redisplay, and that allows you to distinguish calls to
internal_condition_case* that are of interest (i.e. should produce a
backtrace in the *Backtrace* buffer) from those which aren't of
interest.  Which was the issue you raised against my suggestion to
rely on the fact that Lisp only ever uses a single form of
internal_condition_case* functions.

To recap: this sub-thread started when you said you don't want to
cause all calls to internal_condition_case* to generate backtrace in
this new way.  To which I suggested to bind a variable in
internal_condition_case that would prevent generation of backtrace,
relying on redisplaying_p to tell us when the other
internal_condition_case* functions are called outside of redisplay.



^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: Redisplay hook error backtraces
  2022-07-16  6:12                                                   ` Eli Zaretskii
@ 2022-07-16 15:45                                                     ` Alan Mackenzie
  0 siblings, 0 replies; 54+ messages in thread
From: Alan Mackenzie @ 2022-07-16 15:45 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: larsi, monnier, emacs-devel

Hello, Eli.

On Sat, Jul 16, 2022 at 09:12:48 +0300, Eli Zaretskii wrote:
> > Date: Thu, 14 Jul 2022 19:33:09 +0000
> > Cc: larsi@gnus.org, monnier@iro.umontreal.ca, emacs-devel@gnu.org
> > From: Alan Mackenzie <acm@muc.de>

> > > > I think I understand what you're saying, now.  That a condition-case
> > > > called from Lisp will not use any of the internal_condition_case*
> > > > functions.  So we could just assume that if i_c_case* triggers, it must be
> > > > one of the hooks we're interested in that called it.

> > > > I don't think that's right.  It might well be that Lisp code calls a C
> > > > primitive function that itself uses an internal_condition_case.  In this
> > > > scenario, we would not want to generate a backtrace for that nested
> > > > internal_condition_case.

> > > And we won't, because redisplaying_p won't be set.

> > I don't understand.  If redisplay is called normally, it will go through
> > redisplay_internal which sets redisplaying_p to true.  And it will stay
> > true till the end of redisplay_internal, unless one of two special things
> > happen: (i) We enter the debugger; (ii) there's a recursive edit.

> > I don't see how the new backtrace facility can get any useful information
> > out of redisplaying_p; if there were a nested internal_condition_case*,
> > redisplaying_p would still be true, surely?

> > What am I missing?

> My point is that redisplaying_p tells you the code which runs was
> invoked from redisplay, and that allows you to distinguish calls to
> internal_condition_case* that are of interest (i.e. should produce a
> backtrace in the *Backtrace* buffer) from those which aren't of
> interest.  Which was the issue you raised against my suggestion to
> rely on the fact that Lisp only ever uses a single form of
> internal_condition_case* functions.

> To recap: this sub-thread started when you said you don't want to
> cause all calls to internal_condition_case* to generate backtrace in
> this new way.  To which I suggested to bind a variable in
> internal_condition_case that would prevent generation of backtrace,
> relying on redisplaying_p to tell us when the other
> internal_condition_case* functions are called outside of redisplay.

OK.  I'm a little concerned that we'll be getting too many backtraces.
But my "solution" to that wouldn't have done any good anyhow.

I've got rid of redisplay_lisping, renamed backtrace_.... to
backtrace-on-redisplay-error (without the s), and the patch, somewhat
smaller than before, is below.

I'll amend the documentation later.



diff --git a/src/eval.c b/src/eval.c
index 141d2546f0..2940f33512 100644
--- a/src/eval.c
+++ b/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);
@@ -1552,12 +1560,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
@@ -1565,6 +1577,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;
     }
 }
@@ -1697,6 +1710,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.
@@ -1812,6 +1830,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
@@ -4274,6 +4326,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
diff --git a/src/keyboard.c b/src/keyboard.c
index 2863058d63..6b2757f5b1 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -1330,6 +1330,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
@@ -1837,7 +1838,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];
@@ -1895,6 +1896,17 @@ safe_run_hooks (Lisp_Object hook)
   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);
+}
+
 \f
 /* Nonzero means polling for input is temporarily suppressed.  */
 
diff --git a/src/lisp.h b/src/lisp.h
index dc496cc165..7caf5e2745 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -4526,6 +4526,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:
@@ -4823,6 +4824,7 @@ extern bool detect_input_pending (void);
 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_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);
diff --git a/src/xdisp.c b/src/xdisp.c
index 1940d16a01..086875410c 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -17915,8 +17915,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.  */


-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply related	[flat|nested] 54+ messages in thread

end of thread, other threads:[~2022-07-16 15:45 UTC | newest]

Thread overview: 54+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-06-25 13:42 Why is it so difficult to get a Lisp backtrace? Alan Mackenzie
2022-06-25 15:26 ` Alan Mackenzie
2022-06-25 15:30   ` Stefan Kangas
2022-06-25 15:52     ` Alan Mackenzie
2022-06-25 16:27       ` Stefan Kangas
2022-06-25 15:35   ` Lars Ingebrigtsen
2022-06-25 15:59     ` Alan Mackenzie
2022-06-25 16:51       ` Eli Zaretskii
2022-06-25 19:45         ` Alan Mackenzie
2022-06-26  5:20           ` Eli Zaretskii
2022-06-27 19:48             ` Fontification error backtrace [Was: Why is it so difficult to get a Lisp backtrace?] Alan Mackenzie
2022-06-28 11:43               ` Lars Ingebrigtsen
2022-06-28 11:57               ` Eli Zaretskii
2022-06-28 17:28                 ` Alan Mackenzie
2022-06-28 18:17                   ` Eli Zaretskii
2022-06-28 19:31                     ` Alan Mackenzie
2022-06-29 19:00                       ` Eli Zaretskii
2022-07-12 19:48                         ` Redisplay hook error bactraces [Was: Fontification error backtrace] Alan Mackenzie
2022-07-13 12:33                           ` Eli Zaretskii
2022-07-13 18:41                             ` Redisplay hook error backtraces Alan Mackenzie
2022-07-13 19:00                               ` Eli Zaretskii
2022-07-13 20:12                                 ` Alan Mackenzie
2022-07-13 20:49                                   ` Stefan Monnier
2022-07-14  5:12                                   ` Eli Zaretskii
2022-07-14  9:01                                     ` Alan Mackenzie
2022-07-14  9:10                                       ` Eli Zaretskii
2022-07-14 13:42                                         ` Alan Mackenzie
2022-07-14 13:59                                           ` Eli Zaretskii
2022-07-14 16:07                                             ` Alan Mackenzie
2022-07-14 16:18                                               ` Stefan Monnier
2022-07-14 19:47                                                 ` Alan Mackenzie
2022-07-14 20:48                                                   ` Stefan Monnier
2022-07-15  5:50                                                   ` Eli Zaretskii
2022-07-15 18:18                                                     ` Alan Mackenzie
2022-07-16  6:03                                                       ` Eli Zaretskii
2022-07-14 17:09                                               ` Eli Zaretskii
2022-07-14 19:33                                                 ` Alan Mackenzie
2022-07-16  6:12                                                   ` Eli Zaretskii
2022-07-16 15:45                                                     ` Alan Mackenzie
2022-07-13 19:13                           ` Redisplay hook error bactraces [Was: Fontification error backtrace] Stefan Monnier
2022-07-13 19:24                             ` Eli Zaretskii
2022-07-13 19:58                             ` Alan Mackenzie
2022-07-13 20:45                               ` Stefan Monnier
2022-06-30 20:34                       ` Fontification error backtrace [Was: Why is it so difficult to get a Lisp backtrace?] Stefan Monnier
2022-07-01  5:39                         ` Eli Zaretskii
2022-07-01 15:34                           ` Juri Linkov
2022-07-01 16:04                             ` Eli Zaretskii
2022-07-01 19:14                               ` Juri Linkov
2022-07-01 19:26                           ` Stefan Monnier
2022-07-02  5:53                             ` Eli Zaretskii
2022-07-02 21:15                               ` Stefan Monnier
2022-07-02 20:27                         ` Alan Mackenzie
2022-07-02 21:12                           ` Stefan Monnier
2022-06-26  8:45 ` Why is it so difficult to get a Lisp backtrace? Stefan Monnier

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).