Debuggers

Using Ritz to debug Clojure in Emacs

Hugo Duncan / @hugoduncan

https://github.com/hugoduncan/ritz-conj

History

debug-repl George Jahad @georgejahad (with an insight by Alex Osborne @atosborne)
swank-clojure Phil Hagelburg @technomancy
CDT George Jahad @georgejahad

Started Ritz to provide Clojure debugging in SLIME via SLDB

nREPL

https://github.com/clojure/tools.nrepl

nREPL
  • transport (socket & bencode)

  • middleware stack

Driven by Chas Emerick / @cemerick

load-file

load-file op

Support for Clojure and ClojureScript

Leiningen Profiles

Configure your favourite middleware in ~/.lein/profiles.clj.

:user
  {:repl-options
    {:nrepl-middleware
     [ritz.nrepl.middleware.doc/wrap-doc]}}

Or add project specific middleware in project.clj

:dev
  {:repl-options
    {:nrepl-middleware
     [ritz.nrepl.middleware.doc/wrap-doc]}}

reply

Colin Jones @trptcolin https://github.com/trptcolin/reply

CounterClockWise

Laurent Petit @petitlaurent http://code.google.com/p/counterclockwise.

VimClojure

Meikel Brandmeyer @kotarak VimClojure.

and …

nrepl.el

https://github.com/kingtim/nrepl.el

An Emacs client for the nREPL protocol, unfettered by support of any other lisp.

Driven by Tim King

Install nrepl.el

nrepl.el is available as an Emacs package in Marmalade.

(require 'package)
(add-to-list 'package-archives
 '("marmalade"
   . "http://marmalade-repo.org/packages/"))
(package-initialize)

The git HEAD is packaged to MELPA.

(add-to-list 'package-archives
 '("melpa"
   . "http://melpa.milkbox.net/packages/"))

Install nrepl.el ...

In either case, to actually install nrepl:

M-x package-install nrepl

Running nrepl.el

Browse to a file in your clojure project an jack-in:

M-x nrepl-jack-in

Running nrepl.el ...

Start an nREPL server, possibly with lein.

lein repl :headless

Connect to the nREPL server. lein announces the port the REPL is running on.

M-x nrepl localhost 41045

Ritz

https://github.com/pallet/ritz

Started life as a fork of swank-clojure, but is now a very different codebase.

Initially was to provide SLDB for Clojure in SLIME.

Now refactored to support nREPL.

Ritz Components

Libs

Ritz nREPL Middleware

Can be used in any nREPL server

javadoc
javadoc for symbol
apropos
doc for related functions
doc
Clojure doc for symbol
describe-symbol
Clojure doc and description
complete
simple and fuzzy completion
eval
keep track of source forms

More Ritz Components

nrepl-codeq
Middleware for function history via codeq
nrepl-hornetq
An nREPL server over HornetQ

Ritz Debugger

Refactored after reading Out of the Tar Pit, Ben Moseley, Peter Marks 2006.

  • Isolate mutable state
  • Provide simple data query and update ops
  • No preferred access path

Map for each connection, with assoc and update-in functions for different areas within the connection.

Ritz Debugger Middleware

nREPL and jdi

Could be used by other (non-emacs) clients.

Emacs package for nrepl.el extensions.

Ritz Debugger

debug ops

Using Ritz nREPL Debugger

Install

Install the nrepl-ritz.el Emacs package:

M-x package-install nrepl-ritz

Add lein-ritz to you :plugins in ~/.lein/profiles.clj

