Subject: bug#37547: CC Mode 5.33.2 (Dired by date); c-electric-backspace throws error `c-unfind-coalesced-tokens: Wrong type argument: integer-or-marker-p, nil`

From: Alan Mackenzie <[email-masked]>
Date: Sun, 29 Sep 2019 16:14:42 +0000

Hello, Wei.

Thank you for taking the trouble to report this bug, and thanks even
more for stripping it down to a bare minimum amount of text.

On Sun, Sep 29, 2019 at 02:36:47 +0000, Sun, Wei via CC-Mode-help wrote:
> Package: cc-mode

> $ cat test.cc
> // 你好a

> I want to delete the `a`, when press `backspace` after `a`, emacs will throw an error:

> `c-unfind-coalesced-tokens: Wrong type argument: integer-or-marker-p,
> nil`

> the backtrace is:

> Debugger entered--Lisp error: (wrong-type-argument integer-or-marker-p nil)
>   c-forward-sws()
>   c-unfind-coalesced-tokens(6 7)
>   c-before-change(6 7)
>   delete-char(-1 nil)
>   delete-backward-char(1 nil)
>   backward-delete-char-untabify(1)
>   c-electric-backspace(nil)
>   funcall-interactively(c-electric-backspace nil)
>   call-interactively(c-electric-backspace nil nil)
>   command-execute(c-electric-backspace)

Yes.  The cause of this problem is the use of the regular expression
"\\<\\>" in CC Mode, which was meant "not to match anything".  It turns
out that if an Asian character is directly followed by a European
character without spaces between them, this position can be at both the
start of a word and the end of a word.  This is what has happened here.

This bug was actually fixed in the Emacs master branch last year, but
was too late to make it into Emacs 26.  The fix was to use an actual
"will never match" regular expression, namely "a\\`".

> what's more, if you are visiting a large c++ file, after this error
> occurs, then everything will become very slow.

This happens because the error with the regexp causes CC Mode's "before
change function" to get removed from before-change-hook.  When CC Mode
detects this, as a precaution against file corruption, it assumes every
change is over the entire buffer.  This prevents any loss of text, but
runs very slowly.

[ Dump of CC Mode's configuration appreciated, but snipped.]

As a workaround to the bug, please apply the attached patch and byte
compile CC Mode.  (CC Mode is the ?14 files
emacs/lisp/progmodes/cc-*.el).  If you want any help in applying this
patch or byte compiling the result, feel free to send me private email.

Thanks again for this bug report!

-- 
Alan Mackenzie (Nuremberg, Germany).

diff -r 26d4dd0886aa -r cb028a1b5b77 cc-defs.el
--- a/cc-defs.el	Wed Mar 28 18:28:54 2018 +0000
+++ b/cc-defs.el	Wed Mar 28 18:29:49 2018 +0000
@@ -85,7 +85,7 @@
   (progn
     (require 'font-lock)
     (let (font-lock-keywords)
-      (font-lock-compile-keywords '("\\<\\>"))
+      (font-lock-compile-keywords '("a\\`"))
       font-lock-keywords))))
 
 
@@ -1776,8 +1776,8 @@
 
     ;; Produce a regexp that matches nothing.
     (if adorn
-	"\\(\\<\\>\\)"
-      "\\<\\>")))
+	"\\(a\\`\\)"
+      "a\\`")))
 
 (put 'c-make-keywords-re 'lisp-indent-function 1)
 
