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

From: Gerd Möllmann <[email-masked]>
Date: Sat, 09 Aug 2025 18:30:26 +0200

Alan Mackenzie <[email-masked]> writes:

> Hello, everybody.

Hallo Alan!

> I have an equally simple recipe, which suggests undo is not the culprit.
> Note step 3.
>
> 1. emacs -Q
> 2. Type asdf.
> 3. M-: buffer-undo-list.  This gives:
>
>     (nil (148 . 152) (t . 0) nil (1 . 148) (t . 0))
>
> 4. Type <backspace>.
> 5. M-: buffer-undo-list.  This gives:
>
>     (nil (#("f" 0 1 (fontified t)) . -151) (#<marker at 151 in *scratch*>
>     . -1) (#<marker at 151 in *scratch*> . -1) (#<marker at 151 in
>     *scratch*> . -1) (#<marker at 151 in *scratch*> . -1) (#<marker at
>     151 in *scratch*> . -1) (#<marker at 15\1 in *scratch*> . -1)
>     (#<marker at 151 in *scratch*> . -1) (#<marker at 151 in *scratch*> .
>     -1) (#<marker at 151 in *scratch*> . -1) 152 ...)
>
>   [ That enforced dumning down of the output by truncation is most
>   annoying.  I haven't found a way to get a complete output. ]

(I think M-: (pp buffer-undo-list) would do the trick for the
truncated output.)

>
> Unlike other people here, I see this in the emacs-30 branch, too.

Yes, make sense. After Eli's hint, I found this incoming call tree:

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

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

>
>> => 
>
>> Value:
>> (nil ("a" . 1) (#<marker at 1 in x.el> . -1)
>>      (#<marker at 1 in x.el> . -1) (#<marker at 1 in x.el> . -1)
>>      (#<marker at 1 in x.el> . -1) (#<marker at 1 in x.el> . -1)
>>      (#<marker at 1 in x.el> . -1) (#<marker at 1 in x.el> . -1)
>>      (#<marker at 1 in x.el> . -1) (#<marker at 1 in x.el> . -1)
>>      (#<marker at 1 in x.el> . -1) (#<marker at 1 in x.el> . -1)
>>      (#<marker at 1 in x.el> . -1) (#<marker at 1 in x.el> . -1)
>>      (#<marker at 1 in x.el> . -1) (#<marker at 1 in x.el> . -1)
>>      (#<marker at 1 in x.el> . -1) (#<marker at 1 in x.el> . -1)
>>      (#<marker at 1 in x.el> . -1) (#<marker at 1 in x.el> . -1)
>>      (#<marker at 1 in x.el> . -1) nil (1 . 2) (t . 0))
>> Local in buffer x.el; global value is nil
>
>> So far I can't see what is the cause. I would have guessed something in
>> or around primitive-undo, but that hasn't changed forever.
>
> My theory is that the cause is general poor marker hygiene.  In the elisp
> manual, there is (or was) a recommendation that after finishing use of a
> marker, one should remove it from the buffer's list of markers with
>
>     (move-marker foo nil)
>
> (or the C equivalent).  There are lots of places (probably several
> hundred) where this is not done.

Yeah, for sure.

> I tried gdb with a break point on Fpoint_marker, but every hit I got
> (about 15) was from save-excursion, which does clean up the marker it
> uses for point.
>
> I tried disabling inessential features like font-lock-mode, and setting
> post-self-insert-hook to nil.  This made no essential difference fo
> buffer-undo-list.
>
> [ .... ]

Yes, see above. I think it's that adding to the undo list when deleting
text (at least when deleting, haven't checked), and the creation of
many markers.