This is not necessarily the current version of this TIP.
|Title:||Registration of Custom Error Handler Scripts|
|Version:||$Revision: 1.4 $|
Eckhard Lehmann <ecky-l at web dot de>|
Larry W. Virden <lvirden at yahoo dot com>
|Created:||Sunday, 29 October 2006|
|Keywords:||Tcl, error, trap|
This TIP proposes the possibility to register custom scripts or commands in the usual Tcl event handler style as error handlers.
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 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 does not include enough information to successfully reproduce the error - information about the application's 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 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 occurs in the execution of Tcl code. This opens a range of implementation possibilities for error handling, for instance:
registering a breakpoint command that stops execution at the error position and opens a console for introspection
registering a more advanced (Tk) debugger that opens on error for introspection.
registering a command that captures the state of each call-frame up to the one where the error was thrown and writes that state to a file. This file can later be debugged (with an appropriate tool) - similar to memory dump files for C debuggers.
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.
(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.
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).
tclEvent.c and tclInt.h - declare and define a new object command TclSetErrorHandlerObjCmd(data, interp, objc, objv), through which an error handler can be registered.
tclBasic.c - Tcl_CreateInterp() should initialize the new members of the Interp structure and register the ::tcl::seterrorhandler command.
tclBasic.c - TclEvalObjvInternal() is called from others to evaluate Tcl expressions and returns a code that can be TCL_ERROR. Invoke the error handler here, if necessary.
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 within a catch.
tclExecute.c - enhance the two macros DECACHE_STACK_INFO() and CACHE_STACK_INFO() to increment/decrement the catchLevel member if the following code is within a catch (I think this is the case when (catchTop != initCatchTop) in the TclExecuteByteCode() function?). This makes certain the catch information is available when code is executed by TclEvalObjvInternal().
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 fulfills this specification. It works while other Tcl functionality is not affected (proven by repeated run of the test suite).
The reference implementation is available from sourceforge as a patch against Tcl 8.5a5 .