Subject: bug#40052: CC Mode 5.34 (C++//l); cc-mode hangs fontifying src/hotspot/share/runtime/globals.hpp in OpenJDK

From: Alan Mackenzie <[email-masked]>
Date: Thu, 16 Apr 2020 19:48:14 +0000

Hello, Nick.

On Fri, Mar 13, 2020 at 18:02:03 +0800, Nick Gasson wrote:
> Package: cc-mode

> Open this file with emacs --no-init-file, page down a few times and
> Emacs locks up:

> http://hg.openjdk.java.net/jdk/jdk/raw-file/29edf1cb3c02/src/hotspot/share/runtime/globals.hpp

Thanks for taking the trouble to report this bug.  It has been a tough
one to crack.

When I tried it, my Emacs slowed down to a crawl rather than locking up
completely.  I'm assuming that this would have been the case on your
machine, too.

Most obviously, there is a huge macro in globals.hpp.  Although there
are some optimisations in CC Mode for large macros, they are incomplete,
so working on or around such a macro can be slow.

What is taking most of the time is a CC Mode function which goes back
over syntactic whitespace (spaces, tabs, comments, and when starting
outside of a macro, that macro).

This hits a bug in Emacs where it allows the backward search operation
for a comment start, when point is at the beginning of the line
following an escaped newline.  Such a position can never be an end of a
comment in C++.

However, this search backwards for a comment start is being done many
thousand times in globals.hpp because of the consecutive escaped
newlines.

The fix to Emacs won't actually be getting released until Emacs 28, so
in the meantime, please try the following patch.  The files to patch are
in directory ...../emacs/lisp/progmodes.  After patching them, please
byte compile the two files.  (If you want any help in patching or byte
compiling, feel free to send me private email.)

In my timings, scrolling with the patch applied is about four times as
fast as without it.  I've also included a correction to a bug about
"regexp stack overflow" which happened when one tried actually to edit
this buffer.  Clearly this speed is still not fast enough, but it might
be bearable at a pinch.  I will carry on trying to speed it up.

Please let me know how you get on with the patch.  Thanks!

diff -r 99da14f7dde7 cc-engine.el
--- a/cc-engine.el	Sun Mar 08 15:07:06 2020 +0000
+++ b/cc-engine.el	Thu Apr 16 19:33:34 2020 +0000
@@ -1648,6 +1648,16 @@
 	    (forward-char 2)
 	    t))))
 
+(defmacro c-forward-comment-minus-1 ()
+  "Call (forward-comment -1), taking care of escaped newlines.
+Return the result of `forward-comment' if it gets called, nil otherwise."
+  `(when
+       (or (not comment-end-can-be-escaped)
+	   (progn (skip-chars-backward " \t\n")
+		  (not (and (eq (char-after) ?\n)
+			    (eq (char-before) ?\\)))))
+     (forward-comment -1)))
+
 (defun c-backward-single-comment ()
   "Move backward past whitespace and the closest preceding comment, if any.
 Return t if a comment was found, nil otherwise.  In either case, the
@@ -1681,12 +1691,12 @@
       ;; same line.
       (re-search-forward "\\=\\s *[\n\r]" start t)
 
-      (if (if (forward-comment -1)
+      (if (if (c-forward-comment-minus-1)
 	      (if (eolp)
 		  ;; If forward-comment above succeeded and we're at eol
 		  ;; then the newline we moved over above didn't end a
 		  ;; line comment, so we give it another go.
-		  (forward-comment -1)
+		  (c-forward-comment-minus-1)
 		t))
 
 	  ;; Emacs <= 20 and XEmacs move back over the closer of a
@@ -1715,7 +1725,7 @@
 
 	    (if (let (moved-comment)
 		  (while
-		      (and (not (setq moved-comment (forward-comment -1)))
+		      (and (not (setq moved-comment (c-forward-comment-minus-1)))
 		      ;; Cope specifically with ^M^J here -
 		      ;; forward-comment sometimes gets stuck after ^Ms,
 		      ;; sometimes after ^M^J.
diff -r 99da14f7dde7 cc-mode.el
--- a/cc-mode.el	Sun Mar 08 15:07:06 2020 +0000
+++ b/cc-mode.el	Thu Apr 16 19:33:34 2020 +0000
@@ -1413,7 +1413,7 @@
 
       ;; Move to end of logical line (as it will be after the change, or as it
       ;; was before unescaping a NL.)
-      (re-search-forward "\\(\\\\\\(.\\|\n\\)\\|[^\\\n\r]\\)*" nil t)
+      (re-search-forward "\\(?:\\\\\\(?:.\\|\n\\)\\|[^\\\n\r]\\)*" nil t)
       ;; We're at an EOLL or point-max.
       (if (equal (c-get-char-property (point) 'syntax-table) '(15))
 	  (if (memq (char-after) '(?\n ?\r))
@@ -1521,7 +1521,7 @@
 	   (progn
 	     (goto-char (min (1+ end)	; 1+, in case a NL has become escaped.
 			     (point-max)))
-	     (re-search-forward "\\(\\\\\\(.\\|\n\\)\\|[^\\\n\r]\\)*"
+	     (re-search-forward "\\(?:\\\\\\(?:.\\|\n\\)\\|[^\\\n\r]\\)*"
 				nil t)
 	     (point))
 	   c-new-END))
@@ -1602,8 +1602,8 @@
 		   (c-beginning-of-macro))))
       (goto-char (1+ end))		; After the \
       ;; Search forward for EOLL
-      (setq lim (re-search-forward "\\(\\\\\\(.\\|\n\\)\\|[^\\\n\r]\\)*"
-				   nil t))
+      (setq lim (re-search-forward "\\(?:\\\\\\(?:.\\|\n\\)\\|[^\\\n\r]\\)*"
+       				   nil t))
       (goto-char (1+ end))
       (when (c-search-forward-char-property-with-value-on-char
 	     'syntax-table '(15) ?\" lim)

> Emacs  : GNU Emacs 27.0.90 (build 1, aarch64-unknown-linux-gnu, GTK+ Version 3.24.13)
>  of 2020-03-09
> Package: CC Mode 5.34 (C++//l)
> Buffer Style: gnu
> c-emacs-features: (pps-extended-state col-0-paren posix-char-classes gen-string-delim gen-comment-delim syntax-properties 1-bit)

[ CC Mode state dump snipped, but appreciated. ]

-- 
Alan Mackenzie (Nuremberg, Germany).