VBA error handling mechanism
Err.Raise()
starts the same process as if a Run-time error has occurred:
- On Run-time error or
Err.Raise()
VBA looks for an error handler along the call stack starting with the current procedure. - If none of the procedure has an error handler, the error bubbles up to the default error handler: a modal error message is displayed and execution stops.
- If an error handler is defined in the current or closest caller, the error is trapped there.
- Run-time errors or
Err.Raise()
occurring in the error handler bubbles up again i.e. error trapping is disabled when executing the error handler (to avoid infinite loop). - After execution transferred to another handler in another procedure, it never returns. Resume is local to the procedure.
bubble ^ VBA Run-time | | On Error.. error message is displayed | | and execution stops (default) | | |__Sub1() ^ | On Error.. error handler in procedure | | |__Sub2() ^ | | | |__Sub3() ^ | On Error.. error handler in procedure | | |__Sub4() ^ | | | | Run-time error or Err.Raise in Sub4() is trapped in Sub3() Run-time error or Err.Raise in Sub3() is trapped in Sub3() Run-time error or Err.Raise in Sub3()'s error handler is trapped in Sub1()
About Line numbers vs Line labels
It seems like, at least in 64-bit Excel for Microsoft 365, there is not much difference any longer between Line numbers and Line labels.
Here "999" is Line number and "999:" is Line label:
Sub errtrap() Sub errtrap() On Error GoTo 999 On Error GoTo 999 Err.Raise 3 Err.Raise 3 Exit Sub Exit Sub 999 999: Resume Next Resume Next End Sub End Sub
These are not really true in VBA Docs either:
- Line numbers should be unique within the module - NOT. Only within procedure.
- Line labels should start with a letter - NOT. See the above compiles and runs.
At least using both "999" and "999:" within the same procedure gives Duplicate declaration error.
There is a little difference between Line numbers and Line labels when it comes to F8-debugging step-by-step:
- Empty line with only a label is a F8-Step line.
- Empty line with only a Line number is not: F8 steps on the next non-empty code line right away.
VBA Error handler Execution model
Custom run-time error handlers in VBA are implemented by jumping around within the procedure set up and controlled by Line numbers/labels, On Error
and Resume
statements. It's a bit like try...catch blocks but with more freedom.
Next is a pseudo-implementation model I have come up to understand what may happen internally.
Lets say every procedure has an Error Handling Mode internal variable. The On Error
statement sets this variable to 3 possible values, which will go into effect on the next Run-time error or Err.Raise()
:
On Error GoTo Line ---> sets to [Line] On Error Resume Next ---> sets to [Next] On Error GoTo 0 ---> sets to [0]
By default On Error GoTo 0
is in effect i.e. error trapping is disabled. Run-time errors in the procedure will bubble and eventually handled above.
There is also a Code Execution State of the current line being executed: normal or trapped. Trapped mode [T] is entered only after On Error GoTo Line
is in effect and a Run-time error occurs or the Err.Raise()
statement issued. Execution jumps to Line and Trapped mode is activated.
Code Error Execution Handling State Mode ----- Sub() ---------------------------------+ [0][Line][Next]| bubble [0] [ ] | ^ [ ] .. | | [ ] Raise | jump ---+---> [Next] [ ] next-statement | | [ ] ... | V [ ] | [Line] [ ] | [T] Line +--[enter]---------------+ | [T] | | | [T] | .. | | [T] | .. | | bubble [T] | .. | | ^ [T] | .. | | | [T] | Raise (no return) | | jump ---+ [T] | .. | | [T] | .. | | [T] | .. | | [T] | .. | | [T] | | | +---> Re-try statement [T] | Resume | | jump ---+---> next-statement [T] +--[exit][jump]----------+ | +---> line | | [T] | | | [T] | On Error GoTo -1 | | [T] +--[exit]----------------+ | [ ] .. | [ ] .. | [ ] .. | ---------------------------------------------+ [T] denotes lines executed in error trapped mode [ ] are normal execution code lines [enter] set [T] AND ignore Error Handling Mode on next error [exit] clear [T] AND honour Error Handling Mode on next error
The main difference in trapped mode [T] is:
- The Error Handling Mode is ignored, disabled. Run-time error or
Err.Raise()
will bubble. Even if anOn Error
statement appears in the error handler, which is legal and changes the Error Handling Mode. -
Resume
is a valid statement
There are 2 ways to exit trapped mode:
-
Resume
, which always jumps -
On Error GoTo -1
On Error GoTo -1
It does not change the Error Handling Mode and useful only in trapped mode (in normal execution mode it only clears the ErrObject
):
- clears the
ErrObject
- clears the [T] flag
- continues execution on the next line in normal execution mode
BEWARE: errors now will cause endless loop if Error Handling Mode hasn't change from [Line].
On Error GoTo -1
is the same as Resume
without jump - i.e. jump to the next line.
These are equivalent:
Sub errtrap() Sub errtrap() On Error GoTo label On Error GoTo label ... ... Exit Sub Exit Sub label: label: ... ... On Error GoTo -1 Resume out ... out: ... ... ... End Sub End Sub
On Error Resume Next
When On Error Resume Next
is set and an error occurs code doesn't enter trapped mode:
- fill
ErrObject
- execute next statement
-
ErrObject
retains its values until Clear
Resume
Resume Resume Next Resume Line
Resume can only be executed in trapped mode [T] (otherwise 20, Resume without error).
- clears the
ErrObject
- clears the [T] flag
- transfers execution back to the same, next or specified line in normal execution mode
Resume jump is always local to the procedure, it never jumps "back" to a nested procedure that caused the error.
Behaviour of the ErrObject
Every On Error
and Resume
statement clears the Err Object.
A Run-time error or Err.Raise()
statement fills values in the ErrObject
. The only difference I have found is:
- Repeated
Err.Raise()
on a not clearedErrObject
sets only ErrNum. - Repeated internal Run-time errors update all values in the
ErrObject
.
Infinite loops with trapped code
It is a danger to end up in infinite loops using error handlers and issuing certain On Error/Resume
statements.
InfiniteLoop1()
Resume
in the error handler re-tries the Err.Raise 3
statement, which is trapped again...
Sub InfiniteLoop1() On Error GoTo 1 Err.Raise 3 Exit Sub ' never reached! 1 Resume End Sub
InfiniteLoop2()
On Error GoTo -1
clears the [T] flag and the next Err.Raise()
is trapped again...
Sub InfiniteLoop2() On Error GoTo 1 Err.Raise 3 Exit Sub ' never reached! 1 On Error GoTo -1 Err.Raise 11 Resume Next ' never reached! End Sub
InfiniteLoop3()
On Error GoTo -1
clears the [T] flag, which causes Resume
an invalid statement, which is trapped again...
Sub InfiniteLoop3() On Error GoTo 1 Err.Raise 3 Exit Sub ' never reached! 1 On Error GoTo -1 Resume Next ' Run-time error 20, Resume without error ' next line never reached! End Sub
No comments:
Post a Comment