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.
This commit is contained in:
Alan Mackenzie 2020-12-24 11:29:27 +00:00
parent ccb3efffc5
commit 3096437593
2 changed files with 266 additions and 100 deletions

View file

@ -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

View file

@ -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 (, [, <.