I'm using SLIME to debug my Common Lisp function. Inside the function, I've made it artificially signal an error (trying to "debug"—perhaps I should be stepping) like so:
(define-condition unknown-zone (error)
((text :initarg :text :reader text)))
(defun parse-mime-date (date)
(let ((last-space (position #\Space date :from-end t)))
(let ((date-time (net.telent.date:parse-time (subseq date 0 last-space)))
(zone (subseq date (1+ last-space))))
(unless (or (char= (elt zone 0) #\+)
(char= (elt zone 0) #\-))
(error 'unknown-zone :text (format nil "Unknown timezone: ~a" zone)))
(let ((hours (parse-integer (subseq zone 0 3)))
(minutes (parse-integer
(concatenate 'string
(list (elt zone 0))
(subseq zone 3)))))
(error 'unknown-zone :text "LOL")
(let ((adjusted-date-time (- date-time (* 60 (+ minutes (* 60 hours))))))
(format t "date-time: ~a; zone: ~a~%" date-time zone)
(format t "adjusted: ~a" (net.telent.date:universal-time-to-http-date adjusted-date-time)))))))
I'm trying to work around what appears to be a deficiency in net.telent.date:parse-time (it seems to botch up timezone handling, though I'm not 100% yet).
The "LOL" unknown-zone error is of course the artificial breakpoint.
When it hits this part of the function, SLDB faithfully opens up with the backtrace:
Bad type argument:
NS-MAIL2ZD::UNKNOWN-ZONE
[Condition of type SIMPLE-TYPE-ERROR]
Restarts:
0: [RETRY] Retry SLIME REPL evaluation request.
1: [*ABORT] Return to SLIME's top level.
2: [REMOVE-FD-HANDLER] Remove #<SB-IMPL::HANDLER INPUT on descriptor 7: #<CLOSURE (LABELS SWANK-BACKEND::RUN :IN SWANK-BACKEND:ADD-FD-HANDLER) {10030AD9FB}>>
3: [ABORT] Exit debugger, returning to top level.
Backtrace:
0: (MAKE-CONDITION NS-MAIL2ZD::UNKNOWN-ZONE :TEXT "LOL")
1: (ERROR NS-MAIL2ZD::UNKNOWN-ZONE :TEXT "LOL")
2: (NS-MAIL2ZD:PARSE-MIME-DATE "Wed, 14 Mar 2012 06:59:36 +1100")
3: (SB-INT:SIMPLE-EVAL-IN-LEXENV (NS-MAIL2ZD:PARSE-MIME-DATE *LOL*) #<NULL-LEXENV>)
4: (EVAL (NS-MAIL2ZD:PARSE-MIME-DATE *LOL*))
--more--
Then I page down to the frame:
Backtrace:
0: (MAKE-CONDITION NS-MAIL2ZD::UNKNOWN-ZONE :TEXT "LOL")
1: (ERROR NS-MAIL2ZD::UNKNOWN-ZONE :TEXT "LOL")
Locals:
SB-KERNEL::ARGUMENTS = (:TEXT "LOL")
SB-KERNEL::DATUM = NS-MAIL2ZD::UNKNOWN-ZONE
2: (NS-MAIL2ZD:PARSE-MIME-DATE "Wed, 14 Mar 2012 06:59:36 +1100")
Now I hit e to invoke sldb-eval-in-frame and type last-space, as that should be available where the error was signaled.
It seems this isn't how it's meant (?) to work:
The variable LAST-SPACE is unbound.
[Condition of type UNBOUND-VARIABLE]
Restarts:
0: [ABORT] Return to sldb level 1.
1: [RETRY] Retry SLIME REPL evaluation request.
2: [*ABORT] Return to SLIME's top level.
3: [REMOVE-FD-HANDLER] Remove #<SB-IMPL::HANDLER INPUT on descriptor 7: #<CLOSURE (LABELS SWANK-BACKEND::RUN :IN SWANK-BACKEND:ADD-FD-HANDLER) {10030AD9FB}>>
4: [ABORT] Exit debugger, returning to top level.
Backtrace:
0: ((LAMBDA (#:G1144)) #<unavailable argument>)
--more--
Is there a way to do what I want? Am I over-complicating matters?
Thanks!
Addendum: I've tried using (break) (this seems a tad more canonical), but I still can't see let-bound variables with e. :<
There is another option to debug a Lisp function: use an interpreter if available. Most implementations can switch between interpreted and compiled code. You could run all code compiled, but the function you want to debug could be running interpreted. In the extreme case you could even break into a stepper for the interpreted function (if available).
Note that it makes sense to define a default optimization setting based on your requirements.
don't set safety very low globally. Do that only in code sections where this is useful.
don't set speed very high globally. Do that only in code sections where this is useful.
keep debug information, use a setting of 2. Depending on the implementation a high debug setting may shut down tail call optimization (TCO) - which can be undesirable for execution and desirable for debugging.
I would propose different settings for:
development: safe + debug friendly
deployment: safe
optimization of numeric code, locally for speed
By default you should use a setting that is useful for interactive use:
speed 1-2. speed is somewhat important
safety 2-3. safety is very important. all operations are checked at runtime
debug 2-3. debug information is kept and the operations should be interruptible by a debugger.
space 1. The size of the code is not important.
compilation-speed 1. The speed of the compilation process is not so important.
The range is from 0 to 3. No number defaults to 3. 3 is higher.
Depending on the implementation there might be additional optimization settings and also some variables to configure the compiler.
In speed-critical sections you can set speed higher than safety and debug. But be aware that in some cases this changes the semantics of the code execution (error detection, overflows, ...) and that missing runtime checks may make it possible that your code corrupts the Lisp heap.
In some delivery situations it also may be useful to to have a very low debug setting. But if your Lisp compiler is set to high speed optimizations and low debug, debugging the code by looking at the backtrace of the stack may get much harder.
Try using the complete name of the symbol (i.e. package::symbol-name) instead of just the symbol name (i.e. symbol-name). In the current case, maybe net.telent.date::last-space.
Also make sure that you compiled with debugging support set to maximum (for instance, try placing a (declaim (optimize debug)) before your function and recompile the file - C-c C-k).
Checking that the code you're trying to debug has enough debug information: in the sldb window, put the cursor on the relevant line in the backtrace and press t - this should expand the frame and show values for all the locals. Pressing t again collapses the local frame information. If there is not enough debug information, you won't see the local variables, but some made up names (such as SB-DEBUG:ARG-0 under SBCL). Inside the sldb window, if you press Enter with the cursor on a value, it will expand that value into an inspector window (useful if the value is a long list which is shown truncated).
Also, SLIME debugging support seems to vary with the implementation. The advice above works on SBCL under Linux, YMMV under different implementations.
The variable seems to be optimized away. From the SBCL Manual:
The value of a variable may be unavailable for these reasons:
The value of the debug optimization quality may have omitted debug
information needed to determine whether the variable is available.
Unless a variable is an argument, its value will only be available
when debug is at least
The compiler did lifetime analysis and
determined that the value was no longer needed, even though its scope
had not been exited. Lifetime analysis is inhibited when the debug
optimization quality is
The variable’s name is an uninterned
symbol (gensym). To save space, the compiler only dumps debug
information about uninterned variables when the debug optimization
quality is
The frame’s location is unknown (see Section 5.3.5 [Unknown Locations and Interrupts], page 31) because the debugger was entered
due to an interrupt or unexpected hardware error. Under these
conditions the values of arguments will be available, but might be
incorrect. This is the exception mentioned above.
The variable (or
the code referencing it) was optimized out of existence. Variables
with no reads are always optimized away. The degree to which the
compiler deletes variables will depend on the value of the
compilation-speed optimization quality, but most source-level
optimizations are done under all compilation policies.
The variable is never set and its definition looks like (LET ((var1 var2)) ...) In this case, var1 is substituted with var2.
The variable is never set and is referenced exactly once. In this case, the reference is substituted with the variable initial value
You can see all available local variables with t.
If you add a reference to your variable after your error conditions, probably you'll be able to get its value in the corresponding debug frame.
Related
I may be asking for the impossible, but am wondering nonetheless.
Is it possible to obtain an analog of the stack-trace for macros? That is, if one set a break-point inside a certain function, the macro-stack-trace would list all macros (perhaps with their inputs) that were macroexpanded to get to that level in the code.
From what I understand, this is currently impossible, but it may be due to my shallow understanding. Does Allegro or SBCL allow or track this kind of information? It appears that this would be really useful for debugging macros.
Any help or advice is appreciated.
As SBCL is a compiler-only implementation meaning all code is automatically compiled (in contrast to being "interpreted"). Calls to macros are expanded as part of compilation, so the fact that something was a macro call is lost.
(defmacro m (n)
`(/ 10 ,n))
(defun foo (x) (m x))
SBCL:
* (foo 0)
debugger invoked on a DIVISION-BY-ZERO in thread
#<THREAD "main thread" RUNNING {1001E06493}>:
arithmetic error DIVISION-BY-ZERO signalled
Operation was /, operands (10 0).
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [ABORT] Exit debugger, returning to top level.
(SB-KERNEL::INTEGER-/-INTEGER 10 0)
0] backtrace
Backtrace for: #<SB-THREAD:THREAD "main thread" RUNNING {1001E06493}>
0: (SB-KERNEL::INTEGER-/-INTEGER 10 0)
1: (FOO 0)
2: (SB-INT:SIMPLE-EVAL-IN-LEXENV (FOO 0) #<NULL-LEXENV>)
3: (EVAL (FOO 0))
4: (INTERACTIVE-EVAL (FOO 0) :EVAL NIL)
[...]
Some implementations, e.g. Allegro CL, support both interpreted as well as compiled code, the first being helpful in debugging, the second giving better performance. (I show here the command-line interactions. Allegro also offers a GUI to set breakpoints that I'm not familiar with.)
cl-user(4): (foo 0)
Error: Attempt to divide 10 by zero.
[condition type: division-by-zero]
Restart actions (select using :continue):
0: Return to Top Level (an "abort" restart).
1: Abort entirely from this (lisp) process.
[1] cl-user(5): :zoom
Evaluation stack:
(error division-by-zero :operation ...)
->(/ 10 0)
(foo 0)
(eval (foo 0))
[...]
The zoom command takes many options to be more verbose, this shows the form (block foo (m x)):
[1] cl-user(6): :zoom :all t
Evaluation stack:
... 4 more newer frames ...
((:runsys "lisp_apply"))
[... sys::funcall-tramp ]
(excl::error-from-code 17 nil ...)
(sys::..runtime-operation "integer_divide" :unknown-args)
(excl::/_2op 10 0)
->(/ 10 0)
[... excl::eval-as-progn ]
(block foo (m x))
(foo 0)
(sys::..runtime-operation "comp_to_interp" 0)
[... excl::%eval ]
(eval (foo 0))
When you (compile 'foo) the macro calls will be expanded away (like for SBCL) and not show up in backtraces anymore (but Allegro's source-level debugging could help).
In general when it comes to defining macros, to help debugging try to expand into function calls and not big bodies of code. E.g. instead of:
(defmacro with-foo ((var-x var-y thing) &body body)
`(let ((,var-x (..derive from ,thing ..))
(,var-y (..derive from ,thing ..)))
,#body))
I would write it like:
(defmacro with-foo ((var-x var-y thing) &body body)
`(call-with-foo (lambda (,var-x ,var-y) ,#body) ,thing))
(defun call-with-foo (func thing)
(let ((x (..derive from thing ..)
(y (..derive from thing ..))
(funcall func x y)))
so it ends up in the stack trace and is easy to redefine.
See this great post by Kent Pitman:
Incidentally, too, back to CL, you should know that when I write these
WITH-xxx macros, I almost always accompany them with a CALL-WITH-xxx
so that I can do either kind of call. But I find I almost never use
the CALL-WITH-xxx even when I was the one to provide it as an option.
The main reason I write them is not to use them but to make
redefinition easier, since I can redefine the CALL-WITH-xxx without
redefining the macro, and so I don't have to recompile the callers if
the definition changes.
Yes, AllegroCl supports tracing and in general debugging of macros. Quite an effort for not sure how much benefit, but Franz tends to do good things to make CL more viable. Pro tip: there is a an option to turn off what I think they call source-level debugging of macros, and you will want to do that if your code makes heavy use of macros or compilation times can get crazy. Just turn it back on when you think you need the source debugging.
I'm trying to get a better understanding of how S-expressions are evaluated in different lisps and wanted to see they would handle interesting ill-formed expressions. I get that Common Lisp and Scheme are totally different languages, but is there a specific difference in their semantics that explains the difference in behavior. For example, Lisp-1s and Lisp-2s have observable differences in their behavior, as do hygienic vs non-hygienic macro systems.
I have a program containing an unreachable ill-formed if expression in Scheme and Common Lisp.
;; foo.scm
(if #t 1 (if))
(display "12")
And the Common Lisp version
;; foo.lisp
(if t 1 (if))
(display "12")
chicken and guile both produce a syntax error.
Chicken:
% chicken foo.scm
Syntax error: (foo.scm:1) in `if' - pair expected
(if)
Expansion history:
<syntax> (##core#begin (if #t 1 (if)))
<syntax> (if #t 1 (if))
<syntax> (##core#if #t 1 (if))
<syntax> (if) <--
Guile:
% guile foo.scm
...
.../foo.scm:1:9: source expression failed to match any pattern in form (if)
sbcl and clisp both print 12 and emit no warnings.
SBCL:
% sbcl --load foo.lisp
This is SBCL 1.3.11, an implementation of ANSI Common Lisp.
...
12
0]^D
CLISP
% clisp foo.lisp
"12"
Implementations of Common Lisp: Interpreter and Compilers
In Common Lisp the type of code execution depends on what the Lisp system implements and how you use it. Often Common Lisp implementations have multiple ways to execute code: an interpreter and one or more compilers. Even a single running implementation may have that and it will allow the user to switch between those.
Interpreter: executing directly from the Lisp data structure. It is not an interpreter of virtual machine code, like the JVM. One would not expect that the interpreter validates the full code tree/graph during execution. The interpreter usually looks only at the current top form it executes.
Compiler: compiles Lisp code to C, some Byte Code or Machine Code. Since the compiler generates code before the code runs, it will do a syntax check of all the code (for a possible exception, see the remark at the bottom) it sees.
Toplevel evaluation: may use an interpreter, a compiler or a mix of both.
GNU CLISP has both an interpreter and a compiler
Example in GNU CLISP:
LOAD of a text file typically uses the interpreter:
[1]> (load "test.lisp")
;; Loading file test.lisp ...
;; Loaded file test.lisp
T
The Interpreter will not detect the error, because it does not check the whole expression for syntactic correctness. Since the else clause with the syntax error is never used, the Interpreter will never look at it.
CLISP also has a compiler:
[2]> (compile-file "test.lisp")
;; Compiling file /tmp/test.lisp ...
** - Continuable Error
in #:|1 1 (IF T 1 ...)-1| in line 1 : Form too short, too few arguments: (IF)
If you continue (by typing 'continue'): Ignore the error and proceed
The following restarts are also available:
ABORT :R1 Abort main loop
As you see, the CLISP compiler detects the syntax error and gives a clear error message.
SBCL uses a compiler, but also has an interpreter
SBCL by default uses a compiler, which will detect the error. For top-level forms it uses for some forms a simpler evaluation mechanism. One can also switch to an interpreter.
If you write a simple IF form in SBCL, the evaluator does not use full compilation and doesn't catch the error:
CL-USER> (if t 1 (if))
1
If you write the same code inside a function definition, the error gets detected, because function definitions will be compiled by default:
CL-USER> (defun foo () (if t 1 (if)))
; in: DEFUN FOO
; (IF)
;
; caught ERROR:
; error while parsing arguments to special operator IF:
; too few elements in
; ()
; to satisfy lambda list
; (SB-C::TEST SB-C::THEN &OPTIONAL SB-C::ELSE):
; between 2 and 3 expected, but got 0
;
; compilation unit finished
; caught 1 ERROR condition
WARNING: redefining COMMON-LISP-USER::FOO in DEFUN
FOO
If you would switch to the full SBCL interpreter, the error would not be detected at definition time:
CL-USER> (setf *evaluator-mode* :interpret)
:INTERPRET
CL-USER> (defun foo () (if t 1 (if)))
WARNING: redefining COMMON-LISP-USER::FOO in DEFUN
FOO
Optimisation and syntax checking
Note that some optimising compilers might not check the syntax of unreachable code.
Summary
In most Common Lisp implementations you need to use the compiler to get full syntax warnings/errors.
Looks like the CL part is handled by nicely by Rainer. I just want to point out that R5RS are not required to act the way as your two implementations are.
R5RS is very underspecified and for if it only specifies what to do when used right we have this passage about error handling:
When speaking of an error situation, this report uses the phrase "an
error is signalled" to indicate that implementations must detect and
report the error. If such wording does not appear in the discussion
of an error, then implementations are not required to detect or report
the error, though they are encouraged to do so. An error situation
that implementations are not required to detect is usually referred to
simply as "an error."
So, to wrap this up in R5RS the string "banana" is just as correct as Guile and Chickens response to the evaluation of (if #t 1 (if)).
R6RS on the other hand states in general that when an exceptional situation is detected by the implementation, an exception is raised. This means that if with less than two expressions or more than 3 should raise an exception, but it doesn't say it has to happen compile time since the language might be parsing and interpreting as it goes.
When serving large files from Clack/Hunchentoot with Slime connected, I sometimes see error messages like SB-IMPL::SIMPLE-STREAM-PERROR "Couldn't write to ~s"... Those are caused by the browser prematurely dropping connections (which is totally OK). The problem is that each time it happens, SLDB pops up. Which is annoying.
Is there a way I can inhibit certain errors in SLDB such as the above? I still would like to see them in an error log, but definitely not in SLDB.
You can subclass PROCESS-CONNECTION for your acceptor and do your own error handling for this error.
Let's start by defining a custom acceptor:
(defclass no-error-acceptor (hunchentoot:acceptor)
())
Then we can create a wrapper around PROCESS-CONNECTION that inhibits printing of a message for this specific error:
(defmethod hunchentoot:process-connection ((acceptor no-error-acceptor) (socket t))
(handler-case
(call-next-method)
(sb-impl::simple-stream-perror (condition)
;; Perhaps log the error here?
nil)))
Make sure you actually start the server using this acceptor in order for it to be used.
UPDATED
Since your system uses Hunchentoot, you could set the global variable HUNCHENTOOT:*CATCH-ERRORS-P* to T. This should guarantee that the all the conditions arising in code managed by Hunchentoot are catched by Hanchentoot itself and not passed to the debugger.
To disable the debugger in any Common Lisp implementation (both inside a shell REPL as well as the Slime REPL inside Emacs) you could use the predefined global variable *debugger-hook*, by assigning to it a two argument function. The function will receive the condition and the current value of *debugger-hook* when it is called, and can handle the condition or return normally, and in this case the debugger is invoked. For instance, you could simply print the condition:
* (defun my-debug(condition hook)
(declare (ignore hook))
(print condition)
(abort))
DEBUG-IGNORE
* (setf *debugger-hook* #'my-debug)
#<FUNCTION MY-DEBUG>
This second method however cannot work when using Hunchentoot together with Slime, due to the way the two packages interact with respect to the debugging strategies.
In this case one could adopt the solution found by Mike Ivanov, that redefines the swank-debugger-hook function before starting Swank:
(in-package swank)
(setq swank-debugger-hook-orig #'swank-debugger-hook)
(defun swank-debugger-hook (condition hook)
(etypecase condition
(sb-int:simple-stream-error
(progn
(princ "*** Stream error" *error-output*)
(abort)))
(t (funcall swank-debugger-hook-orig condition hook))))
(in-package cl-user)
(swank:create-server :port 4008 :dont-close t)
In the context of writing Racket macros, what does "3D syntax" mean?
I've heard the phrase a few times. Including once in reference to a macro I was writing. But that was awhile ago; I fixed it, and now I can't remember exactly what I was doing wrong originally.
Also: Is 3D syntax always bad? Or is it like eval (where if you think you need to use it, you're probably wrong, but there are some valid uses in expert hands)?
Syntax objects are usually supposed to be just serializable data. 3D-syntax weakens this condition: it allows us to sneak in arbitrary values, and not just plain data. That's what makes them "3d": they are values that rise above the regular flat things you'd expect out of syntax objects.
For example, we can sneak in lambda values!
#lang racket
(define ns (make-base-namespace))
(define (set-next! n)
(parameterize ([current-namespace ns])
(eval #`(define next #,n)))) ;; <-- 3d-syntax here
(define (compute s)
(parameterize ([current-namespace ns])
(eval s)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define counter 0)
(set-next! (lambda ()
(set! counter (add1 counter))
counter))
(compute '(+ (next)
(next)
(next)
(next)))
Doing this is usually a bad thing, because the presence of such values probably means an ill-founded attempt to leak information across phases of compilation. The result is something that's likely not separately-compilable. If you see an error that sounds something like:
write: cannot marshal value that is embedded in compiled code value
then that is most likely due to a macro having produced a piece of 3d-syntax that can't be serialized to bytecode.
Sometimes, in rare situations, we really do want 3d-syntax, often in dynamic evaluation contexts. As a concrete example, a debugger in DrRacket may want to annotate the syntax of a program so that function applications directly call back into functions of the debugger, so that we can do things like interactive code coverage coloring in the program editor. In that sense, 3d-syntax can act as a communication channel between dynamically-evaluated code and its ambient environment.
I'm in search for a better technique for doing it. My general struggle is with the fact that debugger enters either too late or too early to be able to catch the value of the variables.
What I tried first:
(loop for i from 0 to 10 do
(break))
When debugger enter on break, I can't access i :( So it's a wasted effort. I've tried e option of debugger (eval in frame), but SLIME generally just bugs out, and I have to reconnect to SWANK. v or t don't help, because the variable just "isn't there".
What I ended up doing:
(loop for i from 0 to 10 do
(signal i))
This is stupid, but works, because it puts i on the stack of the frame I can examine in debugger. But this is just... well, it's hackish in the worst sense of the word. Isn't there some way to "watch" a variable, or have a more meaningful way to put a breakpoint, such that I can see more variables around the place the breakpoint is entered?
Your first snippet works just fine for me with CCL (default optimize settings), Emacs 24, and a recently pulled Slime:
Break
[Condition of type SIMPLE-CONDITION]
Restarts:
0: [CONTINUE] Return from BREAK.
1: [RETRY] Retry SLIME REPL evaluation request.
2: [*ABORT] Return to SLIME's top level.
3: [ABORT-BREAK] Reset this thread
4: [ABORT] Kill this thread
Backtrace:
0: (#<Anonymous Function #x186F9B7E>)
Locals:
I = 0
1: (CCL::CHEAP-EVAL (LOOP FOR I FROM 0 TO 10 DO (BREAK)))
⋮
sldb-eval-in-frame works fine for me, too. Maybe you should try a different Lisp implementation or a different version of Slime.
Also, note that different optimize settings might be important here, and some implementations give better debugging results for interpreted code (if an interpreter is available, that is). Try something like (declaim (optimize (debug 3) (speed 0) (space 0))).