Subject: Re: bug#72487: calculator-electric-mode causes freeze on subsequent use of minibuffer

From: martin rudalics <[email-masked]>
Date: Mon, 12 Aug 2024 11:50:49 +0200

> When using the calculator function of GNU Emacs 31.0.50 (bug also found
 > in 29.4), usage of `calculator-electric-mode' causes Emacs to hang on
 > subsequent uses of the minibuffer.
 >
 > Emacs -Q
 >
 > M-: (setf calculator-electric-mode t) <RET>
 >
 > M-x calcu <RET> q M-x M-p <RET>
 >
 > Note that the freeze does not depend on specifically calling calculator
 > a second time, and calculator may be repeated with C-x z without issue.
 >
 > The freeze occurs specifically when the minibuffer exits a second time,
 > even via C-g, before its contents are processed. Thus, for example, the
 > following still yields a freeze:
 >
 > Emacs -Q
 >
 > M-: (setf calculator-electric-mode t) <RET>
 >
 > M-x d-o-e <RET> kill-emacs <RET>
 >
 > M-x calcu <RET> q M-x kill-emacs <RET>
 >
 > Apparently, calculator-electric-mode leaves the minibuffer window in a
 > broken state after first usage, rather than resetting it correctly.
 >
 > Emacs must be killed externally at this point, and generally cannot be
 > salvaged with C-g. It becomes trapped in a loop emitting terminal bells
 > (seen with `visible-bell' mode on, or via a termscript from `-nw').

This bug is a consequence of accessing the list of previous buffers for
a minibuffer window.  That list is conceptually unmaintained and
therefore unsafe for minibuffer windows.  In particular, 'kill-buffer'
and subsequently 'replace-buffer-in-windows' do not consider minibuffer
windows.  The doc-string for the latter now states explicitly that
"Minibuffer windows are not considered."  Handling minibuffer windows in
'replace-buffer-in-windows' might require some deep surgery and is
certainly not recommended for a release branch.

I can offer the attached patch to provisionally handle this problem.

martin
diff --git a/src/minibuf.c b/src/minibuf.c
index 1dfee0a59c9..8932c7cd749 100644
--- a/src/minibuf.c
+++ b/src/minibuf.c
@@ -1248,27 +1248,39 @@ read_minibuf_unwind (void)
 minibuffer_unwind (void)
 {
   struct frame *f;
-  struct window *w;
-  Lisp_Object window;
-  Lisp_Object entry;
 
   if (NILP (exp_MB_frame)) return; /* "Can't happen." */
   f = XFRAME (exp_MB_frame);
-  window = f->minibuffer_window;
-  w = XWINDOW (window);
   if (FRAME_LIVE_P (f))
     {
-      /* minibuf_window = sf->minibuffer_window; */
-      if (!NILP (w->prev_buffers))
+      Lisp_Object window = f->minibuffer_window;
+
+      if (WINDOW_LIVE_P (window))
 	{
-	  entry = Fcar (w->prev_buffers);
-	  w->prev_buffers = Fcdr (w->prev_buffers);
-	  set_window_buffer (window, Fcar (entry), 0, 0);
-	  Fset_window_start (window, Fcar (Fcdr (entry)), Qnil);
-	  Fset_window_point (window, Fcar (Fcdr (Fcdr (entry))));
+	  struct window *w = XWINDOW (window);
+
+	  /* minibuf_window = sf->minibuffer_window; */
+	  if (!NILP (w->prev_buffers))
+	    {
+	      Lisp_Object entry = Fcar (w->prev_buffers);
+
+	      if (BUFFERP (Fcar (entry))
+		  && BUFFER_LIVE_P (XBUFFER (Fcar (entry))))
+		{
+		  w->prev_buffers = Fcdr (w->prev_buffers);
+		  set_window_buffer (window, Fcar (entry), 0, 0);
+		  Fset_window_start (window, Fcar (Fcdr (entry)), Qnil);
+		  Fset_window_point (window, Fcar (Fcdr (Fcdr (entry))));
+		  /* set-window-configuration may/will have unselected the
+		     mini-window as the selected window.  Restore it. */
+		  Fset_frame_selected_window (exp_MB_frame, window, Qnil);
+		}
+	      else
+		set_window_buffer (window, nth_minibuffer (0), 0, 0);
+	    }
+	  else
+	    set_window_buffer (window, nth_minibuffer (0), 0, 0);
 	}
-      else
-	set_window_buffer (window, nth_minibuffer (0), 0, 0);
     }
 }