Subject: Re: bug#77065: 31.0.50; Infinite loop in move_it_to

From: Yifan Zhu <[email-masked]>
Date: Mon, 17 Mar 2025 19:36:15 -0700

On 3/17/25 7:27 AM, Eli Zaretskii wrote:
> Thanks, but I don't have access to a system where these prerequisites
> can be met.  So either someone can reproduce this, debug the problem,
> and describe the reason for the loop, or I'd need to ask you to do it,
> if possible.  Or, if you can show a recipe for reproducing the problem
> without installing LaTeX and xenops, I could try reproducing this on
> the systems to which I have access.

Right now I have a conjecture about what might be causing the infinite 
loop -- when line number are produced, we can no longer use it->hpos == 
0 to test if the glyph is the first glyph. Perhaps this is best 
explained by the following diff:

diff --git a/src/xdisp.c b/src/xdisp.c
index d7e0691c44d..0f4210ab814 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -9972,6 +9972,7 @@ move_it_in_display_line_to (struct it *it,
    ptrdiff_t prev_pos = IT_CHARPOS (*it);
    bool saw_smaller_pos = prev_pos < to_charpos;
    bool line_number_pending = false;
+  int initial_hpos = 0;
    int this_line_subject_to_line_prefix = 0;

  #ifdef GLYPH_DEBUG
@@ -10037,7 +10038,10 @@ #define BUFFER_POS_REACHED_P()                    \
        && should_produce_line_number (it))
      {
        if (it->current_x == it->first_visible_x)
-        maybe_produce_line_number (it);
+           {
+          maybe_produce_line_number (it);
+          initial_hpos = it->hpos;
+           }
        else
          line_number_pending = true;
      }
