Skip to content

Stack Trace

Will Duquette edited this page Jan 25, 2020 · 3 revisions

Notes on TCL 8.6 Stack Traces

Tcl 8.6 stack traces are similar to but simpler than TCL 7.6 stack traces, as described below. However, a number of things (such as loop bodies) are no longer included. In particular, Tcl_AddErrorInfo is still used to add the error info to the stack trace. The focus now seems to be on proc invocations, and on code that is called outside of the normal flow of control, e.g., namespace unknown handlers, etc.

Command/Function Module Line What
A "for" tclCmdAH.c 2684 ("for" initial command)
A "for" tclCmdAH.c 2801 ("for" loop-end command)
B SortCompare tclCmdIL.c 4303 (-compare command)
D "dict incr" tclDictObj.c 2120 (reading increment)
D "dict filter" tclDictObj.c 3043 ("dict filter" filter script key variable)
D "dict filter" tclDictObj.c 3050 ("dict filter" filter script value variable)
C "dict update" tclDictObj.c 3223 (body of "dict update")
C "dict with" tclDictObj.c 3372 (body of "dict with")
B ensembles tclEnsemble.c 2302 ... ensemble unknown subcommand ...
B ensembles tclEnsemble.c 2340 ... ensemble unknown subcommand ...
B ensembles tclEnsemble.c 2346 (ensemble unknown subcommand handler)
D IncrObj tclExecute.c 1884 (reading increment)
D IncrObj tclExecute.c 1932 (reading increment)
D TEBCresume tclExecute.c 3816 (reading value of variable to increment)
B TimeLimitCallback tclInterp.c 4002 (while waiting for event)
D GetOpenModeEx tclIOUtil.c 1608 access mode errors
B Prompt tclMain.c 911 (script that generates a prompt)
B "package require" tclPkg.c 445 ("package unknown" script)
D "proc" tclProc.c 168 (creating proc "$name")
B "after" tclTimer.c 1193 ("after" script)
D IncrObjVar2 tclVar.c 2095 (reading value of variable to increment)

Notes:

  • A: Stack trace in existing code base that should be added.
  • B: Cases that should be handled if the relevant feature is ever added to Molt.
  • C: Execution of a command body. Why should these be included when loop bodies aren't?
  • D: Cases that add context, but that don't seem necessary.

My plan is to support A now, B whenever it's appropriate, and C (with misgivings).

Notes on TCL 7.6 Stack Traces

Stack Trace construction

  • A TCL C function can detect an error or propagate an error.
  • It knows the difference by the ERR_ALREADY_LOGGED interpreter flag.
  • Either way, it calls `Tcl_AddErrorInfo()`` to save a stack trace entry.
  • The entry can vary depending on whether this is a newly detected error or a propagated error.

In `Tcl_Eval()``, for example, a detection entry looks like this:

    while executing
"{some TCL command} ..."

and a propagation entry looks like this:

    invoked from within
"{the calling TCL command} ..."

The command is elided and the ellipsis included only if the command is too long.

The Tcl_AddErrorInfo function does this:

  • On a newly detected error, it clears the TCL errorInfo variable and sets the errorCode to NONE if it isn't already set.
    • Note: it's setting the actual TCL variables.
  • Then, either way it appends the message to the current value of errorInfo.

Thus, errorInfo gets built up with the error at the top and the ultimate caller at the bottom.

How Molt Should Do It

The natural thing is to augment the MoltReturn data, and particularly the ResultCode::Error enum. At present, the Error constant has one argument, a String. In addition or instead it should take a struct, ErrorInfo, which can accumulate stack trace info for later programmatic query (unsupported in TCL 7.6). We also need to update the errorInfo variable; and it may be appropriate to do that as we go, as now; otherwise it needs to be done when the error is finally caught.

Speculation: What should Molt's error handling look like?

TCL's error handling evolved over many years and versions. Should we use an errorInfo variable? Should we provide a command to return the most recent error? Should we require that you catch it? Perhaps errorInfo should be the property of the molt-shell, rather than molt itself?

Contributors to the stack trace

Some notes:

  • Many functions appear to simply AddErrorInfo and return TCL_ERROR or -1 without setting the actual interp result. Molt would probably use the same mechanism for both kinds.
Command/Function Module Line What
AfterProc tclEvent.c 1870 ("after" script)
InterpProc tclProc.c 510 (procedure "{name}" line %d)
SortCompareProc tclCmdIL.c 1428 (converting list element...)
SortCompareProc tclCmdIL.c 1443 (converting list element...)
SortCompareProc tclCmdIL.c 1468 (user-defined comparison command)
Tcl_BackgroundError tclEvent.c 1255 "", Setting up for background error
Tcl_Eval tclBasic.c 1486 "while executing", "invoked from within"
Tcl_EvalFile tclIOUtil.c 421 (file "{name}" line %d)
Tcl_GetOpenMode tclIOUtil.c 256 "while processing open access modes"
Tcl_Main tclMain.c 158 Appends "" before reading errorInfo
Tcl_ParseVar tclParse.c 1333 (parsing index for array "...")
Tcl_PkgRequire tclPkg.c 203 ("package ifneeded" script)
Tcl_PkgRequire tclPkg.c 203 ("package unknown" script)
"case" tclCmdAH.c n/a "case" is obsolete.
"error" tclCmdAH.c 393 The error message.
"eval" tclCmdAH.c 454 "eval" body line %d
"for" tclCmdAH.c 1162 "for" initial command
"for" tclCmdAH.c 1179 "for" body line %d
"for" tclCmdAH.c 1188 "for" loop-end command
"foreach" tclCmdAH.c 1330 "foreach" body line %d
"incr" tclCmdIL.c 196 (reading value of variable to increment)
"incr" tclCmdIL.c 206 (reading increment)
"interp", slaves tclInterp.c n/a Won't be implementing this any time soon.
"load" tclLoad.c n/a Dynamic loading isn't a thing in Rust.
"switch" tclCmdMZ.c 1702 (... arm line %d)
"time" tclCmdMZ.c 1768 ("time" body line %d)
"uplevel" tclProc.c 313 ("uplevel" body line %d)
"while" tclCmdMZ.c 2104 ("while" body line %d)