summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlan Mackenzie <acm@muc.de>2020-12-24 11:29:27 +0000
committerAlan Mackenzie <acm@muc.de>2020-12-24 11:29:27 +0000
commit3096437593ca6d1ea07809f7d0e2198705f20e55 (patch)
tree013eeb294c23ba48540962c1cdc9a7ab39bd24c0
parentccb3efffc5a41353abb6ae223a7dcff1ea20e5fb (diff)
downloademacs-3096437593ca6d1ea07809f7d0e2198705f20e55.tar.gz
emacs-3096437593ca6d1ea07809f7d0e2198705f20e55.tar.bz2
emacs-3096437593ca6d1ea07809f7d0e2198705f20e55.zip
CC Mode: introduce a new cache for brace structures. This fixes bug #45248
Also fix three infinite loops. The new cache accelerates backward searches for struct beginnings in c-looking-at-or-maybe-in-bracelist. * lisp/progmodes/cc-engine.el (c-beginning-of-statement-1): In the final loop over unary operators, add a check (> (point) lim) to avoid certain infinite loops. (c-beginning-of-decl-1): In the first loop add a similar check on point and lim. (c-laomib-loop): New function extracted from c-looking-at-or-maybe-in-bracelist. (c-laomib-cache): New buffer local variable. (c-laomib-get-cache, c-laomib-put-cache, c-laomib-fix-elt) (c-laomib-invalidate-cache): New functions which implement the cache. (c-looking-at-or-maybe-in-bracelist): Replace two invocations of c-go-up-list-backwards with calls to c-parse-state. Extract the new function c-laomib-loop. Insert code which calls c-laomib-loop minimally, with the help of the new cache. * lisp/progmodes/cc-mode.el (c-basic-common-init): Initialise the new cach (at mode start). (c-before-change): Invalidate the new cache. (c-fl-decl-start): Add an extra check (> (point) bod-lim) to prevent looping. Determine the enclosing brace to pass as arguments to c-looking-at-or-maybe-in-bracelist.
-rw-r--r--lisp/progmodes/cc-engine.el332
-rw-r--r--lisp/progmodes/cc-mode.el34
2 files changed, 266 insertions, 100 deletions
diff --git a/lisp/progmodes/cc-engine.el b/lisp/progmodes/cc-engine.el
index 218bbb47cd5..51f620ea1f0 100644
--- a/lisp/progmodes/cc-engine.el
+++ b/lisp/progmodes/cc-engine.el
@@ -1414,12 +1414,14 @@ comment at the start of cc-engine.el for more info."
(setq ret 'label)))
;; Skip over the unary operators that can start the statement.
- (while (progn
- (c-backward-syntactic-ws lim)
- ;; protect AWK post-inc/decrement operators, etc.
- (and (not (c-at-vsemi-p (point)))
- (/= (skip-chars-backward "-.+!*&~@`#") 0)))
+ (while (and (> (point) lim)
+ (progn
+ (c-backward-syntactic-ws lim)
+ ;; protect AWK post-inc/decrement operators, etc.
+ (and (not (c-at-vsemi-p (point)))
+ (/= (skip-chars-backward "-.+!*&~@`#") 0))))
(setq pos (point)))
+
(goto-char pos)
ret)))
@@ -3567,8 +3569,9 @@ mhtml-mode."
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Defuns which analyze the buffer, yet don't change `c-state-cache'.
(defun c-get-fallback-scan-pos (here)
- ;; Return a start position for building `c-state-cache' from
- ;; scratch. This will be at the top level, 2 defuns back.
+ ;; Return a start position for building `c-state-cache' from scratch. This
+ ;; will be at the top level, 2 defuns back. Return nil if we don't find
+ ;; these defun starts a reasonable way back.
(save-excursion
(save-restriction
(when (> here (* 10 c-state-cache-too-far))
@@ -11177,6 +11180,7 @@ comment at the start of cc-engine.el for more info."
(c-backward-syntactic-ws lim)
(not (or (memq (char-before) '(?\; ?} ?: nil))
(c-at-vsemi-p))))
+ (not (and lim (<= (point) lim)))
(save-excursion
(backward-char)
(not (looking-at "\\s(")))
@@ -11615,6 +11619,195 @@ comment at the start of cc-engine.el for more info."
(or (looking-at c-brace-list-key)
(progn (goto-char here) nil))))
+(defun c-laomib-loop (lim)
+ ;; The "expensive" loop from `c-looking-at-or-maybe-in-bracelist'. Move
+ ;; backwards over comma separated sexps as far as possible, but no further
+ ;; than LIM, which may be nil, meaning no limit. Return the final value of
+ ;; `braceassignp', which is t if we encountered "= {", usually nil
+ ;; otherwise.
+ (let ((braceassignp 'dontknow)
+ (class-key
+ ;; Pike can have class definitions anywhere, so we must
+ ;; check for the class key here.
+ (and (c-major-mode-is 'pike-mode)
+ c-decl-block-key)))
+ (while (eq braceassignp 'dontknow)
+ (cond ((eq (char-after) ?\;)
+ (setq braceassignp nil))
+ ((and class-key
+ (looking-at class-key))
+ (setq braceassignp nil))
+ ((and c-has-compound-literals
+ (looking-at c-return-key))
+ (setq braceassignp t)
+ nil)
+ ((eq (char-after) ?=)
+ ;; We've seen a =, but must check earlier tokens so
+ ;; that it isn't something that should be ignored.
+ (setq braceassignp 'maybe)
+ (while (and (eq braceassignp 'maybe)
+ (zerop (c-backward-token-2 1 t lim)))
+ (setq braceassignp
+ (cond
+ ;; Check for operator =
+ ((and c-opt-op-identifier-prefix
+ (looking-at c-opt-op-identifier-prefix))
+ nil)
+ ;; Check for `<opchar>= in Pike.
+ ((and (c-major-mode-is 'pike-mode)
+ (or (eq (char-after) ?`)
+ ;; Special case for Pikes
+ ;; `[]=, since '[' is not in
+ ;; the punctuation class.
+ (and (eq (char-after) ?\[)
+ (eq (char-before) ?`))))
+ nil)
+ ((looking-at "\\s.") 'maybe)
+ ;; make sure we're not in a C++ template
+ ;; argument assignment
+ ((and
+ (c-major-mode-is 'c++-mode)
+ (save-excursion
+ (let ((here (point))
+ (pos< (progn
+ (skip-chars-backward "^<>")
+ (point))))
+ (and (eq (char-before) ?<)
+ (not (c-crosses-statement-barrier-p
+ pos< here))
+ (not (c-in-literal))
+ ))))
+ nil)
+ (t t)))))
+ ((and
+ (c-major-mode-is 'c++-mode)
+ (eq (char-after) ?\[)
+ ;; Be careful of "operator []"
+ (not (save-excursion
+ (c-backward-token-2 1 nil lim)
+ (looking-at c-opt-op-identifier-prefix))))
+ (setq braceassignp t)
+ nil))
+ (when (eq braceassignp 'dontknow)
+ (cond ((and
+ (not (eq (char-after) ?,))
+ (save-excursion
+ (c-backward-syntactic-ws)
+ (eq (char-before) ?})))
+ (setq braceassignp nil))
+ ((/= (c-backward-token-2 1 t lim) 0)
+ (if (save-excursion
+ (and c-has-compound-literals
+ (eq (c-backward-token-2 1 nil lim) 0)
+ (eq (char-after) ?\()))
+ (setq braceassignp t)
+ (setq braceassignp nil))))))
+ braceassignp))
+
+;; The following variable is a cache of up to four entries, each entry of
+;; which is a list representing a call to c-laomib-loop. It contains the
+;; following elements:
+;; 0: `lim' argument - used as an alist key, never nil.
+;; 1: Position in buffer where the scan started.
+;; 2: Position in buffer where the scan ended.
+;; 3: Result of the call to `c-laomib-loop'.
+(defvar c-laomib-cache nil)
+(make-variable-buffer-local 'c-laomib-cache)
+
+(defun c-laomib-get-cache (containing-sexp)
+ ;; Get an element from `c-laomib-cache' matching CONTAINING-SEXP.
+ ;; Return that element or nil if one wasn't found.
+ (let ((elt (assq containing-sexp c-laomib-cache)))
+ (when elt
+ ;; Move the fetched `elt' to the front of the cache.
+ (setq c-laomib-cache (delq elt c-laomib-cache))
+ (push elt c-laomib-cache)
+ elt)))
+
+(defun c-laomib-put-cache (lim start end result)
+ ;; Insert a new element into `c-laomib-cache', removing another element to
+ ;; make room, if necessary. The four parameters LIM, START, END, RESULT are
+ ;; the components of the new element (see comment for `c-laomib-cache').
+ ;; The return value is of no significance.
+ (when lim
+ (let ((old-elt (assq lim c-laomib-cache))
+ ;; (elt (cons containing-sexp (cons start nil)))
+ (new-elt (list lim start end result))
+ big-ptr
+ (cur-ptr c-laomib-cache)
+ togo togo-ptr (size 0) cur-size
+ )
+ (if old-elt (setq c-laomib-cache (delq old-elt c-laomib-cache)))
+
+ (while (>= (length c-laomib-cache) 4)
+ ;; We delete the least recently used elt which doesn't enclose START,
+ ;; or..
+ (dolist (elt c-laomib-cache)
+ (if (or (<= start (cadr elt))
+ (> start (car (cddr elt))))
+ (setq togo elt)))
+
+ ;; ... delete the least recently used elt which isn't the biggest.
+ (when (not togo)
+ (while (cdr cur-ptr)
+ (setq cur-size (- (nth 2 (cadr cur-ptr)) (car (cadr cur-ptr))))
+ (when (> cur-size size)
+ (setq size cur-size
+ big-ptr cur-ptr))
+ (setq cur-ptr (cdr cur-ptr)))
+ (setq togo (if (cddr big-ptr)
+ (car (last big-ptr))
+ (car big-ptr))))
+
+ (setq c-laomib-cache (delq togo c-laomib-cache)))
+
+ (push new-elt c-laomib-cache))))
+
+(defun c-laomib-fix-elt (lwm elt paren-state)
+ ;; Correct a c-laomib-cache entry ELT with respect to buffer changes, either
+ ;; doing nothing, signalling it is to be deleted, or replacing its start
+ ;; point with one lower in the buffer than LWM. PAREN-STATE is the paren
+ ;; state at LWM. Return the corrected entry, or nil (if it needs deleting).
+ ;; Note that corrections are made by `setcar'ing the original structure,
+ ;; which thus remains intact.
+ (cond
+ ((or (not lwm) (> lwm (cadr elt)))
+ elt)
+ ((<= lwm (nth 2 elt))
+ nil)
+ (t
+ (let (cur-brace)
+ ;; Search for the last brace in `paren-state' before (car `lim'). This
+ ;; brace will become our new 2nd element of `elt'.
+ (while
+ ;; Search one brace level per iteration.
+ (and paren-state
+ (progn
+ ;; (setq cur-brace (c-laomib-next-BRACE paren-state))
+ (while
+ ;; Go past non-brace levels, one per iteration.
+ (and paren-state
+ (not (eq (char-after
+ (c-state-cache-top-lparen paren-state))
+ ?{)))
+ (setq paren-state (cdr paren-state)))
+ (cadr paren-state))
+ (> (c-state-cache-top-lparen (cdr paren-state)) (car elt)))
+ (setq paren-state (cdr paren-state)))
+ (when (cadr paren-state)
+ (setcar (cdr elt) (c-state-cache-top-lparen paren-state))
+ elt)))))
+
+(defun c-laomib-invalidate-cache (beg _end)
+ ;; Called from late in c-before-change. Amend `c-laomib-cache' to remove
+ ;; details pertaining to the buffer after position BEG.
+ (save-excursion
+ (goto-char beg)
+ (let ((paren-state (c-parse-state)))
+ (dolist (elt c-laomib-cache)
+ (when (not (c-laomib-fix-elt beg elt paren-state))
+ (setq c-laomib-cache (delq elt c-laomib-cache)))))))
+
(defun c-looking-at-or-maybe-in-bracelist (&optional containing-sexp lim)
;; Point is at an open brace. If this starts a brace list, return a list
;; whose car is the buffer position of the start of the construct which
@@ -11635,14 +11828,10 @@ comment at the start of cc-engine.el for more info."
;; Here, "brace list" does not include the body of an enum.
(save-excursion
(let ((start (point))
- (class-key
- ;; Pike can have class definitions anywhere, so we must
- ;; check for the class key here.
- (and (c-major-mode-is 'pike-mode)
- c-decl-block-key))
(braceassignp 'dontknow)
inexpr-brace-list bufpos macro-start res pos after-type-id-pos
- in-paren parens-before-brace)
+ in-paren parens-before-brace
+ paren-state paren-pos)
(setq res (c-backward-token-2 1 t lim))
;; Checks to do only on the first sexp before the brace.
@@ -11651,8 +11840,10 @@ comment at the start of cc-engine.el for more info."
(cond
((and (or (not (eq res 0))
(eq (char-after) ?,))
- (c-go-up-list-backward nil lim) ; FIXME!!! Check ; `lim' 2016-07-12.
- (eq (char-after) ?\())
+ (setq paren-state (c-parse-state))
+ (setq paren-pos (c-pull-open-brace paren-state))
+ (eq (char-after paren-pos) ?\())
+ (goto-char paren-pos)
(setq braceassignp 'c++-noassign
in-paren 'in-paren))
((looking-at c-pre-id-bracelist-key)
@@ -11669,9 +11860,11 @@ comment at the start of cc-engine.el for more info."
(cond
((or (not (eq res 0))
(eq (char-after) ?,))
- (and (c-go-up-list-backward nil lim) ; FIXME!!! Check `lim' 2016-07-12.
- (eq (char-after) ?\()
- (setq in-paren 'in-paren)))
+ (and (setq paren-state (c-parse-state))
+ (setq paren-pos (c-pull-open-brace paren-state))
+ (eq (char-after paren-pos) ?\()
+ (setq in-paren 'in-paren)
+ (goto-char paren-pos)))
((looking-at c-pre-id-bracelist-key))
((looking-at c-return-key))
(t (setq after-type-id-pos (point))
@@ -11724,79 +11917,36 @@ comment at the start of cc-engine.el for more info."
(t
(goto-char pos)
- ;; Checks to do on all sexps before the brace, up to the
- ;; beginning of the statement.
- (while (eq braceassignp 'dontknow)
- (cond ((eq (char-after) ?\;)
- (setq braceassignp nil))
- ((and class-key
- (looking-at class-key))
- (setq braceassignp nil))
- ((and c-has-compound-literals
- (looking-at c-return-key))
- (setq braceassignp t)
- nil)
- ((eq (char-after) ?=)
- ;; We've seen a =, but must check earlier tokens so
- ;; that it isn't something that should be ignored.
- (setq braceassignp 'maybe)
- (while (and (eq braceassignp 'maybe)
- (zerop (c-backward-token-2 1 t lim)))
- (setq braceassignp
- (cond
- ;; Check for operator =
- ((and c-opt-op-identifier-prefix
- (looking-at c-opt-op-identifier-prefix))
- nil)
- ;; Check for `<opchar>= in Pike.
- ((and (c-major-mode-is 'pike-mode)
- (or (eq (char-after) ?`)
- ;; Special case for Pikes
- ;; `[]=, since '[' is not in
- ;; the punctuation class.
- (and (eq (char-after) ?\[)
- (eq (char-before) ?`))))
- nil)
- ((looking-at "\\s.") 'maybe)
- ;; make sure we're not in a C++ template
- ;; argument assignment
- ((and
- (c-major-mode-is 'c++-mode)
- (save-excursion
- (let ((here (point))
- (pos< (progn
- (skip-chars-backward "^<>")
- (point))))
- (and (eq (char-before) ?<)
- (not (c-crosses-statement-barrier-p
- pos< here))
- (not (c-in-literal))
- ))))
- nil)
- (t t)))))
- ((and
- (c-major-mode-is 'c++-mode)
- (eq (char-after) ?\[)
- ;; Be careful of "operator []"
- (not (save-excursion
- (c-backward-token-2 1 nil lim)
- (looking-at c-opt-op-identifier-prefix))))
- (setq braceassignp t)
- nil))
- (when (eq braceassignp 'dontknow)
- (cond ((and
- (not (eq (char-after) ?,))
- (save-excursion
- (c-backward-syntactic-ws)
- (eq (char-before) ?})))
- (setq braceassignp nil))
- ((/= (c-backward-token-2 1 t lim) 0)
- (if (save-excursion
- (and c-has-compound-literals
- (eq (c-backward-token-2 1 nil lim) 0)
- (eq (char-after) ?\()))
- (setq braceassignp t)
- (setq braceassignp nil))))))
+ (when (eq braceassignp 'dontknow)
+ (let* ((cache-entry (and containing-sexp
+ (c-laomib-get-cache containing-sexp)))
+ (lim2 (or (cadr cache-entry) lim))
+ sub-bassign-p)
+ (if cache-entry
+ (cond
+ ((<= (point) (cadr cache-entry))
+ ;; We're inside the region we've already scanned over, so
+ ;; just go to that scan's end position.
+ (goto-char (nth 2 cache-entry))
+ (setq braceassignp (nth 3 cache-entry)))
+ ((> (point) (cadr cache-entry))
+ ;; We're beyond the previous scan region, so just scan as
+ ;; far as the end of that region.
+ (setq sub-bassign-p (c-laomib-loop lim2))
+ (if (<= (point) (cadr cache-entry))
+ (progn
+ (c-laomib-put-cache containing-sexp
+ start (nth 2 cache-entry)
+ (nth 3 cache-entry) ;; sub-bassign-p
+ )
+ (setq braceassignp (nth 3 cache-entry))
+ (goto-char (nth 2 cache-entry)))
+ (setq braceassignp sub-bassign-p)))
+ (t))
+
+ (setq braceassignp (c-laomib-loop lim))
+ (when lim
+ (c-laomib-put-cache lim start (point) braceassignp)))))
(cond
(braceassignp
diff --git a/lisp/progmodes/cc-mode.el b/lisp/progmodes/cc-mode.el
index 7a111017074..f6d36f5670c 100644
--- a/lisp/progmodes/cc-mode.el
+++ b/lisp/progmodes/cc-mode.el
@@ -639,6 +639,8 @@ that requires a literal mode spec at compile time."
;; doesn't work with filladapt but it's better than nothing.
(set (make-local-variable 'fill-paragraph-function) 'c-fill-paragraph)
+ ;; Initialize the cache for `c-looking-at-or-maybe-in-bracelist'.
+ (setq c-laomib-cache nil)
;; Initialize the three literal sub-caches.
(c-truncate-lit-pos-cache 1)
;; Initialize the cache of brace pairs, and opening braces/brackets/parens.
@@ -2054,7 +2056,9 @@ Note that this is a strict tail, so won't match, e.g. \"0x....\".")
(if c-get-state-before-change-functions
(mapc (lambda (fn)
(funcall fn beg end))
- c-get-state-before-change-functions))))
+ c-get-state-before-change-functions))
+
+ (c-laomib-invalidate-cache beg end)))
(c-clear-string-fences))))
(c-truncate-lit-pos-cache beg)
;; The following must be done here rather than in `c-after-change'
@@ -2205,7 +2209,8 @@ Note that this is a strict tail, so won't match, e.g. \"0x....\".")
old-pos
(new-pos pos)
capture-opener
- bod-lim bo-decl)
+ bod-lim bo-decl
+ paren-state containing-brace)
(goto-char (c-point 'bol new-pos))
(unless lit-start
(setq bod-lim (c-determine-limit 500))
@@ -2224,12 +2229,16 @@ Note that this is a strict tail, so won't match, e.g. \"0x....\".")
(setq old-pos (point))
(let (pseudo)
(while
- (progn
- (c-syntactic-skip-backward "^;{}" bod-lim t)
- (and (eq (char-before) ?})
- (save-excursion
- (backward-char)
- (setq pseudo (c-cheap-inside-bracelist-p (c-parse-state))))))
+ (and
+ ;; N.B. `c-syntactic-skip-backward' doesn't check (> (point)
+ ;; lim) and can loop if that's not the case.
+ (> (point) bod-lim)
+ (progn
+ (c-syntactic-skip-backward "^;{}" bod-lim t)
+ (and (eq (char-before) ?})
+ (save-excursion
+ (backward-char)
+ (setq pseudo (c-cheap-inside-bracelist-p (c-parse-state)))))))
(goto-char pseudo))
t)
(> (point) bod-lim)
@@ -2262,7 +2271,14 @@ Note that this is a strict tail, so won't match, e.g. \"0x....\".")
(and (eq (char-before) ?{)
(save-excursion
(backward-char)
- (consp (c-looking-at-or-maybe-in-bracelist))))
+ (setq paren-state (c-parse-state))
+ (while
+ (and
+ (setq containing-brace
+ (c-pull-open-brace paren-state))
+ (not (eq (char-after containing-brace) ?{))))
+ (consp (c-looking-at-or-maybe-in-bracelist
+ containing-brace containing-brace))))
)))
(not (bobp)))
(backward-char)) ; back over (, [, <.