XRayXLDocs · 0.4.0

How errors read in a trace

Every VBA call in a trace ends with an exit row, and that row's outcome column says how the call ended. When nothing goes wrong it says returned. This page is about the rest: what each word means, how a chain of calls reads when an error passes through it, and the cases where VBA or Excel handle an error in a way you might not expect.

The column reference is in TraceRowModel.md; how the tracer works out an outcome is in VBATracing.md.


The six outcomes

outcome what happened to the call
returned it finished normally
threw an error was raised in it, and it went no further
unwound an error raised below it passed through it on the way out; it ran nothing after that
handled it caught an error raised below it, and carried on
unhandled an error left VBA through it into a worksheet cell, which shows #VALUE!
abandoned VBA stopped it: it neither returned nor passed an error on

An XLL function's exit row always reads returned, because it is written only when the function returns.

Read outcome together with trust. trust says what closed the call, and so whether its ticks is a measurement or only a ceiling:

trust the call was closed by ticks is
exit its own ending a measurement
end the End statement a measurement
backstop the next VBA statement to run, after the call had already gone an upper bound
flush disarming an upper bound

A call that ended in an error runs no ending of its own, so it is always closed by something else: backstop, flush or end.


An error caught by a caller

The common case. Report has an error handler and calls Load, which calls Parse, which raises:

Sub Report()
    On Error GoTo Failed
    Load
    Exit Sub
Failed:
    Debug.Print Err.Description
End Sub

Sub Load()
    Parse
End Sub

Sub Parse()
    Err.Raise 5
End Sub
function depth outcome trust
Parse 3 threw backstop
Load 2 unwound backstop
Report 1 handled exit

Read it outwards from the raise: Parse raised it, it passed through Load, and Report caught it. Parse and Load never ran their endings, so they are closed when Report runs its next statement.

An error caught in the same procedure reads returned. With On Error Resume Next, or a handler in the procedure that raised, nothing leaves the call, and it does return. threw, unwound and handled describe an error crossing from one call to another.

A handler that raises again. If Report's handler raised a new error that left it, Report reads threw, and whichever call catches that one reads handled.


An error into a worksheet cell

A VBA function used in a cell formula has no VBA caller to pass an error to. Excel catches it and the cell shows #VALUE!:

function depth outcome trust
Parse 2 threw backstop
PriceOf 1 unhandled backstop

PriceOf is the function the cell called, so it reads unhandled: the error left VBA there. The functions it called keep threw and unwound, so the raise is still found. The calls close when Excel starts its next VBA call, or at disarm (trust=flush).


An error nothing catches: VBA's error dialog

An error that no call catches, in a macro, a sheet event, an Application.OnTime macro or a macro started with Application.Run, makes VBA stop and show its Run-time error dialog. Pressing End stops all VBA at once. No call runs its ending, and the tracer sees nothing happen at that moment. The calls stay open until the tracer finds them gone. That happens when the next VBA call starts, or at disarm, whichever comes first. Either way they read the same:

function depth outcome trust
Parse 3 threw backstop or flush
Load 2 abandoned backstop or flush
Report 1 abandoned backstop or flush

Parse raised the error. Load and Report read abandoned: the error never passed through them, and End stopped them where they were. Their ticks run until the calls were found closed, which includes however long the dialog was open.

A call chain that Excel started for a cell is the exception. Its error went to the cell, so it reads threw … unhandled as above, and only the calls beneath it read abandoned.

Pressing Debug stops VBA in the editor instead, with every call still running:

These need a person at the editor, so no automated test covers them.

Errors that do not reach their caller

Two situations look as if an error should go back to the caller's On Error, but VBA stops at the dialog instead. The caller reads abandoned, even with a handler:

An error in a call Excel started on its own reads threw. A sheet event fired by a user's edit, or an OnTime macro, is the first call in its chain, so the raiser is also the outermost call. It reads threw, not unhandled: only a cell formula makes an error unhandled.


The End statement

End in your code stops all VBA, as the dialog's End button does. Here the tracer sees it happen, so every open call closes at once, abandoned with trust=end. That includes the call that ran End: no error was raised, so no call reads threw.


Stopping VBA from the editor

These cases need a person at the editor, so no automated test covers them.


Edge cases and limits


How the tracer tells a stopped call from a running one

VBA gives no signal when its dialog's End button is pressed, so the tracer works it out from the stack.

The details are in Implementation.md, under A stack that dies whole was ended by VBA and A trailer can outlive its frame.