Version 23 of Errors management

Updated 2013-12-14 10:38:38 by PeterLewerin

***Fabricio Rocha - 08-Feb-2010 - Error treatment in programming always seems to be an underestimated topic, often untold by and to newbies, while it's a useful thing that might be naturally taught along with the basics in a programming language. Only after some two years of studying Tcl/Tk I was able to find some information about this subject and develop myself a very basic and limited idea of how applications can avoid being crashed by bugs or misuse, so I would like to discuss some error management techniques with the experienced folks, while building up a tutorial from this discussion (something highly useful by aspiring Tclers like me). And, please, treat the errors you find...

Peter Lewerin - 2013-12-07 - I've overhauled the page a bit, concentrating the content around actual error management (leaving the in-depth description of the commands to the commands' respective pages) and adding the new throw/try commands.

Which error-management features are provided by Tcl?

Error termination is one of the modes of termination (see Script termination: results and control) provided by Tcl.

Exception raising commands

The following commands cause an error (raise an exception) that must be handled by code on an earlier level on the call stack: if the error isn't handled (see below), the program stops.

return

The return command has been around for a long time, but has only been able to raise exceptions since Tcl 8.5.

The primary use for return is to return a value, or result, from a called procedure to the caller. In many programming languages, the return value has to do double-duty by either returning a valid value or else a value outside the regular domain of the value to signify that an error has occurred. Famously, the C function getchar returns an int value instead of a char value, since all positive return values mean character codes and the value -1 means failure (to be specific, the end-of-file condition). Tcl, on the other hand, allows the programmer to pass a code that tells the interpreter and the whole application whether something went wrong or not without encroaching on the domain of the returned value or requiring the programmer to examine it to determine if it should be taken as valid.

The basic invocation is return result, where result is passed to the caller of the command that return was called in as the, well, result (return value) of that command.

When used to raise an exception, the minimal invocation is return -code error result, and in this case result is treated as an error message to be handled (displayed) by exception handling code. Instead of -code error you may write -code 1: it has the same meaning.

Bug alert: using the -errorinfo option will mess with the -errorstack value in the return options dictionary, at least in Tcl 8.6.1 (see Investigating exceptions when I get around to finishing that page (PL)).

Exception handling code typically gets passed a dictionary, the return options dictionary, (see below) when an exception is raised. The -code and -level options to return are stored in that dictionary. Other options, such as -errorinfo, -errorcode, -errorline, and -errorstack, may be added to the invocation of return and have their values stored in the return options dictionary. The option -options, with a dictionary as value, may be used to set the return options dictionary all at once.

The return command always passes a return code equal to the value of the -code option and a result value equal to the result parameter. It sets up the return options dictionary a bit differently depending on the invocation.

See return and the man page for a more thorough description of the command.

error

The error command has been the basic exception-raising command since Tcl 8.4.

It can be invoked in three ways:

The invocation......is functionally equivalent to:
"Unary error"error resultreturn -code error -level 0 result
"Binary error"error result inforeturn -code error -level 0 -errorinfo info result
"Ternary error"error result info codereturn -code error -level 0 -errorinfo info -errorcode code result

Bug alert: binary and ternary error will mess with the -errorstack value in the return options dictionary, at least in Tcl 8.6.1 (see Investigating exceptions when I get around to finishing that page (PL)).

The error command always passes a return code of 1 and a result value equal to the result parameter. It sets up the return options dictionary a bit differently depending on the invocation.

See error and the man page for a more thorough description of the command.

throw

The throw command was added in Tcl 8.6.

It is invoked like this:

throw code result

which is functionally equivalent to:

return -code error -level 0 -errorcode code result

The throw command always passes a return code of 1 and a result value equal to the result parameter.

See throw and the man page for a more thorough description of the command.

Dealing with an unknown command

This is a very special case of a conditionally exception-raising command. When Tcl is asked to execute a command procedure it doesn't know, it first attempts to save the situation by invoking another command named unknown, which then goes through a multi-step procedure to find the command that was asked for. If it still fails to find the command that was invoked, it raises an exception. You can override this behavior and e.g. make a rule that every unknown command whose name begins with a vowel actually is an invocation of foo, while any other unknown command is an invocation of bar:

# it's a good idea to save the original unknown
rename unknown _original_unknown

