Subject: bug#56256: Emacs 28.1 gets stuck when typing some C++ code

From: Alan Mackenzie <[email-masked]>
Date: Tue, 28 Jun 2022 16:28:27 +0000

Hello, Iru.

Thanks for taking the trouble to report this bug, and thanks even more
for such a concise and helpful report.

On Mon, Jun 27, 2022 at 18:22:15 +0800, Iru Cai wrote:
> Emacs gets stuck when trying to type the '&' character at the end of the 
> buffer when there's the following code in C++ mode (this code is from 
> https://www.gingerbill.org/article/2015/08/19/defer-in-cpp/):

> ```

> template <typename F>
> struct privDefer {
>      F f;
>      privDefer(F f) : f(f) {}
>      ~privDefer() { f(); }
> };

> template <typename F>
> privDefer<F> defer_func(F f) {
>      return privDefer<F>(f);
> }

> #define DEFER_1(x, y) x##y
> #define DEFER_2(x, y) DEFER_1(x, y)
> #define DEFER_3(x)    DEFER_2(x, __COUNTER__)
> #define defer(code) auto DEFER_3(_defer_) = defer_func([

> ```

> Steps to reproduce:

> 1. create a C++ source file with this code and open it with Emacs, if 
> the file doesn't have a C++ source suffix, enable c++mode

> 2. type the '&' symbol at the last line after "defer_func(["

> 3. then Emacs gets stuck

> I can see this bug in Emacs 28.1 and git revision 
> 5b1bb1af030597aab7f7895b6e3da9b430f9438a. I've also tried ``emacs -Q`` 
> and ``emacs -Q -nw``, the bug still exists.

Yes.  What is triggering the bug is the newly typed & being at the end of
a #define line.

The CC Mode function c-font-lock-c++-lambda-captures is in a loop,
handling one lambda capture at a time.  At the end of the file, it has
just gone forward over the whitespace, and wants to return to the &.
However it wrongly uses the function c-backward-token-2, which treats all
the macro lines as whitespace, hence ends up at the }, 6 lines earlier.
It then searches forward for the "next" [, and ends up at the same one it
was at before.  This loops infinitely.

I think the following patch should fix it.  Could you try it out on your
real C++ code, please, and tell us whether the bug is actually fixed.
(If you want any help with applying the patch or byte compiling CC Mode
afterwards, feel free to send me private email.):

diff -r 03c932b2922b cc-fonts.el
--- a/cc-fonts.el	Sat Jun 18 15:40:47 2022 +0000
+++ b/cc-fonts.el	Tue Jun 28 16:15:37 2022 +0000
@@ -1823,7 +1823,7 @@
   ;; font-lock-keyword-face.  It always returns NIL to inhibit this and
   ;; prevent a repeat invocation.  See elisp/lispref page "Search-based
   ;; Fontification".
-  (let (mode capture-default id-start id-end declaration sub-begin sub-end)
+  (let (mode capture-default id-start id-end declaration sub-begin sub-end tem)
     (while (and (< (point) limit)
 		(search-forward "[" limit t))
       (when (progn (backward-char)
@@ -1835,15 +1835,18 @@
 			(char-after)))
 	;; Is the first element of the list a bare "=" or "&"?
 	(when mode
-	  (forward-char)
-	  (c-forward-syntactic-ws)
-	  (if (memq (char-after) '(?, ?\]))
-	      (progn
-		(setq capture-default mode)
-		(when (eq (char-after) ?,)
-		  (forward-char)
-		  (c-forward-syntactic-ws)))
-	    (c-backward-token-2)))
+	  (setq tem nil)
+	  (save-excursion
+	    (forward-char)
+	    (c-forward-syntactic-ws)
+	    (if (memq (char-after) '(?, ?\]))
+		(progn
+		  (setq capture-default mode)
+		  (when (eq (char-after) ?,)
+		    (forward-char)
+		    (c-forward-syntactic-ws))
+		  (setq tem (point)))))
+	  (if tem (goto-char tem)))
 
 	;; Go round the following loop once per captured item.  We use "\\s)"
 	;; rather than "\\]" here to avoid infinite looping in this situation:

-- 
Alan Mackenzie (Nuremberg, Germany).