Subject: bug#25869: CC Mode 5.32.99 (C/l); Sometimes Emacs use 100% CPU on comment-dwim for a long time

From: Alan Mackenzie <[email-masked]>
Date: Sun, 9 Apr 2017 14:25:15 +0000

Hello again, Mohammed.

On Sun, Mar 05, 2017 at 22:11:09 +0000, Alan Mackenzie wrote:
> On Sat, Feb 25, 2017 at 16:26:58 +0530, Mohammed Sadiq wrote:

> > When writing code, I encountered a bug in comment-dwim, which takes 100%
> > CPU for a long time, and then failing to comment the line.

> > How to reproduce using the following code:
> > 1. Mark some line in the first function.
> >    Eg: g_type_ensure (CC_TYPE_INFO_OVERVIEW);
> >    do C-a C-SPC C-e
> > 2. Do comment-dwim
> >    ie, M-;

> > The code that produced the issue is the following:
> > Note: if I remove the array at the bottom of code, M-; begins to work
> > fine. The code follows is an excerpt from gnome-control-center.

> First of all, thank you for such a detailed and complete bug report.

> The bug that this code triggers has been in CC Mode for quite some
> while, but it takes a rather unusual combination of constructs in the C
> source to trigger it.

> What is happening is that the M-; first inserts "/* ".  This causes all
> the code up to the next terminating "*/" to become, temporarily, a
> comment.  One of CC Mode's functions moves forward over this comment.  A
> bit later on, it has cause to move backwards over it, but only moves as
> far back as the "/*" which was the original start of the second comment
> (but is now merely part of the first comment).  This is the bug, I
> think.

> This might be quite tricky to fix.  Give me a little time, please!

> [ snip source code ]

> > Thanks

> [ CC Mode configuration dump appreciated, but snipped. ]

OK, I think the following patch should do the trick.  Would you please
apply it, recompile cc-engine.el (e.g., with

    $ emacs -Q -batch -f batch-byte-compile cc-engine.el

), reload it (or restart Emacs), and either confirm to me that the bug
has been fixed, or tell me what's still wrong about it.  Thanks!

diff -r 51f7a9ff5450 cc-engine.el
--- a/cc-engine.el	Sat Feb 25 14:39:10 2017 +0000
+++ b/cc-engine.el	Sun Apr 09 14:04:38 2017 +0000
@@ -324,34 +324,41 @@
 	    (goto-char here)
 	    nil))))))
 
