Bug 17897 – Incorrect number of destructor calls in example

Status
RESOLVED
Resolution
DUPLICATE
Severity
major
Priority
P1
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2017-10-13T12:08:31Z
Last change time
2017-12-09T15:05:01Z
Keywords
wrong-code
Assigned to
No Owner
Creator
Jack Applegame

Comments

Comment #0 by japplegame — 2017-10-13T12:08:31Z
Test case: import std.stdio; struct Foo { this(int) {} ~this() {} } struct Bar { this(this) { writefln("Bar.this(this): %X", &this); } this(Foo, Foo) { writefln("Bar.this(int): %X", &this); } ~this() { writefln("Bar.~this(): %X", &this); } } void fun(Bar n) { writefln("fun: %X", &n); } void main() { Foo foo; fun(Bar(foo, Foo(0))); } Result: Bar.this(int): XXXXXXXXXXXX fun: XXXXXXXXXXXX Bar.~this(): XXXXXXXXXXXX Bar.~this(): XXXXXXXXXXXX Expected: Bar.this(int): XXXXXXXXXXXX Bar.this(this): XXXXXXXXXXXX fun: XXXXXXXXXXXX Bar.~this(): XXXXXXXXXXXX Bar.~this(): XXXXXXXXXXXX Discussion: https://forum.dlang.org/post/[email protected]
Comment #1 by schveiguy — 2017-10-13T13:29:52Z
Postblit is not called for moves.
Comment #2 by schveiguy — 2017-10-13T13:33:25Z
Sorry, I didn't mean to commit so early. A postblit is not called for a move, which is done for rvalues being sent into a function: import std.stdio; struct S { this(this) { writeln("postblit"); } } void foo(S s) { } void main() { foo(S()); // no postblit, this is an rvalue. The struct isn't actually moved anyway. S s; foo(s); // postblit called, because we made a copy of s onto the stack to send into foo. }
Comment #3 by alphaglosined — 2017-10-13T13:39:57Z
(In reply to Steven Schveighoffer from comment #2) > Sorry, I didn't mean to commit so early. > > A postblit is not called for a move, which is done for rvalues being sent > into a function: snip Except this isn't just a move. No, the number of destructor calls must be equal to the number of constructor/postblit calls. Otherwise reference counting types are not going to work. Postblit must be called in the given example. Count: 1 - create Count: 1 - fun call Count: 0 - destructor (deallocate memory) Count: segfault, memory has already been freed - destructor (deallocate memory) Something is still wrong in the given example.
Comment #4 by simen.kjaras — 2017-10-13T13:51:48Z
(In reply to Steven Schveighoffer from comment #1) > Postblit is not called for moves. You're misreading the bug report (not surprising, as it was marked incorrectly). Expanded example: import std.stdio; struct Foo { this(int) {} ~this() {} } struct Baz {} struct Bar(T) { this(T a) { writefln("Bar.this(T): %X", &this); } this(T a, T b) { writefln("Bar.this(T, T): %X", &this); } ~this() { writefln("Bar.~this(): %X", &this); } } void fun(T)(Bar!T n) { writefln("fun: %X", &n); } unittest { // 1 // Basically what Jack posted: int n; writefln("\nunittest 1: %X", &n); fun(Bar!Foo(Foo(0), Foo(0))); } unittest { // 2 // Single Foo in Bar's constructor: int n; writefln("\nunittest 2: %X", &n); fun(Bar!Foo(Foo(0))); } unittest { // 3 // Saving Foo(0) to a temporary: int n; writefln("\nunittest 3: %X", &n); auto foo = Foo(0); fun(Bar!Foo(foo, foo)); } unittest { // 4 // using a type without constructor and destructor: int n; writefln("\nunittest 4: %X", &n); fun(Bar!Baz(Baz(), Baz())); } Which gives this output: unittest 1: 19FDAC Bar.this(T, T): 19FDB3 fun: 19FD80 Bar.~this(): 19FD80 Bar.~this(): 19FDB3 unittest 2: 19FDB4 Bar.this(T): 19FDB8 fun: 19FD8C Bar.~this(): 19FD8C unittest 3: 19FDA8 Bar.this(T, T): 19FDAD fun: 19FD7C Bar.~this(): 19FD7C unittest 4: 19FDB8 Bar.this(T, T): 19FDBC fun: 19FD94 Bar.~this(): 19FD94 As we can see for unittest1, the destructor is called twice - once for the temporary in the unittest block, and once when we exit fun(). This is caused by some interaction with Foo's constructor and destructor, as evinced by the fact that commenting out either fixes the problem (see unittest 4). Other things that fixes it include changing Bar's constructor to take a single Foo instead of two (as seen in unittest 2), and saving Foo(0) to a temporary before passing it to Bar() (as seen in unittest 3).
Comment #5 by schveiguy — 2017-10-13T13:58:55Z
The bug in the example is that the destructor is called twice, not that postblit is not called. I assumed you were going to file a bug on the destructor calls, but looks like this is it, so I'll just change the title. (In reply to Simen Kjaeraas from comment #4) > (In reply to Steven Schveighoffer from comment #1) > As we can see for unittest1, the destructor is called twice - once for the > temporary in the unittest block, and once when we exit fun(). No, it's called once for the local in fun, and another time for no reason (an actual bug). It's still a move.
Comment #6 by schveiguy — 2017-10-13T13:59:23Z
Expected should read: Bar.this(int): XXXXXXXXXXXX fun: XXXXXXXXXXXX Bar.~this(): XXXXXXXXXXXX
Comment #7 by japplegame — 2017-10-13T14:42:48Z
(In reply to Steven Schveighoffer from comment #5) > The bug in the example is that the destructor is called twice, not that > postblit is not called. Not exactly. In fact, it is important that the number of constructor calls was equal to the number of destructors calls (as Richard said). So this bug can be interpreted as missed postblit or extra destructor. I believe that the compiler is free to choose to make a copy or not. Or am I mistaken and the exact behavior is somewhere in specs?
Comment #8 by schveiguy — 2017-10-13T14:58:26Z
I don't know if it's in the spec or not, but clearly, there is no reason for the temporary not to be constructed in-place on the stack for passage into fun. It does not need to construct it and then make a copy, that would be wasteful. Yes, the ultimate guarantee is that the number of postblits/ctor calls must equal the number of dtor calls. But the bug is definitely that an extra dtor call is made. Compiling with -vcg-ast shows no such call, I only see one. The extra dtor is completely erroneous.
Comment #9 by japplegame — 2017-10-26T13:55:58Z
Comment #10 by temtaime — 2017-10-26T13:57:01Z
After fixing https://issues.dlang.org/show_bug.cgi?id=17246 this outputs: Bar.this(int): 14FB84 fun: 14FB20 Bar.~this(): 14FB20 Object gets moved without notification. This should not behave this way
Comment #11 by schveiguy — 2017-10-26T14:10:33Z
(In reply to Temtaime from comment #10) > After fixing https://issues.dlang.org/show_bug.cgi?id=17246 this outputs: > > Bar.this(int): 14FB84 > fun: 14FB20 > Bar.~this(): 14FB20 > > Object gets moved without notification. > This should not behave this way This is perfectly valid. You are allowed to move a struct without calling the postblit. See the spec here: https://dlang.org/spec/struct.html "A struct is defined to not have an identity; that is, the implementation is free to make bit copies of the struct as convenient." In fact, I think we can close this as a duplicate, as it now works properly. However, I'm kind of surprised there is a move here, should be unnecessary if implemented properly.
Comment #12 by schveiguy — 2017-12-09T15:05:01Z
Closing as duplicate *** This issue has been marked as a duplicate of issue 17246 ***