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.