Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent accidental passing by reference of reduced expressions when it was not valid originally. #24957

Merged
merged 6 commits into from
Feb 23, 2018

Conversation

VSadov
Copy link
Member

@VSadov VSadov commented Feb 20, 2018

Fixes:##24806

Customer scenario

Customer writes some code where compiler is able to reduce complex expressions into simpler ones. That makes the resulting code smaller and possibly faster.
However if while doing so a reduction replaced an expression that could not be passed by reference - like (x + 0) into one that can - like x, the code may subtly change its semantics by passing the x by reference.

Generally C# is very explicit about passing variables by reference and (x + 1) would be an error in such contexts.
There are two situations where it is not an error

  • receiver of a struct/generic, method calls
  • ordinary byval argument that is matched to an in parameter of a method/operator/conversion

In the above cases the expressions in the source are required to only be readable, but also can pass the variable by reference if it is possible.
As a result we must be careful to not make passing by reference possible in the process of reduction.

Bugs this fixes

##24806

Workarounds, if any

No workaround. User must be very careful to not use the code as described or not take dependency on "no-aliasing" behavior.

Risk

Ultimately this is a bit of a corner case. Accidental aliasing is relatively hard to achieve and hard to observe. It is unlikely to be breaking in the majority of cases.

We are fixing this to have a rational specified behavior that we can comfortably preserve in the future.

Performance impact

Low perf impact.
The new checks are relatively cheap.
A new node will be allocated in very rare cases and is typically just a node for a node swap. (something needs to be reduced in order for the new node even be needed) - the bound tree cannot get bigger because of this.

Is this a regression from a previous update?

N/A.

Root cause analysis

The problem we are fixing is not new. In some cases the bug was present for more than a decade.
We are adding more contexts now where the bug is observable and therefore we are fixing it here.

How was the bug found?

Customer reported. More scenarios from internal testing.

Test documentation updated?

N/A

@VSadov VSadov requested a review from a team as a code owner February 20, 2018 22:41
@VSadov
Copy link
Member Author

VSadov commented Feb 20, 2018

@dotnet/roslyn-compiler - please review

@gafter gafter self-requested a review February 21, 2018 22:10
IL_002e: callvirt ""bool object.Equals(object)""
IL_0033: call ""void System.Console.WriteLine(bool)""
IL_0038: ret
IL_0010: ldloc.0
Copy link
Member

@gafter gafter Feb 21, 2018

Choose a reason for hiding this comment

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

ldloc [](start = 12, length = 5)

The only change appears to be the addition of these two instructions, which have no effect. Why did this happen? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

The receiver of Equals is an RValue that is lowered into passable by reference sequence. As a result we force a copy now. It is correct but a bit redundant. The JIT will understand this store/load as a NOP, so it is not a big concern. It is very specific to this particular pattern.

It can be rectified, but I did not feel like it is important to be a part of this fix.


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

Copy link
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 small peephole optimization to deal with this case. Thanks for nudging me to do it :-)


In reply to: 169812182 [](ancestors = 169812182,169805013)

Copy link
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:

@gafter
Copy link
Member

gafter commented Feb 22, 2018

Latest delta looks good, thanks!

@VSadov
Copy link
Member Author

VSadov commented Feb 22, 2018

@dotnet-bot test windows_release_unit64_prtest please

@jcouv
Copy link
Member

jcouv commented Feb 22, 2018

Should an entry be added to breaking changes documentation? #Resolved

@VSadov
Copy link
Member Author

VSadov commented Feb 22, 2018

Yes. We should have done this last time when we (almost) fixed this bug.
I will add an entry in the doc.


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

@VSadov
Copy link
Member Author

VSadov commented Feb 22, 2018

The doc update PR is #24999


In reply to: 367763681 [](ancestors = 367763681,367762051)

IL_0026: ret
}
");
}
Copy link
Member

@jcouv jcouv Feb 22, 2018

Choose a reason for hiding this comment

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

Nit: double empty lines below #Resolved

@@ -144,4 +144,9 @@ internal partial class BoundStatementList
protected override ImmutableArray<BoundNode> Children =>
(this.Kind == BoundKind.StatementList || this.Kind == BoundKind.Scope) ? StaticCast<BoundNode>.From(this.Statements) : ImmutableArray<BoundNode>.Empty;
}

internal partial class BoundPassByCopy
Copy link
Member

@jcouv jcouv Feb 22, 2018

Choose a reason for hiding this comment

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

Is there some test(s) for GetOperations? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

The node only appears in the lowered tree, so I am not sure it is observable. I have added a case here mostly out of consistency/completeness.


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

@@ -186,6 +186,24 @@ private BoundExpression VisitExpressionImpl(BoundExpression node)
visited.Type.Equals(node.Type, TypeCompareKind.IgnoreDynamicAndTupleNames) ||
IsUnusedDeconstruction(node));

if (visited != null & visited != node)
Copy link
Member

@jcouv jcouv Feb 22, 2018

Choose a reason for hiding this comment

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

Did you mean &&? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

Short circuiting is not needed here so having no branches could make it a bit faster.
But probably does not make a lot of difference though. I will change it to &&


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

return true;

case BoundKind.DeconstructValuePlaceholder:
// is this always a proxy for a temp local?
Copy link
Member

@jcouv jcouv Feb 22, 2018

Choose a reason for hiding this comment

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

Here's the link to replace the comment: #24160 #Resolved

var result = RefKind.None;
if (!receiver.Type.IsReferenceType && LocalRewriter.CanBePassedByReference(receiver))
{
result = receiver.Type.IsReadOnly ?
Copy link
Member

@jcouv jcouv Feb 22, 2018

Choose a reason for hiding this comment

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

Nit: odd identation. Could fit on one line. #Resolved

Copy link
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.

Done with review pass (iteration 4).

Copy link
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
Member Author

VSadov commented Feb 22, 2018

CC:@jaredpar for the ask mode approval

@VSadov
Copy link
Member Author

VSadov commented Feb 23, 2018

apt-get install failed on ubuntu

@VSadov
Copy link
Member Author

VSadov commented Feb 23, 2018

@dotnet-bot test ubuntu_16_mono_debug_prtest please

@jaredpar
Copy link
Member

approved pending suites

@VSadov
Copy link
Member Author

VSadov commented Feb 23, 2018

Ugh - now a failure about "CreateCompilationWithMscorlibAndSystemCore"

I guess the megachange with test helpers has been merged :-)
Need to rebase.

@VSadov
Copy link
Member Author

VSadov commented Feb 23, 2018

@dotnet-bot test ubuntu_16_mono_debug_prtest please

@VSadov
Copy link
Member Author

VSadov commented Feb 23, 2018

something with apt-get again ...

@VSadov
Copy link
Member Author

VSadov commented Feb 23, 2018

@dotnet-bot test windows_release_vs-integration_prtest please

@VSadov VSadov merged commit fcceec9 into dotnet:dev15.7.x Feb 23, 2018
@VSadov VSadov deleted the fix24806_2 branch February 23, 2018 18:44
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.

4 participants