Hi,

A while ago, I've tracked down the cause of an insidious memory corruption problem in one of my D programs. The problem was caused by an inadvertent allocation in a destructor (called by the GC). The current GC implementation is completely unprepared to handle such a situation - an allocation during a GC run will break the GC's invariants, and will ultimately result in memory corruption. I've filed this problem as issue 5653.

I've created a simple test case which illustrates the problem:

//////////////////////////////////////////////////////////////////////////////
const message = "Hello, world!";

char[] s = null;

class C
{
        ~this()
        {
                s = message.dup;
        }
}

version(D_Version2)
        import core.memory;
else
        import std.gc;

void main()
{
        C c;
        c = new C();
        c = new C(); // clobber any references to first instance

        version(D_Version2)
                GC.collect();
        else
                fullCollect();

        assert(s !is null, "Destructor wasn't called");
        assert(s == message, "Memory was corrupted");
}
//////////////////////////////////////////////////////////////////////////////

The exact reason the above program corrupts memory is that .dup will allocate memory by taking an item from a free list. However, after the destructor returns, the GC continues on to rebuild the free list with the information it had before the .dup allocation. The first machine word of the allocated region will be overwritten with a pointer to the next item in the free list.

I wrote a patch to the D1 GC to forbid allocations from destructors, and was considering to port it to D2 and wrap it in a pull request, but realized that my patch breaks the GC in case a destructor throws. However, looking at the GC code it doesn't look like the GC is prepared to handle that situation either... while I haven't noticed any ways in which it could lead to memory corruption, if the program would catch exceptions propagated through a GC run, it could lead to persistent memory leaks (inconsistency between flags and freelists) and destructors of other objects being called several times (due to free lists not being rebuilt).

Thus, my question is: what's the expected behavior of D programs when a destructor throws?

--
Best regards,
 Vladimir                            mailto:[email protected]

Reply via email to