TIP #290 Version 1.1: Registration of Custom Error Handler Scripts

This is not necessarily the current version of this TIP.


TIP:290
Title:Registration of Custom Error Handler Scripts
Version:$Revision: 1.1 $
Author:Eckhard Lehmann <ecky-l at web dot de>
State:Draft
Type:Project
Tcl-Version:8.5
Vote:Pending
Created:Sunday, 29 October 2006
Keywords:Tcl, error, trap

Abstract

This TIP proposes the possibility to register custom scripts or commands in the usual Tcl event handler style as error handlers.

Rationale

Errors are thrown in the Tcl interpreter through the error command or from a C extension that returns TCL_ERROR. When an error is thrown, the global errorInfo variable is filled with a rudimentary stacktrace information and the error message itself. The global errorCode variable can contain an error code if this is provided by the command that has thrown the error.

Errors can be caught with the catch command. In this case, the errorInfo variable is still filled with the information mentioned above, but the error is not presented to the interpreter. If the error is not caught, it is presented to the interpreter and the execution of the current code is aborted immediately

The information in errorInfo is in some simple cases useful for reproducing and tracking down the error source and fixing the problem. In more complicated cases however, errorInfo includes not enough information to sucessfully reproduce the error - information about the applications state is missing.

In other languages such as LISP and SMALLTALK, this problem is addressed by stopping the execution at the position where the error was thrown (preserving the current callframe) and presenting the developer with a console that enables him to introspect the running program. Although Tcl has very good introspection capabilities, it is not possible to use them in an error case, because the execution just aborts and the stacktrace is unwound at once. For errors generated with the error command, it is possible to overwrite this command and provide a more advanced functionality, but this is not possible if errors are generated in C code by return TCL_ERROR.

The proposed implementation addresses this problem by a custom error handler that is executed whenever an error occures in the execution of Tcl code. This opens a range of implementation possibilities for error handling, for instance:

Specification

The implementation consists of two parts: an registration command, linked in as ::tcl::seterrorhandler and various places where the error handler is called if appropriate. For this to work, there are some minor changes necessary to the Tcl execution engine and to the Interp structure.

  1. tclInt.h - Interp introduce three new members to the Interp structure: (Tcl_Obj *errorHandler) holds the handler code to execute, (int errorHandlerFlags) holds flags for execution conditions and (int catchLevel) determines the current level of catch blocks surrounding the executed code.

  2. tclInt.h - introduce four new macro definitions for the errorHandlerFlags member specified above: ERRHANDLER_ONCAUGHT is set when the handler should be executed on caught errors, ERRHANDLER_ONUNCAUGHT is set when the handler should be executed on uncaught errors, ERRHANDLER_FINISHED indicates whether the handler has been run already for the currently encountered error, ERRHANDLER_RUNNING indicates whether the error handler is currently running (so that it is not triggered from errors inside the handler itself).

  3. tclEvent.c/tclInt.h - declare and define a new object command TclSetErrorHandlerObjCmd(data, interp, objc, objv), through which an error handler can be registered.

  4. tclBasic.c - Tcl_CreateInterp() should initialize the new members of the Interp structure and register the ::tcl::seterrorhandler command.

  5. tclBasic.c - TclEvalObjvInternal() This function is called from others to evaluate Tcl expressions and returns a code that can be TCL_ERROR. Invoke the error handler here, if necessary.

  6. tclCmdAH.c - Tcl_CatchObjCmd() increment the catchLevel member in the interp structure before executing the enclosed code and decrement it afterwards. The catchLevel member indicates whether errors are catched.

  7. tclExecute.c - enhance the two macros DECACHE_STACK_INFO() and CACHE_STACK_INFO() to increment/decrement the catchLevel member if the following code is catched (I think this is the case when (catchTop != initCatchTop) in the TclExecuteByteCode() function?). This makes sure that the catch information is available when code is executed by TclEvalObjvInternal().

  8. tclExecute.c - TclExecuteByteCode (line 1796 ff) after a call to TclEvalObjvInternal, unset the ERRHANDLER_FINISHED flag, if the execution is at the top of the execution stack. This way, the mechanism works for following errors.

The reference implementation fullfills this specification. It works while other Tcl functionality is not affected (proven by repeated run of the test suite).

Reference Implementation

The reference implementation will shortly be available as a patch against Tcl 8.5a5.

Usage Example

Here is a sample procedure that can be used to stop execution on error and introspect the program on stdin/stdout. It was implemented by ... as and is available on ...:

package provide debug 1.0

proc up {} {
    uplevel 2 {
        breakpoint
    }
}

proc down {} {
    return -code continue
}

proc breakpoint {args} {
    set cmd ""
    set level [expr {[info level]-1}]
    catch {console show}
    set prompt "Debug ($level) % "
    while {1} {
        puts -nonewline $prompt
        flush stdout
        gets stdin line
        append cmd $line\n
        if {[info complete $cmd]} {
            set code [catch {uplevel #$level $cmd} result]
            if {$code == 0 && [string length $result]} {
                puts stdout $result
            } elseif {$code == 3} {
                break
            } elseif {$code == 4} {
                # continue
                return
            } else {
                puts stderr $result
            }
            set prompt "Debug ($level) % "
            set cmd ""
        } else {
            set prompt "    "
        }
    }
}

To use it for uncaught errors, the breakpoint procedure can be registerred as error handler:

package re debug
::tcl::seterrorhandler -uncaught breakpoint

When an error raises, breakpoint is called in the current callframe and Tcl introspection commands like info vars etc. can be used to get information about the program state.

Copyright

This document has been placed in the public domain.


Powered by TclThis is not necessarily the current version of this TIP.

TIP AutoGenerator - written by Donal K. Fellows