]> git.eshelyaron.com Git - emacs.git/commitdiff
Add `redisplay_counter` to catch nested redisplays and abort outer one
authorStefan Monnier <monnier@iro.umontreal.ca>
Tue, 26 Dec 2023 03:40:02 +0000 (22:40 -0500)
committerEshel Yaron <me@eshelyaron.com>
Thu, 24 Jul 2025 09:47:33 +0000 (11:47 +0200)
The redisplay code is not re-entrant.  To allow running ELisp code
from within redisplay, we have some hacks (e.g. `inhibit-redisplay`)
that try to avoid the resulting breakage.
This commit adds another one of those hacks, which tries
to get closer to the core of the problem, thereby making it "safe"
to override `inhibit-redisplay`, e.g. to debug jit-lock code.

* src/dispextern.h (redisplay_counter): Declare.
* src/xdisp.c (redisplay_counter): Define.
(redisplay_internal) Increment it.
(dsafe__call): Use it, in case `inhibit-redisplay` is overridden.
* src/eval.c (call_debugger): Use it as well to refine the test
we already had.

(cherry picked from commit c3f3fe136cefc4c3aed960b175053e7c048aa979)

src/dispextern.h
src/eval.c
src/xdisp.c

index bd48005b83f8026c138b750975e24da05aa5f492..19ab104d2e68810fdd5fac3afee6210dcfdd8a05 100644 (file)
@@ -3559,6 +3559,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 unsigned int redisplay_counter;
 extern bool display_working_on_window_p;
 extern void unwind_display_working_on_window (void);
 extern bool help_echo_showing_p;
index b606990a297c5a53279bff8832b9e3d67d002c10..1128cc928be3fe4ad5bbcc79f556463a6a92c253 100644 (file)
@@ -302,6 +302,7 @@ call_debugger (Lisp_Object arg)
   /* Resetting redisplaying_p to 0 makes sure that debug output is
      displayed if the debugger is invoked during redisplay.  */
   debug_while_redisplaying = redisplaying_p;
+  int redisplay_counter_before = redisplay_counter;
   redisplaying_p = 0;
   specbind (Qdebugger_may_continue,
            debug_while_redisplaying ? Qnil : Qt);
@@ -323,9 +324,10 @@ 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.  */
-  /* FIXME: Move this to the redisplay code?  */
   if (debug_while_redisplaying
-      && !EQ (Vdebugger, Qdebug_early))
+      && redisplay_counter_before != redisplay_counter)
+    /* FIXME: Rather than jump all the way to `top-level`
+       we should exit only the current redisplay.  */
     Ftop_level ();
 
   return unbind_to (count, val);
index c04d159187fb5540fc2937c6e95445442fb36770..bb25cb88fa3146fa8f3b6e74658f0432c665e7bb 100644 (file)
@@ -3054,6 +3054,17 @@ hscrolling_current_line_p (struct window *w)
                        Lisp form evaluation
  ***********************************************************************/
 
+/* The redisplay is not re-entrant.  But we can run ELisp code during
+   redisplay, which in turn can trigger redisplay.
+   We try to make this inner redisplay work correctly, but it messes up
+   the state of the outer redisplay, so when we return to this outer
+   redisplay, we need to abort it.
+   To dect this case, we keep a counter that identifies every call to the
+   redisplay, so we can detect when a nested redisplay happened by the
+   fact that the counter has changed.  */
+
+unsigned int redisplay_counter = 0;
+
 /* Error handler for dsafe_eval and dsafe_call.  */
 
 static Lisp_Object
@@ -3085,10 +3096,17 @@ dsafe__call (bool inhibit_quit, Lisp_Object (f) (ptrdiff_t, Lisp_Object *),
       specbind (Qinhibit_redisplay, Qt);
       if (inhibit_quit)
        specbind (Qinhibit_quit, Qt);
+      int redisplay_counter_before = redisplay_counter;
       /* Use Qt to ensure debugger does not run,
-        so there is no possibility of wanting to redisplay.  */
+        to reduce the risk of wanting to redisplay.  */
       val = internal_condition_case_n (f, nargs, args, Qt,
                                       dsafe_eval_handler);
+      if (redisplay_counter_before != redisplay_counter)
+        /* A nested redisplay happened, abort this one!  */
+        /* FIXME: Rather than jump all the way to `top-level`
+           we should exit only the current redisplay.  */
+        Ftop_level ();
+
       val = unbind_to (count, val);
     }
 
@@ -17109,6 +17127,8 @@ redisplay_internal (void)
   bool polling_stopped_here = false;
   Lisp_Object tail, frame;
 
+  redisplay_counter++;
+
   /* Set a limit to the number of retries we perform due to horizontal
      scrolling, this avoids getting stuck in an uninterruptible
      infinite loop (Bug #24633).  */