21

Say that emacs throws some error that I don't understand. Or maybe the error says "Symbol's value as variable is void: modes", but there are many occurrences of the symbol modes in my code, so I need some context. Can Emacs be configured to mention the line number of the lisp code so I can know what code is causing the error?

I have tried doing (setq stack-trace-on-error '(buffer-read-only)) and ran the perpetrating code in an effort to get a stack trace. No stack trace either.

I have also tried calling edebug-defun on my function and stepping through it. It is not until I step out of the function that the error is thrown.

(I am really not as interested in the cause of the particular error I am facing currently as I am in developing general debugging skills for elisp. Please advise on how I can gleam a line number, or a sexp, or a stack trace from an error.)

Jackson
  • 1,218
  • 9
  • 19
  • Did you already try non-`nil` `debug-on-error`? Doesn't that help? – Drew Feb 01 '15 at 06:05
  • Nope. That doesn't seem to do anything. (After I set it to `t` and then proceed to eval an error-throwing function.) – Jackson Feb 01 '15 at 06:26
  • Probably what happens is that some other code catches the error and just prints the error message. Also check that `debug-ignored-errors` doesn't list any errors. If you set `debug-on-signal` to non-`nil`, and it was the case the other code handled the error, you will be able to get the error before the other code did. – wvxvw Feb 01 '15 at 06:55
  • I am currently in a similar situation and was reading this question. I am wondering about stack-trace-on-error. This variable is not documented in Emacs 25.1. – Matthias Feb 08 '17 at 14:07

3 Answers3

19

Emacs provides a good amount of debugging facilities including M-x toggle-debug-on-error, M-x toggle-debug-on-quit, debug on signal (which can be used by sending USR2 to Emacs from outside), debug-on-entry (of a function), debug-on-message (when seeing a specific regexp match of a message) and finally, debug itself as alternative to instrumenting a function with C-u C-M-x.

Both debug and edebug offer enough functionality to inspect the state of Emacs when evaluating the code that you're interested in, hit e and enter an expression.

However, while edebug jumps to the place in the instrumented function and therefore gives you a clue where to look (which is kind of silly since you already know what exactly you've instrumented), debug doesn't do this at all. I've pulled off a smaller hack after discovering that whenever debug evaluates a buffer, it emits the value of point associated with the error; in other words using this information on the buffer can give you a line number in the backtrace!

(with-eval-after-load 'debug
  (defun debugger-setup-buffer (debugger-args)
    "Initialize the `*Backtrace*' buffer for entry to the debugger.
That buffer should be current already."
    (setq buffer-read-only nil)
    (erase-buffer)
    (set-buffer-multibyte t)        ;Why was it nil ?  -stef
    (setq buffer-undo-list t)
    (let ((standard-output (current-buffer))
          (print-escape-newlines t)
          (print-level 8)
          (print-length 50))
      (backtrace))
    (goto-char (point-min))
    (delete-region (point)
                   (progn
                     (search-forward "\n  debug(")
                     (forward-line (if (eq (car debugger-args) 'debug)
                                       2    ; Remove implement-debug-on-entry frame.
                                     1))
                     (point)))
    (insert "Debugger entered")
    ;; lambda is for debug-on-call when a function call is next.
    ;; debug is for debug-on-entry function called.
    (pcase (car debugger-args)
      ((or `lambda `debug)
       (insert "--entering a function:\n"))
      ;; Exiting a function.
      (`exit
       (insert "--returning value: ")
       (setq debugger-value (nth 1 debugger-args))
       (prin1 debugger-value (current-buffer))
       (insert ?\n)
       (delete-char 1)
       (insert ? )
       (beginning-of-line))
      ;; Debugger entered for an error.
      (`error
       (insert "--Lisp error: ")
       (prin1 (nth 1 debugger-args) (current-buffer))
       (insert ?\n))
      ;; debug-on-call, when the next thing is an eval.
      (`t
       (insert "--beginning evaluation of function call form:\n"))
      ;; User calls debug directly.
      (_
       (insert ": ")
       (prin1 (if (eq (car debugger-args) 'nil)
                  (cdr debugger-args) debugger-args)
              (current-buffer))
       (insert ?\n)))
    ;; After any frame that uses eval-buffer,
    ;; insert a line that states the buffer position it's reading at.
    (save-excursion
      (let ((tem eval-buffer-list))
        (while (and tem
                    (re-search-forward "^  eval-\\(buffer\\|region\\)(" nil t))
          (beginning-of-line)
          (insert (format "Error at line %d in %s: "
                          (with-current-buffer (car tem)
                            (line-number-at-pos (point)))
                          (with-current-buffer (car tem)
                            (buffer-name))))
          (pop tem))))
    (debugger-make-xrefs)))

With this the original question in the title should be answered. As for your problem with getting a backtrace in the first place, I'm out of useful ideas.

wasamasa
  • 21,803
  • 1
  • 65
  • 97
  • Thanks for your help, but I still don't understand how to get the line number. `M-x debug` ...? Then what do I press? – Jackson Feb 07 '15 at 21:56
  • With this code you'll see a line number in backtraces made by `debug`, you can check by visiting a faulty elisp file, doing `M-x toggle-debug-on-error` and `M-x eval-buffer`, then a backtrace with a line number at the problematic position should pop up. – wasamasa Feb 07 '15 at 23:37
  • Will this work if you don't use `eval-buffer`? For example, if you just press a keyboard shortcut that runs a private command that fails and opens the debugger in the `*Backtrace*` buffer.. – Håkon Hægland Mar 16 '15 at 18:05
  • No, it won't. You get the function value of the symbol (which can either be a list or something byte-compiled) and that's pretty much it. – wasamasa Mar 16 '15 at 18:36
7

Maybe because it's 2018 now, but in my case, I only had to turn on debugging like wasamasa suggested: M-x toggle-debug-on-error

After this, M-x eval-buffer on my faulty Elisp file gave context by providing the position of the error, like this: Debugger entered--Lisp error: (invalid-read-syntax ")") eval-buffer() ; Reading at buffer position 523 [....]

M-x goto-char jumps to the error position: M-x goto-char 523

Drew
  • 75,699
  • 9
  • 109
  • 225
mistige
  • 169
  • 1
  • 3
  • Nice find! It appears this has been added in 2017, back when they reworked that function to work on a list of backtrace items. – wasamasa Sep 11 '18 at 07:39
  • Additionally, it seems a button to jump to the location has been added. – wasamasa Nov 20 '22 at 12:06
  • Worked for locating an issue with a function that was added to `kill-emacs-hook`. The trace link didn't work as the function call is wrapped with a lambda function (is that the right way to say that?), but the combination of the actual error (void variable) and the function name gave me all I needed to know. Also in emacs 28.1, it's a menu item in `MenuBar->Options->Enter Debugger on Error` – Jim Jan 11 '23 at 15:54
1

I've extended wasamasa's answer to include additional information:

(save-excursion
  (let ((tem eval-buffer-list))
    (while (and tem
                (re-search-forward "^  eval-\\(buffer\\|region\\)(" nil t))
      (beginning-of-line)
      (insert (apply 'format "Error at line %d, column %d (point %d) in %s\n"
                     (with-current-buffer (car tem)
                       (list (line-number-at-pos (point))
                             (current-column)
                             (point)
                             (buffer-name)))))
      (pop tem))))
Jackson
  • 1,218
  • 9
  • 19