@@ -76,14 +76,19 @@ public void Transfer (BlockProxy block, LocalDataFlowState<TValue, TValueLattice
76
76
77
77
// If not, the BranchValue represents a return or throw value associated with the FallThroughSuccessor of this block.
78
78
// (ConditionalSuccessor == null iff ConditionKind == None).
79
- // If we get here, we should be analyzing a method body, not an attribute instance since attributes can't have throws or return statements
80
- // Field/property initializers also can't have throws or return statements.
81
- Debug . Assert ( OwningSymbol is IMethodSymbol ) ;
79
+ // If we get here, we should be analyzing code in a method or field/property initializer,
80
+ // not an attribute instance, since attributes can't have throws or return statements
81
+ Debug . Assert ( OwningSymbol is IMethodSymbol or IFieldSymbol or IPropertySymbol ,
82
+ $ "{ OwningSymbol . GetType ( ) } : { branchValueOperation . Syntax . GetLocation ( ) . GetLineSpan ( ) } ") ;
82
83
83
84
// The BranchValue for a thrown value is not involved in dataflow tracking.
84
85
if ( block . Block . FallThroughSuccessor ? . Semantics == ControlFlowBranchSemantics . Throw )
85
86
return ;
86
87
88
+ // Field/property initializers can't have return statements.
89
+ Debug . Assert ( OwningSymbol is IMethodSymbol ,
90
+ $ "{ OwningSymbol . GetType ( ) } : { branchValueOperation . Syntax . GetLocation ( ) . GetLineSpan ( ) } ") ;
91
+
87
92
// Return statements with return values are represented in the control flow graph as
88
93
// a branch value operation that computes the return value.
89
94
@@ -125,9 +130,9 @@ bool IsReferenceToCapturedVariable (ILocalReferenceOperation localReference)
125
130
126
131
if ( local . IsConst )
127
132
return false ;
128
-
129
- var declaringSymbol = ( IMethodSymbol ) local . ContainingSymbol ;
130
- return ! ReferenceEquals ( declaringSymbol , OwningSymbol ) ;
133
+ Debug . Assert ( local . ContainingSymbol is IMethodSymbol or IFieldSymbol , // backing field for property initializers
134
+ $ " { local . ContainingSymbol . GetType ( ) } : { localReference . Syntax . GetLocation ( ) . GetLineSpan ( ) } " ) ;
135
+ return ! ReferenceEquals ( local . ContainingSymbol , OwningSymbol ) ;
131
136
}
132
137
133
138
TValue GetLocal ( ILocalReferenceOperation operation , LocalDataFlowState < TValue , TValueLattice > state )
@@ -159,7 +164,7 @@ void SetLocal (ILocalReferenceOperation operation, TValue value, LocalDataFlowSt
159
164
state . Set ( local , newValue ) ;
160
165
}
161
166
162
- TValue ProcessSingleTargetAssignment ( IOperation targetOperation , ISimpleAssignmentOperation operation , LocalDataFlowState < TValue , TValueLattice > state , bool merge )
167
+ TValue ProcessSingleTargetAssignment ( IOperation targetOperation , IAssignmentOperation operation , LocalDataFlowState < TValue , TValueLattice > state , bool merge )
163
168
{
164
169
switch ( targetOperation ) {
165
170
case IFieldReferenceOperation :
@@ -187,9 +192,14 @@ TValue ProcessSingleTargetAssignment (IOperation targetOperation, ISimpleAssignm
187
192
// This can happen in a constructor - there it is possible to assign to a property
188
193
// without a setter. This turns into an assignment to the compiler-generated backing field.
189
194
// To match the linker, this should warn about the compiler-generated backing field.
190
- // For now, just don't warn. https://github.com/dotnet/linker /issues/2731
195
+ // For now, just don't warn. https://github.com/dotnet/runtime /issues/93277
191
196
break ;
192
197
}
198
+ // Even if the property has a set method, if the assignment takes place in a property initializer,
199
+ // the write becomes a direct write to the underlying field. This should be treated the same as
200
+ // the case where there is no set method.
201
+ if ( OwningSymbol is IPropertySymbol && ( ControlFlowGraph . OriginalOperation is not IAttributeOperation ) )
202
+ break ;
193
203
194
204
// Property may be an indexer, in which case there will be one or more index arguments followed by a value argument
195
205
ImmutableArray < TValue > . Builder arguments = ImmutableArray . CreateBuilder < TValue > ( ) ;
@@ -293,6 +303,20 @@ TValue ProcessSingleTargetAssignment (IOperation targetOperation, ISimpleAssignm
293
303
}
294
304
295
305
public override TValue VisitSimpleAssignment ( ISimpleAssignmentOperation operation , LocalDataFlowState < TValue , TValueLattice > state )
306
+ {
307
+ return ProcessAssignment ( operation , state ) ;
308
+ }
309
+
310
+ public override TValue VisitCompoundAssignment ( ICompoundAssignmentOperation operation , LocalDataFlowState < TValue , TValueLattice > state )
311
+ {
312
+ return ProcessAssignment ( operation , state ) ;
313
+ }
314
+
315
+ // Note: this is called both for normal assignments and ICompoundAssignmentOperation.
316
+ // The resulting value of a compound assignment isn't important for our dataflow analysis
317
+ // (we don't model addition of integers, for example), so we just treat these the same
318
+ // as normal assignments.
319
+ TValue ProcessAssignment ( IAssignmentOperation operation , LocalDataFlowState < TValue , TValueLattice > state )
296
320
{
297
321
var targetOperation = operation . Target ;
298
322
if ( targetOperation is not IFlowCaptureReferenceOperation flowCaptureReference )
@@ -305,7 +329,7 @@ public override TValue VisitSimpleAssignment (ISimpleAssignmentOperation operati
305
329
// for simplicity. This could be generalized if we encounter a dataflow behavior where this makes a difference.
306
330
307
331
Debug . Assert ( IsLValueFlowCapture ( flowCaptureReference . Id ) ) ;
308
- Debug . Assert ( ! flowCaptureReference . GetValueUsageInfo ( OwningSymbol ) . HasFlag ( ValueUsageInfo . Read ) ) ;
332
+ Debug . Assert ( flowCaptureReference . GetValueUsageInfo ( OwningSymbol ) . HasFlag ( ValueUsageInfo . Write ) ) ;
309
333
var capturedReferences = state . Current . CapturedReferences . Get ( flowCaptureReference . Id ) ;
310
334
if ( ! capturedReferences . HasMultipleValues ) {
311
335
// Single captured reference. Treat this as an overwriting assignment.
@@ -360,8 +384,31 @@ public override TValue VisitFlowCaptureReference (IFlowCaptureReferenceOperation
360
384
// Debug.Assert (IsLValueFlowCapture (operation.Id));
361
385
Debug . Assert ( operation . GetValueUsageInfo ( OwningSymbol ) . HasFlag ( ValueUsageInfo . Write ) ,
362
386
$ "{ operation . Syntax . GetLocation ( ) . GetLineSpan ( ) } ") ;
387
+ Debug . Assert ( operation . GetValueUsageInfo ( OwningSymbol ) . HasFlag ( ValueUsageInfo . Reference ) ,
388
+ $ "{ operation . Syntax . GetLocation ( ) . GetLineSpan ( ) } ") ;
363
389
return TopValue ;
364
390
}
391
+
392
+ if ( operation . GetValueUsageInfo ( OwningSymbol ) . HasFlag ( ValueUsageInfo . Write ) ) {
393
+ // If we get here, it means we're visiting a flow capture reference that may be
394
+ // assigned to. Similar to the IsInitialization case, this can happen for an out param
395
+ // where the variable is declared before being passed as an out param, for example:
396
+
397
+ // string s;
398
+ // Method (out s, b ? 0 : 1);
399
+
400
+ // The second argument is necessary to create multiple branches so that the compiler
401
+ // turns both arguments into flow capture references, instead of just passing a local
402
+ // reference for s.
403
+
404
+ // This can also happen for a deconstruction assignments, where the write is not to a byref.
405
+ // Once the analyzer implements support for deconstruction assignments (https://github.com/dotnet/linker/issues/3158),
406
+ // we can try enabling this assert to ensure that this case is only hit for byrefs.
407
+ // Debug.Assert (operation.GetValueUsageInfo (OwningSymbol).HasFlag (ValueUsageInfo.Reference),
408
+ // $"{operation.Syntax.GetLocation ().GetLineSpan ()}");
409
+ return TopValue ;
410
+ }
411
+
365
412
return GetFlowCaptureValue ( operation , state ) ;
366
413
}
367
414
@@ -428,14 +475,14 @@ public override TValue VisitDelegateCreation (IDelegateCreationOperation operati
428
475
return HandleDelegateCreation ( lambda . Symbol , instance , operation ) ;
429
476
}
430
477
431
- Debug . Assert ( operation . Target is IMethodReferenceOperation ) ;
432
- if ( operation . Target is not IMethodReferenceOperation methodReference )
478
+ Debug . Assert ( operation . Target is IMemberReferenceOperation ,
479
+ $ "{ operation . Target . GetType ( ) } : { operation . Syntax . GetLocation ( ) . GetLineSpan ( ) } ") ;
480
+ if ( operation . Target is not IMemberReferenceOperation memberReference )
433
481
return TopValue ;
434
482
435
- TValue instanceValue = Visit ( methodReference . Instance , state ) ;
436
- IMethodSymbol ? method = methodReference . Method ;
437
- Debug . Assert ( method != null ) ;
438
- if ( method == null )
483
+ TValue instanceValue = Visit ( memberReference . Instance , state ) ;
484
+
485
+ if ( memberReference . Member is not IMethodSymbol method )
439
486
return TopValue ;
440
487
441
488
// Track references to local functions
0 commit comments