-(defun c-end-of-macro ()
+(defun c-end-of-macro (&optional lim)
   "Go to the end of a preprocessor directive.
 More accurately, move the point to the end of the closest following
 line that doesn't end with a line continuation backslash - no check is
 done that the point is inside a cpp directive to begin with.
 
+If LIM is provided, it is a limit position at which point is left
+if the end of the macro doesn't occur earlier.
+
 Note that this function might do hidden buffer changes.  See the
 comment at the start of cc-engine.el for more info."
-   (if (and (cdr c-macro-cache)
-	    (<= (point) (cdr c-macro-cache))
-	    (>= (point) (car c-macro-cache)))
-       (goto-char (cdr c-macro-cache))
-     (unless (and (car c-macro-cache)
-		  (<= (point) c-macro-cache-start-pos)
-		  (>= (point) (car c-macro-cache)))
-       (setq c-macro-cache nil
-	     c-macro-cache-start-pos nil
-	     c-macro-cache-syntactic nil
-	     c-macro-cache-no-comment nil))
-     (while (progn
-	      (end-of-line)
-	      (when (and (eq (char-before) ?\\)
-			 (not (eobp)))
-		(forward-char)
-		t)))
-     (when (car c-macro-cache)
-       (setcdr c-macro-cache (point))
-       (setq c-macro-cache-syntactic nil))))
+  (save-restriction
+    (if lim (narrow-to-region (point-min) lim))
+    (if (and (cdr c-macro-cache)
+	     (<= (point) (cdr c-macro-cache))
+	     (>= (point) (car c-macro-cache)))
+	(goto-char (cdr c-macro-cache))
+      (unless (and (car c-macro-cache)
+		   (<= (point) c-macro-cache-start-pos)
+		   (>= (point) (car c-macro-cache)))
+	(setq c-macro-cache nil
+	      c-macro-cache-start-pos nil
+	      c-macro-cache-syntactic nil
+	      c-macro-cache-no-comment nil))
+      (while (progn
+	       (end-of-line)
+	       (when (and (eq (char-before) ?\\)
+			  (not (eobp)))
+		 (forward-char)
+		 t)))
+      (when (and (car c-macro-cache)
+		 (bolp)
+		 (not (eq (char-before (1- (point))) ?\\)))
+	(setcdr c-macro-cache (point))
+	(setq c-macro-cache-syntactic nil)))))
 
 (defun c-syntactic-end-of-macro ()
   ;; Go to the end of a CPP directive, or a "safe" pos just before.
@@ -1846,13 +1853,10 @@
   (let (;; `rung-pos' is set to a position as early as possible in the
 	;; unmarked part of the simple ws region.
 	(rung-pos (point)) next-rung-pos rung-end-pos last-put-in-sws-pos
-	rung-is-marked next-rung-is-marked simple-ws-end
+	rung-is-marked next-rung-is-marked simple-ws-end macro-start macro-end
 	;; `safe-start' is set when it's safe to cache the start position.
-	;; It's not set if we've initially skipped over comments and line
-	;; continuations since we might have gone out through the end of a
-	;; macro then.  This provision makes `c-forward-sws' not populate the
-	;; cache in the majority of cases, but otoh is `c-backward-sws' by far
-	;; more common.
+	;; This is the case except when we have an unterminated block comment
+	;; within a macro.
 	safe-start)
 
     ;; Skip simple ws and do a quick check on the following character to see
@@ -1928,7 +1932,33 @@
 
 	    ;; Now move over any comments (x)or a CPP construct.
 	    (setq simple-ws-end (point))
-	    (c-forward-comments)
+	    (setq safe-start t)
+	    ;; Take elaborate precautions to detect an open block comment at
+	    ;; the end of a macro.  If we find one, we set `safe-start' to nil
+	    ;; and break off any further scanning of comments.
+	    (let ((com-begin (point)) com-end in-macro)
+	      (when (and (c-forward-single-comment)
+	    		 (setq com-end (point))
+	    		 (save-excursion
+	    		   (goto-char com-begin)
+	    		   (c-beginning-of-macro)))
+	    	(setq in-macro t)
+	    	(goto-char com-begin)
+	    	(if (progn (c-end-of-macro com-end)
+	    		   (< (point) com-end))
+	    	    (setq safe-start nil)))
+	      (if in-macro
+	    	  (while (and safe-start
+	    		      com-end (> com-end com-begin)
+	    		      (setq com-begin (point))
+	    		      (when (and (c-forward-single-comment)
+	    				 (setq com-end (point)))
+	    			(goto-char com-begin)
+	    			(if (progn (c-end-of-macro com-end)
+	    				   (< (point) com-end))
+	    			    (setq safe-start nil))
+	    			safe-start)))
+	    	(c-forward-comments)))
 
 	    (cond
 	     ((/= (point) simple-ws-end)
@@ -1939,6 +1969,7 @@
 	     ((save-excursion
 		(and c-opt-cpp-prefix
 		     (looking-at c-opt-cpp-start)
+		     (setq macro-start (point))
 		     (progn (skip-chars-backward " \t")
 			    (bolp))
 		     (or (bobp)
@@ -1949,8 +1980,20 @@
 	      (while (and (eq (char-before) ?\\)
 			  (= (forward-line 1) 0))
 		(end-of-line))
+	      (setq macro-end (point))
+	      ;; Check for an open block comment at the end of the macro.
+	      (goto-char macro-start)
+	      (let (s in-block-comment)
+		(while
+		    (progn
+		      (setq s (parse-partial-sexp (point) macro-end
+						  nil nil s 'syntax-table))
+		      (< (point) macro-end))
+		  (setq in-block-comment
+			(and (elt s 4)	     ; in a comment
+			     (null (elt s 7))))) ; a block comment
+		(if in-block-comment (setq safe-start nil)))
 	      (forward-line 1)
-	      (setq safe-start t)
 	      ;; Don't cache at eob in case the buffer is narrowed.
 	      (not (eobp)))
 
@@ -1958,7 +2001,6 @@
 		   (looking-at c-noise-macro-name-re))
 	      ;; Skip over a noise macro.
 	      (goto-char (match-end 1))
-	      (setq safe-start t)
 	      (not (eobp)))))
 
 	;; We've searched over a piece of non-white syntactic ws.  See if this
@@ -2021,8 +2063,7 @@
 	  (if (setq rung-is-marked next-rung-is-marked)
 	      (setq rung-pos (1- (c-next-single-property-change
 				  rung-is-marked 'c-is-sws nil rung-end-pos)))
-	    (setq rung-pos next-rung-pos))
-	  (setq safe-start t)))
+	    (setq rung-pos next-rung-pos))))
 
       ;; Make sure that the newly marked `c-in-sws' region doesn't connect to
       ;; another one after the point (which might occur when editing inside a

-- 
Alan Mackenzie (Nuremberg, Germany).