digitalmars.D - Nasty corner case behaviour
- H. S. Teoh (47/47) Apr 17 2019 Got bitten by this today:
- Suleyman (28/29) Apr 18 2019 The value of flag is lost.
- Nick Sabalausky (Abscissa) (2/4) Apr 18 2019 Yeeouch! Bugzilla issue #?
- H. S. Teoh (6/11) Apr 19 2019 Just filed a new bug:
- Suleyman (27/28) Apr 19 2019 Sneaky enough.
- H. S. Teoh (15/22) Apr 19 2019 [...]
- Meta (3/44) Apr 19 2019 I find this one a bit confusing. Shouldn't the struct be moved to
- Suleyman (8/11) Apr 19 2019 It does. the `this` parameter in the following function is copied
Got bitten by this today: import std.algorithm : filter, map; import std.stdio; struct S { bool flag; auto method() { return [ 1 ].filter!(e => flag); } } void main() { auto s = S(true); writeln(s.method); auto arr = [ s ]; writeln(arr[0].method); auto mappedArray = arr.map!(e => e.method); writeln(mappedArray.front); writeln(mappedArray); } Expected output: [1] [1] [1] [[1]] Actual output: [1] [1] [1] [[]] Exercise for the reader: explain the problem. Hint: it's not a bug in Phobos. If method() is changed to the following, the problem goes away: auto method() { auto _flag = flag; return [ 1 ].filter!(e => _flag); } It should be obvious by now that the problem is caused by the lambda incorrectly closing over a member variable in a struct instance that subsequently goes out of scope, while the returned closure lives on. Unfortunately, neither -dip25 nor -dip1000 catches this leakage, even if main() is marked safe. T -- Тише едешь, дальше будешь.
Apr 17 2019
On Thursday, 18 April 2019 at 05:01:17 UTC, H. S. Teoh wrote:...The value of flag is lost. To easily see this make flag an int. import std.algorithm : filter, map; import std.stdio; struct S { int flag; auto method() { return [ 1 ].filter!((e) { writeln("\nflag: ", flag); return flag == 10; }); } } void main() { auto s = S(10); auto arr = [ s ]; auto mappedArray = arr.map!(e => e.method); writeln(mappedArray); } Expected output: [[ flag: 10 1]] Actual output: [[ flag: 1872694912 ]]
Apr 18 2019
On 4/18/19 1:01 AM, H. S. Teoh wrote:Got bitten by this today:
Apr 18 2019
On Thu, Apr 18, 2019 at 11:05:10PM -0400, Nick Sabalausky (Abscissa) via Digitalmars-d wrote:On 4/18/19 1:01 AM, H. S. Teoh wrote:Just filed a new bug: https://issues.dlang.org/show_bug.cgi?id=19812 T -- This is not a sentence.Got bitten by this today:
Apr 19 2019
On Thursday, 18 April 2019 at 05:01:17 UTC, H. S. Teoh wrote:...Sneaky enough. The problem is the value copy semantics of S which results in an address to a dead stack frame being saved. If you make it a ref parameter the issue goes away. import std.algorithm : filter, map; import std.stdio; struct S { bool flag; auto method() { return [ 1 ].filter!(e => flag); } } void main() { auto s = S(true); writeln(s.method); auto arr = [ s ]; writeln(arr[0].method); auto mappedArray = arr.map!((ref e) => e.method); writeln(mappedArray.front); writeln(mappedArray); } Output: [1] [1] [1] [[1]]
Apr 19 2019
On Fri, Apr 19, 2019 at 10:17:08PM +0000, Suleyman via Digitalmars-d wrote:On Thursday, 18 April 2019 at 05:01:17 UTC, H. S. Teoh wrote:[...] Using ref solves it for this specific case, but will still exhibit the same problem if the struct referenced by the ref is a temporary due to other factors. E.g., if you pass the struct to a function that then returns map!(S.method) back to the caller, then the ref will still be referencing a temporary that goes out of scope while the lambda still holds the reference. The underlying problem is that the lambda closes over a variable whose lifetime is not guaranteed to be at least the lifetime of the lambda. It's exactly this sort of scoping issues that -dip1000 is supposed to prevent, but it seems somehow this case was missed. T -- IBM = I'll Buy Microsoft!...Sneaky enough. The problem is the value copy semantics of S which results in an address to a dead stack frame being saved. If you make it a ref parameter the issue goes away.
Apr 19 2019
On Thursday, 18 April 2019 at 05:01:17 UTC, H. S. Teoh wrote:Got bitten by this today: import std.algorithm : filter, map; import std.stdio; struct S { bool flag; auto method() { return [ 1 ].filter!(e => flag); } } void main() { auto s = S(true); writeln(s.method); auto arr = [ s ]; writeln(arr[0].method); auto mappedArray = arr.map!(e => e.method); writeln(mappedArray.front); writeln(mappedArray); } Expected output: [1] [1] [1] [[1]] Actual output: [1] [1] [1] [[]] Exercise for the reader: explain the problem. Hint: it's not a bug in Phobos. If method() is changed to the following, the problem goes away: auto method() { auto _flag = flag; return [ 1 ].filter!(e => _flag); } It should be obvious by now that the problem is caused by the lambda incorrectly closing over a member variable in a struct instance that subsequently goes out of scope, while the returned closure lives on. Unfortunately, neither -dip25 nor -dip1000 catches this leakage, even if main() is marked safe. TI find this one a bit confusing. Shouldn't the struct be moved to the heap when the compiler detects that it's being closed over?
Apr 19 2019
On Friday, 19 April 2019 at 23:42:53 UTC, Meta wrote:I find this one a bit confusing. Shouldn't the struct be moved to the heap when the compiler detects that it's being closed over?It does. the `this` parameter in the following function is copied to a heap closure space. auto method() { return [ 1 ].filter!(e => this.flag); } But `this` is a reference so the pointer is copied instead of the value.
Apr 19 2019