Skip to content

Mark lambda conversions as side-affecting#25917

Merged
agocke merged 5 commits intodotnet:dev15.7.xfrom
agocke:bad-lambda-binding
Apr 5, 2018
Merged

Mark lambda conversions as side-affecting#25917
agocke merged 5 commits intodotnet:dev15.7.xfrom
agocke:bad-lambda-binding

Conversation

@agocke
Copy link
Copy Markdown
Member

@agocke agocke commented Apr 4, 2018

Customer scenario

The scenario listed in #22700 demonstrates the problem. The direct cause
of the crash is that local lowering produces two instances of the lambda and
lamda conversions in the resulting bound nodes. This crashes the closure
conversion pass since the same lambda cannot appear in two places in the tree.
However, even if that were allowed, this would violate the language
specification.

Placing a lambda inside an indexer which is nested in a compound
assignment creates two calls which contain the delegate produced by the
lambda expression. Since the delegate produced by the conversion can be
compared by-reference to any other delegate produced by the same lambda,
the code must be referentially transparent -- it must produce the same result
when evaluated repeatedly. This could be achieved by spilling the evaluation
result into a temporary. However, the local lowering only spills if the expression
is side-affecting (which lambda conversions are not), when what it actually
needs is for the expression to be referentially transparent.

The fix is small: mark lambda conversions as side-affecting. There are
certain cases, like the creation of temporaries for out-of-order calls
with named parameters, where the lambda conversion is only required to
be non-side-affecting instead of referentially transparent, but it does
not currently seem worth the implementation effort to split up the code
path.

Bugs this fixes

#22700

Workarounds, if any

Manually spill the conversion into a local variable.

Risk

Very small, focused change. Introduces a temp, so this is more conservative than alternatives.

Performance impact

None. No significant amount of new code.

Is this a regression from a previous update?

No.

Root cause analysis

Very rare scenario with very little use in the real world.

How was the bug found?

Customer reported

The scenario listed in dotnet#22700
demonstrates the problem. The direct cause of the crash is that local
lowering produces two instances of the lambda and lamda conversions in
the resulting bound nodes. This crashes the closure conversion pass
since the same lambda cannot appear in two places in the tree. However,
even if that were allowed, this would violate the language
specification.

Placing a lambda inside an indexer which is nested in a compound
assignment creates two calls which contain the delegate produced by the
lambda expression. If the expression kind is marked as side-affecting it
will be spilled into a temporary variable. However, the local lowering
doesn't distinguish between whether an expression is side-affecting and
whether or not it is referentially transparent. In this case, both
matter. Since the delegate produced by the conversion can be compared by
reference to any other delegate produced by the same lambda, the code
must be referentially transparent -- it must produce the same result
when evaluated repeatedly.

The fix is small: mark lambda conversions as side-affecting. There are
certain cases, like the creation of temporaries for out-of-order calls
with named parameters, where the lambda conversion is only required to
be non-side-affecting instead of referentially transparent, but it does
not currently seem worth the implementation effort to split up the code
path.

Fixes dotnet#22700
@agocke agocke added this to the 15.7 milestone Apr 4, 2018
@agocke agocke requested review from a team and VSadov April 4, 2018 05:34
@agocke agocke changed the title Mark lambda conversions as side-effecting Mark lambda conversions as side-affecting Apr 4, 2018
@agocke
Copy link
Copy Markdown
Member Author

agocke commented Apr 4, 2018

cc @jaredpar for ask-mode approval

break;

case ConversionKind.AnonymousFunction:
case ConversionKind.MethodGroup:
Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs Apr 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConversionKind.MethodGroup [](start = 37, length = 26)

Let's investigate whether this change is going to have any impact on scenarios involving Linq expression trees #Closed

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a similar test using an expression lambda. Please let me know if there's something else you were thinking of.

@AlekseyTs
Copy link
Copy Markdown
Contributor

AlekseyTs commented Apr 4, 2018

Done with review pass (iteration 1) #Closed

Console.WriteLine(object.ReferenceEquals(_f1, _f2));
}

int this[Expression<Func<int>> f] {
Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs Apr 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expression<Func> [](start = 13, length = 21)

If this test is in response to my comment about expression trees, this isn't the scenario. The scenario is when the expression that uses lambda as an argument is converted to an expression tree, not just the lambda itself #Closed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We now put lambda or a delegate in a temp in some cases, that might affect our ability to create an expression tree.


In reply to: 179239269 [](ancestors = 179239269)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! Thanks I will try to write such a test.

Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs Apr 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that your change affects more scenarios, not just compound assignments.


In reply to: 179240865 [](ancestors = 179240865)

@agocke
Copy link
Copy Markdown
Member Author

agocke commented Apr 4, 2018

@AlekseyTs I found a case where this code path could be hit using the changed baseline from the PDB tests. It doesn't appear to produce any change in the expression tree. The only other two places this change affects, compound assignments and named parameter re-ordering, can't be explicitly specified in an expression tree.

Copy link
Copy Markdown
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@VSadov
Copy link
Copy Markdown
Member

VSadov commented Apr 4, 2018

The root cause of the crash is that we have code that relies on identity of bound nodes.
I wonder if there are no other cases besides lambdas where this could happen. - I.E. if we rely on identity of a BoundLocalAccess somewhere, could we get in the same trouble?

@AlekseyTs
Copy link
Copy Markdown
Contributor

The root cause of the crash is that we have code that relies on identity of bound nodes.

Even though that is true, I believe there would be a behavior problem as we would be creating two delegates instead of one.

I wonder if there are no other cases besides lambdas where this could happen. - I.E. if we rely on identity of a BoundLocalAccess somewhere, could we get in the same trouble?

In general, we should not be relying on identity of bound nodes because it is rather common to reuse the same nodes in multiple places in the same tree.


return;

bool IsLambdaConversion(BoundExpression expr)
Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs Apr 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsLambdaConversion [](start = 17, length = 18)

I think our convention is to start local function names with a lower-case letter. #Closed

Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (iteration 4)

@jaredpar
Copy link
Copy Markdown
Member

jaredpar commented Apr 5, 2018

test windows_debug_unit64_prtest please

@jaredpar
Copy link
Copy Markdown
Member

jaredpar commented Apr 5, 2018

test windows_release_unit64_prtest please

@jaredpar
Copy link
Copy Markdown
Member

jaredpar commented Apr 5, 2018

approved

Copy link
Copy Markdown
Member

@VSadov VSadov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Copy Markdown
Member

@gafter gafter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@agocke agocke merged commit 62535f7 into dotnet:dev15.7.x Apr 5, 2018
@agocke agocke deleted the bad-lambda-binding branch April 5, 2018 17:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants