Small-time Patching

22/09/2016

Update: Added the improved backtrace

Update: Merged!

Today #emacs reminded me of an oddity in Emacs I’ve sort of learned to live with: Backtraces are, well, see for yourself:

Debugger entered--Lisp error: (wrong-type-argument number-or-marker-p t)
  +(1 t)
  eval((+ 1 t) nil)
  eval-expression((+ 1 t) nil)
  call-interactively(eval-expression nil nil)
  command-execute(eval-expression)

I can live with errors printed as a list. What I can’t live with is none of the call stack lines being printed as a S-Expression… To fix this, one must dive a bit deeper than usual as only the debugger porcelain is implemented in Emacs Lisp. Its workhorse is backtrace in eval.c:

DEFUN ("backtrace", Fbacktrace, Sbacktrace, 0, 0, "",
       doc: /* Print a trace of Lisp function calls currently active.
Output stream used is value of `standard-output'.  */)
  (void)
{
  union specbinding *pdl = backtrace_top ();
  Lisp_Object tem;
  Lisp_Object old_print_level = Vprint_level;

  if (NILP (Vprint_level))
    XSETFASTINT (Vprint_level, 8);

  while (backtrace_p (pdl))
    {
      write_string (backtrace_debug_on_exit (pdl) ? "* " : "  ");
      if (backtrace_nargs (pdl) == UNEVALLED)
        {
          Fprin1 (Fcons (backtrace_function (pdl), *backtrace_args (pdl)),
                  Qnil);
          write_string ("\n");
        }
      else
        {
          tem = backtrace_function (pdl);
          Fprin1 (tem, Qnil);   /* This can QUIT.  */
          write_string ("(");
          {
            ptrdiff_t i;
            for (i = 0; i < backtrace_nargs (pdl); i++)
              {
                if (i) write_string (" ");
                Fprin1 (backtrace_args (pdl)[i], Qnil);
              }
          }
          write_string (")\n");
        }
      pdl = backtrace_next (pdl);
    }

  Vprint_level = old_print_level;
  return Qnil;
}

Despite the rather unusual look and weird naming, it’s not too hard to find the culprit. Most of the time is spent inside a loop that walks through the call stack, accesses the top-most function and args with backtrace_function and backtrace_args, prints a lisp object with Fprin1 (which is just another way to use prin1 from C code) and writes out normal strings with write_string. Qnil refers to the global nil symbol, tem is a naming convention for a temporary variable. It should be sufficient to print the opening paren first, then the function, a space and proceed normally from that point:

tem = backtrace_function (pdl);
write_string ("(");
Fprin1 (tem, Qnil); /* This can QUIT.  */
write_string (" ");

Time to recompile Emacs with make, boot the binary with src/emacs -Q, and trigger a backtrace with M-: (+ 1 t). Unfortunately that does not yield a prettier backtrace yet, but rather a Search failed: "\n debug(". As the debugger is busted, I had to resort to ag to find where exactly the breakage occurs. It’s not too hard to fix, mind you, all you have to do is to patch debugger-setup-buffer in debug.el to search for "\n (debug" instead.

The result is the following teensy patch:

diff --git a/lisp/emacs-lisp/debug.el b/lisp/emacs-lisp/debug.el
index 22a3f39..4020620 100644
--- a/lisp/emacs-lisp/debug.el
+++ b/lisp/emacs-lisp/debug.el
@@ -279,7 +279,7 @@ That buffer should be current already."
   (goto-char (point-min))
   (delete-region (point)
             (progn
-              (search-forward "\n  debug(")
+              (search-forward "\n  (debug")
               (forward-line (if (eq (car args) 'debug)
                                      ;; Remove debug--implement-debug-on-entry
                                      ;; and the advice's `apply' frame.
diff --git a/src/eval.c b/src/eval.c
index 72facd5..e32e7a1 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -3409,8 +3409,9 @@ Output stream used is value of `standard-output'.  */)
       else
    {
      tem = backtrace_function (pdl);
-     Fprin1 (tem, Qnil);   /* This can QUIT.  */
      write_string ("(");
+     Fprin1 (tem, Qnil);   /* This can QUIT.  */
+     write_string (" ");
      {
        ptrdiff_t i;
        for (i = 0; i < backtrace_nargs (pdl); i++)

And an IMHO vastly improved backtrace:

Debugger entered--Lisp error: (wrong-type-argument number-or-marker-p t)
  (debug error (wrong-type-argument number-or-marker-p t))
  (+ 1 t)
  (eval (+ 1 t) nil)
  (eval-expression (+ 1 t) nil)
  (funcall-interactively eval-expression (+ 1 t) nil)
  (call-interactively eval-expression nil nil)
  (command-execute eval-expression)

Not sure whether to bother submitting this… Let me know what you think!