diff -r 26d4dd0886aa -r cb028a1b5b77 cc-engine.el
--- a/cc-engine.el	Wed Mar 28 18:28:54 2018 +0000
+++ b/cc-engine.el	Wed Mar 28 18:29:49 2018 +0000
@@ -874,7 +874,7 @@
 	stack
 	;; Regexp which matches "for", "if", etc.
 	(cond-key (or c-opt-block-stmt-key
-		      "\\<\\>"))	; Matches nothing.
+		      "a\\`"))	; Matches nothing.
 	;; Return value.
 	(ret 'same)
 	;; Positions of the last three sexps or bounds we've stopped at.
@@ -7158,7 +7158,7 @@
 		(progn
 		  (c-forward-syntactic-ws)
 		  (when (or (and c-record-type-identifiers all-types)
-			    (not (equal c-inside-<>-type-key "\\(\\<\\>\\)")))
+			    (not (equal c-inside-<>-type-key "\\(a\\`\\)")))
 		    (c-forward-syntactic-ws)
 		    (cond
 		     ((eq (char-after) ??)
@@ -8566,7 +8566,7 @@
       ;; Skip over type decl prefix operators.  (Note similar code in
       ;; `c-forward-declarator'.)
       (if (and c-recognize-typeless-decls
-	       (equal c-type-decl-prefix-key "\\<\\>"))
+	       (equal c-type-decl-prefix-key "a\\`"))
 	  (when (eq (char-after) ?\()
 	    (progn
 	      (setq paren-depth (1+ paren-depth))
@@ -10192,7 +10192,7 @@
 	      ;; legal because it's part of a "compound keyword" like
 	      ;; "enum class".	Of course, if c-after-brace-list-key
 	      ;; is nil, we can skip the test.
-	      (or (equal c-after-brace-list-key "\\<\\>")
+	      (or (equal c-after-brace-list-key "a\\`")
 		  (save-match-data
 		    (save-excursion
 		      (not
diff -r 26d4dd0886aa -r cb028a1b5b77 cc-langs.el
--- a/cc-langs.el	Wed Mar 28 18:28:54 2018 +0000
+++ b/cc-langs.el	Wed Mar 28 18:29:49 2018 +0000
@@ -1263,7 +1263,7 @@
 	   (c--set-difference (c-lang-const c-assignment-operators)
 			      '("=")
 			      :test 'string-equal)))
-      "\\<\\>"))
+      "a\\`"))
 (c-lang-defvar c-assignment-op-regexp
   (c-lang-const c-assignment-op-regexp))
 
@@ -1486,7 +1486,7 @@
   ;; language)
   t (if (c-lang-const c-block-comment-ender)
 	(regexp-quote (c-lang-const c-block-comment-ender))
-      "\\<\\>"))
+      "a\\`"))
 (c-lang-defvar c-block-comment-ender-regexp
 	       (c-lang-const c-block-comment-ender-regexp))
 
@@ -1505,7 +1505,7 @@
   ;; language)
   t (if (c-lang-const c-block-comment-starter)
 	(regexp-quote (c-lang-const c-block-comment-starter))
-      "\\<\\>"))
+      "a\\`"))
 (c-lang-defvar c-block-comment-start-regexp
   (c-lang-const c-block-comment-start-regexp))
 
@@ -1514,7 +1514,7 @@
   ;; language; it does in all 7 CC Mode languages).
   t (if (c-lang-const c-line-comment-starter)
 	(regexp-quote (c-lang-const c-line-comment-starter))
-      "\\<\\>"))
+      "a\\`"))
 (c-lang-defvar c-line-comment-start-regexp
 	       (c-lang-const c-line-comment-start-regexp))
 
@@ -1529,7 +1529,7 @@
 
 (c-lang-defconst c-doc-comment-start-regexp
   "Regexp to match the start of documentation comments."
-  t    "\\<\\>"
+  t    "a\\`"
   ;; From font-lock.el: `doxygen' uses /*! while others use /**.
   (c c++ objc) "/\\*[*!]"
   java "/\\*\\*"
@@ -2978,7 +2978,7 @@
   "Regexp matching a keyword that is followed by a colon, where
   the whole construct can precede a declaration.
   E.g. \"public:\" in C++."
-  t "\\<\\>"
+  t "a\\`"
   c++ (c-make-keywords-re t (c-lang-const c-protection-kwds)))
 (c-lang-defvar c-decl-start-colon-kwd-re
   (c-lang-const c-decl-start-colon-kwd-re))
