@@ -81,15 +81,24 @@ private BoundExpression VisitAssignmentOperator(BoundAssignmentOperator node, bo
8181 break ;
8282 }
8383
84- return MakeStaticAssignmentOperator ( node . Syntax , loweredLeft , loweredRight , node . IsRef , used ) ;
84+ return MakeStaticAssignmentOperator ( node . Syntax , loweredLeft , loweredRight , node . IsRef , used , AssignmentKind . SimpleAssignment ) ;
85+ }
86+
87+ private enum AssignmentKind
88+ {
89+ SimpleAssignment ,
90+ CompoundAssignment ,
91+ IncrementDecrement ,
92+ Deconstruction ,
93+ NullCoalescingAssignment ,
8594 }
8695
8796 /// <summary>
8897 /// Generates a lowered form of the assignment operator for the given left and right sub-expressions.
8998 /// Left and right sub-expressions must be in lowered form.
9099 /// </summary>
91100 private BoundExpression MakeAssignmentOperator ( SyntaxNode syntax , BoundExpression rewrittenLeft , BoundExpression rewrittenRight ,
92- bool used , bool isChecked , bool isCompoundAssignment )
101+ bool used , bool isChecked , AssignmentKind assignmentKind )
93102 {
94103 switch ( rewrittenLeft . Kind )
95104 {
@@ -102,15 +111,15 @@ private BoundExpression MakeAssignmentOperator(SyntaxNode syntax, BoundExpressio
102111 indexerAccess . ArgumentNamesOpt ,
103112 indexerAccess . ArgumentRefKindsOpt ,
104113 rewrittenRight ,
105- isCompoundAssignment , isChecked ) ;
114+ isCompoundAssignment : assignmentKind == AssignmentKind . CompoundAssignment , isChecked ) ;
106115
107116 case BoundKind . DynamicMemberAccess :
108117 var memberAccess = ( BoundDynamicMemberAccess ) rewrittenLeft ;
109118 return _dynamicFactory . MakeDynamicSetMember (
110119 memberAccess . Receiver ,
111120 memberAccess . Name ,
112121 rewrittenRight ,
113- isCompoundAssignment ,
122+ isCompoundAssignment : assignmentKind == AssignmentKind . CompoundAssignment ,
114123 isChecked ) . ToExpression ( ) ;
115124
116125 case BoundKind . EventAccess :
@@ -132,7 +141,7 @@ private BoundExpression MakeAssignmentOperator(SyntaxNode syntax, BoundExpressio
132141 throw ExceptionUtilities . Unreachable ( ) ;
133142
134143 default :
135- return MakeStaticAssignmentOperator ( syntax , rewrittenLeft , rewrittenRight , isRef : false , used : used ) ;
144+ return MakeStaticAssignmentOperator ( syntax , rewrittenLeft , rewrittenRight , isRef : false , used : used , assignmentKind ) ;
136145 }
137146 }
138147
@@ -168,7 +177,8 @@ private BoundExpression MakeStaticAssignmentOperator(
168177 BoundExpression rewrittenLeft ,
169178 BoundExpression rewrittenRight ,
170179 bool isRef ,
171- bool used )
180+ bool used ,
181+ AssignmentKind assignmentKind )
172182 {
173183 switch ( rewrittenLeft . Kind )
174184 {
@@ -192,7 +202,8 @@ private BoundExpression MakeStaticAssignmentOperator(
192202 false ,
193203 default ( ImmutableArray < int > ) ,
194204 rewrittenRight ,
195- used ) ;
205+ used ,
206+ assignmentKind ) ;
196207 }
197208
198209 case BoundKind . IndexerAccess :
@@ -212,7 +223,8 @@ private BoundExpression MakeStaticAssignmentOperator(
212223 indexerAccess . Expanded ,
213224 indexerAccess . ArgsToParamsOpt ,
214225 rewrittenRight ,
215- used ) ;
226+ used ,
227+ assignmentKind ) ;
216228 }
217229
218230 case BoundKind . Local :
@@ -248,7 +260,8 @@ private BoundExpression MakeStaticAssignmentOperator(
248260 sequence . Value ,
249261 rewrittenRight ,
250262 isRef ,
251- used ) ,
263+ used ,
264+ assignmentKind ) ,
252265 sequence . Type ) ;
253266 }
254267 goto default ;
@@ -264,6 +277,17 @@ private BoundExpression MakeStaticAssignmentOperator(
264277 }
265278 }
266279
280+ private bool IsExtensionPropertyWithByValPossiblyStructReceiverWhichHasHomeAndCanChangeValueBetweenReads ( BoundExpression rewrittenReceiver , PropertySymbol property )
281+ {
282+ return CanChangeValueBetweenReads ( rewrittenReceiver , localsMayBeAssignedOrCaptured : true , structThisCanChangeValueBetweenReads : true ) &&
283+ IsNewExtensionMemberWithByValPossiblyStructReceiver ( property ) &&
284+ CodeGen . CodeGenerator . HasHome ( rewrittenReceiver ,
285+ CodeGen . CodeGenerator . AddressKind . ReadOnlyStrict ,
286+ _factory . CurrentFunction ,
287+ peVerifyCompatEnabled : false ,
288+ stackLocalsOpt : null ) ;
289+ }
290+
267291 private BoundExpression MakePropertyAssignment (
268292 SyntaxNode syntax ,
269293 BoundExpression ? rewrittenReceiver ,
@@ -273,7 +297,8 @@ private BoundExpression MakePropertyAssignment(
273297 bool expanded ,
274298 ImmutableArray < int > argsToParamsOpt ,
275299 BoundExpression rewrittenRight ,
276- bool used )
300+ bool used ,
301+ AssignmentKind assignmentKind )
277302 {
278303 // Rewrite property assignment into call to setter.
279304 var setMethod = property . GetOwnOrInheritedSetMethod ( ) ;
@@ -293,25 +318,73 @@ private BoundExpression MakePropertyAssignment(
293318 }
294319
295320 ArrayBuilder < LocalSymbol > ? argTempsBuilder = null ;
321+
322+ bool needSpecialExtensionPropertyReceiverReadOrder = false ;
323+ ArrayBuilder < BoundExpression > ? storesOpt = null ;
324+
325+ if ( rewrittenReceiver is not null &&
326+ assignmentKind is not ( AssignmentKind . CompoundAssignment or AssignmentKind . NullCoalescingAssignment or AssignmentKind . Deconstruction or AssignmentKind . IncrementDecrement ) &&
327+ IsExtensionPropertyWithByValPossiblyStructReceiverWhichHasHomeAndCanChangeValueBetweenReads ( rewrittenReceiver , property ) &&
328+ ( arguments . Length != 0 || ! IsSafeForReordering ( rewrittenRight , RefKind . None ) ) )
329+ {
330+ // The receiver has location, but extension property/indexer takes receiver by value.
331+ // This means that we need to ensure that the receiver value is read after
332+ // any side-effecting arguments and right hand side are evaluated, so that the
333+ // setter receives the last value of the receiver, not the value before the
334+ // arguments/rhs were evaluated. Receiver side effects should be evaluated at
335+ // the very beginning, of course.
336+
337+ needSpecialExtensionPropertyReceiverReadOrder = true ;
338+ storesOpt = ArrayBuilder < BoundExpression > . GetInstance ( ) ;
339+ }
340+
341+ #if DEBUG
342+ BoundExpression ? rewrittenReceiverBeforePossibleCapture = rewrittenReceiver ;
343+ #endif
296344 arguments = VisitArgumentsAndCaptureReceiverIfNeeded (
297345 ref rewrittenReceiver ,
298- forceReceiverCapturing : false ,
346+ forceReceiverCapturing : needSpecialExtensionPropertyReceiverReadOrder ,
299347 arguments ,
300348 property ,
301349 argsToParamsOpt ,
302350 argumentRefKindsOpt ,
303- storesOpt : null ,
351+ storesOpt ,
304352 ref argTempsBuilder ) ;
305353
306- arguments = MakeArguments (
307- arguments ,
308- property ,
309- expanded ,
310- argsToParamsOpt ,
311- ref argumentRefKindsOpt ,
312- ref argTempsBuilder ,
313- invokedAsExtensionMethod : false ) ;
354+ if ( needSpecialExtensionPropertyReceiverReadOrder )
355+ {
356+ #if DEBUG
357+ Debug . Assert ( rewrittenReceiverBeforePossibleCapture != ( object ? ) rewrittenReceiver ) ;
358+ #endif
359+ Debug . Assert ( storesOpt is { } ) ;
360+ Debug . Assert ( storesOpt . Count != 0 ) ;
361+ Debug . Assert ( argTempsBuilder is not null ) ;
362+
363+ // Store everything that is non-trivial into a temporary; record the
364+ // stores in storesToTemps and make the actual argument a reference to the temp.
365+ arguments = ExtractSideEffectsFromArguments ( arguments , property , expanded , argsToParamsOpt , ref argumentRefKindsOpt , storesOpt , argTempsBuilder ) ;
366+
367+ if ( ! IsSafeForReordering ( rewrittenRight , RefKind . None ) )
368+ {
369+ BoundLocal capturedRight = _factory . StoreToTemp ( rewrittenRight , out BoundAssignmentOperator assignmentToTemp , refKind : RefKind . None ) ;
370+ argTempsBuilder . Add ( capturedRight . LocalSymbol ) ;
371+ storesOpt . Add ( assignmentToTemp ) ;
372+ rewrittenRight = capturedRight ;
373+ }
374+ }
375+ else
376+ {
377+ arguments = MakeArguments (
378+ arguments ,
379+ property ,
380+ expanded ,
381+ argsToParamsOpt ,
382+ ref argumentRefKindsOpt ,
383+ ref argTempsBuilder ,
384+ invokedAsExtensionMethod : false ) ;
385+ }
314386
387+ var sideEffects = storesOpt is null ? [ ] : storesOpt . ToImmutableAndFree ( ) ;
315388 var argTemps = argTempsBuilder . ToImmutableAndFree ( ) ;
316389
317390 if ( used )
@@ -342,7 +415,7 @@ private BoundExpression MakePropertyAssignment(
342415 return new BoundSequence (
343416 syntax ,
344417 AppendToPossibleNull ( argTemps , rhsTemp ) ,
345- ImmutableArray . Create ( setterCall ) ,
418+ sideEffects . Add ( setterCall ) , // https://github.com/dotnet/roslyn/issues/78829 - there is no test coverage for 'sideEffects' on this code path
346419 boundRhs ,
347420 rhsTemp . Type ) ;
348421 }
@@ -357,14 +430,15 @@ private BoundExpression MakePropertyAssignment(
357430
358431 if ( argTemps . IsDefaultOrEmpty )
359432 {
433+ Debug . Assert ( sideEffects . IsEmpty ) ;
360434 return setterCall ;
361435 }
362436 else
363437 {
364438 return new BoundSequence (
365439 syntax ,
366440 argTemps ,
367- ImmutableArray < BoundExpression > . Empty ,
441+ sideEffects ,
368442 setterCall ,
369443 setMethod . ReturnType ) ;
370444 }
0 commit comments