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 14:41:43 +0000

Hello, Gerd.

On Sun, Aug 10, 2025 at 15:11:24 +0200, Gerd Möllmann wrote:
> Alan Mackenzie <[email-masked]> writes:

> >> > 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).

> Hi Alan,

> Thanks.

> > 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.

> Hm, you mean eq markers inside the same undo group has duplicates? That
> I haven't seen yet. There is code in primitive-undo for that case,
> though, so it seems to be expected in some case. No idea.

Ah, OK.

> If the same marker appears twice in different undo groups, then that's
> normal. The entries for markers mean something like "add some delta to
> the marker's position when we undo this record".

Yes

> > 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.

Yes, I've called it buffer-marker-list, and it looks like this:

diff --git a/src/lisp.h b/src/lisp.h
index 64b5c227583..bc5a404c261 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -1400,6 +1400,9 @@ #define XSETTHREAD(a, b) XSETPSEUDOVECTOR (a, b, PVEC_THREAD)
 #define XSETMUTEX(a, b) XSETPSEUDOVECTOR (a, b, PVEC_MUTEX)
 #define XSETCONDVAR(a, b) XSETPSEUDOVECTOR (a, b, PVEC_CONDVAR)
 #define XSETNATIVE_COMP_UNIT(a, b) XSETPSEUDOVECTOR (a, b, PVEC_NATIVE_COMP_UNIT)
+/* EXPERIMENTAL STOUGH, 2025-08-10 */
+#define XSETMARKER(a, b) XSETPSEUDOVECTOR (a, b, PVEC_MARKER)
+/* END OF EXPERIMENTAL STOUGH */
 
 /* Efficiently convert a pointer to a Lisp object and back.  The
    pointer is represented as a fixnum, so the garbage collector
diff --git a/src/marker.c b/src/marker.c
index 4ab68ec7bbe..c5be58d1d7a 100644
--- a/src/marker.c
+++ b/src/marker.c
@@ -424,6 +424,24 @@ buf_bytepos_to_charpos (struct buffer *b, ptrdiff_t bytepos)
 
 /* Operations on markers. */
 
+/* EXPERIMENTAL STOUGH, 2025-08-10 */
+DEFUN ("buffer-marker-list", Fbuffer_marker_list, Sbuffer_marker_list, 0, 0, 0,
+       doc: /* Return (a copy of) the list of markers in the current buffer.  */)
+  (void)
+{
+  Lisp_Object markers = Qnil;
+  Lisp_Object mk;
+
+  for (struct Lisp_Marker *ma = BUF_MARKERS (current_buffer); ma; ma = ma->next)
+    {
+      XSETMARKER (mk, ma);
+      markers = (Fcons (mk, markers));
+    }
+  return Fnreverse (markers);
+}
+
+/* END OF EXPERIMENTAL STOUGH */
+
 DEFUN ("marker-buffer", Fmarker_buffer, Smarker_buffer, 1, 1, 0,
        doc: /* Return the buffer that MARKER points into, or nil if none.
 Returns nil if MARKER points into a dead buffer.  */)
@@ -833,6 +851,9 @@ verify_bytepos (ptrdiff_t charpos)
 void
 syms_of_marker (void)
 {
+  /* EXPERIMENTAL STOUGH, 2025-08-10 */
+  defsubr (&Sbuffer_marker_list);
+  /* END OF EXPERIMENTAL STOUGH */
   defsubr (&Smarker_position);
   defsubr (&Smarker_last_position);
   defsubr (&Smarker_buffer);

> I don't believe it creates the markers. But I'm pretty sure that
> record_change can put a lot of markers to a buffer's undo list. It just
> considers all markers in a buffer's markers list as relevant for undo.
> IOW, when we undo, all these markers need their positions changed. It
> has no way to recognize markers that are only used temporarily. 

In emacs -Q, C-x b foo <RET>.

Now do M-: (length (buffer-marker-list)).  It returns 9.  Repeat.  It
now returns 14.  Then, successively, 19, 26, 31, 36, ....  OK, between
the 19 and 26 I had finger trouble.

But it would appear that the command loop, or something close to it, is
adding five frivolous point markers at each iteration.  This is in my
"EXPERIMENTAL"ly patched master.  It may be even worse in a vanilla
master.

> ...

> > 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?

> Please see above. I hope that explains it. 

Thanks.  I'll study it more carefully when I'm in a less hacky mood.

-- 
Alan Mackenzie (Nuremberg, Germany).