Thread overview
[Issue 9614] Uninitialized holes in function stack frames confuses GC
May 01, 2015
ag0aep6g@gmail.com
Oct 05, 2017
Eugene Wissner
Dec 17, 2022
Iain Buclaw
May 01, 2015
https://issues.dlang.org/show_bug.cgi?id=9614

ag0aep6g@gmail.com changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |ag0aep6g@gmail.com

--- Comment #1 from ag0aep6g@gmail.com ---
A complete test case:

----
import std.stdio;

size_t from_f, from_g;

void main ()
{
    put();
    f();
    writefln("%#x", from_f); /* prints "0x1111" */
    writefln("%#x", from_g); /* prints "0x1111" */
}

void put()
{
    /* Put 0x1111 where there will be a gap between f's stack frame and g's
    stack frame. */
    size_t[2] a;
    a[0] = 0x1111;
}

void f()
{
    size_t a;
    from_f = *(&a - 1); /* This reads put's 0x1111. This isn't so bad as we're
        reading from beyond the live stack. */
    g();
}

void g()
{
    size_t a;
    from_g = *(&a + 3); /* Reads put's 0x1111 again. This is bad, because we're
        reading from the middle of the live stack. If the value were a GC
        pointer, it would keep its allocation alive. */
}
----

Tested with dmd 2.067.1 on linux.
Problem doesn't show with -m32.
ldc doesn't seem to have the issue.

--
October 05, 2017
https://issues.dlang.org/show_bug.cgi?id=9614

Eugene Wissner <belka@caraus.de> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |belka@caraus.de

--- Comment #2 from Eugene Wissner <belka@caraus.de> ---
This problem affects dmd, with or without -m32, and ldc aswell.

When referencing memory via a variable on the stack, by the time the garbage collector runs, due to alignment and =voids the stack reference to the variable has not necessarily been overwritten. As a result, memory may be kept alive by garbage pointers.

Even calling the GC manually high up the stack does not necessarily solve this, as by the time the GC determines the stack pointer, it's already several stackframes down from usercode.

This is especially problematic if the pointer is to a heavily-crossreferenced data structure, such as an XML tree, where a single pointer anywhere into the tree may keep the whole thing alive.

The correct solution would be to find a way to make stack garbage collection type-aware; however, the mechanics of this heavily depend on the compiler backend.

A compiler-level workaround may be to add a flag to zero any gaps in the stack and initialize void variables to 0.

Alternatively, a user-level workaround may be to periodically wipe the stack somewhere early in the calltree.


import core.memory, std.stdio, core.thread;

void test() {
  // Allocate some memory and store it on a variable on the stack.
  auto array = new ubyte[128*1024*1024];
  // The variable's lifetime ends here.
}

void purify() {
  ubyte[0x1000] filler = void;
  import core.stdc.string: memset;
  // To ensure the compiler doesn't optimize out the unused variable, zero it
manually.
  memset(filler.ptr, 0, 0x1000);
}

void main() {
  writeln("Allocate 100MB");
  auto before = GC.stats.usedSize;
  test();
  // purify();
  GC.collect();
  auto after = GC.stats.usedSize;
  assert(after < before + 100*1024*1024, "GC failed to free the allocation!");
  writeln("GC successfully freed allocated memory.");
}

--
December 17, 2022
https://issues.dlang.org/show_bug.cgi?id=9614

Iain Buclaw <ibuclaw@gdcproject.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
           Priority|P2                          |P4

--