Subject: Re: bug#79200: 31.0.50; Duplicated elements for '#<marker at' in buffer-undo-list

From: Alan Mackenzie <[email-masked]>
Date: Sun, 10 Aug 2025 12:36:30 +0000

Hello, Gerd.

On Sun, Aug 10, 2025 at 06:55:45 +0200, Gerd Möllmann wrote:
> Pip Cet <[email-masked]> writes:

> >> - record_marker_adjustments undo.c:127
> >>   - record_delete record_delete:187
> >>     + casify_region casify_region:555
> >>     + adjust_after_replace adjust_after_replace:1408
> >>     + replace_range replace_range:1639
> >>     + del_range_2 del_range_2:2030
> >>     + record_change record_change:201

> >> where record_marker_adjustments puts basically all present markers in a
> >> buffer's undo list. del_range_2 I would interpret that any text deletion
> >> might do that.

> > I think it may make more sense to find out who goes around creating
> > markers and not cleaning up after them. But I suspect many places cannot
> > easily be fixed because the markers might be exposed to Lisp.

> Probably. Good question is how to find these places, especially when
> they are created in Lisp :-/.

I've made a start on this (see patch below).  It's mainly in the C files,
where I grepped for Fpoint_marker and Fcopy_marker, and tidied up the
uses which didn't tidy up their uses of markers.  But also in some Lisp
files, which are directly part of the command loop (simple.el), or called
from it in hooks (jit.el, electric.el).

Unfortunately, this hasn't helped much.  When I type "asdfasdf" into
*scratch* on emacs -Q, then <BACKSPACE>, I still see five point markers
in buffer-undo-list.  If I <BACKSPACE> a second time, this adds on a
further ten point markers to b-u-list.

It's worth mentioning that these markers aren't eq to eachother.  At
least, not all of them.  I have a strong suspicion that there are
duplicates in the 15 markers in buffer-undo-list after my two deletions.

It is unclear whether the <BACKSPACE> creates these markers, or just puts
existing markers into buffer-undo-list.  Maybe we could do with a
primitive in marker.c which would print out the current buffer's list of
markers.

> >> I think a garbage-collect with the old GC will clean up the undo list
> >> eventually by calling compact_undo_list. With MPS, that doesn't work.
> >> The cleanup in compact_undo_list cannot be used because MPS's weak
> >> objects and GC work completely differently.

> > That's my understanding, too. And it means that this probably isn't
> > horribly important to fix on the master branch. On feature/igc, it looks
> > like something has to be done.

> >> At the moment I'm a bit out of ideas. Waiting for a divine inspiration,
> >> so to speak.

> > Maybe some markers could be marked at creation time as not being
> > important enough to be recorded in the undo list? I'm not really sure
> > which markers are being recorded and which need to be for undo to work.

This might be a silly question, but why do we have to put markers into
buffer-undo-list at all?  Surely they should "fix themselves" as other
changes in the buffer get undone?

> Might be possible but also requires to find where the markers are
> created, find out if they are nver required for undo, and so on. 

Here's a patch which tidies up several markers.  Unfortunately it doesn't
seem to make a massive difference to the problem.

