Stack variable lifetimes can end before their scope

Consider the following:

import std.stdio : writeln;
import core.memory : GC;

struct Hidden {
align(1):
    int spacer;
    C obj;
}

Hidden hidden;
bool destroyed;

class C {
    this() {
        hidden.obj = this;
    }

    ~this() {
        destroyed = true;
    }
}

void check() {
    writeln("C is ", destroyed ? "destroyed" : "alive");
}

void main() {
    auto c = new C;
    check; // C is alive
    GC.collect;
    GC.collect;
    check; // C is destroyed
    writeln("end main");
}

You might think that the C instance should still be alive by the second check because the object clearly has a reference in the c local variable. But to D, that reference is dead as soon as it was created because c is not referenced later in the function. The GC also can't find the reference in the Hidden struct, because it's on a misaligned pointer. The dead reference and the GC-invisible reference, by their powers combined, allow the GC to collect the object while the c variable is still in scope. The two collection runs are just about clearing an accidental reference to the class from a CPU register.

This is not a bug, and won't be fixed. In order to run into it in practice, you need A) a class, rather than a struct, and B) to have written your code to assume the liveness of the object even past there being any references to it in a function, and C) for all other references to your object to be unfindable by the GC (most likely by using a kernel or C API that obscures the pointer), and D) for a GC run to decide to collect the object. So you can avoid this easily with a struct, or by avoiding such assumptions, or by preserving live references when working with foreign APIs, or by preventing the GC from running when the object is in a collectible state.

Should this come up (it's an incredibly rare condition to hit), there's also GC.addRoot and the 'keepalive' dub package.

Discussion