GNU bug report logs - #81903
[PATCH] Emacs crashes when a mode-line construct triggers a glyph matrix adjustment while the mode line is being built

Please note: This is a static page, with minimal formatting, updated once a day.
Click here to see this page with the latest information and nicer formatting.

Package: emacs; Reported by: "Alberti, Andrea" <a.alberti82@HIDDEN>; Keywords: patch; dated Sun, 20 Sep 2026 17:44:07 UTC; Maintainer for emacs is bug-gnu-emacs@HIDDEN.
Changed bug title to '[PATCH] Emacs crashes when a mode-line construct triggers a glyph matrix adjustment while the mode line is being built' from 'Emacs crashes when a mode-line construct triggers a glyph matrix adjustment while the mode line is being built' Request was from "Alberti, Andrea" <a.alberti82@HIDDEN> to control <at> debbugs.gnu.org. Full text available.
Changed bug title to 'Emacs crashes when a mode-line construct triggers a glyph matrix adjustment while the mode line is being built' from '[PATCH] Fix crash by not adjusting the glyph matrix while building the mode line' Request was from "Alberti, Andrea" <a.alberti82@HIDDEN> to control <at> debbugs.gnu.org. Full text available.

Message received at submit <at> debbugs.gnu.org:


Received: (at submit) by debbugs.gnu.org; 20 Sep 2026 17:43:24 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sun Sep 20 13:43:24 2026
Received: from localhost ([127.0.0.1]:33280 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1x8LZR-0004G3-Mw
	for submit <at> debbugs.gnu.org; Sun, 20 Sep 2026 13:43:24 -0400
Received: from lists1p.gnu.org ([2001:470:142::17]:34140)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <a.alberti82@HIDDEN>)
 id 1x8LZN-0004FE-0K
 for submit <at> debbugs.gnu.org; Sun, 20 Sep 2026 13:43:19 -0400
Received: from eggs.gnu.org ([2001:470:142:3::10])
 by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <a.alberti82@HIDDEN>)
 id 1x8LZE-0001uq-Sn
 for bug-gnu-emacs@HIDDEN; Sun, 20 Sep 2026 13:43:09 -0400
Received: from mail-wr2-x10.google.com ([2a00:1450:4864:30::10])
 by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)
 (Exim 4.90_1) (envelope-from <a.alberti82@HIDDEN>)
 id 1x8LZ8-0005YP-Sd
 for bug-gnu-emacs@HIDDEN; Sun, 20 Sep 2026 13:43:06 -0400
Received: by mail-wr2-x10.google.com with SMTP id
 ffacd0b85a97d-482f633cd80so1003730f8f.2
 for <bug-gnu-emacs@HIDDEN>; Sun, 20 Sep 2026 10:43:02 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20251104; t=1789926181; x=1790530981; darn=gnu.org;
 h=content-type:mime-version:message-id:date:subject:to:from:from:to
 :cc:subject:date:message-id:reply-to:content-type;
 bh=HKdIptOV1ZqIg4n1I/ilhysljY3twBkrmg7kvisyML8=;
 b=KzkwSXjOvVuBznI46fynVyH4ZHOpyC6DcS5XuII6FlA/OKAa5pOcabogTJFlGexDii
 pASqiGUiA9m/QvQAkmUG4I9/D6LyXpxn0xYi1aEP7S4ePPBu9yRiCZQm4aWIMcxkf2t1
 Ok5H1yi1nVvpKzLuL7YE8qv6x7oDOHR8ie77ycOZ7Y9zWC9t4Ves2p/gftt5AmPUzHUa
 BN2vVYR6pa6ET/II9/diGbuAL7SbFNujcHSJeXGgpegrp6JEhg71D2aPUMRs926zOssh
 Q+0JyctHJg8KjJzq2Jk7ZgDmXXELTnfoBOe6U6IDG3zVn8Uir5EVFWFaGhzoBj6njjoe
 gLFg==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20260707; t=1789926181; x=1790530981;
 h=content-type:mime-version:message-id:date:subject:to:from:x-gm-gg
 :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to
 :content-type;
 bh=HKdIptOV1ZqIg4n1I/ilhysljY3twBkrmg7kvisyML8=;
 b=hcimQypFnApaX1GHTTkH1pV/T51x+zEsIkK6Ji0Vpf+VJ5fFiOJOhrCSU0Au9vXctZ
 5nQGFFijTYfR1atXtZok1/sET7puBxp3DSVltSIpGymBtN4h07NpzuMCF5Hut7vAjNz7
 YzDvRuov7ZgnP+sZ/+nYvuPboAPNn7F3hbSMKx40gm0sm6S89ZJy1Ku4bcAExWc0/gMK
 W/iUxqwGp50vunxHfqpfIHD0bwPs/HtfO63D2r5OR64oa1WNPS8BYjVhrTh7DrxC/Uad
 f6z6xAZXmH3e17DZM2rIgkG6PLL9W+kxilt/kq7excs0xgrD/+QnlxHR9nnhoq0QxhEB
 TKYQ==
