1

I've been reviewing some code for creating weak-references in VBA by manually dereferencing object pointers without calling IUnknown::AddRef, and I've found a bug that I can't explain. I could come up with a minimal reproducible example using the pure API calls, but I think it's easier just to demonstrate using the WeakReference class from that review. Here's how I can crash Excel:

Dim strongRef As Range
Set strongRef = [A1]

Dim weakRef As New WeakReference
weakRef.Object = strongRef

Debug.Assert strongRef.address = weakRef.Object.address 'fine
    
Set strongRef = Nothing
Debug.Assert weakRef.Object Is Nothing       'fine if step through, crashes if F5

This is buggy behaviour; the WeakReference class is designed in such a way that once the parent reference is destroyed, the weak reference should return Nothing rather than attempt blindly to dereference the parent ObjPtr which would now be pointing to an invalid object instance. The way it does this is explained in detail in the linked question, but essentially it caches the parent object's VTable pointer, then uses this to check the VTable pointer is still valid before every dereference. Basically the class relies on the fact that when the parent object goes out of scope, its memory is reclaimed and so the VTable pointer is overwritten with something else.

That should stop this kind of bug. However it doesn't, and I'm wondering why...


It was my understanding that

Set strongRef = Nothing
  • Calls IUnknown::Release
  • This sets the ref count to zero, the object goes out of scope
  • The object is responsible for releasing its own instance memory, so it uses the this pointer (first arg to IUnknown::Release) to zero the instance memory (including the VTable pointer) and free it for use by the VBA memory allocator again
  • Finally the value at VarPtr(strongRef) is set to zero to indicate it is a null object reference

However I think the bug is happening because the instance memory is not reset as soon as the reference count hits zero, so perhaps VBA's implementation of IUnknown::Release marks the memory as "dirty" to be cleared up at a later date by an asynchronous garbage collector? I'm just guessing here. The thing is, if I step through the code line by line then it works fine, or if you hold the WeakReference in a child class then it works fine (see the examples in the linked post).

UPDATE

I just tried, with a custom VBA class for strongRef, e.g.

Class1

Option Explicit

Static Property Get address() As Double
    Dim value As Double
    If value = 0 Then value = [RandBetween(1,1e10)]
    address = value
End Property

...then I don't get a crash! So it's definitely something to do with specific implementations of IUnknown::Release and is probably why the author of that code never noticed the bug.

6
  • 1
    "Basically the class relies on the fact that when the parent object goes out of scope, its memory is reclaimed and so the VTable pointer is overwritten with something else." Then it only works sometimes by pure luck. The freed memory may happen to be overwritten by something else, or it may not be, or that "something else" may accidentally have the same value as the original (e.g. an object of the same type happens to be constructed in the memory previously occupied by the now-destroyed object). Commented Aug 7, 2020 at 13:41
  • @IgorTandetnik exactly, and I highlighted that in the review. But in this mode of crashing, where the parent is set to Nothing then immediately accessed, by my understanding the memory should be released - but the crash seems guaranteed so it's not failing by chance but instead by some mechanism that I don't understand. Commented Aug 7, 2020 at 13:44
  • @IgorTandetnik TBH I'm in split minds whether this VTable check is a good idea - the thing about VBA is that it's hosted and crashing Excel on a bad pointer is a pain from a developer's p.o.v. because it often means losing a lot of work in progress. The tradition of going down in flames when you get a bad pointer makes sense for compiled languages where the source code is safe, but not for VBA. And the luck element is there, but on 32 bit systems the chances of it going wrong by randomness are low, on 64 bit pretty much zero. Still, tradeoff between hard to diagnose bugs or frequent crashes Commented Aug 7, 2020 at 13:52
  • "The object is responsible for releasing its own instance memory, so it uses the this pointer (first arg to IUnknown::Release) to zero the instance memory (including the VTable pointer) and free it for use by the VBA memory allocator again" No and yes. Yes, the object would release the memory, so it could be reused. No, the object is not required to zero it out first, and basically no one does; it's a waste of CPU cycles. For a while, the now-freed memory will preserve its original contents. If and when the memory is actually reused for something else, then it's likely to get overwritten. Commented Aug 7, 2020 at 13:52
  • 1
    You cannot implement a weak reference where the strong reference doesn't know about the weak references. After all, when the final strong reference needs to let go of the object it references, it needs to notify the weak references, that the object they were referencing is gone. You cannot infer this information by looking at random places in memory. Classic COM doesn't provide the infrastructure necessary for weak references out of the box. The Windows Runtime, on the other hand, does. Commented Aug 7, 2020 at 15:30

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.