@@ -10298,7 +10302,7 @@ #define IT_RESET_X_ASCENT_DESCENT(IT)            \
            if (/* IT->hpos == 0 means the very first glyph
               doesn't fit on the line, e.g. a wide
               image.  */
-              it->hpos == 0
+              it->hpos == initial_hpos
                || (new_x == it->last_visible_x
                && FRAME_WINDOW_P (it->f)))
              {
@@ -10474,6 +10478,7 @@ #define IT_RESET_X_ASCENT_DESCENT(IT)            \
                line_number_pending = false;
                it->current_x = it->first_visible_x;
                maybe_produce_line_number (it);
+              initial_hpos = it->hpos;
                it->current_x += new_x - it->first_visible_x;
              }
            /* Glyph is visible.  Increment number of glyphs that

Some other evidence includes the following gdb session:

(TLDR: in `move_it_in_display_line_to`, maybe_produce_line_number 
increases it->hpos from 0 to 4. Thus afterwards the test for the glyph 
being the first one fails)

(gdb) r ~/Downloads/main.tex
+r ~/Downloads/main.tex
Starting program: /home/yifan/packages/emacs-git/build/bin/emacs 
~/Downloads/main.tex
^Z
Thread 1 "emacs" received signal SIGTSTP, Stopped (user).
(gdb) bt
#0  0x00005555556f54c5 in EQ (x=0x5555570825c4, y=0x5555570825c4) at 
/home/yifan/packages/emacs-git/src/lisp.h:1327
#1  0x00005555557002e8 in face_attr_equal_p (v1=0x5555570825c4, 
v2=0x5555570825c4) at xfaces.c:4417
#2  0x0000555555700416 in lface_equal_p (v1=0x5555588380d0, 
v2=0x7ffffffecc80) at xfaces.c:4450
#3  0x000055555570157d in lookup_face (f=0x555555ecda90, 
attr=0x7ffffffecc80) at xfaces.c:5012
#4  0x00005555557018c0 in lookup_named_face (w=0x555555ecdce8, 
f=0x555555ecda90, symbol=0x67e0, signal_p=false) at xfaces.c:5099
#5  0x0000555555701bd4 in lookup_basic_face (w=0x555555ecdce8, 
f=0x555555ecda90, face_id=0) at xfaces.c:5163
#6  0x0000555555664ed2 in produce_special_glyphs (it=0x7ffffffee420, 
what=IT_CONTINUATION) at xdisp.c:32433
#7  0x000055555560d7e8 in init_iterator (it=0x7ffffffee420, 
w=0x555555ecdce8, charpos=-1, bytepos=-1, row=0x5555559f8ba0 
<scratch_glyph_row>, base_face_id=DEFAULT_FACE_ID) at xdisp.c:3365
#8  0x000055555564ce8d in maybe_produce_line_number (it=0x7fffffff6e20) 
at xdisp.c:25201
#9  0x000055555561f53e in move_it_in_display_line_to (it=0x7fffffff6e20, 
to_charpos=307, to_x=-1, op=MOVE_TO_POS) at xdisp.c:10040
#10 0x0000555555622e6c in move_it_to (it=0x7fffffff6e20, to_charpos=307, 
to_x=-1, to_y=-1, to_vpos=-1, op=8) at xdisp.c:10945
#11 0x0000555555623a79 in move_it_vertically_backward 
(it=0x7fffffff9700, dy=448) at xdisp.c:11159
#12 0x000055555563e600 in redisplay_window (window=0x555555ecdced, 
just_this_one_p=false) at xdisp.c:20925
#13 0x00005555556349fd in redisplay_window_0 (window=0x555555ecdced) at 
xdisp.c:18230
#14 0x00005555557f1e3e in internal_condition_case_1 (bfun=0x5555556349bb 
<redisplay_window_0>, arg=0x555555ecdced, handlers=0x7fffef32b4b3, 
hfun=0x555555634899 <redisplay_window_error>) at eval.c:1644
#15 0x000055555563486f in redisplay_windows (window=0x555555ecdced) at 
xdisp.c:18199
#16 0x000055555563481e in redisplay_windows (window=0x5555587c5bcd) at 
xdisp.c:18193
#17 0x0000555555633616 in redisplay_internal () at xdisp.c:17616
#18 0x00005555556315c3 in redisplay () at xdisp.c:16772
#19 0x000055555572b711 in read_char (commandflag=1, map=0x55555851b4e3, 
prev_event=0x0, used_mouse_menu=0x7fffffffd73a, end_time=0x0) at 
keyboard.c:2672
#20 0x000055555573e443 in read_key_sequence (keybuf=0x7fffffffd9b0, 
prompt=0x0, dont_downcase_last=false, can_return_switch_frame=true, 
fix_current_buffer=true, prevent_redisplay=false, 
disable_text_conversion_p=false) at keyboard.c:10757
#21 0x0000555555727d97 in command_loop_1 () at keyboard.c:1424
#22 0x00005555557f1d97 in internal_condition_case (bfun=0x555555727989 
<command_loop_1>, handlers=0x90, hfun=0x555555726eba <cmd_error>) at 
eval.c:1620
#23 0x00005555557275d6 in command_loop_2 (handlers=0x90) at keyboard.c:1163
#24 0x00005555557f12ec in internal_catch (tag=0x11f10, 
func=0x5555557275ac <command_loop_2>, arg=0x90) at eval.c:1300
#25 0x0000555555727568 in command_loop () at keyboard.c:1141
#26 0x0000555555726a5c in recursive_edit_1 () at keyboard.c:749
#27 0x0000555555726c08 in Frecursive_edit () at keyboard.c:832
#28 0x0000555555722cc5 in main (argc=2, argv=0x7fffffffde98) at emacs.c:2560
(gdb) break move_it_in_display_line_to
+break move_it_in_display_line_to
Breakpoint 2 at 0x55555561f329: file xdisp.c, line 9963.
(gdb) c
+c
Continuing.

Thread 1 "emacs" hit Breakpoint 2, move_it_in_display_line_to 
(it=0x7fffffff6e20, to_charpos=307, to_x=-1, op=MOVE_TO_POS) at xdisp.c:9963
9963    {
(gdb) watch it->hpos
+watch it->hpos
Hardware watchpoint 3: it->hpos
(gdb) p it->hpos
+p it->hpos
$1 = 0
(gdb) c
+c
Continuing.

Thread 1 "emacs" hit Hardware watchpoint 3: it->hpos

Old value = 0
New value = 1
maybe_produce_line_number (it=0x7fffffff6e20) at xdisp.c:25292
25292         if (p)
(gdb)
+c
Continuing.

Thread 1 "emacs" hit Hardware watchpoint 3: it->hpos

Old value = 1
New value = 2
maybe_produce_line_number (it=0x7fffffff6e20) at xdisp.c:25292
25292         if (p)
(gdb)
+c
Continuing.

Thread 1 "emacs" hit Hardware watchpoint 3: it->hpos

Old value = 2
New value = 3
maybe_produce_line_number (it=0x7fffffff6e20) at xdisp.c:25292
25292         if (p)
(gdb)
+c
Continuing.

Thread 1 "emacs" hit Hardware watchpoint 3: it->hpos

Old value = 3
New value = 4
maybe_produce_line_number (it=0x7fffffff6e20) at xdisp.c:25292
25292         if (p)
(gdb) fini
+fini
Run till exit from #0  maybe_produce_line_number (it=0x7fffffff6e20) at 
xdisp.c:25292
0x000055555561f53e in move_it_in_display_line_to (it=0x7fffffff6e20, 
to_charpos=307, to_x=-1, op=MOVE_TO_POS) at xdisp.c:10040
10040               maybe_produce_line_number (it);
(gdb) next
+next
10045         if (it->area == TEXT_AREA && !it->string_from_prefix_prop_p)
(gdb)
+next
10046           handle_line_prefix (it);
(gdb)
+next
10051         this_line_subject_to_line_prefix = 
it->string_from_prefix_prop_p;
(gdb)
+next
10054     if (IT_CHARPOS (*it) < CHARPOS (this_line_min_pos))
(gdb)
+next
10059         int x, i, ascent = 0, descent = 0;
(gdb)
+next
10068         if ((op & MOVE_TO_POS) != 0
(gdb)
+next
10069             && BUFFERP (it->object)
(gdb)
+next
10070             && it->method == GET_FROM_BUFFER
(gdb)
+next
10110         if (!get_next_display_element (it))
(gdb)
+next
10116         if (it->line_wrap == TRUNCATE)
(gdb)
+next
10135             if (it->line_wrap == WORD_WRAP && it->area == TEXT_AREA)
(gdb)
+next
10137                 bool next_may_wrap = may_wrap;
(gdb)
+next
10139                 if (char_can_wrap_after (it))
(gdb)
+next
10140                   next_may_wrap = true;
(gdb)
+next
10144                 if (may_wrap && char_can_wrap_before (it))
(gdb)
+next
10170                 may_wrap = next_may_wrap;
(gdb)
+next
10176         ascent = it->max_ascent;
(gdb)
+next
10177         descent = it->max_descent;
(gdb)
+next
10183         x = it->current_x;
(gdb)
+next
10185         PRODUCE_GLYPHS (it);
(gdb)
+next
10187         if (it->area != TEXT_AREA)
(gdb)
+next
10222         if (it->nglyphs)
(gdb)
+next
10226             int single_glyph_width = it->pixel_width / it->nglyphs;
(gdb)
+next
10228             int x_before_this_char = x;
(gdb)
+next
10229             int hpos_before_this_char = it->hpos;
(gdb)
+next
10231             for (i = 0; i < it->nglyphs; ++i, x = new_x)
(gdb)
+next
10233                 new_x = x + single_glyph_width;
(gdb)
+next
10236                 if ((op & MOVE_TO_X) && new_x > to_x)
(gdb)
+next
10271                     it->line_wrap != TRUNCATE
(gdb)
+next
10270                 if (/* Lines are continued.  */
(gdb)
+next
10273                         new_x > it->last_visible_x
(gdb)
+next
10272                     && (/* And glyph doesn't fit on the line.  */
(gdb)
+next
10284                     && (!this_line_subject_to_line_prefix
(gdb)
+next
10296                     bool moved_forward = false;
(gdb)
+next
10301                         it->hpos == 0
(gdb)
+next
10298                     if (/* IT->hpos == 0 means the very first glyph
(gdb)
+next
10302                         || (new_x == it->last_visible_x
(gdb)
+next
10414                       IT_RESET_X_ASCENT_DESCENT (it);
(gdb) quit