Subject: bug#37045: lisp-indent-region hangs in lisp-indent-calc-next when indenting the three characters "|#\n"

From: Noam Postavsky <[email-masked]>
Date: Fri, 16 Aug 2019 07:47:20 -0400

found 37045 26.1
tags 37045 + patch
quit

[email-masked] writes:

> Indent-region sometimes hangs when indenting a lisp file.  A minimal
> example is the file just containing the characters "|#" followed by a
> newline.

It's actually any unfinished string literal (in Common Lisp, |...| is
treated by Emacs as a string literal, though technically it's an escaped
symbol).

> This hang is due to an infinite loop in the while loop in the
> `lisp-indent-calc-next' function.  The following version of this
> function fixes this problem, but is certainly not the appropriate
> long-term fix.

I think the right fix is just to check for end of buffer:

@@ -810,7 +810,7 @@ lisp-indent-calc-next
                (setq last-sexp (nth 2 ppss)))
              (setq depth (car ppss))
              ;; Skip over newlines within strings.
-             (nth 3 ppss))
+             (and (not (eobp)) (nth 3 ppss)))
       (let ((string-start (nth 8 ppss)))
         (setq ppss (parse-partial-sexp (point) (point-max)
                                        nil nil ppss 'syntax-table))

Full patch with test (and some other comment updates) attached below.
Is this okay for emacs-26?  The bug is a regression from Emacs 25.

>From ab3f0d0d09163862c62efa4d67e4e56cdef60716 Mon Sep 17 00:00:00 2001
From: Noam Postavsky <[email-masked]>
Date: Fri, 16 Aug 2019 07:26:40 -0400
Subject: [PATCH] Fix lisp indent infloop on unfinished strings (Bug#37045)

* lisp/emacs-lisp/lisp-mode.el (lisp-indent-calc-next): Stop trying to
skip over strings if we've hit the end of buffer.
* test/lisp/emacs-lisp/lisp-mode-tests.el
(lisp-indent-unfinished-string): New test.
---
 lisp/emacs-lisp/lisp-mode.el            | 15 ++++++++++-----
 test/lisp/emacs-lisp/lisp-mode-tests.el |  6 +++++-
 2 files changed, 15 insertions(+), 6 deletions(-)

diff --git a/lisp/emacs-lisp/lisp-mode.el b/lisp/emacs-lisp/lisp-mode.el
index 74bf0c87c5..bde0a4ea6d 100644
--- a/lisp/emacs-lisp/lisp-mode.el
+++ b/lisp/emacs-lisp/lisp-mode.el
@@ -810,7 +810,7 @@ lisp-indent-calc-next
                (setq last-sexp (nth 2 ppss)))
              (setq depth (car ppss))
              ;; Skip over newlines within strings.
-             (nth 3 ppss))
+             (and (not (eobp)) (nth 3 ppss)))
       (let ((string-start (nth 8 ppss)))
         (setq ppss (parse-partial-sexp (point) (point-max)
                                        nil nil ppss 'syntax-table))
@@ -826,17 +826,22 @@ lisp-indent-calc-next
                                        indent-stack)))))
     (prog1
         (let (indent)
-          (cond ((= (forward-line 1) 1) nil)
-                ;; Negative depth, probably some kind of syntax error.
+          (cond ((= (forward-line 1) 1)
+                 ;; Can't move to the next line, apparently end of buffer.
+                 nil)
                 ((null indent-stack)
-                 ;; Reset state.
+                 ;; Negative depth, probably some kind of syntax
+                 ;; error.  Reset the state.
                  (setq ppss (parse-partial-sexp (point) (point))))
                 ((car indent-stack))
                 ((integerp (setq indent (calculate-lisp-indent ppss)))
                  (setf (car indent-stack) indent))
                 ((consp indent)       ; (COLUMN CONTAINING-SEXP-START)
                  (car indent))
-                ;; This only happens if we're in a string.
+                ;; This only happens if we're in a string, but the
+                ;; loop should always skip over strings (unless we hit
+                ;; end of buffer, which is taken care of by the first
+                ;; clause).
                 (t (error "This shouldn't happen"))))
       (setf (lisp-indent-state-stack state) indent-stack)
       (setf (lisp-indent-state-ppss-point state) ppss-point)
diff --git a/test/lisp/emacs-lisp/lisp-mode-tests.el b/test/lisp/emacs-lisp/lisp-mode-tests.el
index 63632449ca..e4ba929ecb 100644
--- a/test/lisp/emacs-lisp/lisp-mode-tests.el
+++ b/test/lisp/emacs-lisp/lisp-mode-tests.el
@@ -284,7 +284,11 @@ lisp-indent-with-read-only-field
     (lisp-indent-line)
     (should (equal (buffer-string) "prompt> foo"))))
 
-
+(ert-deftest lisp-indent-unfinished-string ()
+  "Don't infloop on unfinished string (Bug#37045)."
+  (with-temp-buffer
+    (insert "\"\n")
+    (lisp-indent-region (point-min) (point-max))))
 
 (provide 'lisp-mode-tests)
 ;;; lisp-mode-tests.el ends here
-- 
2.11.0