import core.memory; // failure case for bug fixed by druntime rev 282 // how it works: // The erroneous code used the first element in the LRU cache to determine the // length of the block, instead of looking at the current blocks blockinfo. So // we need a somewhat alignment of moons to get this to fail. First, we create // the perfect storm: 1) a long blockinfo at the beginning (size = 32), 2) the // second blockinfo is smaller (16 bytes) and 3) a block of memory that is // exactly 16 bytes past the element in the 2nd block. While this seems kind // of unlikely, in code that uses a lot of appending, it can easily happen. // // The bug causes the array appender to think that a pointer in the 16 bytes // past the second blockinfo is actually in the second blockinfo. This is // because it uses the length from the first blockinfo for checking (which is // 32, so since the pointer is only 16 bytes into the block, it is considered // inside). // On top of all this, the third block must be marked as containing pointers, // and the second block not containing pointers. The reason is because, it's // impossible for the runtime to append in place, since the outside pointer // can't possibly match the array length in the block. But because the runtime // copies the blockinfo's attributes when reallocating, it copies the // attributes from the *wrong* block. The only way to make this cause a // problem would be to then collect memory, which should incorrectly deallocate // data that the array containing pointers points to, and then use those // pointers. However, for our purposes, we know this is possible, but we can // deterministically check the attributes of the array after appending and // verify that they are wrong. // void main() { // fill up the cache to make it wrap, The block info cache has 8 elements, // and the first element is not the first one filled, so we want to wrap to // that first element we want to fill in for(int i = 0; i < 7; i++) { auto n = new int[1]; n ~= 1; } // this will allocate a 32-byte block, and appending will insert it into // the cache at the beginning. auto y = new int[4]; y ~= 1; // now, allocate a 16 byte block with pointers, this will be our block that // gets corrupted. The current GC allocates down, so we allocate this one // first. auto x = new char[][1]; // this block contains no pointers, and appending will insert it into the // second element of the cache y = new int[1]; y ~= 1; // sanity check assert((cast(void *)x.ptr - cast(void*)y.ptr) == 16, "Bug check code is wrong, please reorder allocations!"); // verify the noscan flag is 0 on the pointer-containing blocks assert((GC.getAttr(x.ptr) & GC.BlkAttr.NO_SCAN) == 0); x ~= "hello".dup; // this should leave the attributes alone assert((GC.getAttr(x.ptr) & GC.BlkAttr.NO_SCAN) == 0); // fails on 2.042 }