The GC, destructors, exceptions and memory corruption

Vladimir Panteleev vladimir at thecybershadow.net
Thu May 12 21:53:43 PDT 2011


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:vladimir at thecybershadow.net


More information about the Digitalmars-d mailing list