Is there any reason the below crashes excel 2016?
Option Explicit
Option Compare Text
Option Base 1
Sub p1()
Dim t(1), w()
t(1) = w
If IsMissing(w) Then DoEvents
If IsMissing(t(1)) Then DoEvents
End Sub
w
is an uninitiated array, but passing w
into ismissing(w)
is fine, yet when passing it via the t(1)
, excel reproducibly terminates ungracefully...
This is the shortest bit of code I have managed to generate using vba only that always kills the excel application (i.e. not using external code like createobject
or shell
).
Are there other such examples?
cheers
Microsoft Excel may crash due to any one or more reasons given below, Incompatible Add-Ins. Outdated MS Excel program. Conflict with other programs or antivirus tool.
If your Excel opens and closes immediately then the following are the responsible causes behind this: Outdated MS Excel application. Network connection Problem. Incompatible Add-Ins.
You confused the hell out of IsMissing
by making it think an uninitialized array was a malformed ParamArray
, and hit a bug in its implementation.
According to the Windows event viewer, the underlying exception that brings down Excel is an access violation in vbe7.dll:
The first report in I saw (commented on above) was a Rubberduck callback failing because Excel was toast.
Faulting application name: EXCEL.EXE, version: 15.0.5067.1000, time stamp: 0x5b76360d
Faulting module name: VBE7.DLL, version: 7.1.10.68, time stamp: 0x58def301
Exception code: 0xc0000005
Fault offset: 0x00000000001cd38c
Faulting process id: 0xac4
Faulting application start time: 0x01d45de81ef072ed
Faulting application path: C:\Program Files\Microsoft Office\Office15\EXCEL.EXE
Faulting module path: C:\PROGRA~1\COMMON~1\MICROS~1\VBA\VBA7.1\VBE7.DLL
The access violation is due to the VB run-time attempting to dereference a null pointer. This is the where the debugger lands you as it dies:
00007FFF9CDDD383 mov rax,qword ptr [rsp+30h]
00007FFF9CDDD388 mov rax,qword ptr [rax+8]
00007FFF9CDDD38C movzx eax,word ptr [rax] <--fails, rax is 0.
00007FFF9CDDD38F cmp eax,1
00007FFF9CDDD392 jne 00007FFF9CD7B8A4
So, let's take a look at what the IsMissing
function is checking for. VBA is basically a beast of COM, and an optional parameter that isn't passed is received by the called function as a Variant
with a type of VT_ERROR per RPC convention. It isn't passed as Null
, Empty
, or any other VB construct. The following code demonstrates:
Sub Foo()
Bar
End Sub
Sub Bar(Optional x)
Debug.Print VarPtr(x) 'This has a valid pointer.
Debug.Print IsError(x) 'True
Debug.Print VarType(x) '10 (vbError)
End Sub
You can read a slightly more technical explanation on Raymond Chen's blog.
IsMissing
is intended to be passed an incoming parameter, so it also has to be aware of ParamArray
s (under the hood, a ParamArray
is just a Variant()
). Although, if you don't give any parameters to it the procedure still gets an array - it just has a UBound
lower than the LBound
:
Sub Calling()
Called
End Sub
Sub Called(ParamArray params())
Debug.Print LBound(params) '<-- 0
Debug.Print UBound(params) '<-- -1
End Sub
Here's where it gets interesting. You can't have both a ParamArray
and an optional argument in the same procedure signature - it can be one or the other. When you look at the function signature, you'll see that it takes a pointer to a Variant
(that is, it expects a ByRef
parameter):
[entry(0x60000007), helpcontext(0x000f69b5)]
VARIANT_BOOL _stdcall IsMissing([in] VARIANT* ArgName);
But code like this doesn't give the function what it's expecting to see:
Sub DieExcelDie()
Dim x(1), y()
x(1) = y
x(1) = IsMissing(x(1))
End Sub
x(1)
has to be marshalled into a ByRef Variant
to be passed as a parameter to IsMissing
. So what it sees is an array of Variant
that has both an LBound
and UBound
value of zero (this answer goes a bit into what an uninitialized array looks like in memory). The parameter has not already gone through the RPC mechanism, so a this point it is completely indistinguishable from a ParamArray
containing one passed parameter. It would check to see if the variant was typed as VT_ERROR, see that it was an array, and attempt to process it that way. Since it doesn't comply with the spec for an empty parameter list, it likely checks to see what the first passed parameter was... by dereferencing the data area. The SAFEARRAY
structure looks like this1:
Going back to the disassembly and without going completely down the rabbit hole of disassembling vbe7.dll, the rax+8
on the previous instruction is most likely loading up the pvData
pointer (it's 8 bytes on a 64 bit version of Excel), which you can see is zero.
This is simply a bug in the implementation of IsMissing
- it should be checking for a null pointer instead of blindly dereferencing it. I suspect, although can't verify, that it was introduced in VB5 along with the changes that allowed both non-Variant
optional parameters and default values.
1Image was taken as a screenshot from How Visual Basic 6 Stores Data on codeguru.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With