[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The first section of this chapter explains how programs can handle errors, by means of condition handlers. It also explains how a program can signal an error if it detects something it doesn't like.
The second explains how users can handle errors, by means of an interactive debugger; that is, it explains how to recover if you do something wrong. A new user of the Lisp Machine, or someone who just wants to know how to deal with errors and not how to cause them, should ignore the first section and skip ahead to (debugger).
The remaining sections describe some other debugging facilities. Anyone who is going to be writing programs for the Lisp Machine should familiarize himself with these.
The trace facility provides the ability to perform certain actions at the time a function is called or at the time it returns. The actions may be simple typeout, or more sophisticated debugging functions.
The advise facility is a somewhat similar facility for modifying the behavior of a function.
The step facility allows the evaluation of a form to be intercepted at every step so that the user may examine just what is happening throughout the execution of the form.
The MAR facility provides the ability to cause a trap on any memory reference to a word (or a set of words) in memory. If something is getting clobbered by agents unknown, this can help track down the source of the clobberage.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Programmers often want to control what action is taken by their programs when errors or other exceptional situations occur. Usually different situations are handled in different ways, and in order to express what kind of handling each situation should have, each situation must have an associated name. In Zetalisp there is the concept of a condition. Every condition has a name, which is a symbol. When an unusual situation occurs, some condition is signalled, and a handler for that condition is invoked.
When a condition is signalled, the system (essentially) searches up the stack of nested function invocations looking for a handler established to handle that condition. The handler is a function which gets called to deal with the condition. The condition mechanism itself is just a convenient way for finding an appropriate handler function given the name of an exceptional situation. On top of this is built the error-condition system, which defines what arguments are passed to a handler function and what is done with the values returned by a handler function. Almost all current use of the condition mechanism is for errors, but the user may find other uses for the underlying mechanism.
.need 1000 The search for an appropriate handler is done by the function signal:
Condition handlers are established through the condition-bind special form:
(condition-bind ((cond-1 hand-1) (cond-2 hand-2) ...) body) |
Example: (condition-bind ((:wrong-type-argument 'my-wta-handler) ((lossage-1 lossage-2) lossage-handler)) (princ "Hello there.") (= t 69)) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
[This section is incorrect. The mechanism by which errors are signalled does not work. It will be redesigned someday.]
The use of the condition mechanism by the error system defines an additional protocol for what arguments are passed to error-condition handlers and what values they may return.
There are basically four possible responses to an error: proceeding, restarting, throwing, or entering the debugger. The default action, taken if no handler exists or deigns to handle the error (returns non-nil), is to enter the debugger. A handler may give up on the execution that produced the error by throwing (see *throw, (*throw-fun)). Proceeding means to repair the error and continue execution. The exact meaning of this depends on the particular error, but it generally takes the form of supplying a replacement for an unacceptable argument to some function, and retrying the invocation of that function. Restarting means throwing to a special standard catch-tag, error-restart. Handlers cause proceeding and restarting by returning certain special values, described below.
Each error condition is signalled with some parameters, the meanings of which depend on the condition. For example, the condition :unbound-variable, which means that something tried to find the value of a symbol which was unbound, is signalled with at least one parameter, the unbound symbol. It is always all right to signal an error condition with extra parameters beyond those whose meanings are defined by the condition.
An error condition handler is applied to several arguments. The first argument is the name of the condition that was signalled (a symbol). This allows the same function to handle several different conditions, which is useful if the handling of those conditions is very similar. (The first argument is also the name of the condition for non-error conditions.) The second argument is a format control string (see the description of format, on (format-fun)). The third argument is t if the error is proceedable; otherwise it is nil. The fourth argument is t if the error is restartable; otherwise it is nil. The fifth argument is the name of the function that signalled the error, or nil if the signaller can't figure out the correct name to pass. The rest of the arguments are the parameters with which the condition was signalled. If the format control string is used with these parameters, a readable English message should be produced. Since more information than just the parameters might be needed to print a reasonable message, the program signalling the condition is free to pass any extra parameters it wants to, after the parameters which the condition is defined to take. This means that every handler must expect to be called with an arbitrarily high number of arguments, so every handler should have a &rest argument (see (&rest)).
An error condition handler may return any of several values. If it returns nil, then it is stating that it does not wish to handle the condition after all; the process of signalling will continue looking for a prior handler (established farther down on the stack) as if the handler which returned nil had not existed at all. (This is also true for non-error conditions.) If the handler does wish to handle the condition, it can try to proceed from the error if it is proceedable, or restart from it if it is restartable, or it can throw to a catch tag. Proceeding and restarting are done by returning two values. The first value is one of the following symbols:
:return
eh:return-value
:error-restart
The condition handler must not return any other sort of values. However, it can legitimately throw to any tag instead of returning at all. If a handler tries to proceed an unproceedable error or restart an unrestartable one, an error is signalled.
Note that if the handler returns nil, it is not said to have handled the error; rather, it has decided not to handle it, but to "continue to signal" it so that someone else may handle it. If an error is signalled and none of the handlers for the condition decide to handle it, the debugger is entered.
Here is an example of an excessively simple handler for the :wrong-type-argument condition.
[Note that this code does not work in system 56.]
;;; This function handles the :wrong-type-argument condition, ;;; which takes two defined parameters: a symbol indicating ;;; the correct type, and the bad value. (defun sample-wta-handler (condition control-string proceedable-flag restartable-flag function correct-type bad-value &rest rest) (prog () (format error-output "~%There was an error in ~S~%" function) (lexpr-funcall (function format) error-output control-string correct-type bad-value rest) (cond ((and proceedable-flag (yes-or-no-p "Do you want use nil instead?")) (return 'return nil)) (t (return nil))))) ;don't handle |
If an error condition reaches the error handler, the RESUME (or control-C) command may be used to continue from it. If the condition name has a eh:proceed property, that property is called as a function with two arguments, the stack-group and the "ete" (an internal error-handler data structure). Usually it will ignore these arguments. If this function returns, its value will be returned from the ferror or cerror that signalled the condition. If no such property exists, the error-handler asks the user for a form, evaluates it, and causes ferror or cerror to return that value. Putting such a property on can be used to change the prompt for this form, avoid asking the user, or change things in more far-reaching ways.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Some error conditions are signalled by the Lisp system when it detects that something has gone wrong. Lisp programs can also signal errors, by using any of the functions ferror, cerror, or error. ferror is the most commonly used of these. cerror is used if the signaller of the error wishes to make the error be proceedable or restartable, or both. error is provided for Maclisp compatibility.
A ferror or cerror that doesn't have any particular condition to signal should use nil as the condition name. The only kind of handler that will be invoked by the signaller in this case is the kind that handles all conditions, such as is set up by
(condition-bind ((nil something) ...) ...) |
Examples: (cond ((> sz 60) (ferror nil "The size, ~S, was greater than the maximum" sz)) (t (foo sz))) (defun func (a b) (cond ((and (> a 3) (not (symbolp b))) (ferror ':wrong-type-argument "The name, ~1G~S, must be a symbol" 'symbolp b)) (t (func-internal a b)))) |
If the error is not handled and the debugger is entered, the error message is printed by calling format with control-string as the control string and the elements of params as the additional arguments. Alternatively, the formatted output functions ((format:outfmt-fun)) can be used to generate the error message:
(ferror nil (format:outfmt "Frob has " (format:plural (format:onum n-elts) " element") " which is too few")) |
If proceedable-flag is t and the error goes to the debugger, if the user says to proceed from the error he will be asked for a replacement object which cerror will return. If proceedable-flag is not t and not nil, the user will not be asked for a replacement object and cerror will return no particular value when the error is proceeded.
Note: Many programs that want to signal restartable errors will want to use the error-restart special form; see (error-restart-fun).
Example: (do () ((symbolp a)) ; Do this stuff until a becomes a symbol. (setq a (cerror t nil ':wrong-type-argument "The argument ~2G~A was ~1G~S, which is not ~3G~A" 'symbolp a 'a "a symbol"))) |
In order to fit this definition into Zetalisp way of handling errors, error is defined to be:
(cerror (not (null interrupt)) nil (or (get interrupt 'eh:condition-name) interrupt) (if (missing? object) ;If no object given "~*~A" "~S ~A") object message) |
Here is what that means in English: first of all, the condition to be signalled is nil if interrupt is nil. If there is some condition whose meaning is close to that of one of the Maclisp user interrupt channels, the name of that channel has an eh:condition-name property, and the value of that property is the name of the condition to signal. Otherwise, interrupt is the name of the condition to signal; probably there will be no handler and the debugger will be entered.
If interrupt is specified, the error will be proceedable. The error will not be restartable. The format control string and the arguments are chosen so that the right error message gets printed, and the handler is passed everything there is to pass.
Example: (error-restart (setq a (* b d)) (cond ((> a maxtemp) (cerror nil t 'overheat "The frammistat will overheat by ~D. degrees!" (- a maxtemp)))) (setq q (cons a a))) |
error-restart is implemented as a macro that expands into: (prog () loop (*catch 'error-restart (return (progn form-1 form-2 ...))) (go loop)) |
(check-arg foo stringp "a string") |
The general form of check-arg is
(check-arg var-name predicate description type-symbol) |
The use of the type-symbol is not really well-defined yet, but the intention is that if it is numberp (for example), the condition handlers can tell that a number was needed, and might try to convert the actual supplied value to a number and proceed.
[We need to establish a conventional way of "registering" the type-symbols to be used for various expected types. It might as well be in the form of a table right here.]
The predicate is usually a symbol such as fixp, stringp, listp, or closurep, but when there isn't any convenient predefined predicate, or when the condition is complex, it can be a form. In this case you should supply a type-symbol which encodes the type. For example:
(check-arg a (and (numberp a) ( a 10.) (> a 0.)) "a number from one to ten" one-to-ten) |
The argument a was 17, which is not a number from one to ten. |
In general, what constitutes a valid argument is specified in three ways in a check-arg. description is human-understandable, type-symbol is program-understandable, and predicate is executable. It is up to the user to ensure that these three specifications agree.
check-arg uses predicate to determine whether the value of the variable is of the correct type. If it is not, check-arg signals the :wrong-type-argument condition, with four parameters. First, type-symbol if it was supplied, or else predicate if it was atomic, or else nil. Second, the bad value. Third, the name of the argument (var-name). Fourth, a string describing the proper type (description). If the error is proceeded, the variable is set to the value returned, and check-arg starts over, checking the type again. Note that only the first two of these parameters are defined for the :wrong-type-argument condition, and so :wrong-type-argument handlers should only depend on the meaning of these two.
(check-arg foo :number) |
The general form of check-arg-type is:
(check-arg-type var-name type-name description) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Some condition names are used by the kernel Lisp system, and are documented below; since they are of global interest, they are on the keyword package. Programs outside the kernel system are free to define their own condition names; it is intended that the description of a function include a description of any conditions that it may signal, so that people writing programs that call that function may handle the condition if they desire. When you decide what package your condition names should be in, you should apply the same criteria you would apply for determining which package a function name should be in; if a program defines its own condition names, they should not be on the keyword package. For example, the condition names chaos:bad-packet-format and arpa:bad-packet-format should be distinct. For further discussion, see (package).
The following table lists all standard conditions and the parameters they take; more will be added in the future. These are all error-conditions, so in addition to the condition name and the parameters, the handler receives the other arguments described above.
:wrong-type-argument type-name value
:inconsistent-arguments list-of-inconsistent-argument-values
:wrong-number-of-arguments function number-of-args-supplied list-of-args-supplied
:invalid-function function-name
:invalid-form form
:undefined-function function-name
:unbound-variable variable-name
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
As in Maclisp, there is an errset facility which allows a very simple form of error handling. If an error occurs inside an errset, and no condition handler handles it, i.e. the debugger would be entered, control is returned (thrown) to the errset. The errset can control whether or not the debugger's error message is printed. All errors are caught by errset, whether they are signalled by ferror, cerror, error, or the Lisp system itself.
A problem with errset is that it is too powerful; it will apply to any unhandled error at all. If you are writing code that anticipates some specific error, you should find out what condition that error signals and set up a handler. If you use errset and some unanticipated error crops up, you may not be told--this can cause very strange bugs. Note that the variable errset allows all errsets to be disabled for debugging purposes.
(err) is a dumb way to cause an error. If executed inside an errset, that errset returns nil, and no message is printed. Otherwise an unseen throw-tag error occurs.
(err form) evaluates form and causes the containing errset to return the result. If executed when not inside an errset, an unseen throw-tag error occurs.
(err form flag), which exists in Maclisp, is not supported.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
When an error condition is signalled and no handlers decide to handle the error, an interactive debugger is entered to allow the user to look around and see what went wrong, and to help him continue the program or abort it. This section describes how to use the debugger.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
There are two kinds of errors; those generated by the Lisp Machine's microcode, and those generated by Lisp programs (by using ferror or related functions). When there is a microcode error, the debugger prints out a message such as the following:
>>TRAP 5543 (TRANS-TRAP) The symbol FOOBAR is unbound. While in the function *EVAL SI:LISP-TOP-LEVEL1 |
The first line of this error message indicates entry to the debugger and contains some mysterious internal microcode information: the micro program address, the microcode trap name and parameters, and a microcode backtrace. Users can ignore this line in most cases. The second line contains a description of the error in English. The third line indicates where the error happened by printing a very abbreviated "backtrace" of the stack (see below); in the example, it is saying that the error was signalled inside the function *eval, which was called by si:lisp-top-level1.
Here is an example of an error from Lisp code:
>>ERROR: The argument X was 1, which is not a symbol, While in the function FOO *EVAL SI:LISP-TOP-LEVEL1 |
Here the first line contains the English description of the error message, and the second line contains the abbreviated backtrace. foo signalled the error by calling ferror, however ferror is censored out of the backtrace.
After the debugger's initial message it prints the function that got the error and its arguments.
The debugger can be manually entered either by causing an error (e.g. by typing a ridiculous symbol name such as ahsdgf at the Lisp read-eval-print loop) or by typing the BREAK key with the META shift held down while the program is reading from the terminal. Typing the BREAK key with both CONTROL and META held down will force the program into the debugger immediately, even if it is running. If the BREAK key is typed without META, it puts you into a read-eval-print loop using the break function (see (break-fun)) rather into the debugger.
If process is not a process but a stack group, the current state of the stack group will be examined. The caller should ensure that no one tries to resume that stack group while the debugger is looking at it.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Once inside the debugger, the user may give a wide variety of commands. This section describes how to give the commands, and then explains them in approximate order of usefulness. A summary is provided at the end of the listing.
When the error hander is waiting for a command, it prompts with an arrow:
At this point, you may either type in a Lisp expression, or type a command (a Control or Meta character is interpreted as a command, whereas most normal characters are interpreted as the first character of an expression). If you type the HELP key or the ? key, you will get some introductory help with the error handler.
If you type a Lisp expression, it will be interpreted as a Lisp form, and will be evaluated in the context of the function which got the error. That is, all bindings which were in effect at the time of the error will be in effect when your form is evaluated, with certain exceptions explained below. The result of the evaluation will be printed, and the debugger will prompt again with an arrow. If, during the typing of the form, you change your mind and want to get back to the debugger's command level, type the ABORT key or a Control-G; the debugger will respond with an arrow prompt. In fact, at any time that typein is expected from you, while you are in the debugger, you may type ABORT or Control-G to flush what you are doing and get back to command level. This read-eval-print loop maintains the values of +, *, and - just as the top-level one does.
If an error occurs in the evaluation of the Lisp expression you type, you will get into a second error handler looking at the new error. You can abort the computation and get back to the first error by typing the ABORT key (see below). However, if the error is trivial the abort will be done automatically and the original error message will be reprinted.
Various debugger commands ask for Lisp objects, such as an object to return, or the name of a catch-tag. Whenever it tries to get a Lisp object from you, it expects you to type in a form; it will evaluate what you type in. This provides greater generality, since there are objects to which you might want to refer that cannot be typed in (such as arrays). If the form you type is non-trivial (not just a constant form), the debugger will show you the result of the evaluation, and ask you if it is what you intended. It expects a Y or N answer (see the function y-or-n-p, (y-or-n-p-fun)), and if you answer negatively it will ask you for another form. To quit out of the command, just type ABORT or Control-G.
When the debugger evaluates a form, the variable bindings at the point of error are in effect with the following exceptions:
terminal-io is rebound to the stream the error handler is using. eh:old-terminal-io is bound to the value terminal-io had at the point of error.
standard-input and standard-output are rebound to be synonymous with terminal-io; their old bindings are saved in eh:old-standard-input and eh:old-standard-output.
+ and * are rebound to the error handler's previous form and previous value. When the debugger is first entered, + is the last form typed, which is typically the one that caused the error, and * is the value of the previous form.
evalhook (see (evalhook-var)) is rebound to nil, turning off the step facility if it had been in use when the error occurred.
Note that the variable bindings are those in effect at the point of error, not those of the current frame being looked at. This may be changed in the future.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
All debugger commands are single characters, usually with the Control or Meta bits. The single most useful command is ABORT (or Control-Z), which exits from the debugger and throws out of the computation that got the error. This is the ABORT key, not a 5-letter command. ITS users should note that Control-Z is not CALL. Often you are not interested in using the debugger at all and just want to get back to Lisp top level; so you can do this in one character.
The ABORT command returns control to the most recent read-eval-print loop. This can be Lisp top level, a break, or the debugger command loop associated with another error. Typing ABORT multiple times will throw back to successively older read-eval-print or command loops until top level is reached. Typing Control-Meta-ABORT, on the other hand, will always throw to top level. Control-Meta-ABORT is not a debugger command, but a system command which is always available no matter what program you are in.
Note that typing ABORT in the middle of typing a form to be evaluated by the debugger aborts that form, and returns to the debugger's command level, while typing ABORT as a debugger command returns out of the debugger and the erring program, to the previous command level.
Self-documentation is provided by the HELP or ? command, which types out some documentation on the debugger commands, including any special commands which apply to the particular error currently being handled.
Often you want to try to proceed from the error. To do this, use the RESUME (or Control-C) command. The exact way RESUME works depends on the kind of error that happened. For some errors, there is no standard way to proceed at all, and RESUME will just tell you this and return to the debugger's command level. For the very common "unbound variable" error, it will get a Lisp object from you, which will be used in place of the (nonexistent) value of the symbol. For unbound-variable or undefined-function errors, you can also just type Lisp forms to set the variable or define the function, and then type RESUME; it will proceed without asking anything.
The debugger knows about a "current stack frame", and there are several commands which use it. The initially "current" stack frame is the one which signalled the error; either the one which got the microcode-detected error, or the one which called ferror, cerror, or error. When the debugger starts it up it shows you this frame in the following format:
FOO: Arg 0 (X): 13 Arg 1 (Y): 1 |
The CLEAR-SCREEN (or Control-L) command clears the screen, retypes the error message that was initially printed when the debugger was entered, and then prints out a description of the current frame, in the above format.
Several commands are provided to allow you to examine the Lisp control stack and to make other frames current than the one which got the error. The control stack (or "regular pdl") keeps a record of all functions which are currently active. If you call foo at Lisp's top level, and it calls bar, which in turn calls baz, and baz gets an error, then a backtrace (a backwards trace of the stack) would show all of this information. The debugger has two backtrace commands. Control-B simply prints out the names of the functions on the stack; in the above example it would print
BAZ BAR FOO SI:*EVAL SI:LISP-TOP-LEVEL1 SI:LISP-TOP-LEVEL |
BAZ: Arg 0 (X): 13 Arg 1 (Y): 1 BAR: Arg 0 (ADDEND): 13 FOO: Arg 0 (FROB): (A B C . D) |
The Control-N command moves "down" to the "next" frame (that is, it changes the current frame to be the frame which called it), and prints out the frame in this same format. Control-P moves "up" to the "previous" frame (the one which this one called), and prints out the frame in the same format. Meta-< moves to the top of the stack, and Meta-> to the bottom; both print out the new current frame. Control-S asks you for a string, and searches the stack for a frame whose executing function's name contains that string. That frame becomes current and is printed out. These commands are easy to remember since they are analogous to editor commands.
Meta-L prints out the current frame in "full screen" format, which shows the arguments and their values, the local variables and their values, and the machine code with an arrow pointing to the next instruction to be executed. Refer to (understanding-compiled-code) for help in reading this machine code.
Meta-N moves to the next frame and prints it out in full-screen format, and Meta-P moves to the previous frame and prints it out in full-screen format. Meta-S is like Control-S but does a full-screen display.
Commands such as Control-N and Meta-N, which are meaningful to repeat, take a prefix numeric argument and repeat that many types. The numeric argument is typed by using Control- or Meta- and the number keys, as in the editor.
Control-E puts you into the editor, looking at the source code for the function in the current frame. This is useful when you have found a function which caused the error and needs to be fixed. The editor command Control-Z will return to the error handler, if it is still there.
Meta-C is similar to Control-C, but in the case of an unbound variable or undefined function, actually setqs the variable or defines the function, so that the error will not happen again. Control-C (or RESUME) provides a replacement value but does not actually change the variable.
Control-R is used to return a value from the current frame; the frame that called that frame continues running as if the function of the current frame had returned. This command prompts you for a form, which it will evaluate; it returns the resulting value, possibly after confirming it with you.
The Control-T command does a *throw to a given tag with a given value; you are prompted for the tag and the value.
Control-Meta-R is a variation of Control-R; it starts the current frame over with the same function and arguments. If the function has been redefined in the meantime (perhaps you edited it and fixed its bug) the new definition is used. Control-Meta-R asks for confirmation before doing it.
The Control-Meta-N, Control-Meta-P, and Control-Meta-B commands are like the corresponding Control- commands but don't censor the stack. When running interpreted code, the error handler tries to skip over frames that belong to functions of the interpreter, such as *eval, prog, and cond, and only show "interesting" functions. The Control-Meta-U command goes up the stack to the next interesting function, and makes that the current frame.
Control-Meta-A takes a numeric argument n, and prints out the value of the nth argument of the current frame. It leaves * set to the value of the argument, so that you can use the Lisp read-eval-print loop to examine it. It also leaves + set to a locative pointing to the argument on the stack, so that you can change that argument (by calling rplacd on the locative). Control-Meta-L is similar, but refers to the nth local variable of the frame. Control-Meta-F is similar but refers to the function executing in the frame; it ignores its numeric argument and doesn't allow you to change the function.
Control-Meta-W calls the window error handler, a display-oriented debugger which is not documented in this manual. It should, however, be usable without further documentation.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Control-A
Control-Meta-A
Control-B
Meta-B
Control-Meta-B
Control-C or RESUME
Meta-C
Control-Meta-C
Control-E
Control-Meta-F
Control-G or ABORT
Control-L or CLEAR SCREEN
Meta-L
Control-Meta-L
Control-N or LINE
Meta-N
Control-Meta-N
Control-P or RETURN
Meta-P
Control-Meta-N
Control-R
Meta-R
Control-Meta-R
Control-S
Meta-S
Control-T
Control-Meta-U
Control-Meta-W
Control-Z or ABORT
? or Help
Meta-<
Meta->
Control-0 through Control-Meta-9
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |