Bug 8269 – The 'with statement' does not observe temporary object lifetime
Status
RESOLVED
Resolution
FIXED
Severity
major
Priority
P3
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2012-06-19T12:23:00Z
Last change time
2014-08-10T08:56:44Z
Keywords
bounty, pull, wrong-code
Assigned to
yebblies
Creator
acehreli
Comments
Comment #0 by acehreli — 2012-06-19T12:23:56Z
The spec at
http://dlang.org/statement.html#WithStatement
says:
<quote>
The WithStatement
with (expression)
{
...
ident;
}
is semantically equivalent to:
{
Object tmp;
tmp = expression;
...
tmp.ident;
}
</quote>
Unfortunately, the anonymous object in the following code is destroyed even before entering the 'with' scope:
import std.stdio;
struct S {
this(int i = 0)
{
writeln("constructed");
}
~this()
{
writeln("destructed");
}
}
void main() {
with(S(1)) {
writeln("inside 'with' statement");
}
}
Observed output:
constructed
destructed
inside 'with' statement
Expected output:
constructed
inside 'with' statement
destructed
Ali
Comment #1 by andrej.mitrovich — 2013-02-08T14:08:31Z
*** Issue 9145 has been marked as a duplicate of this issue. ***
Comment #2 by andrej.mitrovich — 2013-02-08T14:08:58Z
Slightly renamed title to make it more searchable.
Comment #3 by andrej.mitrovich — 2013-03-23T05:18:18Z
(In reply to comment #0)
> The spec at
>
> http://dlang.org/statement.html#WithStatement
>
> says:
>
> <quote>
> The WithStatement
> with (expression)
> {
> ...
> ident;
> }
> is semantically equivalent to:
> {
> Object tmp;
> tmp = expression;
> ...
> tmp.ident;
> }
> </quote>
What it does end up doing is inject an initializer and a comma expression, and then takes the address of that. It's totally bizarre..
Comment #4 by code — 2013-10-25T09:57:03Z
*** Issue 11351 has been marked as a duplicate of this issue. ***
Comment #5 by verylonglogin.reg — 2014-07-11T10:21:49Z
Smaller testcase:
---
struct S
{
bool alive = true;
~this() { alive = false; }
}
void main()
{
with(S()) // <-- `S` is created and destructed here
assert(alive); // fails
}
---
This is a major issue as it breaks RAII.
Comment #6 by andrei — 2014-07-29T18:36:14Z
@yebblies: why did you remove the "preapproved" tag?
Comment #7 by yebblies — 2014-07-30T05:52:38Z
(In reply to Andrei Alexandrescu from comment #6)
> @yebblies: why did you remove the "preapproved" tag?
I don't see the point of it on a wrong code bug. Either the bug is valid and it will be fixed, or it isn't and it won't.
Comment #8 by andrei — 2014-07-30T14:23:04Z
I see - fine. I was hoping to boost its visibility :o).
Comment #9 by yebblies — 2014-07-30T14:37:13Z
(In reply to Andrei Alexandrescu from comment #8)
> I see - fine. I was hoping to boost its visibility :o).
I can't speak for the other dmd contributors, but I never look at the preapproved list. I will look at the ice and wrong-code lists when I'm in that kind of mood.
If you really want to boost its profile, tou could try putting a big bounty on it :D
This is actually a perfect example of a good bug for a bounty - it is non-controversial, and has a small, well-defined scope. It's almost entirely debugging and implementation.
Anyway, it's next on my list, assuming I don't get distracted. As Andrej noted the implementation of 'with' is a big weird and needs some work.
I would suspect that this is likely because of how the with statement currently
expands itself, although it's certainly possible that it's e2ir (though I don't
think it actually is present at the e2ir layer, was about to check that)
Comment #12 by yebblies — 2014-08-09T06:04:24Z
It is how it expands itself.
with(exp) {...} gets re-written to
with(auto tmp = exp, tmp) {...}
and then
with(auto withptr = &(auto tmp = exp, tmp)) {...}
which ends up looking like
auto withptr = &(auto tmp = exp, tmp)
...
in the glue layer. Since tmp is local to the expression, the dtor code is inserted right after it.
https://github.com/D-Programming-Language/dmd/pull/3855
Comment #13 by damianday — 2014-08-09T10:20:59Z
Why would the with statement be creating a temporary in the first place? It seems terribly inefficient, surely it can just alias the original object and be done with it?
Comment #14 by yebblies — 2014-08-09T11:18:42Z
(In reply to Damian from comment #13)
> Why would the with statement be creating a temporary in the first place? It
> seems terribly inefficient, surely it can just alias the original object and
> be done with it?
It does when it can, but it can't when the with argument is an rvalue.
eg
Struct generateStructForMe() { ... }
with(generateStructForMe())
{
auto x = member1 + member2;
}
You want this to expand to (and in lvalue cases it will)
auto withptr = &generateStructForMe();
auto x = withptr.member1 + withptr.member2;
But this can't work, because the struct has to be stored somewhere before you can take its address.
The struct literals in these examples are also rvalues and work the same way. Class references and struct pointers do not have this problem an can be copied freely.
Comment #15 by blah38621 — 2014-08-09T19:23:30Z
What does it do about postblits in this case?
Comment #16 by yebblies — 2014-08-10T07:36:44Z
(In reply to Orvid King from comment #15)
> What does it do about postblits in this case?
The same thing that happens with
auto tmp = someRvalue();
with(tmp)
{
}
Comment #17 by github-bugzilla — 2014-08-10T08:56:44Z