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

From: martin rudalics <[email-masked]>
Date: Thu, 8 Apr 2021 08:59:48 +0200

> FWIW, I've been trying to do the same and have been similarly
 > unsuccessful.

I attach a version where I make a shadow copy of Vwindow_list called
Vwindow_list_2.  The two should behave the same just that Vwindow_list_2
is never accessed by other code.  So we can put an assertion like

     eassert (!NILP (Fequal (Vwindow_list, Vwindow_list_2)));

anywhere in the code.  I put one in window_list and it should trigger
the same way as the length check before.

Now if anyone can suggest some strategic positions where to put these
assertions, I'll be all ears.

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/nsfns.m b/src/nsfns.m
index 5c4cc915e7..82136b0639 100644
--- a/src/nsfns.m
+++ b/src/nsfns.m
@@ -1447,7 +1447,7 @@ Turn the input menu (an NSMenu) into a lisp list for tracking on lisp side.
 
   /* Make sure windows on this frame appear in calls to next-window
      and similar functions.  */
-  Vwindow_list = Qnil;
+  Vwindow_list = Vwindow_list_2 = Qnil;
 
   return unbind_to (count, frame);
 }
diff --git a/src/w32fns.c b/src/w32fns.c
index 9db367bfaf..f2d5e34c3e 100644
--- a/src/w32fns.c
+++ b/src/w32fns.c
@@ -6148,7 +6148,7 @@ DEFUN ("x-create-frame", Fx_create_frame, Sx_create_frame,
 
   /* Make sure windows on this frame appear in calls to next-window
      and similar functions.  */
-  Vwindow_list = Qnil;
+  Vwindow_list = Vwindow_list_2 = Qnil;
 
   return unbind_to (count, frame);
 }
diff --git a/src/window.c b/src/window.c
index eb16e2a433..da0d2bc57d 100644
--- a/src/window.c
+++ b/src/window.c
@@ -86,6 +86,7 @@
    Functions creating or deleting windows should invalidate this cache
    by setting it to nil.  */
 Lisp_Object Vwindow_list;
+Lisp_Object Vwindow_list_2;
 
 /* True mean window_change_record has to record all live frames.  */
 static bool window_change_record_frames;
@@ -307,7 +308,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 +320,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 +600,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 +2577,11 @@ window_list (void)
 	  arglist = Fnreverse (arglist);
 	  Vwindow_list = nconc2 (Vwindow_list, arglist);
 	}
+
+      Vwindow_list_2 = Fcopy_sequence (Vwindow_list);
     }
+  else
+    eassert (!NILP (Fequal (Vwindow_list, Vwindow_list_2)));
 
   return Vwindow_list;
 }
@@ -2950,6 +2962,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 +3062,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;
 
@@ -3277,7 +3293,7 @@ DEFUN ("delete-other-windows-internal", Fdelete_other_windows_internal,
   free_window_matrices (r);
 
   fset_redisplay (f);
-  Vwindow_list = Qnil;
+  Vwindow_list = Vwindow_list_2 = Qnil;
 
   if (!WINDOW_LEAF_P (w))
     {
@@ -3411,6 +3427,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 +3437,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 +3999,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 +4095,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);
 }
 
@@ -4296,7 +4340,7 @@ make_window (void)
   w->scroll_bar_height = -1;
   w->column_number_displayed = -1;
   /* Reset window_list.  */
-  Vwindow_list = Qnil;
+  Vwindow_list = Vwindow_list_2 = Qnil;
   /* Return window.  */
   XSETWINDOW (window, w);
   return window;
@@ -5086,7 +5130,7 @@ DEFUN ("delete-window-internal", Fdelete_window_internal, Sdelete_window_interna
 	}
 
       fset_redisplay (f);
-      Vwindow_list = Qnil;
+      Vwindow_list = Vwindow_list_2 = Qnil;
 
       wset_next (w, Qnil);  /* Don't delete w->next too.  */
       free_window_matrices (w);
@@ -5174,6 +5218,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 +7279,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;
 }
 
@@ -7279,7 +7328,7 @@ delete_all_child_windows (Lisp_Object window)
       wset_buffer (w, Qnil);
     }
 
-  Vwindow_list = Qnil;
+  Vwindow_list = Vwindow_list_2 = Qnil;
 }
 
 static ptrdiff_t
@@ -8128,8 +8177,9 @@ init_window_once (void)
   selected_window = Qnil;
   staticpro (&selected_window);
 
-  Vwindow_list = Qnil;
+  Vwindow_list = Vwindow_list_2 = Qnil;
   staticpro (&Vwindow_list);
+  staticpro (&Vwindow_list_2);
 
   minibuf_selected_window = Qnil;
   staticpro (&minibuf_selected_window);
@@ -8148,6 +8198,7 @@ init_window_once (void)
   PDUMPER_RESET_LV (minibuf_window, Qnil);
   PDUMPER_RESET_LV (selected_window, Qnil);
   PDUMPER_RESET_LV (Vwindow_list, Qnil);
+  PDUMPER_RESET_LV (Vwindow_list_2, Qnil);
   PDUMPER_RESET_LV (minibuf_selected_window, Qnil);
 
   /* Hack: if mode_line_in_non_selected_windows is true (which it may
@@ -8178,7 +8229,7 @@ init_window_once (void)
 void
 init_window (void)
 {
-  Vwindow_list = Qnil;
+  Vwindow_list = Vwindow_list_2 = Qnil;
 }
 
 void
diff --git a/src/window.h b/src/window.h
index b6f88e8f55..74d3cbb7be 100644
--- a/src/window.h
+++ b/src/window.h
@@ -1171,6 +1171,7 @@ #define CHECK_LIVE_WINDOW(WINDOW)				\
 
 /* These used to be in lisp.h.  */
 extern Lisp_Object Vwindow_list;
+extern Lisp_Object Vwindow_list_2;
 
 extern Lisp_Object window_list (void);
 extern Lisp_Object window_parameter (struct window *, Lisp_Object parameter);
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;
diff --git a/src/xfns.c b/src/xfns.c
index d90644819b..fbe9014555 100644
--- a/src/xfns.c
+++ b/src/xfns.c
@@ -4225,7 +4225,7 @@ DEFUN ("x-create-frame", Fx_create_frame, Sx_create_frame,
 
   /* Make sure windows on this frame appear in calls to next-window
      and similar functions.  */
-  Vwindow_list = Qnil;
+  Vwindow_list = Vwindow_list_2 = Qnil;
 
  return unbind_to (count, frame);
 }