Next: , Previous: Evaluation Model, Up: Debugging


5.21.2 Debugging when an error occurs

A common requirement is to be able to show as much useful context as possible when a Scheme program hits an error. The most immediate information about an error is the kind of error that it is – such as “division by zero” – and any parameters that the code which signalled the error chose explicitly to provide. This information originates with the error or throw call (or their C code equivalents, if the error is detected by C code) that signals the error, and is passed automatically to the handler procedure of the innermost applicable catch, lazy-catch or with-throw-handler expression.

5.21.2.1 Intercepting basic error information

Therefore, to catch errors that occur within a chunk of Scheme code, and to intercept basic information about those errors, you need to execute that code inside the dynamic context of a catch, lazy-catch or with-throw-handler expression, or the equivalent in C. In Scheme, this means you need something like this:

     (catch #t
            (lambda ()
              ;; Execute the code in which
              ;; you want to catch errors here.
              ...)
            (lambda (key . parameters)
              ;; Put the code which you want
              ;; to handle an error here.
              ...))

The catch here can also be lazy-catch or with-throw-handler; see Throw Handlers and Lazy Catch for the details of how these differ from catch. The #t means that the catch is applicable to all kinds of error; if you want to restrict your catch to just one kind of error, you can put the symbol for that kind of error instead of #t. The equivalent to this in C would be something like this:

     SCM my_body_proc (void *body_data)
     {
       /* Execute the code in which
          you want to catch errors here. */
       ...
     }
     
     SCM my_handler_proc (void *handler_data,
                          SCM key,
                          SCM parameters)
     {
       /* Put the code which you want
          to handle an error here. */
       ...
     }
     
     {
       ...
       scm_c_catch (SCM_BOOL_T,
                    my_body_proc, body_data,
                    my_handler_proc, handler_data,
                    NULL, NULL);
       ...
     }

Again, as with the Scheme version, scm_c_catch could be replaced by scm_internal_lazy_catch or scm_c_with_throw_handler, and SCM_BOOL_T could instead be the symbol for a particular kind of error.

5.21.2.2 Capturing the full error stack

The other interesting information about an error is the full Scheme stack at the point where the error occurred; in other words what innermost expression was being evaluated, what was the expression that called that one, and so on. If you want to write your code so that it captures and can display this information as well, there are three important things to understand.

Firstly, the code in question must be executed using the debugging version of the evaluator, because information about the Scheme stack is only available at all from the debugging evaluator. Using the debugging evaluator means that the debugger option (see Debugger options) called debug must be enabled; this can be done by running (debug-enable 'debug) or (turn-on-debugging) at the top level of your program; or by running guile with the --debug command line option, if your program begins life as a Scheme script.

Secondly, the stack at the point of the error needs to be explicitly captured by a make-stack call (or the C equivalent scm_make_stack). The Guile library does not do this “automatically” for you, so you will need to write code with a make-stack or scm_make_stack call yourself. (We emphasise this point because some people are misled by the fact that the Guile interactive REPL code does capture and display the stack automatically. But the Guile interactive REPL is itself a Scheme program1 running on top of the Guile library, and which uses catch and make-stack in the way we are about to describe to capture the stack when an error occurs.)

Thirdly, in order to capture the stack effectively at the point where the error occurred, the make-stack call must be made before Guile unwinds the stack back to the location of the prevailing catch expression. This means that the make-stack call must be made within the handler of a lazy-catch or with-throw-handler expression, or the optional "pre-unwind" handler of a catch. (For the full story of how these alternatives differ from each other, see Exceptions. The main difference is that catch terminates the error, whereas lazy-catch and with-throw-handler only intercept it temporarily and then allow it to continue propagating up to the next innermost handler.)

So, here are some examples of how to do all this in Scheme and in C. For the purpose of these examples we assume that the captured stack should be stored in a variable, so that it can be displayed or arbitrarily processed later on. In Scheme:

     (let ((captured-stack #f))
       (catch #t
              (lambda ()
                ;; Execute the code in which
                ;; you want to catch errors here.
                ...)
              (lambda (key . parameters)
                ;; Put the code which you want
                ;; to handle an error after the
                ;; stack has been unwound here.
                ...)
              (lambda (key . parameters)
                ;; Capture the stack here:
                (set! captured-stack (make-stack #t))))
       ...
       (if captured-stack
           (begin
             ;; Display or process the captured stack.
             ...))
       ...)

And in C:

     SCM my_body_proc (void *body_data)
     {
       /* Execute the code in which
          you want to catch errors here. */
       ...
     }
     
     SCM my_handler_proc (void *handler_data,
                          SCM key,
                          SCM parameters)
     {
       /* Put the code which you want
          to handle an error after the
          stack has been unwound here. */
       ...
     }
     
     SCM my_preunwind_proc (void *handler_data,
                            SCM key,
                            SCM parameters)
     {
       /* Capture the stack here: */
       *(SCM *)handler_data = scm_make_stack (SCM_BOOL_T, SCM_EOL);
     }
     
     {
       SCM captured_stack = SCM_BOOL_F;
       ...
       scm_c_catch (SCM_BOOL_T,
                    my_body_proc, body_data,
                    my_handler_proc, handler_data,
                    my_preunwind_proc, &captured_stack);
       ...
       if (captured_stack != SCM_BOOL_F)
       {
         /* Display or process the captured stack. */
         ...
       }
       ...
     }

Note that you don't have to wait until after the catch or scm_c_catch has returned. You can also do whatever you like with the stack immediately after it has been captured in the pre-unwind handler, or in the normal (post-unwind) handler. (Except that for the latter case in C you will need to change handler_data in the scm_c_catch(...) call to &captured_stack, so that my_handler_proc has access to the captured stack.)

5.21.2.3 Displaying or interrogating the captured stack

Once you have a captured stack, you can interrogate and display its details in any way that you want, using the stack-... and frame-... API described in Examining the Stack and Examining Stack Frames.

If you want to print out a backtrace in the same format that the Guile REPL does, you can use the display-backtrace procedure to do so. You can also use display-application to display an individual application frame – that is, a frame that satisfies the frame-procedure? predicate – in the Guile REPL format.

5.21.2.4 What the Guile REPL does

The Guile REPL code (in ice-9/boot-9.scm) uses a catch with a pre-unwind handler to capture the stack when an error occurs in an expression that was typed into the REPL, and saves the captured stack in a fluid (see Fluids and Dynamic States) called the-last-stack. You can then use the (backtrace) command, which is basically equivalent to (display-backtrace (fluid-ref the-last-stack)), to print out this stack at any time until it is overwritten by the next error that occurs.

— Scheme Procedure: backtrace [highlights]
— C Function: scm_backtrace_with_highlights (highlights)
— C Function: scm_backtrace ()

Display a backtrace of the stack saved by the last error to the current output port. If highlights is given it should be a list; the elements of this list will be highlighted wherever they appear in the backtrace.

You can also use the (debug) command to explore the saved stack using an interactive command-line-driven debugger. See Interactive Debugger for more information about this.

— Scheme Procedure: debug

Invoke the Guile debugger to explore the context of the last error.


Footnotes

[1] In effect, it is the default program which is run when no commands or script file are specified on the Guile command line.