The attached program simulates a simple reference counted struct. 'fun' is supposed to return a newly initialized S with a count of 1. Instead it calls the destructor dropping the count to zero and then returns a copy of the initialized struct. Leaving out the 'if' statment and returning the fresh 'S' directly does not exibit this behavior.
This issue is critical as it has a high probability to indroduce hard to detect/track down bugs when automatic reference counting is used.
Tested on DMD 2.063.2/Win32 and /Win64 and DMD master/Win32.
Comment #1 by sludwig — 2013-08-09T22:55:04Z
Created attachment 1241
Reproduction case
Comment #2 by maxim — 2013-08-10T00:44:27Z
Reduced:
extern(C) int printf(const char*, ...);
struct S {
static int count;
this(int ignoreme)
{
int oldcount = count;
printf("%X ctor %d=>%d\n", &this, oldcount, ++count);
}
~this()
{
int oldcount = count;
printf("%X dtor %d=>%d\n", &this, oldcount, --count);
}
this(this)
{
int oldcount = count;
printf("%X postblit %d=>%d\n", &this, oldcount, ++count);
}
}
S fun()
{
S s1 = S(42), s2 = S(24);
if (true) return s1;
else return s2;
}
void main()
{
S s = fun();
}
In case of if(true) compiler does not insert postblit call.
Comment #3 by sludwig — 2013-08-17T07:31:21Z
I digged a little in the DMD sources and found a commit by Kenji Hara that at least affects this example and has a commented out code block that looks a little suspicious : https://github.com/D-Programming-Language/dmd/commit/b4bc25d72e601436f3999abc5c9c31e464385052#L4R1241
Changing "#if 0//DMDV2" back to "#if DMDV2" inserts a proper postblit call, but then the returned t has its initialized field set to false. This does not happen with the "#if 0" AFAICS. Unfortunately I know far to less about the mechanics at work there to make an informed attempt to fix this.
Comment #4 by maxim — 2013-08-17T09:26:45Z
(In reply to comment #3)
> I digged a little in the DMD sources and found a commit by Kenji Hara that at
> least affects this example and has a commented out code block that looks a
> little suspicious :
> https://github.com/D-Programming-Language/dmd/commit/b4bc25d72e601436f3999abc5c9c31e464385052#L4R1241
>
> Changing "#if 0//DMDV2" back to "#if DMDV2" inserts a proper postblit call, but
> then the returned t has its initialized field set to false. This does not
> happen with the "#if 0" AFAICS. Unfortunately I know far to less about the
> mechanics at work there to make an informed attempt to fix this.
Sounds like a regression.
The original test case still fails on DMD HEAD:
---
0018FD74 this() 0
0018FD75 this(this) 1
0018FD74 ~this() 2
0018FD9C ~this() 1
core.exception.AssertError@app(47): Assertion failure
---
This is due to the last destructor running on an uninitialized instance (initialized == false).
Comment #8 by k.hara.pg — 2013-09-29T08:36:03Z
(In reply to comment #7)
> The original test case still fails on DMD HEAD:
>
> ---
> 0018FD74 this() 0
> 0018FD75 this(this) 1
> 0018FD74 ~this() 2
> 0018FD9C ~this() 1
> core.exception.AssertError@app(47): Assertion failure
> ---
>
> This is due to the last destructor running on an uninitialized instance
> (initialized == false).
To me it looks like that the original test case contains a bug.
In S.this(this), `initialized` field is incorrectly set to false. It will stop to decrement S.count at the destruction of the copied objects. Therefore the last assertion in main fails because S.count == 1.
Comment #9 by sludwig — 2013-09-29T09:20:09Z
Sorry, you are absolutely right. The "initialized = false" was supposed to go to the destructor instead to test if the destructor is called twice on the same instance. I'm still seeing a similar issue in my project, but I can't reproduce it with the fixed this(this).