From: Eckhard L. <ec...@we...> - 2006-11-01 14:04:25
|
TIP #290: REGISTRATION OF CUSTOM ERROR HANDLER SCRIPTS ======================================================== Version: $Revision: 1.3 $ Author: Eckhard Lehmann <ecky-l_at_web.de> State: Draft Type: Project Tcl-Version: 8.5 Vote: Pending Created: Sunday, 29 October 2006 URL: http://purl.org/tcl/tip/290.html WebEdit: http://purl.org/tcl/tip/edit/290 Post-History: ------------------------------------------------------------------------- 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: * 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. 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 is available from sourceforge as patch against Tcl 8.5a5 [<URL:http://sourceforge.net/support/tracker.php?aid=1587317>]. 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 Neil Madden as *debug-repl* and is available (with a short discussion on the topic) on <URL:http://lambda-the-ultimate.org/node/1544#comment-18446:> 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}] 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. ------------------------------------------------------------------------- TIP AutoGenerator - written by Donal K. Fellows |