8

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

5
  • 2
    According the the event viewer, it's an access violation in combase.dll. Without attaching a debugger to it, I'm guessing it's trying to free a null pointer in the data area of a doubly wrapped Variant. The DoEvents calls are a red herring though, as are the Option statements. If you're looking for "shortest", you can golf out at least 81 characters with assignments like x = IsMissing(t(1)). And that's without touching the whitespace. :-P Commented Oct 7, 2018 at 1:32
  • 2
    Public Sub Boom() : Dim a(1), b() : a(1) = b : a(1) = IsMissing(a(1)) : End Sub (chat transcript) Commented Oct 7, 2018 at 3:10
  • 'Sub plop(): Dim b(): IsMissing Array(b)(1): End Sub`😉 Commented Oct 8, 2018 at 22:13
  • Nope, plop() does not crash anything. Commented Oct 9, 2018 at 14:53
  • 1
    Ah, I had option base 1 specified.... try: Sub plop(): Dim b(): IsMissing Array(b)(0): End Sub Commented Oct 9, 2018 at 18:30

1 Answer 1

18
+200

tl;dr

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 ParamArrays (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:

SAFEARRAY in memory

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.


1 Image was taken as a screenshot from How Visual Basic 6 Stores Data on codeguru.

Sign up to request clarification or add additional context in comments.

4 Comments

Excellent, that is a very comprehensive answer based on indepth error analysis, and using tools I certainly dont have skills to use. Thank you! For info, I hit this snag when trying to un-nest paramarrays.
@Comintern ... I'm in awe. Appreciate the answer ... it was enlightening.
@TechnoDabbler Thanks, that was written on a dare. :-D
Superb! A technical tour de force!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.