1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-06 06:20:55 -08:00

Add redisplay_counter to catch nested redisplays and abort outer one

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.
This commit is contained in:
Stefan Monnier 2023-12-25 22:40:02 -05:00
parent 4c9b376607
commit c3f3fe136c
3 changed files with 26 additions and 3 deletions

View file

@ -3559,6 +3559,7 @@ int partial_line_height (struct it *it_origin);
bool in_display_vector_p (struct it *); bool in_display_vector_p (struct it *);
int frame_mode_line_height (struct frame *); int frame_mode_line_height (struct frame *);
extern bool redisplaying_p; extern bool redisplaying_p;
extern unsigned int redisplay_counter;
extern bool display_working_on_window_p; extern bool display_working_on_window_p;
extern void unwind_display_working_on_window (void); extern void unwind_display_working_on_window (void);
extern bool help_echo_showing_p; extern bool help_echo_showing_p;

View file

@ -302,6 +302,7 @@ call_debugger (Lisp_Object arg)
/* Resetting redisplaying_p to 0 makes sure that debug output is /* Resetting redisplaying_p to 0 makes sure that debug output is
displayed if the debugger is invoked during redisplay. */ displayed if the debugger is invoked during redisplay. */
debug_while_redisplaying = redisplaying_p; debug_while_redisplaying = redisplaying_p;
int redisplay_counter_before = redisplay_counter;
redisplaying_p = 0; redisplaying_p = 0;
specbind (Qdebugger_may_continue, specbind (Qdebugger_may_continue,
debug_while_redisplaying ? Qnil : Qt); debug_while_redisplaying ? Qnil : Qt);
@ -323,9 +324,10 @@ call_debugger (Lisp_Object arg)
/* Interrupting redisplay and resuming it later is not safe under /* Interrupting redisplay and resuming it later is not safe under
all circumstances. So, when the debugger returns, abort the all circumstances. So, when the debugger returns, abort the
interrupted redisplay by going back to the top-level. */ interrupted redisplay by going back to the top-level. */
/* FIXME: Move this to the redisplay code? */
if (debug_while_redisplaying 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 (); Ftop_level ();
return unbind_to (count, val); return unbind_to (count, val);

View file

@ -3054,6 +3054,17 @@ hscrolling_current_line_p (struct window *w)
Lisp form evaluation 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. */ /* Error handler for dsafe_eval and dsafe_call. */
static Lisp_Object static Lisp_Object
@ -3085,10 +3096,17 @@ dsafe__call (bool inhibit_quit, Lisp_Object (f) (ptrdiff_t, Lisp_Object *),
specbind (Qinhibit_redisplay, Qt); specbind (Qinhibit_redisplay, Qt);
if (inhibit_quit) if (inhibit_quit)
specbind (Qinhibit_quit, Qt); specbind (Qinhibit_quit, Qt);
int redisplay_counter_before = redisplay_counter;
/* Use Qt to ensure debugger does not run, /* 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, val = internal_condition_case_n (f, nargs, args, Qt,
dsafe_eval_handler); 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); val = unbind_to (count, val);
} }
@ -17109,6 +17127,8 @@ redisplay_internal (void)
bool polling_stopped_here = false; bool polling_stopped_here = false;
Lisp_Object tail, frame; Lisp_Object tail, frame;
redisplay_counter++;
/* Set a limit to the number of retries we perform due to horizontal /* Set a limit to the number of retries we perform due to horizontal
scrolling, this avoids getting stuck in an uninterruptible scrolling, this avoids getting stuck in an uninterruptible
infinite loop (Bug #24633). */ infinite loop (Bug #24633). */