Skip to content

Commit 1d9820f

Browse files
authored
Fix receiver capturing for various extension scenarios (#79472)
Fixes #79379 Fixes #79436
1 parent a92e9e0 commit 1d9820f

15 files changed

+13381
-2582
lines changed

src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -720,28 +720,39 @@ private void EmitArgument(BoundExpression argument, RefKind refKind)
720720
EmitExpression(argument, true);
721721
break;
722722

723-
case RefKind.In:
724-
var temp = EmitAddress(argument, AddressKind.ReadOnly);
725-
AddExpressionTemp(temp);
726-
break;
727-
728723
default:
729-
Debug.Assert(refKind is RefKind.Ref or RefKind.Out or RefKindExtensions.StrictIn);
730-
// NOTE: passing "ReadOnlyStrict" here.
731-
// we should not get an address of a copy if at all possible
732-
var unexpectedTemp = EmitAddress(argument, refKind == RefKindExtensions.StrictIn ? AddressKind.ReadOnlyStrict : AddressKind.Writeable);
733-
if (unexpectedTemp != null)
724+
Debug.Assert(refKind is RefKind.In or RefKind.Ref or RefKind.Out or RefKindExtensions.StrictIn);
725+
var temp = EmitAddress(argument, GetArgumentAddressKind(refKind));
726+
if (temp != null)
734727
{
735728
// interestingly enough "ref dynamic" sometimes is passed via a clone
736729
// receiver of a ref field can be cloned too
737-
Debug.Assert(argument.Type.IsDynamic() || argument is BoundFieldAccess { FieldSymbol.RefKind: not RefKind.None }, "passing args byref should not clone them into temps");
738-
AddExpressionTemp(unexpectedTemp);
730+
Debug.Assert(refKind is RefKind.In || argument.Type.IsDynamic() || argument is BoundFieldAccess { FieldSymbol.RefKind: not RefKind.None }, "passing args byref should not clone them into temps");
731+
AddExpressionTemp(temp);
739732
}
740733

741734
break;
742735
}
743736
}
744737

738+
internal static AddressKind GetArgumentAddressKind(RefKind refKind)
739+
{
740+
switch (refKind)
741+
{
742+
case RefKind.None:
743+
throw ExceptionUtilities.UnexpectedValue(refKind);
744+
745+
case RefKind.In:
746+
return AddressKind.ReadOnly;
747+
748+
default:
749+
Debug.Assert(refKind is RefKind.Ref or RefKind.Out or RefKindExtensions.StrictIn);
750+
// NOTE: returning "ReadOnlyStrict" here.
751+
// we should not get an address of a copy if at all possible
752+
return refKind == RefKindExtensions.StrictIn ? AddressKind.ReadOnlyStrict : AddressKind.Writeable;
753+
}
754+
}
755+
745756
private void EmitAddressOfExpression(BoundAddressOfOperator expression, bool used)
746757
{
747758
// NOTE: passing "ReadOnlyStrict" here.

src/Compilers/CSharp/Portable/Lowering/ExtensionMethodReferenceRewriter.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,13 @@ static BoundExpression updateCall(
109109
if (receiverRefKind != RefKind.None)
110110
{
111111
var builder = ArrayBuilder<RefKind>.GetInstance(method.ParameterCount, RefKind.None);
112-
builder[0] = argumentRefKindFromReceiverRefKind(receiverRefKind);
112+
builder[0] = ReceiverArgumentRefKindFromReceiverRefKind(receiverRefKind);
113113
argumentRefKinds = builder.ToImmutableAndFree();
114114
}
115115
}
116116
else
117117
{
118-
argumentRefKinds = argumentRefKinds.Insert(0, argumentRefKindFromReceiverRefKind(receiverRefKind));
118+
argumentRefKinds = argumentRefKinds.Insert(0, ReceiverArgumentRefKindFromReceiverRefKind(receiverRefKind));
119119
}
120120

121121
invokedAsExtensionMethod = true;
@@ -141,14 +141,14 @@ static BoundExpression updateCall(
141141
boundCall.ResultKind,
142142
originalMethodsOpt,
143143
type);
144-
145-
static RefKind argumentRefKindFromReceiverRefKind(RefKind receiverRefKind)
146-
{
147-
return SyntheticBoundNodeFactory.ArgumentRefKindFromParameterRefKind(receiverRefKind, useStrictArgumentRefKinds: false);
148-
}
149144
}
150145
}
151146

147+
public static RefKind ReceiverArgumentRefKindFromReceiverRefKind(RefKind receiverRefKind)
148+
{
149+
return SyntheticBoundNodeFactory.ArgumentRefKindFromParameterRefKind(receiverRefKind, useStrictArgumentRefKinds: false);
150+
}
151+
152152
[return: NotNullIfNotNull(nameof(method))]
153153
private static MethodSymbol? VisitMethodSymbolWithExtensionRewrite(BoundTreeRewriter rewriter, MethodSymbol? method)
154154
{

src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs

Lines changed: 96 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)