Subject: bug#47244: 28.0.50; SIGSEGV in long-runnning Emacs

From: martin rudalics <[email-masked]>
Date: Wed, 7 Apr 2021 18:16:35 +0200

> Don't know where to go from here, but I have a breakpoint set on
 >
 >        replace_buffer_in_windows_safely_count = 0;
 >
 > conditioned on count != 0.

Good.  IIUC this

 > (gdb) p list_length(Vwindow_list)
 > $2 = 2

and this

 > (gdb) xlist
 > $13 = 0x1a
 > Lisp_Int0
 > $14 = 6

together indicate that Vwindow_list spontaneously shrinks from 6 to 2
elements (which is "impossible" as we know already).  Attaching the next
patch which should catch this situation automatically.  It will still be
of no help because we won't catch the one that apparently clobbers that
list but hopefully more useful than the previous diff.

martin
diff --git a/src/buffer.c b/src/buffer.c
index 8e33162989..70b37e61a2 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -1951,6 +1951,9 @@ DEFUN ("kill-buffer", Fkill_buffer, Skill_buffer, 0, 1, "bKill buffer: ",
      This gets rid of them for certain.  */
   reset_buffer_local_variables (b, 1);
 
+  if (!b->base_buffer)
+    eassert (b->window_count == 0);
+
   bset_name (b, Qnil);
 
   block_input ();
@@ -1991,6 +1994,8 @@ DEFUN ("kill-buffer", Fkill_buffer, Skill_buffer, 0, 1, "bKill buffer: ",
 
   run_buffer_list_update_hook (b);
 
+  eassert (BUFFER_LIVE_P (XBUFFER (XWINDOW (selected_window)->contents)));
+
   return Qt;
 }
 
@@ -2151,6 +2156,9 @@ set_buffer_internal_1 (register struct buffer *b)
 
   BUFFER_CHECK_INDIRECTION (b);
 
+  if (!BUFFER_LIVE_P (b))
+    error ("Selecting deleted buffer");
+
   old_buf = current_buffer;
   current_buffer = b;
   last_known_column_point = -1;   /* Invalidate indentation cache.  */
diff --git a/src/window.c b/src/window.c
index eb16e2a433..c6fbe24742 100644
--- a/src/window.c
+++ b/src/window.c
@@ -87,6 +87,8 @@
    by setting it to nil.  */
 Lisp_Object Vwindow_list;
 
+static int window_list_length = 0;
+
 /* True mean window_change_record has to record all live frames.  */
 static bool window_change_record_frames;
 
@@ -307,7 +309,11 @@ wset_buffer (struct window *w, Lisp_Object val)
   if (BUFFERP (val))
     /* Make sure that we do not assign the buffer
        to an internal window.  */
-    eassert (MARKERP (w->start) && MARKERP (w->pointm));
+    {
+      eassert (BUFFER_LIVE_P (XBUFFER (val)));
+      eassert (MARKERP (w->start) && MARKERP (w->pointm));
+    }
+
   w->contents = val;
   adjust_window_count (w, 1);
 }
@@ -315,6 +321,7 @@ wset_buffer (struct window *w, Lisp_Object val)
 static void
 wset_old_buffer (struct window *w, Lisp_Object val)
 {
+  eassert (BUFFER_LIVE_P (XBUFFER (val)));
   w->old_buffer = val;
 }
 
@@ -594,6 +601,8 @@ select_window_1 (Lisp_Object window, bool inhibit_point_swap)
 
   selected_window = window;
 
+  eassert (BUFFER_LIVE_P (XBUFFER (XWINDOW (selected_window)->contents)));
+
   /* Go to the point recorded in the window.
      This is important when the buffer is in more
      than one window.  It also matters when
@@ -2569,7 +2578,11 @@ window_list (void)
 	  arglist = Fnreverse (arglist);
 	  Vwindow_list = nconc2 (Vwindow_list, arglist);
 	}
+
+      window_list_length = list_length (Vwindow_list);
     }
+  else
+    eassert (window_list_length == list_length (Vwindow_list));
 
   return Vwindow_list;
 }
@@ -2950,6 +2963,8 @@ DEFUN ("window-list-1", Fwindow_list_1, Swindow_list_1, 0, 3, 0,
   CHECK_ALL_WINDOWS                 /* Arg is ignored */
 };
 
+static int replace_buffer_in_windows_safely_count;
+
 static Lisp_Object
 window_loop (enum window_loop type, Lisp_Object obj, bool mini,
 	     Lisp_Object frames)
@@ -3048,6 +3063,8 @@ window_loop (enum window_loop type, Lisp_Object obj, bool mini,
 		if (EQ (window, selected_window)
 		    && XBUFFER (w->contents) == current_buffer)
 		  Fset_buffer (w->contents);
+
+		replace_buffer_in_windows_safely_count += 1;
 	      }
 	    break;
 