proc unknown args {
    if {[string match -nocase {[aeiou]*} [lindex $args 0]]} {
        # begins with a vowel, invoke foo
        foo {*}[lrange $args 1 end]
    } elseif {[string match -nocase "y*" [lindex $args 0]]} {
        # is "y" a vowel? not sure: invoke original unknown
        uplevel 1 [list _original_unknown {*}$args]
    } else {
        bar {*}[lrange $args 1 end]
    }
}

Since Tcl 8.5, there is also the namespace unknown command, which allows the programmer to name a procedure which will be called when a command/procedure lookup fails in the scope of a specific namespace.

The unknown command doesn't always raise an exception, but when it does, it passes a return code of 1 and a result value equal to invalid command name "yyz" (should the (non-existent) command yyz be invoked).

See unknown and the man page for a more thorough description of the command. The namespace unknown command is described here .

Exception handling commands

An exception that isn't handled by the program is eventually handled by a default handler which basically only does two things: 1) output an error message, and 2) stop the program. If you want to do something else, like for instance deal with the error somehow and go on, or give up and at least save important data, you need to define an exception handler. The handler will deal with exceptions from commands invoked in the body script or from commands called by those commands, and so on.

An exception handler has two parts: the grabber and the dispatcher (not the usual terms, but I'm trying to avoid verbs like catch and trap, which are used in the commands already). The grabber executes a script or body and intercepts any exceptions raised during the execution. At termination of the script, the handler captures three items of information:

  1. The return code, which classifies the termination as ok (0: normal return), error (1: an exception was raised), or return, break, or continue (2, 3, or 4: used for program flow control). The result value is equal to the value of the equal to the value of the -code option explicitly or implicitly used when terminating the script.
  2. The result value, which is generally either the return value of the last command successfully executed in script, or the error message of an exception raised while executing script.
  3. The return options dictionary, which is described elsewhere.

This information can then be used to dispatch to the exact piece of code that the programmer has assigned to deal with the kind of termination in question.

catch

The catch command has been the basic exception-handling command since Tcl 8.4.

The catch command performs the grabbing part of exception handling, but does not dispatch. If fail is a command that raises an exception, this invocation:

catch { fail }

simply prevents the exception from stopping the program. The traditional invocation looks like this:

set returnCode [catch { script } result]

In this case, after running catch the variable result contains the result value at termination of script (see the general description of exception handlers, above). The return value of catch (which is stored in returnCode in this snippet) is the return code of the termination (again, see above).

This means that we can build a simplistic dispatcher using the return code from catch (in this example, only dealing with the non-zero and zero cases):

set returnCode [catch {
    set filename foo.bar
    open $filename r
} result] 
if $returnCode {
    puts "Ouch: $result"
} else {
    set f $result
}

This construct will either set the variable f to whatever open returns, or if something happens, dispatch to the code puts "Ouch: $result".

A more general dispatching construct:

switch [catch { script }] {
    0 { puts "all is well, proceed" }
    1 { puts "handling an exception" }
    2 { puts "return was invoked" }
    3 { puts "break was invoked" }
    4 { puts "continue was invoked" }
}

For more fine-grained dispatching while handling exceptions, information from the return options dictionary (especially the -errorcode value) can be used. (The format of the value of -errorcode is explained here .)

switch [catch { script } result options] {
    0 { puts "all is well, proceed with the value ($result)" }
    1 {
        switch -regexp -- [dict get $options "-errorcode"] {
            {POSIX EACCES} {
                puts "Handling the POSIX \"permission denied\" error: $result"
            }
            POSIX {
                puts "Handling any other kind of POSIX error: $result"
            }
            default {
                puts "Handling any kind of exception at all: $result"
            }
        }
    }
    default { # other handling }
}

See catch and the man page for a more thorough description of the command.

try

The try command was added in Tcl 8.6.

The try command performs both the grabbing and dispatching parts of exception handling. The dispatching is defined by adding handler clauses to the command:

try {
    script
} on ok {} {
    puts "all is well, proceed"
} on error {} {
    puts "handling an exception"
} on return {} {
    puts "return was invoked"
} on break {} {
    puts "break was invoked"
} on continue {} {
    puts "continue was invoked"
}

It isn't necessary to define all kinds of handler clauses, indeed no handler clause at all needs to be defined. If none of the defined handler clauses get the dispatch, the exception is propagated outside the try construct.

Dispatching can also be based on the -errorcode value in the return options dictionary by using trap-style handler clauses:

try {
    script
} on ok {result} {
    puts "all is well, proceed with the value ($result)"
} trap {POSIX EACCES} {result} {
    puts "Handling the POSIX \"permission denied\" error: $result"
} trap POSIX {result} {
    puts "Handling any other kind of POSIX error: $result"
} on error {result} {
    puts "Handling any kind of exception at all: $result"
} on return {} - on break {} - on continue {} {
    # other handling
}

The handler clauses should be ordered from most specific to most generic. Note that the on error handler clause must be placed after any trap handler clauses, because it picks up any kind of exception.

If a handler clause needs to examine the return options dictionary, it can be passed as a second variable to the handler clause:

try {
    script
} on ok {result} {
    puts "all is well, proceed with the value ($result)"
} trap {POSIX EACCES} {result options} {
    puts "Handling the POSIX \"permission denied\" error on line [dict get $options "-errorline"]: $result"
} on error {result} {
    puts "Handling any kind of exception at all: $result"
}

You can add a finally script clause to the try construct. The code in script will be run whether an error occurs or not and regardless of which handler clause, if any, was dispatched to.

# $f is an open file handle
try {
    # do something with $f that might fail
} on ok {result} {
    # handle success
} on error {result options} {
    # handle failure
} finally {
    close $f
}

The return value of the try construct is the return value of the handler clause that was dispatched to, or of the script if none of the handler clauses were engaged. The value of the finally clause is never used.

See try and the man page for a more thorough description of the command.

Background error handling

Tcl/Tk automatically grabs exceptions that are raised in background processing (e.g. when events are processed in an update or vwait call) and dispatches them to a programmer-defined handler procedure, if available. If such an exception handler is registered with interp bgerror (available as of Tcl 8.5) the result of the exception-raising command and the return options dictionary will be passed to the handler. If no such handler is registered for the active interpreter, it instead attempts to call a global command procedure named bgerror which the programmer needs to define (it doesn't exist otherwise). The bgerror command only gets one argument passed to it, an error message. If bgerror isn't available, we simply get an error message.

If fail is a command that raises an exception, and we run this code in a Tk-enabled console:

bind . <K> fail

and then press K, we get a dialog box pointing out that an error has occurred.

If we define the bgerror command:

proc bgerror {result} {
    puts "I'm bgerror"
}

and then press K, we get the message "I'm bgerror".

If we define this command:

proc myHandler {result options} {
    puts "I'm myHandler"
}

and then run this command:

interp bgerror {} myHandler

and then press K, we get the message "I'm myHandler".

See bgerror and the man page for a more thorough description of the command. The interp bgerror invocation is described here and background exception handling here .

The return options dictionary and other exception information sources

The return options dictionary

Whenever an exception is raised, since version 8.5 Tcl creates a dictionary of keys and values that describe the exception:

KeyUsed for/describes
-codereturn category: only 1 signifies an actual exception
-levelstack level: 0 for all exceptions
-errorinfoa brief human-readable description of the event, with an appended stack trace
-errorcodemachine-readable: either NONE or a list of code words that classify the exception
-errorlinethe line where the exception occurred
-errorstackmachine-readable: an even-sized list of code words and sub-lists that

(I've heard rumours of a seventh key, -during that's supposed to store information on an original exception if another exception occurs during the handling of the first, but I'm unable to find any documentation for it.)

As described above, this dictionary is passed, along with a result value, by an exception handler to the code that the exception is dispatched to.

The return options dictionary is passed along with other return types than exceptions, but note that the -error* keys are only available after an exception.

errorcode is described here .

errorstack is described here .

errorCode and errorInfo

Before Tcl 8.5, basic information on exceptions could be found in two global variables , errorCode and errorInfo. For backwards compatibility, the variables are still in use.

(I'll be back soon -- PL)


::errorCode

A reserved and global variable called errorCode is automatically created by the Tcl interpreter during the execution of a script for holding information about errors occurred in runtime, so its contents are changed everytime an error happens. errorCode is a variable-length list whose first element is a string which indicates the type of error which happened, and the following elements, if existant, are details about the errors which can be used by a procedure for error treatment. As of Tcl8.5, ::errorCode seems to be still underused by many of the core Tcl commands, and these are the possible values and structures that are generated by these commands and stored in ::errorCode, according to the official documentation [L1 ]:

  • "ARITH" code msg - Arithmetic error. The code element can contain the strings DIVZERO, DOMAIN, OVERFLOW or IOVERFLOW. msg contains a human-readable description of the problem.
  • "CHILDKILLED" pid sigName msg
  • "CHILDSUSP" pid sigName msg - Those errors are related to the use of processes in the underlying OS shell by the Tcl interpreter; more specifically, they contain information about processes which were unexpectedly terminated or suspended. pid is the process identifier; sigName is the signal which caused the process end or suspension; msg is a human-readable explanation of the problem. The list of possible values for sigName is in the system's C standard library signal.h header file (TODO: list them here).
  • "CHILDSTATUS" pid code - These values are set when an external program used by a Tcl script ends with non-zero value, which is considered an abnormal end. In such cases, the second element of ::errorCode will contain the process identifier number and the third one will hold the process "exit code". Actually, some system utilities intended for use in pipe sequences exit non-zero values as the correct result of their operations, so the code value may be the real and valid result of the child process.
  • "POSIX" errName msg - Lots of commands which depend on OS-provided functionalities, like file and socket operations, can result in errors of this family. The possible values for the errName item are listed in the errno.h header file of the C standard library (TODO: list them here). There is some contestation about the precision of these error reports, mainly under Windows, which is not exactly POSIX-compliant.
  • "NONE" - This single value in a one-element ::errorCode is set when a procedure generates an error -- intentionally or not -- but no detailed information is given about this error.

Any procedure can set its own error values in ::errorCode by using the "advanced" options for the command return, as we will see below.

How to use all this stuff?

The infrastructure provided by Tcl allows applications to use exception handling, in the traditional sense of "try to do this, and if something goes wrong tell me and I'll see what can I do". This contrasts to the approach of "errors prediction", which, for example, performs a series of tests on the data which will be passed to a command for checking its validity, before the operation is performed. Both techniques are not excludent, however. Tcl allows various approaches to errors management, with their pros and cons:

Approach 1: return, catch and process the error

1) Always use the advanced return options when writing procedures which can cause or face errors, or which may give back an invalid result;

2) Always use catch for calling commands or your own procedures which can cause or face errors like described in 1;

3) Create a procedure to be called in the case that catch captures an error, for interpreting the error codes and, based on that, show error messages in friendly and standardized dialogs and perform operations which could minimize or solve the error.

Approach 2: tracing ::errorCode

Create a trace on ::errorCode, and a procedure to be called everytime it is modified, for interpreting the codes, display them, provide minimization measures, etc.

Any other? Please add what you do!

LV One useful thing that I sometimes use is creation of log files containing information intended to be useful in determining the state of the program during particular points. Sometimes, displaying information about the values of a number of variables is not as helpful as having that information written to a file - for instance, there are times when a GUI application might not have easy access to stderr for error traces. Writing information to a log file, which is available - and perhaps even emailable - to the programmer responsible is helpful.

Which errors shall be told to the user?

Failure in files, channels and sockets operations?

Errors caused by invalid inputs. It is often useful to use a distinct error code (e.g., INVALID) for data validation errors, as it makes it possible for the application to distinguish between errors in the user's input and errors in the validation or execution code.

Which errors shall NOT be told to the user?

Syntax errors and programming bugs - They'd better be fixed. Sure, but....

LV Certainly they need to be fixed. However, if you hide the info from the user, how will the programmer know what the bug/error is? Unless you have a guaranteed method of getting said info to the programmer (and email doesn't count - the user MIGHT be working off line), then providing the user with sufficent information to a) know what the error is and b) know who to contact or what to do about the problem seems the best approach to me.

Fabricio Rocha - 12-Feb-2010 - One more reason for having a way to intercept and explain this kind of errors to common users is that it seems that any test suite or any test routine will not be able to find some errors that users are able to find. Of course it is not nice to show weaknesses to a final user, but this is something practically unavoidable in software. And in addition to the situations listed by LV, we can consider that, for an open source/free software, providing good information about an error is a way to c) allow a user with sufficient programming knowledge to fix the problem and possibly contribute to the software development.

See Also: