@@ -96,7 +96,7 @@ public void Transfer (BlockProxy block, LocalDataFlowState<TValue, TValueLattice
9696
9797 public abstract TValue HandleArrayElementRead ( TValue arrayValue , TValue indexValue , IOperation operation ) ;
9898
99- public abstract void HandleArrayElementWrite ( TValue arrayValue , TValue indexValue , TValue valueToWrite , IOperation operation ) ;
99+ public abstract void HandleArrayElementWrite ( TValue arrayValue , TValue indexValue , TValue valueToWrite , IOperation operation , bool merge ) ;
100100
101101 // This takes an IOperation rather than an IReturnOperation because the return value
102102 // may (must?) come from BranchValue of an operation whose FallThroughSuccessor is the exit block.
@@ -139,7 +139,7 @@ TValue GetLocal (ILocalReferenceOperation operation, LocalDataFlowState<TValue,
139139 return state . Get ( local ) ;
140140 }
141141
142- void SetLocal ( ILocalReferenceOperation operation , TValue value , LocalDataFlowState < TValue , TValueLattice > state )
142+ void SetLocal ( ILocalReferenceOperation operation , TValue value , LocalDataFlowState < TValue , TValueLattice > state , bool merge = false )
143143 {
144144 var local = new LocalKey ( operation . Local ) ;
145145 if ( IsReferenceToCapturedVariable ( operation ) )
@@ -149,27 +149,14 @@ void SetLocal (ILocalReferenceOperation operation, TValue value, LocalDataFlowSt
149149 if ( InterproceduralState . TrySetHoistedLocal ( local , value ) )
150150 return ;
151151
152- state . Set ( local , value ) ;
152+ var newValue = merge
153+ ? state . Lattice . Lattice . ValueLattice . Meet ( state . Get ( local ) , value )
154+ : value ;
155+ state . Set ( local , newValue ) ;
153156 }
154157
155- public override TValue VisitSimpleAssignment ( ISimpleAssignmentOperation operation , LocalDataFlowState < TValue , TValueLattice > state )
158+ TValue ProcessSingleTargetAssignment ( IOperation targetOperation , ISimpleAssignmentOperation operation , LocalDataFlowState < TValue , TValueLattice > state , bool merge )
156159 {
157- var targetOperation = operation . Target ;
158- if ( targetOperation is IFlowCaptureReferenceOperation flowCaptureReference ) {
159- Debug . Assert ( IsLValueFlowCapture ( flowCaptureReference . Id ) ) ;
160- Debug . Assert ( ! flowCaptureReference . GetValueUsageInfo ( Method ) . HasFlag ( ValueUsageInfo . Read ) ) ;
161- var capturedReference = state . Current . CapturedReferences . Get ( flowCaptureReference . Id ) . Reference ;
162- targetOperation = capturedReference ;
163- if ( targetOperation == null )
164- throw new InvalidOperationException ( ) ;
165-
166- // Note: technically we should avoid visiting the target operation below when assigning to a flow capture reference,
167- // because this should be done when the capture is created. For example, a flow capture used as both an LValue and a RValue
168- // should only evaluate the expression that computes the object instance of a property reference once.
169- // However, we just visit the instance again below for simplicity. This could be generalized if we encounter a dataflow
170- // behavior where this makes a difference.
171- }
172-
173160 switch ( targetOperation ) {
174161 case IFieldReferenceOperation :
175162 case IParameterReferenceOperation : {
@@ -237,17 +224,52 @@ public override TValue VisitSimpleAssignment (ISimpleAssignmentOperation operati
237224 // TODO: when setting a property in an attribute, target is an IPropertyReference.
238225 case ILocalReferenceOperation localRef : {
239226 TValue value = Visit ( operation . Value , state ) ;
240- SetLocal ( localRef , value , state ) ;
227+ SetLocal ( localRef , value , state , merge ) ;
241228 return value ;
242229 }
243230 case IArrayElementReferenceOperation arrayElementRef : {
244231 if ( arrayElementRef . Indices . Length != 1 )
245232 break ;
246233
247- TValue arrayRef = Visit ( arrayElementRef . ArrayReference , state ) ;
248- TValue index = Visit ( arrayElementRef . Indices [ 0 ] , state ) ;
249- TValue value = Visit ( operation . Value , state ) ;
250- HandleArrayElementWrite ( arrayRef , index , value , operation ) ;
234+ // Similarly to VisitSimpleAssignment, this needs to handle cases where the array reference
235+ // is a captured variable, even if the target of the assignment (the array element reference) is not.
236+
237+ TValue arrayRef ;
238+ TValue index ;
239+ TValue value ;
240+ if ( arrayElementRef . ArrayReference is not IFlowCaptureReferenceOperation captureReference ) {
241+ arrayRef = Visit ( arrayElementRef . ArrayReference , state ) ;
242+ index = Visit ( arrayElementRef . Indices [ 0 ] , state ) ;
243+ value = Visit ( operation . Value , state ) ;
244+ HandleArrayElementWrite ( arrayRef , index , value , operation , merge : merge ) ;
245+ return value ;
246+ }
247+
248+ index = Visit ( arrayElementRef . Indices [ 0 ] , state ) ;
249+ value = Visit ( operation . Value , state ) ;
250+
251+ var capturedReferences = state . Current . CapturedReferences . Get ( captureReference . Id ) ;
252+ if ( ! capturedReferences . HasMultipleValues ) {
253+ // Single captured reference. Treat this as an overwriting assignment,
254+ // unless the caller already told us to merge values because this is an
255+ // assignment to one of multiple captured array element references.
256+ var enumerator = capturedReferences . GetEnumerator ( ) ;
257+ enumerator . MoveNext ( ) ;
258+ var capture = enumerator . Current ;
259+ arrayRef = Visit ( capture . Reference , state ) ;
260+ HandleArrayElementWrite ( arrayRef , index , value , operation , merge : merge ) ;
261+ return value ;
262+ }
263+
264+ // The capture id may have captured multiple references, as in:
265+ // (b ? arr1 : arr2)[0] = value;
266+ // We treat this as possible write to each of the captured references,
267+ // which requires merging with the previous values of each.
268+
269+ foreach ( var capture in state . Current . CapturedReferences . Get ( captureReference . Id ) ) {
270+ arrayRef = Visit ( capture . Reference , state ) ;
271+ HandleArrayElementWrite ( arrayRef , index , value , operation , merge : true ) ;
272+ }
251273 return value ;
252274 }
253275 case IDiscardOperation :
@@ -293,8 +315,50 @@ public override TValue VisitSimpleAssignment (ISimpleAssignmentOperation operati
293315 return Visit ( operation . Value , state ) ;
294316 }
295317
296- // Similar to VisitLocalReference
297- public override TValue VisitFlowCaptureReference ( IFlowCaptureReferenceOperation operation , LocalDataFlowState < TValue , TValueLattice > state )
318+ public override TValue VisitSimpleAssignment ( ISimpleAssignmentOperation operation , LocalDataFlowState < TValue , TValueLattice > state )
319+ {
320+ var targetOperation = operation . Target ;
321+ if ( targetOperation is not IFlowCaptureReferenceOperation flowCaptureReference )
322+ return ProcessSingleTargetAssignment ( targetOperation , operation , state , merge : false ) ;
323+
324+ // Note: technically we should avoid visiting the target operation in ProcessNonCapturedAssignment when assigning
325+ // to a flow capture reference, because this should be done when the capture is created.
326+ // For example, a flow capture used as both an LValue and a RValue should only evaluate the expression that
327+ // computes the object instance of a property reference once. However, we just visit the instance again below
328+ // for simplicity. This could be generalized if we encounter a dataflow behavior where this makes a difference.
329+
330+ Debug . Assert ( IsLValueFlowCapture ( flowCaptureReference . Id ) ) ;
331+ Debug . Assert ( ! flowCaptureReference . GetValueUsageInfo ( Method ) . HasFlag ( ValueUsageInfo . Read ) ) ;
332+ var capturedReferences = state . Current . CapturedReferences . Get ( flowCaptureReference . Id ) ;
333+ if ( ! capturedReferences . HasMultipleValues ) {
334+ // Single captured reference. Treat this as an overwriting assignment.
335+ var enumerator = capturedReferences . GetEnumerator ( ) ;
336+ enumerator . MoveNext ( ) ;
337+ targetOperation = enumerator . Current . Reference ;
338+ return ProcessSingleTargetAssignment ( targetOperation , operation , state , merge : false ) ;
339+ }
340+
341+ // The capture id may have captured multiple references, as in:
342+ // (b ? ref v1 : ref v2) = value;
343+ // We treat this as a possible write to each of the captured references,
344+ // which requires merging with the previous values of each.
345+
346+ // Note: technically this should only visit the RHS of the assignment once.
347+ // For now we visit the RHS in ProcessSingleTargetAssignment for simplicity, and
348+ // rely on the warning deduplication to prevent this from producing multiple warnings
349+ // if the RHS has dataflow warnings.
350+
351+ TValue value = TopValue ;
352+ foreach ( var capturedReference in capturedReferences ) {
353+ targetOperation = capturedReference . Reference ;
354+ var singleValue = ProcessSingleTargetAssignment ( targetOperation , operation , state , merge : true ) ;
355+ value = LocalStateLattice . Lattice . ValueLattice . Meet ( value , singleValue ) ;
356+ }
357+
358+ return value ;
359+ }
360+
361+ TValue GetFlowCaptureValue ( IFlowCaptureReferenceOperation operation , LocalDataFlowState < TValue , TValueLattice > state )
298362 {
299363 if ( ! operation . GetValueUsageInfo ( Method ) . HasFlag ( ValueUsageInfo . Read ) ) {
300364 // There are known cases where this assert doesn't hold, because LValueFlowCaptureProvider
@@ -304,10 +368,21 @@ public override TValue VisitFlowCaptureReference (IFlowCaptureReferenceOperation
304368 return TopValue ;
305369 }
306370
307- Debug . Assert ( IsRValueFlowCapture ( operation . Id ) ) ;
371+ // This assert is incorrect for cases like (b ? arr1 : arr2)[0] = v;
372+ // Here the ValueUsageInfo shows that the value usage is for reading (this is probably wrong!)
373+ // but the value is actually an LValueFlowCapture.
374+ // Let's just disable the assert for now.
375+ // Debug.Assert (IsRValueFlowCapture (operation.Id));
376+
308377 return state . Get ( new LocalKey ( operation . Id ) ) ;
309378 }
310379
380+ // Similar to VisitLocalReference
381+ public override TValue VisitFlowCaptureReference ( IFlowCaptureReferenceOperation operation , LocalDataFlowState < TValue , TValueLattice > state )
382+ {
383+ return GetFlowCaptureValue ( operation , state ) ;
384+ }
385+
311386 // Similar to VisitSimpleAssignment when assigning to a local, but for values which are captured without a
312387 // corresponding local variable. The "flow capture" is like a local assignment, and the "flow capture reference"
313388 // is like a local reference.
0 commit comments