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.