-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Fix receiver capturing for various extension scenarios #79472
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
Changes from all commits
b079ba0
cf4369f
df39446
47f2aa8
f478367
eacecf1
814baf6
3951497
f6cbd13
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -81,15 +81,24 @@ private BoundExpression VisitAssignmentOperator(BoundAssignmentOperator node, bo | |
| break; | ||
| } | ||
|
|
||
| return MakeStaticAssignmentOperator(node.Syntax, loweredLeft, loweredRight, node.IsRef, used); | ||
| return MakeStaticAssignmentOperator(node.Syntax, loweredLeft, loweredRight, node.IsRef, used, AssignmentKind.SimpleAssignment); | ||
| } | ||
|
|
||
| private enum AssignmentKind | ||
| { | ||
| SimpleAssignment, | ||
| CompoundAssignment, | ||
| IncrementDecrement, | ||
| Deconstruction, | ||
| NullCoalescingAssignment, | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Generates a lowered form of the assignment operator for the given left and right sub-expressions. | ||
| /// Left and right sub-expressions must be in lowered form. | ||
| /// </summary> | ||
| private BoundExpression MakeAssignmentOperator(SyntaxNode syntax, BoundExpression rewrittenLeft, BoundExpression rewrittenRight, | ||
| bool used, bool isChecked, bool isCompoundAssignment) | ||
| bool used, bool isChecked, AssignmentKind assignmentKind) | ||
| { | ||
| switch (rewrittenLeft.Kind) | ||
| { | ||
|
|
@@ -102,15 +111,15 @@ private BoundExpression MakeAssignmentOperator(SyntaxNode syntax, BoundExpressio | |
| indexerAccess.ArgumentNamesOpt, | ||
| indexerAccess.ArgumentRefKindsOpt, | ||
| rewrittenRight, | ||
| isCompoundAssignment, isChecked); | ||
| isCompoundAssignment: assignmentKind == AssignmentKind.CompoundAssignment, isChecked); | ||
|
|
||
| case BoundKind.DynamicMemberAccess: | ||
| var memberAccess = (BoundDynamicMemberAccess)rewrittenLeft; | ||
| return _dynamicFactory.MakeDynamicSetMember( | ||
| memberAccess.Receiver, | ||
| memberAccess.Name, | ||
| rewrittenRight, | ||
| isCompoundAssignment, | ||
| isCompoundAssignment: assignmentKind == AssignmentKind.CompoundAssignment, | ||
| isChecked).ToExpression(); | ||
|
|
||
| case BoundKind.EventAccess: | ||
|
|
@@ -132,7 +141,7 @@ private BoundExpression MakeAssignmentOperator(SyntaxNode syntax, BoundExpressio | |
| throw ExceptionUtilities.Unreachable(); | ||
|
|
||
| default: | ||
| return MakeStaticAssignmentOperator(syntax, rewrittenLeft, rewrittenRight, isRef: false, used: used); | ||
| return MakeStaticAssignmentOperator(syntax, rewrittenLeft, rewrittenRight, isRef: false, used: used, assignmentKind); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -168,7 +177,8 @@ private BoundExpression MakeStaticAssignmentOperator( | |
| BoundExpression rewrittenLeft, | ||
| BoundExpression rewrittenRight, | ||
| bool isRef, | ||
| bool used) | ||
| bool used, | ||
| AssignmentKind assignmentKind) | ||
| { | ||
| switch (rewrittenLeft.Kind) | ||
| { | ||
|
|
@@ -192,7 +202,8 @@ private BoundExpression MakeStaticAssignmentOperator( | |
| false, | ||
| default(ImmutableArray<int>), | ||
| rewrittenRight, | ||
| used); | ||
| used, | ||
| assignmentKind); | ||
| } | ||
|
|
||
| case BoundKind.IndexerAccess: | ||
|
|
@@ -212,7 +223,8 @@ private BoundExpression MakeStaticAssignmentOperator( | |
| indexerAccess.Expanded, | ||
| indexerAccess.ArgsToParamsOpt, | ||
| rewrittenRight, | ||
| used); | ||
| used, | ||
| assignmentKind); | ||
| } | ||
|
|
||
| case BoundKind.Local: | ||
|
|
@@ -248,7 +260,8 @@ private BoundExpression MakeStaticAssignmentOperator( | |
| sequence.Value, | ||
| rewrittenRight, | ||
| isRef, | ||
| used), | ||
| used, | ||
| assignmentKind), | ||
| sequence.Type); | ||
| } | ||
| goto default; | ||
|
|
@@ -264,6 +277,17 @@ private BoundExpression MakeStaticAssignmentOperator( | |
| } | ||
| } | ||
|
|
||
| private bool IsExtensionPropertyWithByValPossiblyStructReceiverWhichHasHomeAndCanChangeValueBetweenReads(BoundExpression rewrittenReceiver, PropertySymbol property) | ||
| { | ||
| return CanChangeValueBetweenReads(rewrittenReceiver, localsMayBeAssignedOrCaptured: true, structThisCanChangeValueBetweenReads: true) && | ||
| IsNewExtensionMemberWithByValPossiblyStructReceiver(property) && | ||
| CodeGen.CodeGenerator.HasHome(rewrittenReceiver, | ||
| CodeGen.CodeGenerator.AddressKind.ReadOnlyStrict, | ||
| _factory.CurrentFunction, | ||
| peVerifyCompatEnabled: false, | ||
| stackLocalsOpt: null); | ||
| } | ||
|
|
||
| private BoundExpression MakePropertyAssignment( | ||
| SyntaxNode syntax, | ||
| BoundExpression? rewrittenReceiver, | ||
|
|
@@ -273,7 +297,8 @@ private BoundExpression MakePropertyAssignment( | |
| bool expanded, | ||
| ImmutableArray<int> argsToParamsOpt, | ||
| BoundExpression rewrittenRight, | ||
| bool used) | ||
| bool used, | ||
| AssignmentKind assignmentKind) | ||
| { | ||
| // Rewrite property assignment into call to setter. | ||
| var setMethod = property.GetOwnOrInheritedSetMethod(); | ||
|
|
@@ -293,25 +318,73 @@ private BoundExpression MakePropertyAssignment( | |
| } | ||
|
|
||
| ArrayBuilder<LocalSymbol>? argTempsBuilder = null; | ||
|
|
||
| bool needSpecialExtensionPropertyReceiverReadOrder = false; | ||
| ArrayBuilder<BoundExpression>? storesOpt = null; | ||
|
|
||
| if (rewrittenReceiver is not null && | ||
| assignmentKind is not (AssignmentKind.CompoundAssignment or AssignmentKind.NullCoalescingAssignment or AssignmentKind.Deconstruction or AssignmentKind.IncrementDecrement) && | ||
| IsExtensionPropertyWithByValPossiblyStructReceiverWhichHasHomeAndCanChangeValueBetweenReads(rewrittenReceiver, property) && | ||
| (arguments.Length != 0 || !IsSafeForReordering(rewrittenRight, RefKind.None))) | ||
| { | ||
| // The receiver has location, but extension property/indexer takes receiver by value. | ||
| // This means that we need to ensure that the receiver value is read after | ||
| // any side-effecting arguments and right hand side are evaluated, so that the | ||
| // setter receives the last value of the receiver, not the value before the | ||
| // arguments/rhs were evaluated. Receiver side effects should be evaluated at | ||
| // the very beginning, of course. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens when the receiver does not have a home; is that always an error scenario? #Resolved
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Not necessary. If the receiver doesn't have home, no one can change its value after we read it. Therefore, we don't need to worry about capturing it by reference and dereferencing it right before invoking the accessor. If we try to do so, the value will be captured in a temp anyway. |
||
|
|
||
| needSpecialExtensionPropertyReceiverReadOrder = true; | ||
| storesOpt = ArrayBuilder<BoundExpression>.GetInstance(); | ||
| } | ||
|
|
||
| #if DEBUG | ||
| BoundExpression? rewrittenReceiverBeforePossibleCapture = rewrittenReceiver; | ||
| #endif | ||
| arguments = VisitArgumentsAndCaptureReceiverIfNeeded( | ||
| ref rewrittenReceiver, | ||
| forceReceiverCapturing: false, | ||
| forceReceiverCapturing: needSpecialExtensionPropertyReceiverReadOrder, | ||
| arguments, | ||
| property, | ||
| argsToParamsOpt, | ||
| argumentRefKindsOpt, | ||
| storesOpt: null, | ||
| storesOpt, | ||
| ref argTempsBuilder); | ||
|
|
||
| arguments = MakeArguments( | ||
| arguments, | ||
| property, | ||
| expanded, | ||
| argsToParamsOpt, | ||
| ref argumentRefKindsOpt, | ||
| ref argTempsBuilder, | ||
| invokedAsExtensionMethod: false); | ||
| if (needSpecialExtensionPropertyReceiverReadOrder) | ||
| { | ||
| #if DEBUG | ||
| Debug.Assert(rewrittenReceiverBeforePossibleCapture != (object?)rewrittenReceiver); | ||
| #endif | ||
| Debug.Assert(storesOpt is { }); | ||
| Debug.Assert(storesOpt.Count != 0); | ||
| Debug.Assert(argTempsBuilder is not null); | ||
|
|
||
| // Store everything that is non-trivial into a temporary; record the | ||
| // stores in storesToTemps and make the actual argument a reference to the temp. | ||
| arguments = ExtractSideEffectsFromArguments(arguments, property, expanded, argsToParamsOpt, ref argumentRefKindsOpt, storesOpt, argTempsBuilder); | ||
|
|
||
| if (!IsSafeForReordering(rewrittenRight, RefKind.None)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider caching and reusing the result of
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I prefer to not complicate code trying to optimize something that is not yet proven to be a problem |
||
| { | ||
| BoundLocal capturedRight = _factory.StoreToTemp(rewrittenRight, out BoundAssignmentOperator assignmentToTemp, refKind: RefKind.None); | ||
| argTempsBuilder.Add(capturedRight.LocalSymbol); | ||
| storesOpt.Add(assignmentToTemp); | ||
| rewrittenRight = capturedRight; | ||
| } | ||
| } | ||
| else | ||
| { | ||
| arguments = MakeArguments( | ||
| arguments, | ||
| property, | ||
| expanded, | ||
| argsToParamsOpt, | ||
| ref argumentRefKindsOpt, | ||
| ref argTempsBuilder, | ||
| invokedAsExtensionMethod: false); | ||
| } | ||
|
|
||
| var sideEffects = storesOpt is null ? [] : storesOpt.ToImmutableAndFree(); | ||
| var argTemps = argTempsBuilder.ToImmutableAndFree(); | ||
|
|
||
| if (used) | ||
|
|
@@ -342,7 +415,7 @@ private BoundExpression MakePropertyAssignment( | |
| return new BoundSequence( | ||
| syntax, | ||
| AppendToPossibleNull(argTemps, rhsTemp), | ||
| ImmutableArray.Create(setterCall), | ||
| sideEffects.Add(setterCall), // https://github.com/dotnet/roslyn/issues/78829 - there is no test coverage for 'sideEffects' on this code path | ||
| boundRhs, | ||
| rhsTemp.Type); | ||
| } | ||
|
|
@@ -357,14 +430,15 @@ private BoundExpression MakePropertyAssignment( | |
|
|
||
| if (argTemps.IsDefaultOrEmpty) | ||
| { | ||
| Debug.Assert(sideEffects.IsEmpty); | ||
| return setterCall; | ||
| } | ||
| else | ||
| { | ||
| return new BoundSequence( | ||
| syntax, | ||
| argTemps, | ||
| ImmutableArray<BoundExpression>.Empty, | ||
| sideEffects, | ||
| setterCall, | ||
| setMethod.ReturnType); | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
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.
Should we check that
assignmentKind is AssignmentKind.SimpleAssignmentinstead? #ResolvedThere 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 am checking for assignments that I know don't need special treatment. Doing an opposite check is not robust in the long run, if new kind is added.