@@ -3159,7 +3159,7 @@
   t (if (c-lang-const c-type-modifier-kwds)
 	(concat (regexp-opt (c-lang-const c-type-modifier-kwds) t) "\\>")
       ;; Default to a regexp that never matches.
-      "\\<\\>")
+      "a\\`")
   ;; Check that there's no "=" afterwards to avoid matching tokens
   ;; like "*=".
   (c objc) (concat "\\("
@@ -3197,7 +3197,7 @@
 as the end of the operator.  Identifier syntax is in effect when
 this is matched \(see `c-identifier-syntax-table')."
   t ;; Default to a regexp that never matches.
-    "\\<\\>"
+    "a\\`"
   ;; Check that there's no "=" afterwards to avoid matching tokens
   ;; like "*=".
   (c objc) (concat "\\(\\*\\)"
@@ -3356,7 +3356,7 @@
 (c-lang-defconst c-pre-id-bracelist-key
   "A regexp matching tokens which, preceding an identifier, signify a bracelist.
 "
-  t "\\<\\>"
+  t "a\\`"
   c++ "new\\([^[:alnum:]_$]\\|$\\)\\|&&?\\(\\S.\\|$\\)")
 (c-lang-defvar c-pre-id-bracelist-key (c-lang-const c-pre-id-bracelist-key))
 
@@ -3412,7 +3412,7 @@
 	 ;; before the '{' of the enum list, to avoid searching too far.
 	 "[^][{};/#=]*"
 	 "{")
-      "\\<\\>"))
+      "a\\`"))
 (c-lang-defvar c-enum-clause-introduction-re
 	       (c-lang-const c-enum-clause-introduction-re))
 
@@ -3528,7 +3528,7 @@
   "Regexp matching things that can't occur two symbols before a colon in
 a label construct.  This catches C++'s inheritance construct \"class foo
 : bar\".  Only used if `c-recognize-colon-labels' is set."
-  t "\\<\\>"				; matches nothing
+  t "a\\`"				; matches nothing
   c++ (c-make-keywords-re t '("class")))
 (c-lang-defvar c-nonlabel-token-2-key (c-lang-const c-nonlabel-token-2-key))
 
diff -r 26d4dd0886aa -r cb028a1b5b77 cc-vars.el
--- a/cc-vars.el	Wed Mar 28 18:28:54 2018 +0000
+++ b/cc-vars.el	Wed Mar 28 18:29:49 2018 +0000
@@ -1645,8 +1645,8 @@
   :type 'boolean
   :group 'c)
 
-(defvar c-noise-macro-with-parens-name-re "\\<\\>")
-(defvar c-noise-macro-name-re "\\<\\>")
+(defvar c-noise-macro-with-parens-name-re "a\\`")
+(defvar c-noise-macro-name-re "a\\`")
 
 (defcustom c-noise-macro-names nil
   "A list of names of macros which expand to nothing, or compiler extensions
@@ -1673,7 +1673,7 @@
   ;; Convert `c-noise-macro-names' and `c-noise-macro-with-parens-names' into
   ;; `c-noise-macro-name-re' and `c-noise-macro-with-parens-name-re'.
   (setq c-noise-macro-with-parens-name-re
-	(cond ((null c-noise-macro-with-parens-names) "\\<\\>")
+	(cond ((null c-noise-macro-with-parens-names) "a\\`")
 	      ((consp c-noise-macro-with-parens-names)
 	       (concat (regexp-opt c-noise-macro-with-parens-names t)
 		       "\\([^[:alnum:]_$]\\|$\\)"))
@@ -1682,7 +1682,7 @@
 	      (t (error "c-make-noise-macro-regexps: \
 c-noise-macro-with-parens-names is invalid: %s" c-noise-macro-with-parens-names))))
   (setq c-noise-macro-name-re
-	(cond ((null c-noise-macro-names) "\\<\\>")
+	(cond ((null c-noise-macro-names) "a\\`")
 	      ((consp c-noise-macro-names)
 	       (concat (regexp-opt c-noise-macro-names t)
 		       "\\([^[:alnum:]_$]\\|$\\)"))