Listen to the Memory Manager

Let's say you have a nice, working Delphi application. One fine day, for no reason whatsoever, you decide to run your fine and bug free application with FastMM in full debug mode. Just for fun... and suddenly, out of the blue, the app starts crashing with an AV reading of address 0x80808088. What is going on... ????

When a seemingly working application suddenly starts misbehaving, the first instinct would be to look at the latest code change in searching for clues. In the above case, the only change was turning on FastMM debug mode. Could this be a bug in FastMM?

While all software has bugs and it is perfectly possible that FastMM has bugs, too, it is highly unlikely that this is FastMM's fault.

If turning on the CatchUseOfFreedInterfaces define in FastMM configuration shows an attempt to use an interface on a freed object, you have found the culprit. It may require further debugging and inspecting the code, but at least you will know what to look for.

One of the "problems" with TComponent (and subsequently its descendants, including all visual controls in the VCL and FMX frameworks) is that it implements interfaces, but has reference counting disabled in the _AddRef and _Release methods so that it can be used like a regular class.

However, it is often forgotten that in such cases _AddRef and _Release calls, even though they don't do any actual counting, will still be inserted by the compiler and called for any interface reference to such an object instance. So if there is an alive interface reference to the object after calling Free, that interface reference will contain a dangling pointer and when it goes out of scope, _Release will be called on an already nuked object. While you may get away with such a call without FastMM in debugging mode, if it is turned on it will correctly identify the problem.

The following is a very simple test case that will blow up with FastMM in debug mode:
procedure TestComponent; var Comp: TComponent; Intf: IInterface; begin Comp := TComponent.Create(nil); Intf := Comp; Comp.Free; end;

Since the interface reference Intf is automatically managed, it will go out of scope (and invoke the reference counting mechanism) in the epilogue of the TestComponent procedure, invoking _Release on the Comp object instance, which had already been released.

If that interface reference is cleared before the component instance is released, the code will work properly.
procedure TestComponent; var Comp: TComponent; Intf: IInterface; begin Comp := TComponent.Create(nil); Intf := Comp; .... Intf := nil; Comp.Free; end;

While code accessing a released object instance may work properly because remnants of the object are still sitting in memory and nothing bad actually happens, such code can just as easily cause memory corruption and subtle bugs, if the memory where a particular object instance was occupying was reused for something else in the meantime.


Popular posts from this blog

Coming in Delphi 12: Disabled Floating-Point Exceptions

Assigning result to a function from asynchronous code

Beware of loops and tasks