traceback

Since 8.6 and TIP #348, we now have the built-in info errorstack providing the same functionality more efficiently and robustly. The script-level emulation below is only for the curious, or for pre-8.6 releases.


  • A traceback is a precise description of where an error occurred, in terms of the call graph of the program.
  • It is useful to display and/or store it whenever the error is fairly unexpected or hard to reproduce.
  • To do so, the idiom is to
if {[catch {some code} err]} {
    Log "Argh: err\n$::errorInfo"
}

or if you're catching from bgerror:

proc bgerror err {
    Log "Argh: err\n$::errorInfo"
}
  • However useful, ::errorInfo is a bit frustrating in that it only gives the static text of the commands on the call stack. Most notably, it doesn't give the actual values that were passed to the functions in which the error occured.
  • To solve this, I like the following hack:
set ::errorLevel -1
set ::errorStack {}
  
trace add variable ::errorInfo write {
    set __n [info level]
    
    if {($__n>0)&&($__n!=$::errorLevel)} {
        set ::errorLevel $__n
        set __l [info level 0]
        lappend ::errorStack $__l
    }
    # the next line must end with the closing brace on the same line
    # in order to consume the extra arguments at runtime.
    list }

It works by exploiting the fact that ::errorInfo is built progressively while climbing back up the stack; hence, each increment occurs within the scope of each level, and there [info level 0] gives the local function's actual arguments, regardless of whether the variables holding these args have been modified since function entry or not.

  • The code above is crude, to expose the mechanism. It can easily be refined, for example to auto-reset errorLevel and errorStack on subsequent invocations, as suggested by Martin Lemburg. Just insert this at the beginning of the write handler:
if {![regexp {\n    invoked from within\n} $::errorInfo]} {
    set ::errorLevel        -1;
    set ::errorStack        [list];
}
  • Below is Martin's initial code, which ceased to work at some point in the evolution of the precise sequence of updates undergone by ::errorInfo :
if {[string match {*while executing*} $::errorInfo] == 0} {
    set ::errorLevel        -1;
    set ::errorStack        [list];
}
  • Also, please note that prior to 8.4.8 a bug (1038021) in the handling of the interpreter's state makes the above code corrupt ::errorInfo, although ::errorStack is correctly computed. Tested back to 8.3.5.

-Alex

Twylite 20090227: How does this interact with [catch] in Tcl 8.5 (TIP #90 [L1 ] introduced the options dict) and [try] in Tcl 8.5 (introduced by TIP #329 [L2 ])?

HaO: IMHO the same way as errorInfo, as it is based on its creation. For me, it works with both language extensions and errorStack is correctly created


HaO 2010-12-09: I have included this into my tcl 8.5.9 framework and it is brilliant. Insert the addendum with the regexp at the beginning of the trace write definition and display ::errorStack within a custom bgerror routine together with ::errorInfo. errorInfo tells the error and errorStack the current values. Thank you very much, Alex and Martin!

Interactive sample session after copying the upper lines in a fresh wish interpreter:

% proc e1 args { expr 1/0 }
% proc e2 args { e1 {*}$args }
% e2 1 2 3
divide by zero
% set errorInfo
divide by zero
    invoked from within
"expr 1/0 "
    (procedure "e1" line 1)
    invoked from within
"e1 {*}$args "
    (procedure "e2" line 1)
    invoked from within
"e2 1 2 3"
% set errorStack
{e1 1 2 3} {e2 1 2 3}

HaO 2010-12-15: In interactive mode, the term auto_load_index may appear when a potentially unknown procedure is called.

% proc e args {qq $args}
% e 1 2 3
invalid command name "qq"
% set errorStack
auto_load_index {e 1 2 3}

Alex remarked on clt [L3 ], that this is the truth.


HaO 2010-12-16: A bug in 8.5.x - 8.5.9 may cause that traces on error variables crash TCL. . Sorry, this makes this nice trick practically unusable (Discussion: [L4 ], Bug: [L5 ]).