:user {:plugins [[lein-ritz "0.6.0"]]

Using Ritz nREPL Debugger

Run

Run the ritz nREPL server:

lein ritz-nrepl

Connect to the repl server, specifying the port:

M-x nrepl 4005

Break on exception

Examine stack traces before the stack unwinds. Needs to be turned on:

M-x nrepl-ritz-break-on-exception
user> (/ 1 0)

Break on exception - restarts

Filtering of exceptions is via "restarts" displayed as part of the stacktrace.

Divide by zero
java.lang.ArithmeticException

Restarts:
 0: [CONTINUE] Pass exception to program
 1: [ABORT] Abort request.
 2: [IGNORE] Do not enter debugger for this exception type
 3: [IGNORE-MSG] Do not enter debugger for this exception message
 4: [IGNORE-CATCH] Do not enter debugger for exceptions with
    catch location clojure.lang.Compiler.*
 5: [IGNORE-LOC] Do not enter debugger for exceptions with throw
    location clojure.lang.Numbers.*

Stacktrace:
  0: clojure.lang.Numbers.divide (Numbers.java:156)
  1: clojure.lang.Numbers.divide (Numbers.java:3691)
  2: ritz-conj.example/eval2845 (UNKNOWN:-1)

What's caught, and what's not

Any (try ... (finally ..)) block means that JPDA considers an exception within that block as caught

(with-open [f (io/reader f)] ...)

Makes filtering on caught/uncaught meaningless.

Jump to source

Linking source code to stack frames requires that the source is on the classpath.

The Ritz servers arrange this if the source code is in your local repository.

lein pom
mvn dependency:sources

Evaluation

You can evaluate an expression within the context of a frame. Just select the frame, and press:

  • 'e' and enter an expression to see the result in the message area.
  • 'd' and enter an expression to see a pretty printed result in a separate buffer.

Inspector

In ritz-swank, just press Enter on any local variable to pop up the SLIME inspector.

Locals Clearing

Clojure does something called locals clearing, to avoid holding onto the head of lazy sequences. This can result in locals showing up as nil, even when they aren't really.

Can be switched off in 1.4+

Locals Clearing - Disable on Compile

Use a prefix argument (C-u)to the following commands to disable locals clearing on the code being compiled.

C-c C-cnrepl-ritz-compile-expression
C-c C-cslime-compile-defun
C-c C-kslime-compile-file

Locals Clearing - Disable Globally

To disable locals clearing globally:

(alter-var-root #'*compiler-options*
  assoc :disable-locals-clearing true)

Breakpoints

SLIME only for now

Press C-c C-x C-b to set a breakpoint on any line (of clojure or java).

Restarts for Step, Step Over, and Step Out

Disassembly

Press 'D' on any frame to see the JVM bytecode for the frame

Threads

M-x nrepl-ritz-threads
============================================================================================
:id  | :name                     | :status  | :at-breakpoint? | :suspended? | :suspend-count
============================================================================================
     | system                    |          |                 |             |
     |   main                    |          |                 |             |
1    |     main                  | :wait    | false           | false       | 0
1569 |     JDI-VM-Control-Thread | :running | false           | true        | 1
1782 |     msg-pump4905          | :wait    | false           | false       | 0
5228 |   Reference Handler       | :wait    | false           | false       | 0
5229 |   Finalizer               | :wait    | false           | false       | 0
5230 |   Signal Dispatcher       | :running | false           | false       | 0
============================================================================================
          

Project Support

Reload project to pick up classpath changes.

M-x nrepl-ritz-reload-project

Switch project

M-x nrepl-ritz-load-project

Run lein targets on project

M-x nrepl-ritz-lein

Split out into the nrepl-project project as an nREPL middleware.

Related Work

https://github.com/technomancy/limit-break

A version of debug-repl for nrepl.

The simplest thing that could work and not need any extra setup beyond nrepl.el.

Direction - Features

Parity with ritz-swank:

  • Breakpoints
  • Inspector

Other Ideas:

  • Log Evaluation of expressions
  • Make the debugger scriptable

Direction - Other possibilities

Scriptable debugging

http://www.cs.brown.edu/~sk/Publications/Papers/Published/mcskr-dataflow-lang-script-debug-journal/

Conclusion

nREPL middleware provides flexibility.

Make Ritz the goto place for middleware and debugger.