cat > bug.d << CODE
import std.stdio;
struct Vector
{
ubyte[] opSlice()
{
writeln("opslice");
return buf[];
}
~this()
{
writeln("dtor");
}
ubyte[4] buf;
}
void bar(ubyte[])
{
writeln("bar");
}
void main()
{
bar(Vector()[]);
}
CODE
dmd -inline -run bug
---
opslice
dtor <- !!! destroyed
bar <- stale reference
---
The order of evaluation changes when -inline is passed to the compiler. With that the destructor runs before the function call finishes, thus possibly passing a stale reference.
Also see https://github.com/rejectedsoftware/vibe.d/pull/1578 and https://github.com/etcimon/botan/issues/23.
Comment #1 by code — 2016-11-01T01:11:17Z
Turns out this is actually a regression introduced by https://github.com/dlang/dmd/pull/5292.
Also here is the test case with an assertion instead of writeln.
cat > bug.d << CODE
struct Vector
{
this(ubyte a)
{
buf[] = a;
}
ubyte[] opSlice()
{
return buf[];
}
~this()
{
buf[] = 0;
}
ubyte[4] buf;
}
void bar(ubyte[] v)
{
assert(v[0] == 1);
}
void main()
{
bar(Vector(1)[]);
}
CODE
dmd -inline -run bug
Comment #2 by bugzilla — 2018-12-14T06:57:57Z
A further reduction:
struct Vector {
this(ubyte a) {
pragma(inline, false);
buf = a;
}
~this() {
pragma(inline, false);
buf = 0;
}
ubyte buf;
}
void bar(ubyte* v) {
pragma(inline, true);
assert(*v == 1);
}
void main() {
bar(&Vector(1).buf);
}
It's the inlining of bar() that elicits the bug.
Comment #3 by bugzilla — 2018-12-14T11:05:51Z
(In reply to Walter Bright from comment #2)
> It's the inlining of bar() that elicits the bug.
Looking at the AST for main() without inlining:
_D4test3barFPhZv call (dctor info ((__slVecto3 = 0) , (Vector.ctor call (1 param &__slVecto3))));
ddtor (Vector.dtor call &__slVecto);
0L ;
and it looks correct. With inlining:
v = (dctor info ((__slVecto3 = 0) , (Vector.ctor call (1 param &__slVecto3))));
ddtor (Vector.dtor call &__slVecto3);
(*v == 1) || (_d_assertp call (19L param #__a6_746573742e64));
0L ;
and the destructor call is clearly too soon.