@@ -3411,6 +3428,8 @@ DEFUN ("delete-other-windows-internal", Fdelete_other_windows_internal,
 replace_buffer_in_windows (Lisp_Object buffer)
 {
   call1 (Qreplace_buffer_in_windows, buffer);
+
+  eassert (BUFFER_LIVE_P (XBUFFER (XWINDOW (selected_window)->contents)));
 }
 
 /* If BUFFER is shown in a window, safely replace it with some other
@@ -3419,7 +3438,28 @@ replace_buffer_in_windows (Lisp_Object buffer)
 void
 replace_buffer_in_windows_safely (Lisp_Object buffer)
 {
-  if (buffer_window_count (XBUFFER (buffer)))
+  if (!NILP (Fstring_equal (XBUFFER (buffer)->name_,
+			    build_string ("*Server*"))))
+    {
+      int count = buffer_window_count (XBUFFER (buffer));
+
+      replace_buffer_in_windows_safely_count = 0;
+
+
+      if (buffer_window_count (XBUFFER (buffer)))
+	{
+	  Lisp_Object tail, frame;
+
+	  /* A single call to window_loop won't do the job because it only
+	     considers frames on the current keyboard.  So loop manually over
+	     frames, and handle each one.  */
+	  FOR_EACH_FRAME (tail, frame)
+	    window_loop (REPLACE_BUFFER_IN_WINDOWS_SAFELY, buffer, true, frame);
+	}
+
+      eassert (count == replace_buffer_in_windows_safely_count);
+    }
+  else if (buffer_window_count (XBUFFER (buffer)))
     {
       Lisp_Object tail, frame;
 
@@ -3960,6 +4000,8 @@ run_window_change_functions (void)
   /* Record changes for all frames (if asked for), selected window and
      frame.  */
   unbind_to (count, Qnil);
+
+  eassert (BUFFER_LIVE_P (XBUFFER (XWINDOW (selected_window)->contents)));
 }
 
 /* Make WINDOW display BUFFER.  RUN_HOOKS_P means it's allowed
@@ -4054,6 +4096,9 @@ set_window_buffer (Lisp_Object window, Lisp_Object buffer,
   if (!samebuf && !MINI_WINDOW_P (w) && !WINDOW_PSEUDO_P (w))
     FRAME_WINDOW_CHANGE (XFRAME (w->frame)) = true;
 
+  if (WINDOWP (selected_window))
+    eassert (BUFFER_LIVE_P (XBUFFER (XWINDOW (selected_window)->contents)));
+
   unbind_to (count, Qnil);
 }
 
@@ -5174,6 +5219,8 @@ DEFUN ("delete-window-internal", Fdelete_window_internal, Sdelete_window_interna
       error ("Deletion failed");
     }
 
+  eassert (BUFFER_LIVE_P (XBUFFER (XWINDOW (selected_window)->contents)));
+
   return Qnil;
 }
 
@@ -7233,6 +7280,9 @@ DEFUN ("set-window-configuration", Fset_window_configuration,
   minibuf_selected_window = data->minibuf_selected_window;
 
   SAFE_FREE ();
+
+  eassert (BUFFER_LIVE_P (XBUFFER (XWINDOW (selected_window)->contents)));
+
   return FRAME_LIVE_P (f) ? Qt : Qnil;
 }
 
diff --git a/src/xdisp.c b/src/xdisp.c
index cc0a689ba3..ea64e5037a 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -12980,6 +12980,8 @@ restore_selected_window (Lisp_Object window)
       /* Abort if we cannot find a live frame.  */
       emacs_abort ();
     }
+
+  eassert (BUFFER_LIVE_P (XBUFFER (XWINDOW (selected_window)->contents)));
 }
 
 /* Restore WINDOW, if live, as its frame's selected window.  */
@@ -12997,6 +12999,8 @@ restore_frame_selected_window (Lisp_Object window)
       if (EQ (frame, selected_frame))
 	selected_window = window;
     }
+
+  eassert (BUFFER_LIVE_P (XBUFFER (XWINDOW (selected_window)->contents)));
 }
 
 /* Update the tab-bar item list for frame F.  This has to be done
@@ -13074,6 +13078,8 @@ update_tab_bar (struct frame *f, bool save_match_data)
 	  XSETFRAME (frame, f);
 	  selected_frame = frame;
 	  selected_window = FRAME_SELECTED_WINDOW (f);
+
+	  eassert (BUFFER_LIVE_P (XBUFFER (XWINDOW (selected_window)->contents)));
 #endif
 
 	  /* Build desired tab-bar items from keymaps.  */
@@ -14010,6 +14016,8 @@ update_tool_bar (struct frame *f, bool save_match_data)
 	  selected_frame = frame;
 	  selected_window = FRAME_SELECTED_WINDOW (f);
 
+	  eassert (BUFFER_LIVE_P (XBUFFER (XWINDOW (selected_window)->contents)));
+
 	  /* Build desired tool-bar items from keymaps.  */
           new_tool_bar
 	    = tool_bar_items (Fcopy_sequence (f->tool_bar_items),
@@ -17246,6 +17254,8 @@ run_window_scroll_functions (Lisp_Object window, struct text_pos startp)
       set_buffer_internal (XBUFFER (w->contents));
     }
 
+  eassert (BUFFER_LIVE_P (XBUFFER (XWINDOW (selected_window)->contents)));
+
   return startp;
 }
 
@@ -25450,6 +25460,8 @@ display_mode_lines (struct window *w)
 
   unbind_to (count, Qnil);
 
+  eassert (BUFFER_LIVE_P (XBUFFER (XWINDOW (selected_window)->contents)));
+
   if (n > 0)
     w->must_be_updated_p = true;
   return n;