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 Errorstatement appears in the error handler, which is legal and changes the Error Handling Mode. -
Resumeis 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
-
ErrObjectretains 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 clearedErrObjectsets 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