Subject: bug#71929: 30.0.60; crash in mark_image_cache

From: Po Lu <[email-masked]>
Date: Sun, 07 Jul 2024 15:41:53 +0800

Eli Zaretskii <[email-masked]> writes:

> This is the initial frame of the daemon.  It is not a GUI frame, and
> so it should not have a valid image cache.  I guess some change is
> needed in verify_image_cache_refcount?

Not quite: init_frame_faces is apparently called before the frame is
entered into Vframe_list, so, likewise, the face cache's reference count
should be verified before it is incremented.

Sean, please retry with this patch substituted for the previous:

diff --git a/src/frame.c b/src/frame.c
index 7f4bf274ad9..9793b9f5cbe 100644
--- a/src/frame.c
+++ b/src/frame.c
@@ -4831,14 +4831,20 @@ gui_set_font (struct frame *f, Lisp_Object arg, Lisp_Object oldval)
 	  /* Clean F's image cache of images whose values are derived
 	     from the font width.  */
 	  clear_image_cache (f, Qauto);
+	  verify_image_cache_refcount (FRAME_IMAGE_CACHE (f));
 	}
       else
 	{
+	  struct image_cache *old_cache = FRAME_IMAGE_CACHE (f);
+
 	  /* Release the current image cache, and reuse or allocate a
 	     new image cache with IWIDTH.  */
 	  FRAME_IMAGE_CACHE (f)->refcount--;
+	  FRAME_IMAGE_CACHE (f) = NULL;
+	  verify_image_cache_refcount (old_cache);
 	  FRAME_IMAGE_CACHE (f) = share_image_cache (f);
 	  FRAME_IMAGE_CACHE (f)->refcount++;
+	  verify_image_cache_refcount (FRAME_IMAGE_CACHE (f));
 	}
     }
 
diff --git a/src/frame.h b/src/frame.h
index 1d920d1a6bc..8a636c56643 100644
--- a/src/frame.h
+++ b/src/frame.h
@@ -1682,6 +1682,31 @@ IMAGE_OPT_FROM_ID (struct frame *f, int id)
   eassume (0 <= used);
   return 0 <= id && id < used ? FRAME_IMAGE_CACHE (f)->images[id] : NULL;
 }
+
+/* Abort if C is non-NULL and C's `refcount' field disagrees with the
+   number of frames holding references to the same.  */
+
+INLINE void
+verify_image_cache_refcount (struct image_cache *c)
+{
+  int expected;
+  Lisp_Object tail, frame;
+
+  if (c)
+    {
+      expected = 0;
+
+      FOR_EACH_FRAME (tail, frame)
+	{
+	  if (FRAME_IMAGE_CACHE (XFRAME (frame)) == c)
+	    expected++;
+	}
+
+      if (expected != c->refcount)
+	emacs_abort ();
+    }
+}
+
 #endif
 
 /***********************************************************************
diff --git a/src/image.c b/src/image.c
index 2945447b962..9387c78408b 100644
--- a/src/image.c
+++ b/src/image.c
@@ -3625,6 +3625,7 @@ cache_image (struct frame *f, struct image *img)
     {
       c = FRAME_IMAGE_CACHE (f) = share_image_cache (f);
       c->refcount++;
+      verify_image_cache_refcount (c);
     }
 
   /* Find a free slot in c->images.  */
diff --git a/src/xfaces.c b/src/xfaces.c
index 188dd4778bc..ed4d404fbf3 100644
--- a/src/xfaces.c
+++ b/src/xfaces.c
@@ -678,7 +678,10 @@ init_frame_faces (struct frame *f)
   /* Make or share an image cache.  */
   if (FRAME_WINDOW_P (f))
     {
-      FRAME_IMAGE_CACHE (f) = share_image_cache (f);
+      struct image_cache *c = share_image_cache (f);
+
+      verify_image_cache_refcount (c);
+      FRAME_IMAGE_CACHE (f) = c;
       ++FRAME_IMAGE_CACHE (f)->refcount;
     }
 #endif /* HAVE_WINDOW_SYSTEM */
@@ -709,6 +712,7 @@ free_frame_faces (struct frame *f)
       struct image_cache *image_cache = FRAME_IMAGE_CACHE (f);
       if (image_cache)
 	{
+	  verify_image_cache_refcount (image_cache);
 	  --image_cache->refcount;
 	  if (image_cache->refcount == 0)
 	    free_image_cache (f);