X-Gm-Message-State: AFuF++l12UEBXX7dFfocsKnFWTvynO8n664AjTVq1i5+pQXGvJu6raJU
 jzlHCnfgkE6eyovs8XvxS7KHyhMlW9bornxKUgIoRnjlgIPsNtf51Uql9liYS+fG
X-Gm-Gg: AYBFou3kIrd74abDvoKb8j4jkoTZeZ1I2XwrHV+ZD88ECocdkUiExoWXs0agKRVi4RD
 +LiyvuaJiMxT7z4XOtpL1sWi3n51b7fLv4Sk+CAKEYoRDHswaoK69ZpO8M0oQqnRdKE9VS9nIvh
 9i0IQ9+Ctojy3J/ZNSMerCtOtYm7gvKREEc0EAN/lNEJouRmZ29CD6c4GRDQAAacVIrZfZuvrUL
 cSB9RfrUDAC9ufCGyWzh5GbfGkw6RkKcEFS16VuOVE4AJtf1OI5rV571iVIb7AjSKjZA3o4yw6z
 v6+SLJkjbeeI3Ibl2LOAZfgNoFdS6fRZxr1pptpWsI66u5G9EiZHAlVxNrJ/njYdEmXRLfRyMq1
 25YmMMA1bjJ3z1uLbhdOXVQBkCM11y2n5R73FwdS3uGxbCVzVbggoJYtvelmuX2tWVsORsPa8c5
 IxjoilNHCQ9RN9gL7jK+9H/CvDuGDHEjblr9UdHGgVXO0TfanWMiAT9Lv0HJkej+iE+VEVXFyph
 2LRiHcEWILOkQ==
X-Received: by 2002:a05:6000:610:b0:487:afe:c154 with SMTP id
 ffacd0b85a97d-4871e36bf11mr14083565f8f.40.1789926180586; 
 Sun, 20 Sep 2026 10:43:00 -0700 (PDT)
Received: from [10.5.0.2] ([45.150.175.253]) by smtp.gmail.com with ESMTPSA id
 ffacd0b85a97d-487244608a3sm15649386f8f.8.2026.09.20.10.42.59
 for <bug-gnu-emacs@HIDDEN>
 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128);
 Sun, 20 Sep 2026 10:42:59 -0700 (PDT)
From: "Alberti, Andrea" <a.alberti82@HIDDEN>
To: bug-gnu-emacs@HIDDEN
Subject: [PATCH] Fix crash by not adjusting the glyph matrix while building
 the mode line
Date: Sun, 20 Sep 2026 19:42:59 +0200
X-Mailer: MailMate (2.0r6290)
Message-ID: <A65901C5-E34B-4899-BCBD-8A065B295570@HIDDEN>
MIME-Version: 1.0
Content-Type: multipart/mixed;
 boundary="=_MailMate_F1B6C494-B32B-414A-B919-4AFBAC5325A3_="
Received-SPF: pass client-ip=2a00:1450:4864:30::10;
 envelope-from=a.alberti82@HIDDEN; helo=mail-wr2-x10.google.com
