-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Conversation
@dotnet/roslyn-compiler - please review |
IL_002e: callvirt ""bool object.Equals(object)"" | ||
IL_0033: call ""void System.Console.WriteLine(bool)"" | ||
IL_0038: ret | ||
IL_0010: ldloc.0 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Latest delta looks good, thanks! |
@dotnet-bot test windows_release_unit64_prtest please |
Should an entry be added to breaking changes documentation? #Resolved |
Yes. We should have done this last time when we (almost) fixed this bug. In reply to: 367762051 [](ancestors = 367762051) |
IL_0026: ret | ||
} | ||
"); | ||
} |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean &&
? #Resolved
There was a problem hiding this comment.
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? |
There was a problem hiding this comment.
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 ? |
There was a problem hiding this comment.
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
There was a problem hiding this 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).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
CC:@jaredpar for the ask mode approval |
apt-get install failed on ubuntu |
@dotnet-bot test ubuntu_16_mono_debug_prtest please |
approved pending suites |
Ugh - now a failure about "CreateCompilationWithMscorlibAndSystemCore" I guess the megachange with test helpers has been merged :-) |
…t can be passed by reference is prevented from doing so by wrapping in a IdentityValue conversion node.
…only purpose of preventing passing by reference.
@dotnet-bot test ubuntu_16_mono_debug_prtest please |
something with apt-get again ... |
@dotnet-bot test windows_release_vs-integration_prtest please |
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 - likex
, the code may subtly change its semantics by passing thex
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
in
parameter of a method/operator/conversionIn 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