diff --git a/lisp/abbrev.el b/lisp/abbrev.el
index c06b6b06406..9574d4d4d0a 100644
--- a/lisp/abbrev.el
+++ b/lisp/abbrev.el
@@ -783,6 +783,10 @@ abbrev-expansion
 (defun abbrev--before-point ()
   "Try and find an abbrev before point.  Return it if found, nil otherwise."
   (unless (eq abbrev-start-location-buffer (current-buffer))
+;;;; EXPERIMENTAL STOUGH, 2025-08-09
+    (when (markerp abbrev-start-location)
+      (set-marker abbrev-start-location nil))
+;;;; END OF EXPERIMENTAL STOUGH
     (setq abbrev-start-location nil))
 
   (let ((tables (abbrev--active-tables))
@@ -803,7 +807,11 @@ abbrev--before-point
           (when (> end start)
             (setq name (buffer-substring start end))
             (goto-char pos)               ; Restore point.
-            (list (abbrev-symbol name tables) name start end)))
+            (list (abbrev-symbol name tables) name start end))
+;;;; EXPERIMENTAL STOUGH, 2025-08-09
+          ;; (set-marker start nil)  NO!!!
+;;;; END OF EXP STOUGH
+                                                              )
 
       (while (and tables (not (car res)))
         (let* ((table (pop tables))
@@ -1090,7 +1098,12 @@ abbrev--default-expand
             ;; where it started.
             (if (and (> startpos endmark)
                      (= (point) endmark)) ;Obey skeletons that move point.
-                (goto-char startpos))))))))
+                (goto-char startpos)))
+;;;; EXPERIMENTAL STOUGH, 2025-08-09
+          (set-marker startpos nil)
+          (set-marker endmark nil)
+;;;; END OF EXP STOUGH
+                                      )))))
 
 (defun unexpand-abbrev ()
   "Undo the expansion of the last abbrev that expanded.
diff --git a/lisp/electric.el b/lisp/electric.el
index 39e13e1ca0c..c8fcba803cc 100644
--- a/lisp/electric.el
+++ b/lisp/electric.el
@@ -279,7 +279,11 @@ electric-indent-post-self-insert-function
                   ;; to `indent-according-to-mode' but after we may
                   ;; not be (Bug#15767).
                   (when (and (eolp))
-                    (delete-horizontal-space t))))))
+                    (delete-horizontal-space t))))
+;;;; EXPERIMENTAL STOUGH, 2025-08-09
+              (set-marker before nil)
+;;;; END OF EXPERIMENTAL STOUGH
+                                                  ))
           (unless (and electric-indent-inhibit
                        (not at-newline))
             (condition-case-unless-debug ()
diff --git a/lisp/jit-lock.el b/lisp/jit-lock.el
index 711d7ca8cac..3e8c75abc7b 100644
--- a/lisp/jit-lock.el
+++ b/lisp/jit-lock.el
@@ -502,7 +502,12 @@ jit-lock-force-redisplay
        ;; Don't cause refontification (it's already been done), but just do
        ;; some random buffer change, so as to force redisplay.
        (put-text-property start end 'fontified nil)
-       (put-text-property start end 'fontified t)))))
+       (put-text-property start end 'fontified t)
+;;;; EXPERIMENTAL STOUGH, 2025-08-09
+       (move-marker start nil)
+       (move-marker end nil)
+;;;; END OF EXPERIMENTAL STOUGH
+                                                 ))))
 
 ;;; Stealth fontification.
 
@@ -751,7 +756,10 @@ jit-lock--antiblink-post-command
              (jit-lock-context-fontify)
              (cancel-timer jit-lock--antiblink-grace-timer)
              (setq jit-lock--antiblink-grace-timer nil))))
-    ;; Update variables (and release the marker).
+    ;; Update variables (and release the markers).
+;;;; EXPERIMENTAL STOUGH, 2025-08-09
+    (set-marker new-l-b-p nil)
+;;;; END OF EXP. STOUGH
     (set-marker jit-lock--antiblink-line-beginning-position nil)
     (setq jit-lock--antiblink-line-beginning-position new-l-b-p
           jit-lock--antiblink-string-or-comment new-s-o-c)))
diff --git a/src/lread.c b/src/lread.c
index 57d3239e283..b0cc1b6ec0c 100644
--- a/src/lread.c
+++ b/src/lread.c
@@ -2244,8 +2244,12 @@ readevalloop (Lisp_Object readcharfun,
 
 	  /* Just for cleanliness, convert END to a marker
 	     if it is an integer.  */
-	  if (FIXNUMP (end))
-	    end = Fpoint_max_marker ();
+	  /* EXPERIMENTAL STOUGH, 2025-08-09 */
+	  /*   DON'T make this conversion! It is thouroughly unclean,
+	      since we never unchain_marker it, so it is left clogging
+	      up the buffer's marker list. */
+	  /* if (FIXNUMP (end)) */
+	  /*   end = Fpoint_max_marker (); */
 	}
 
       /* On the first cycle, we can easily test here
@@ -2312,8 +2316,11 @@ readevalloop (Lisp_Object readcharfun,
 	  && XHASH_TABLE (read_objects_completed)->count > 0)
 	read_objects_completed = Qnil;
 
-      if (!NILP (start) && continue_reading_p)
-	start = Fpoint_marker ();
+      /* EXPERIMENTAL STOUGH, 2025-08-09 */
+      /* DON'T create another marker each time around this loop!  This just
+	 clogs up the buffer's marker list with useless markers.  */
+      /* if (!NILP (start) && continue_reading_p) */
+      /* 	start = Fpoint_marker (); */
 
       /* Restore saved point and BEGV.  */
       unbind_to (count1, Qnil);
diff --git a/src/window.c b/src/window.c
index 1ac004af5e0..a96a6aa2eb8 100644
--- a/src/window.c
+++ b/src/window.c
@@ -6592,6 +6592,9 @@ window_scroll_line_based (Lisp_Object window, int n, bool whole, bool noerror)
 
   if (lose)
     {
+      /* EXPERIMENTAL STOUGH, 2025-08-09 */
+      unchain_marker (XMARKER (opoint_marker));
+      /* END OF EXP STOUGH */
       if (noerror)
 	return;
       else
@@ -6689,6 +6692,9 @@ window_scroll_line_based (Lisp_Object window, int n, bool whole, bool noerror)
     }
   else
     {
+      /* EXPERIMENTAL STOUGH, 2025-08-09 */
+      unchain_marker (XMARKER (opoint_marker));
+      /* END OF EXP STOUGH */
       if (noerror)
 	return;
       else
@@ -6701,6 +6707,9 @@ window_scroll_line_based (Lisp_Object window, int n, bool whole, bool noerror)
 		  ? make_fixnum (BUF_PT (XBUFFER (w->contents)))
 		  : Fmarker_position (w->pointm)),
 		 w->contents);
+  /* EXPERIMENTAL STOUGH, 2025-08-09 */
+  unchain_marker (XMARKER (opoint_marker));
+  /* END OF EXP STOUGH */
 }

diff --git a/src/xdisp.c b/src/xdisp.c
index 2691296b282..c787657e429 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -12085,6 +12085,9 @@ DEFUN ("display--line-is-continued-p", Fdisplay__line_is_continued_p,
 	}
       SET_PT_BOTH (marker_position (opoint), marker_byte_position (opoint));
       bidi_unshelve_cache (itdata, false);
+      /* EXPERIMENTAL STOUGH, 2025-08-09 */
+      Fset_marker (opoint, Qnil, Qnil);
+      /* END OF EXPERIMENTAL STOUGH */
     }
   set_buffer_internal_1 (oldb);

-- 
Alan Mackenzie (Nuremberg, Germany).