X-Spam_score_int: -17
X-Spam_score: -1.8
X-Spam_bar: -
X-Spam_report: (-1.8 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1,
 DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1,
 FREEMAIL_ENVFROM_END_DIGIT=0.25, FREEMAIL_FROM=0.001,
 RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001,
 SPF_PASS=-0.001 autolearn=ham autolearn_force=no
X-Spam_action: no action
X-Spam-Score: 2.2 (++)
X-Spam-Report: Spam detection software, running on the system "debbugs.gnu.org",
 has NOT identified this incoming email as spam.  The original
 message has been attached to this so you can view it or label
 similar future email.  If you have any questions, see
 the administrator of that system for details.
 Content preview:  I would like to submit a bug report and a patch for a bug
 affecting the display engine, where under certain circumstances described
 below, a pointer references a freed region of memory, often resultin [...]
 Content analysis details:   (2.2 points, 10.0 required)
 pts rule name              description
 ---- ---------------------- --------------------------------------------------
 1.0 SPF_SOFTFAIL           SPF: sender does not match SPF record (softfail)
 -0.0 SPF_HELO_PASS          SPF: HELO matches SPF record
 0.0 FREEMAIL_FROM          Sender email is commonly abused enduser mail
 provider (a.alberti82[at]gmail.com)
 0.2 FREEMAIL_ENVFROM_END_DIGIT Envelope-from freemail username ends
 in digit (a.alberti82[at]gmail.com)
 1.0 FORGED_GMAIL_RCVD      'From' gmail.com does not match 'Received'
 headers
 -0.0 RCVD_IN_DNSWL_NONE     RBL: Sender listed at https://www.dnswl.org/,
 no trust [2001:470:142:0:0:0:0:17 listed in] [list.dnswl.org]
X-Debbugs-Envelope-To: submit
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: 1.2 (+)
X-Spam-Report: Spam detection software, running on the system "debbugs.gnu.org",
 has NOT identified this incoming email as spam.  The original
 message has been attached to this so you can view it or label
 similar future email.  If you have any questions, see
 the administrator of that system for details.
 
 Content preview:  I would like to submit a bug report and a patch for a bug
   affecting the display engine, where under certain circumstances described
   below, a pointer references a freed region of memory, often resultin [...]
    
 
 Content analysis details:   (1.2 points, 10.0 required)
 
  pts rule name              description
 ---- ---------------------- --------------------------------------------------
 -0.0 RCVD_IN_DNSWL_NONE     RBL: Sender listed at https://www.dnswl.org/,
                              no trust
                             [2001:470:142:0:0:0:0:17 listed in]
                             [list.dnswl.org]
  1.0 SPF_SOFTFAIL           SPF: sender does not match SPF record (softfail)
 -0.0 SPF_HELO_PASS          SPF: HELO matches SPF record
  0.0 FREEMAIL_FROM          Sender email is commonly abused enduser mail
                             provider (a.alberti82[at]gmail.com)
  0.2 FREEMAIL_ENVFROM_END_DIGIT Envelope-from freemail username ends
                             in digit (a.alberti82[at]gmail.com)
  1.0 FORGED_GMAIL_RCVD      'From' gmail.com does not match 'Received'
                             headers
 -1.0 MAILING_LIST_MULTI     Multiple indicators imply a widely-seen list
                             manager


--=_MailMate_F1B6C494-B32B-414A-B919-4AFBAC5325A3_=
Content-Type: text/plain

I would like to submit a bug report and a patch for a bug affecting the
display engine, where under certain circumstances described below, a
pointer references a freed region of memory, often resulting in a
segmentation fault.

The type of bug is similar in nature to Bug#81121, which was
recently fixed by Eli in 94eb6389d403eb1b71a81ecdc3b8416cf5f850d6.

** Description of the bug

(extracted verbatim from the commit body message)

Before this patch: Emacs crashes when the mode line is rebuilt and the
template `mode-line-format' contains a construct calling via :eval a
function like `accept-process-output' or `sleep-for' or similar
functions that pause execution, allowing other Lisp code to be
evaluated.  An independent timer or a sentinel from a different
asynchronous process does something (like modifying the window geometry
or changing `vscroll' of a window via `set-window-vscroll') that
requires adjusting the glyph matrix via `adjust_frame_glyphs', which in
turn reallocates it and frees the old matrix.  Because the mode line is
not fully built yet, the C function `display_mode_line' holds a pointer
to the last row of the old glyph matrix, which now references a freed
region of memory.  When control eventually returns to
`display_mode_line', referencing a freed region in the memory causes a
crash of Emacs (or when not crashing, a misbehavior of the displayed
mode line).

** Description of the patch

(extracted verbatim from the commit body message)

This patch introduces in the frame struct a new counter
`dont_adjust_matrices'.  When calling `display_mode_line' or
`format-mode-line', the new counter is incremented before any Lisp code
in the constructs of `mode-line-format' is evaluated, and then
decremented when unwinding.  Executing `adjust_frame_glyphs' is then
gated by `dont_adjust_matrices' being zero; if nonzero, its execution is
skipped and it is signaled via the per-frame flag `fonts_changed' that a
new redisplay of all windows in the frame is needed.  (Bug#XYZ)

** Reproducer

The attachment contains a short reproducer. To keep it simple, the
reproducer exposes the same fundamental bug that hit me while I was
working with AUCTeX+pdf-tools, but avoids using any asynchronous
process. Instead, it directly forces a call to `set-window-vscroll' from
a mode-line construct.  Increasing `window-vscroll' is sufficient to
internally invoke `adjust_frame_glyphs', because a larger value of this
parameter requires more rows in the glyph matrix to fit the whole
displayed buffer.

In the reproducer, I included instructions on how to start Emacs with the
PERTURB debugging feature being enabled, which makes the bug
reproducible.

** How the bug manifested in my workflow

It may be useful to provide some context about how I found this bug. This
description is provided to justify the relevance of this bug.  However,
understanding the detailed mechanics of this part is less important,
because what I am going to describe below is just one way of many
possible ways to trigger this bug.  The reproducer provided in the
previous section appears somewhat artificial but provides a much more
direct way to appreciate the bug.

So, when the bug manifested, I was working with AUCTeX editing a LaTeX
document of mine. I also used pdf-tools package
(https://github.com/vedang/pdf-tools) to display in an Emacs buffer a
preview of the compiled PDF document.

The bug happened in the following sequence of events:

1. The pdf-tools buffer still displays the old version of the PDF, while
   the pdflatex process is running asynchronously, compiling a new
   version of the PDF.
2. A sentinel is armed, which fires when the compilation of the new PDF
   document has finished; the sentinel is used to forward sync the
   viewer with the line of the LaTeX source; this is also known as LaTeX
   SyncTeX forward search.
3. Before finishing compilation (which typically takes a couple of
   seconds), I trigger a scroll operation of the PDF document in the
   pdf-tools buffer.
4. If the scroll operation advances "point" to a place that was not
   cached yet, when the mode line is being built, pdf-tools calls the
   external server process epdfinfo to retrieve information about the PDF
   document. This occurs in the mode-line construct named
   `pdf-misc-size-indication'.  This construct eventually reaches the
   function `pdf-info-query', where the server process epdfinfo is
   queried via pipes. When reaching this line

      (accept-process-output (pdf-info-process) 0.01)

   the code execution is paused, waiting for the server process epdfinfo
   to parse the PDF document and return the requested data.
5. Once in a while, it happened that while Emacs was waiting for epdfinfo
   to finish, the LaTeX compilation happened to just finish, thus calling
   the armed sentinel responsible for the forward syncing.
6. Pixel-precise forward syncing in pdf-tools is performed by calling
   `set-window-vscroll'.
7. Once forward-syncing is complete, control returns to `pdf-info-query',
   where `accept-process-output' finally returns the data requested from
   epdfinfo.  Thus, after the mode-line construct has completed
   evaluation, control returns to `display_mode_line'. However, at this
   point `display_mode_line' holds a reference to the last row of the
   glyph matrix (i.e., the line storing the mode line) which is stale,
   since this address in memory has been freed by `adjust_frame_glyphs'
   just a few moments before.

I checked the official documentation about asynchronous processes:

https://www.gnu.org/software/emacs/manual/html_node/elisp/Sentinels.html

   A sentinel runs only while Emacs is waiting (e.g., for terminal input,
   or for time to elapse, or for process output). This avoids the timing
   errors that could result from running sentinels at random places in
   the middle of other Lisp programs. A program can wait, so that
   sentinels will run, by calling sit-for or sleep-for (see Waiting for
   Elapsed Time or Input), or accept-process-output (see Accepting Output
   from Processes). Emacs also allows sentinels to run when the command
   loop is reading input. delete-process calls the sentinel when it
   terminates a running process.

The bug happens because "This avoids the timing..." is not fully
respected, since before this patch the sentinel was allowed to run while
Emacs was holding a reference to the glyph matrix.

** Why not reuse the existing counter `dont_resize_frames'?

I initially thought that all that was needed was to reuse in
`adjust_frame_glyphs' the same counter `dont_resize_frames', which was
recently introduced by Eli in commit
94eb6389d403eb1b71a81ecdc3b8416cf5f850d6.  This counter would have worked
for the purpose to avoid referring to freed regions of memory.  However,
it became clear later that doing so is not the narrowest criterion to
avoid the bug.  If I had not narrowed the filter criterion further, we
would pay the price of skipping `adjust_frame_glyphs' and causing a
redisplay of all frame windows by setting `fonts_changed', even when this
is not necessary like when `adjust_frame_glyphs' is reached from
`gui_consider_frame_title', which happens to increment the counter
`dont_resize_frames'.  Because this function is called often, this would
cause an unnecessary and undesirable churn in redisplaying all windows in
the frame.  By introducing a separate counter `dont_adjust_matrices', we
completely avoid this problem and we use a counter whose name reveals its
actual purpose, rather than relying on `dont_resize_frames' as a
workaround, which was introduced to prevent resizing frames when in the
middle of Lisp code.

The new counter `dont_adjust_matrices' is per frame, differently than
`dont_resize_frames', which applies to all frames.

In principle, `dont_adjust_matrices' could have been made per window
instead of per frame because the bug happens when the mode line of a
particular window has been rebuilt, and we need to prevent the glyph
matrix of said window from being reallocated.  I decided against scoping
it to a single window for simplicity reasons: this is a very severe bug
that causes the entire crash of the Emacs process; however, in my daily
routines, it manifested once every 60 minutes. So it is a relatively rare
event, and paying the price of redisplaying all windows every 60 minutes
in exchange for a much simpler code was the reason for my decision.

** Other changes introduced in this patch

*** Shifted `dont_resize_frames++' to a couple of lines earlier

I incremented the new counter `dont_adjust_matrices' next to
`dont_resize_frames':

   dont_resize_frames++;
   target_frame->dont_adjust_matrices++;

When editing the old code, it occurred to me that the old code
incremented `dont_resize_frames' after a couple of lines that could in
principle "signal":

   Fselect_window (window, Qt);
   set_buffer_internal_1 (XBUFFER (buffer));

If Emacs "signals" before the counter `dont_resize_frames' is
incremented, no increment would occur, and the unwinding code would run,
which decrements the same counter, bringing it out of sync.

The fix was a simple shift of `dont_resize_frames++;' to a couple of
lines above, just after record_unwind_protect and before any code that
can "signal".

*** Introduced a new boolean `restore_point'

Before the patch, the argument TARGET_FRAME was overloaded with two
meanings: (1) to pass the target frame and (2) to gate the execution of
the branch where "point" is restored in the old window; this branch was
introduced with Bug#32777.  With this patch, TARGET_FRAME is also passed
when the unwinding function needs to decrement the counter
`dont_adjust_matrices'; when `dont_adjust_matrices' must be decremented,
the marker does not necessarily need to be restored at the old "point".
Therefore, it was necessary to introduce a new way to inform when the
marker had to be restored.  I checked the code and there were two
possibilities:

1. Checking the argument OWIN (old window), which is only provided when
   the marker has to be restored.
2. Introducing an independent boolean to flag this situation.

I chose the second of these options because the intent is clearer than
relying on a contract of the function, which is not fully documented in
the commentary and could break in the future.

Let me know if, for brevity, you prefer 1.  We could still use the same
boolean in `unwind_format_mode_line', but derive it from the non-nil
state of OLD_WINDOW:

  Lisp_Object old_window = AREF (vector, 7);
  Lisp_Object target_frame_window = AREF (vector, 8);
  Lisp_Object old_top_frame = AREF (vector, 9);

  bool restore_point = false;
  if (!NILP (old_window))
    restore_point = true;

Then, we could drop the extra argument `restore_point' in every call of
`format_mode_line_unwind_data'.

* AI disclaimer

I personally wrote each code line, code comments, commit message, the
reproducer, and submission letter; following AGENTS.md no snippet was
LLM-generated and then copy-and-pasted.  Claude Opus 5 was used for
fact-checking (e.g., spotting numerous mistakes in the process of
identifying the bug, writing the patch, and correcting many
non-GNU-complying formatting errors).


--=_MailMate_F1B6C494-B32B-414A-B919-4AFBAC5325A3_=
Content-Disposition: attachment;
 filename=0001-Fix-crash-by-not-adjusting-the-glyph-matrix-while-bu.patch
Content-ID: <494AD862-7DEB-4245-AD0F-C8F684C85A4A@HIDDEN>
Content-Type: text/plain;
 name=0001-Fix-crash-by-not-adjusting-the-glyph-matrix-while-bu.patch
Content-Transfer-Encoding: quoted-printable

=46rom c5ccd6f50c9237b9155008e09221a07cf89ba567 Mon Sep 17 00:00:00 2001
From: Andrea Alberti <a.alberti82@HIDDEN>
Date: Sun, 20 Sep 2026 13:10:11 +0200
Subject: [PATCH] Fix crash by not adjusting the glyph matrix while buildi=
ng
 the mode line

Before this patch: Emacs crashes when the mode line is rebuilt and the
template `mode-line-format' contains a construct calling via :eval a
function like `accept-process-output' or `sleep-for' or similar
functions that pause execution, allowing other Lisp code to be
evaluated.  An independent timer or a sentinel from a different
asynchronous process does something (like modifying the window geometry
or changing `vscroll' of a window via `set-window-vscroll') that
requires adjusting the glyph matrix via `adjust_frame_glyphs', which in
turn reallocates it and frees the old matrix.  Because the mode line is
not fully built yet, the C function `display_mode_line' holds a pointer
to the last row of the old glyph matrix, which now references a freed
region of memory.  When control eventually returns to
`display_mode_line', referencing a freed region in the memory causes a
crash of Emacs (or when not crashing, a misbehavior of the displayed
mode line).

This patch introduces in the frame struct a new counter
`dont_adjust_matrices'.  When calling `display_mode_line' or
`format-mode-line', the new counter is incremented before any Lisp code
in the constructs of `mode-line-format' is evaluated, and then
decremented when unwinding.  Executing `adjust_frame_glyphs' is then
gated by `dont_adjust_matrices' being zero; if nonzero, its execution is
skipped and it is signaled via the per-frame flag `fonts_changed' that a
new redisplay of all windows in the frame is needed.  (Bug#XYZ)

* src/dispnew.c (adjust_frame_glyphs): When the counter
`dont_adjust_matrices' is nonzero, skip execution and signal via
`fonts_changed' that a new redisplay is due.
* src/frame.h (struct frame): New counter `dont_adjust_matrices'.
* src/xdisp.c (format_mode_line_unwind_data): Added new arguments
`restore_point' (related to Bug#32777) and `unwind_dont_adjust_matrices'
and assertions to enforce the function contract when compiled with the
development flag `--enable-checking'. FRAME is now always passed as
non-nil; a nil value is no longer used as a flag, and instead
`restore_point' is now used for the same purpose.
(unwind_format_mode_line): Decrement `dont_adjust_matrices' when
`unwind_dont_adjust_matrices' is set.  Use new boolean `restore_point'
to inform whether the marker at the old point must be restored, instead
of relying on FRAME being non-nil, because a non-nil FRAME can be
provided in the branch that decreases `dont_adjust_matrices'; the code
in this branch is now moved under `if (restore_point)'.
(gui_consider_frame_title): Updated arguments passed to
`format_mode_line_unwind_data'.
(display_mode_line): Updated arguments passed to
`format_mode_line_unwind_data'.  Increment `dont_adjust_matrices'.
(Fformat_mode_line): Increment `dont_adjust_matrices'. Moved the line
that increments `dont_resize_frames' before any Lisp code, which could
otherwise signal.
---
 src/dispnew.c |  13 +++++
 src/frame.h   |   4 ++
 src/xdisp.c   | 136 +++++++++++++++++++++++++++++++++++---------------
 3 files changed, 113 insertions(+), 40 deletions(-)

diff --git a/src/dispnew.c b/src/dispnew.c
index dd799c62d02..4354856d3d4 100644
--- a/src/dispnew.c
+++ b/src/dispnew.c
@@ -1849,6 +1849,19 @@ allocate_matrices_for_window_redisplay (struct win=
dow *w)
 void
 adjust_frame_glyphs (struct frame *f)
 {
+  if (f->dont_adjust_matrices > 0)
+    {
+      /* Not allowed to reallocate the glyph matrices when there is
+	 at least another function holding a pointer to an element in
+	 the matrix of one window in this frame.  Before returning,
+	 flag that a new redisplay is needed, so that when the pointer
+	 is released, redisplay can take place.  To state that a
+	 redisplay is needed, we reuse the same flag that is used when
+	 the font in one of the frame's windows has changed.  */
+      f->fonts_changed =3D true;
+      return;
+    }
+
   /* Block input so that expose events and other events that access
      glyph matrices are not processed while we are changing them.  */
   block_input ();
diff --git a/src/frame.h b/src/frame.h
index 64d8a74f36e..71bcbb90f1b 100644
--- a/src/frame.h
+++ b/src/frame.h
@@ -745,6 +745,10 @@ #define EMACS_FRAME_H
 =

   /* Z-order of child frames.  */
   int z_order;
+
+  /* Counter.  When positive, there is at least one window in this
+     frame for which adjusting its glyph matrix is not allowed.  */
+  int dont_adjust_matrices;
 } GCALIGNED_STRUCT;
 =

 /* Most code should use these functions to set Lisp fields in struct fra=
me.  */
diff --git a/src/xdisp.c b/src/xdisp.c
index 6d3fa2fbe4f..34d2334edf2 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -13943,17 +13943,26 @@ #define MODE_LINE_NOPROP_LEN(start) \
 format_mode_line_unwind_data (struct frame *target_frame,
 			      struct buffer *obuf,
 			      Lisp_Object owin,
-			      bool save_proptrans)
+			      bool save_proptrans,
+			      bool restore_point,
+			      bool unwind_dont_adjust_matrices)
 {
   Lisp_Object vector, tmp;
 =

+  /* By contract, TARGET_FRAME must always be provided.  */
+  eassert (target_frame);
+
+  /* By contract, when RESTORE_POINT is true, old window OWIN must be
+     provided.  */
+  eassert (!restore_point || !NILP (owin));
+
   /* Reduce consing by keeping one vector in
      Vwith_echo_area_save_vector.  */
   vector =3D Vmode_line_unwind_vector;
   Vmode_line_unwind_vector =3D Qnil;
 =

   if (NILP (vector))
-    vector =3D make_nil_vector (12);
+    vector =3D make_nil_vector (14);
 =

   ASET (vector, 0, make_fixnum (mode_line_target));
   ASET (vector, 1, make_fixnum (MODE_LINE_NOPROP_LEN (0)));
@@ -13968,7 +13977,8 @@ format_mode_line_unwind_data (struct frame *targe=
t_frame,
     tmp =3D Qnil;
   ASET (vector, 6, tmp);
   ASET (vector, 7, owin);
-  if (target_frame)
+  ASET (vector, 8, target_frame->selected_window);
+  if (restore_point)
     {
       Lisp_Object buffer =3D XWINDOW (target_frame->selected_window)->co=
ntents;
       struct buffer *b =3D XBUFFER (buffer);
@@ -13977,7 +13987,6 @@ format_mode_line_unwind_data (struct frame *targe=
t_frame,
       /* Similarly to `with-selected-window', if the operation selects
 	 a window on another frame, we must restore that frame's
 	 selected window, and (for a tty) the top-frame.  */
-      ASET (vector, 8, target_frame->selected_window);
       if (FRAME_TERMCAP_P (target_frame))
 	ASET (vector, 9, FRAME_TTY (target_frame)->top_frame);
 =

@@ -13990,6 +13999,9 @@ format_mode_line_unwind_data (struct frame *targe=
t_frame,
       current_buffer =3D cb;
     }
 =

+  ASET (vector, 12, restore_point ? Qt : Qnil);
+  ASET (vector, 13, unwind_dont_adjust_matrices ? Qt : Qnil);
+
   return vector;
 }
 =

@@ -14008,44 +14020,48 @@ unwind_format_mode_line (Lisp_Object vector)
   mode_line_string_face =3D AREF (vector, 4);
   mode_line_string_face_prop =3D AREF (vector, 5);
 =

-  /* Select window before buffer, since it may change the buffer.  */
-  if (WINDOW_LIVE_P (old_window))
+  bool restore_point =3D EQ (AREF (vector, 12), Qt);
+  if (restore_point)
     {
-      /* If the operation that we are unwinding had selected a window
-	 on a different frame, reset its frame-selected-window.  For a
-	 text terminal, reset its top-frame if necessary.  */
-      if (WINDOW_LIVE_P (target_frame_window))
+      /* Select window before buffer, since it may change the buffer.  *=
/
+      if (WINDOW_LIVE_P (old_window))
 	{
-	  Lisp_Object frame
-	    =3D WINDOW_FRAME (XWINDOW (target_frame_window));
-
-	  if (!EQ (frame, WINDOW_FRAME (XWINDOW (old_window))))
-	    Fselect_window (target_frame_window, Qt);
+	  /* If the operation that we are unwinding had selected a window
+	     on a different frame, reset its frame-selected-window.  For a
+	     text terminal, reset its top-frame if necessary.  */
+	  if (WINDOW_LIVE_P (target_frame_window))
+	    {
+	      Lisp_Object frame
+		=3D WINDOW_FRAME (XWINDOW (target_frame_window));
 =

-	  if (!NILP (old_top_frame) && !EQ (old_top_frame, frame)
-	      /* This could've been destroyed during the formatting,
-		 possibly because the terminal was deleted.  */
-	      && FRAME_LIVE_P (XFRAME (old_top_frame)))
-	    Fselect_frame (old_top_frame, Qt);
-	}
+	      if (!EQ (frame, WINDOW_FRAME (XWINDOW (old_window))))
+		Fselect_window (target_frame_window, Qt);
 =

-      Fselect_window (old_window, Qt);
+	      if (!NILP (old_top_frame) && !EQ (old_top_frame, frame)
+                  /* This could've been destroyed during the formatting,=

+                     possibly because the terminal was deleted.  */
+		  && FRAME_LIVE_P (XFRAME (old_top_frame)))
+		Fselect_frame (old_top_frame, Qt);
+	    }
 =

-      /* Restore point of target_frame_window's buffer (Bug#32777).
-	 But do this only after old_window has been reselected to
-	 avoid that the window point of target_frame_window moves.  */
-      if (WINDOW_LIVE_P (target_frame_window))
-	{
-	  Lisp_Object buffer =3D AREF (vector, 10);
+	  Fselect_window (old_window, Qt);
 =

-	  if (BUFFER_LIVE_P (XBUFFER (buffer)))
+	  /* Restore point of target_frame_window's buffer (Bug#32777).
+	     But do this only after old_window has been reselected to
+	     avoid that the window point of target_frame_window moves.  */
+	  if (WINDOW_LIVE_P (target_frame_window))
 	    {
-	      struct buffer *cb =3D current_buffer;
+	      Lisp_Object buffer =3D AREF (vector, 10);
+
+	      if (BUFFER_LIVE_P (XBUFFER (buffer)))
+		{
+		  struct buffer *cb =3D current_buffer;
 =

-	      current_buffer =3D XBUFFER (buffer);
-	      set_point_from_marker (AREF (vector, 11));
-	      ASET (vector, 11, Qnil);
-	      current_buffer =3D cb;
+		  current_buffer =3D XBUFFER (buffer);
+		  set_point_from_marker (AREF (vector, 11));
+		  ASET (vector, 11, Qnil);
+		  current_buffer =3D cb;
+		}
 	    }
 	}
     }
@@ -14060,6 +14076,19 @@ unwind_format_mode_line (Lisp_Object vector)
 =

   if (dont_resize_frames > 0)
     dont_resize_frames--;
+
+  bool unwind_dont_adjust_matrices =3D EQ (AREF (vector, 13), Qt);
+  if (unwind_dont_adjust_matrices)
+    {
+      struct frame *f
+	=3D XFRAME (WINDOW_FRAME (XWINDOW (target_frame_window)));
+      /* The lifecycle of the counter `dont_adjust_matrices' is arranged=

+	 so that it cannot take a zero value at this point; however, a
+	 safety clamp is added to ensure that even if the contract
+	 breaks in the future, the counter will never go negative.  */
+      if (FRAME_LIVE_P (f) && f->dont_adjust_matrices > 0)
+	f->dont_adjust_matrices--;
+    }
 }
 =

 =

@@ -14184,7 +14213,7 @@ gui_consider_frame_title (Lisp_Object frame)
 =

       format_data =3D format_mode_line_unwind_data (f, current_buffer,
 						  selected_window,
-						  false);
+						  false, true, false);
       record_unwind_protect (unwind_format_mode_line, format_data);
 =

       Fselect_window (f->selected_window, Qt);
@@ -28226,13 +28255,29 @@ display_mode_line (struct window *w, enum face_=
id face_id, Lisp_Object format)
   it.paragraph_embedding =3D L2R;
 =

   record_unwind_protect (unwind_format_mode_line,
-			 format_mode_line_unwind_data (NULL, NULL,
-						       Qnil, false));
+			 format_mode_line_unwind_data (it.f, NULL,
+						       Qnil, false,
+						       false, true));
+
+  /* DTRT: The counters below must be directly incremented after
+     registering unwind_format_mode_line; see also analogous use case in=

+     format-mode-line for an extended explanation.  */
 =

   /* We cannot allow frame-resizing as long as the code below runs,
      because that could invalidate the it.glyph_row->glyphs pointers.  *=
/
   dont_resize_frames++;
 =

+  /* We cannot allow the glyph matrix of this window to be reallocated
+     while we hold a pointer to its last row, which carries the mode
+     line; otherwise Emacs crashes.  Instead of selectively preventing
+     adjustment of the glyph matrix for this window only, we take the
+     simpler approach and prevent adjusting the glyph matrix of any
+     window in this frame.  The simpler approach is justified by the
+     fact that the race condition is sufficiently rare, but when it
+     happens it leads to an unrecoverable crash of the Emacs process
+     (see bug #xyz-to-be-filled).  */
+  it.f->dont_adjust_matrices++;
+
   /* Temporarily make frame's keyboard the current kboard so that
      kboard-local variables in the mode_line_format will get the right
      values.  */
@@ -29126,19 +29171,30 @@ DEFUN ("format-mode-line", Fformat_mode_line, S=
format_mode_line,
 =

   old_buffer =3D current_buffer;
 =

+  struct frame *target_frame =3D XFRAME (WINDOW_FRAME (w));
+
   /* Save things including mode_line_proptrans_alist,
      and set that to nil so that we don't alter the outer value.  */
   record_unwind_protect (unwind_format_mode_line,
 			 format_mode_line_unwind_data
-			   (XFRAME (WINDOW_FRAME (w)),
-			    old_buffer, selected_window, true));
+			   (target_frame,
+			    old_buffer, selected_window, true,
+			    true, true));
+
+  /* DTRT: These counters must be directly incremented after registering=

+     unwind_format_mode_line, avoiding any function in between that can
+     signal.  Otherwise, if something signals, the registered unwinding
+     function runs without the counter ever being incremented,
+     potentially leading to negative values.  */
+  dont_resize_frames++;
+  target_frame->dont_adjust_matrices++;
+
   mode_line_proptrans_alist =3D Qnil;
 =

   Fselect_window (window, Qt);
   set_buffer_internal_1 (XBUFFER (buffer));
 =

   init_iterator (&it, w, -1, -1, NULL, face_id);
-  dont_resize_frames++;
 =

   /* Make sure `base_line_number` is fresh in case we encounter a `%l`. =
 */
   if (current_buffer =3D=3D XBUFFER ((w)->contents)
-- =

2.55.0


--=_MailMate_F1B6C494-B32B-414A-B919-4AFBAC5325A3_=
Content-Disposition: attachment; filename=repro.el
Content-ID: <B8BFE1EA-8BD1-4C4D-9E4D-B7743D4DCA5C@HIDDEN>
Content-Type: text/plain; name=repro.el
Content-Transfer-Encoding: quoted-printable

;;; repro.el --- Reproducer forcing a reallocation of the glyph matrix fr=
om inside display_mode_line -*- lexical-binding: t; -*-
;;
;; To run this reproducer, before starting Emacs, enable the Malloc PERTU=
RB
;; debugging feature; this debugging feature makes the execution of this
;; reproducer deterministic, since the PERTURB parameter replaces freed m=
emory
;; with 0x55, thus erasing anything that was previously contained.
;;
;; Under Linux, use from command line:
;;
;; MALLOC_PERTURB_=3D85 emacs -Q
;;
;; while under macOS, use:
;;
;; MallocScribble=3D1 emacs -Q

(setq repro-window nil)                 ; used to store the selected wind=
ow
(setq repro-count 0)                    ; count the number of calls to
                                        ; set-window-vscroll from inside =
the
                                        ; mode-line construct

(defun repro-modeline ()
  ;; Changing window-vscroll from inside the code that builds the mode li=
ne
  ;; (i.e., display_mode_line at the C level) is sufficient to internally=

  ;; trigger `adjust_frame_glyphs', which frees the old glyph matrix and
  ;; reallocates a new one.  When the display_mode_line function continue=
s, it
  ;; reads a freed region of memory, which causes Emacs to crash if meanw=
hile
  ;; the freed region of memory has been poisoned.
  (cond ((and repro-window (< repro-count 3))
         (set-window-vscroll repro-window (+ (window-vscroll repro-window=
 t) 2000) t)
         ;; Limit the scrolling to three operations to avoid an infinite =
loop once the
         ;; patch is installed.
         (setq repro-count (1+ repro-count)))
        (t
         ;; Restore WINDOW-SCROLL to zero; if you use the PERTURB debuggi=
ng
         ;; feature, this code is only reached when the patch is installe=
d and
         ;; Emacs does not crash.
         (set-window-vscroll repro-window 0 t)))

  (format "repro-modeline evaluated %d times." repro-count))

(with-current-buffer (get-buffer-create "*repro*")
  (erase-buffer)
  (dotimes (line-num 300) (insert (format "line %d\n" line-num)))
  (setq-local mode-line-format (list
                                "Repro string displayed in the mode line =
when Emacs does not crash; "
                                '(:eval (repro-modeline)))))

(switch-to-buffer "*repro*")
(setq repro-window (selected-window))

;; Force a redisplay; not strictly needed since Emacs would redisplay the=

;; selected window anyhow.
(redisplay t)

;; Bring point to the beginning of the buffer. This is not actually neede=
d to
;; reproduce the crash, but it is nice-to-have when the patch is installe=
d and
;; Emacs avoids crashing.
(goto-char (point-min))


--=_MailMate_F1B6C494-B32B-414A-B919-4AFBAC5325A3_=--




Acknowledgement sent to "Alberti, Andrea" <a.alberti82@HIDDEN>:
New bug report received and forwarded. Copy sent to bug-gnu-emacs@HIDDEN. Full text available.
Report forwarded to bug-gnu-emacs@HIDDEN:
bug#81903; Package emacs. Full text available.
Please note: This is a static page, with minimal formatting, updated once a day.
Click here to see this page with the latest information and nicer formatting.
Last modified: Mon, 21 Sep 2026 07:00:05 UTC

GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997 nCipher Corporation Ltd, 1994-97 Ian Jackson.