aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhilipp Stephani2017-02-28 22:21:37 +0100
committerPhilipp Stephani2017-03-23 23:05:19 +0100
commit4fbd330fae54a9c45d4a717127aa86d75e9938d5 (patch)
tree7af5dde9a3194b504fe6b837a6339b7683d8efa6
parentaa0fb4fed8ef1f3599f573476fc6291f8872c7e7 (diff)
downloademacs-4fbd330fae54a9c45d4a717127aa86d75e9938d5.tar.gz
emacs-4fbd330fae54a9c45d4a717127aa86d75e9938d5.zip
Protect against an infloop in python-mode
There appears to be an edge case caused by using `syntax-ppss' in a narrowed buffer during JIT lock inside of Python triple-quote strings. Unfortunately it is impossible to reproduce without manually destroying the syntactic information in the Python buffer, but it has been observed in practice. In that case it can happen that the syntax caches get sufficiently out of whack so that there appear to be overlapping strings in the buffer. As Python has no nested strings, this situation is impossible and leads to an infloop in `python-nav-end-of-statement'. Protect against this by checking whether the search for the end of the current string makes progress. * python.el (python-nav-end-of-statement): Protect against infloop. * progmodes/python-tests.el (python-tests--python-nav-end-of-statement--infloop): Add unit test.
-rw-r--r--lisp/progmodes/python.el16
-rw-r--r--test/lisp/progmodes/python-tests.el19
2 files changed, 32 insertions, 3 deletions
diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el
index 228a4484616..2697f1a3107 100644
--- a/lisp/progmodes/python.el
+++ b/lisp/progmodes/python.el
@@ -1491,10 +1491,18 @@ Optional argument NOEND is internal and makes the logic to not
1491jump to the end of line when moving forward searching for the end 1491jump to the end of line when moving forward searching for the end
1492of the statement." 1492of the statement."
1493 (interactive "^") 1493 (interactive "^")
1494 (let (string-start bs-pos) 1494 (let (string-start bs-pos (last-string-end 0))
1495 (while (and (or noend (goto-char (line-end-position))) 1495 (while (and (or noend (goto-char (line-end-position)))
1496 (not (eobp)) 1496 (not (eobp))
1497 (cond ((setq string-start (python-syntax-context 'string)) 1497 (cond ((setq string-start (python-syntax-context 'string))
1498 ;; The assertion can only fail if syntax table
1499 ;; text properties and the `syntax-ppss' cache
1500 ;; are somehow out of whack. This has been
1501 ;; observed when using `syntax-ppss' during
1502 ;; narrowing.
1503 (cl-assert (> string-start last-string-end)
1504 :show-args
1505 "Overlapping strings detected")
1498 (goto-char string-start) 1506 (goto-char string-start)
1499 (if (python-syntax-context 'paren) 1507 (if (python-syntax-context 'paren)
1500 ;; Ended up inside a paren, roll again. 1508 ;; Ended up inside a paren, roll again.
@@ -1504,8 +1512,10 @@ of the statement."
1504 (goto-char (+ (point) 1512 (goto-char (+ (point)
1505 (python-syntax-count-quotes 1513 (python-syntax-count-quotes
1506 (char-after (point)) (point)))) 1514 (char-after (point)) (point))))
1507 (or (re-search-forward (rx (syntax string-delimiter)) nil t) 1515 (setq last-string-end
1508 (goto-char (point-max))))) 1516 (or (re-search-forward
1517 (rx (syntax string-delimiter)) nil t)
1518 (goto-char (point-max))))))
1509 ((python-syntax-context 'paren) 1519 ((python-syntax-context 'paren)
1510 ;; The statement won't end before we've escaped 1520 ;; The statement won't end before we've escaped
1511 ;; at least one level of parenthesis. 1521 ;; at least one level of parenthesis.
diff --git a/test/lisp/progmodes/python-tests.el b/test/lisp/progmodes/python-tests.el
index 1e6b867d30b..2f4c2fb849d 100644
--- a/test/lisp/progmodes/python-tests.el
+++ b/test/lisp/progmodes/python-tests.el
@@ -5314,6 +5314,25 @@ class SomeClass:
5314 (or enabled (hs-minor-mode -1))))) 5314 (or enabled (hs-minor-mode -1)))))
5315 5315
5316 5316
5317(ert-deftest python-tests--python-nav-end-of-statement--infloop ()
5318 "Checks that `python-nav-end-of-statement' doesn't infloop in a
5319buffer with overlapping strings."
5320 (python-tests-with-temp-buffer "''' '\n''' ' '\n"
5321 (syntax-propertize (point-max))
5322 ;; Create a situation where strings nominally overlap. This
5323 ;; shouldn't happen in practice, but apparently it can happen when
5324 ;; a package calls `syntax-ppss' in a narrowed buffer during JIT
5325 ;; lock.
5326 (put-text-property 4 5 'syntax-table (string-to-syntax "|"))
5327 (remove-text-properties 8 9 '(syntax-table nil))
5328 (goto-char 4)
5329 (setq-local syntax-propertize-function nil)
5330 ;; The next form should not infloop. We have to disable
5331 ;; ‘debug-on-error’ so that ‘cl-assert’ doesn’t call the debugger.
5332 (should-error (let ((debug-on-error nil))
5333 (python-nav-end-of-statement)))
5334 (should (eolp))))
5335
5317 5336
5318(provide 'python-tests) 5337(provide 'python-tests)
5319 5338