Skip to content

Commit 9b694bf

Browse files
authored
Add support for patterns (#118267)
This adds local and property pattern support, but may not cover all possible ways a pattern can introduce a local variable. Fixes #113922
1 parent 6dc15b5 commit 9b694bf

File tree

18 files changed

+154
-63
lines changed

18 files changed

+154
-63
lines changed

src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs

Lines changed: 81 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ public abstract TValue HandleMethodCall(
171171

172172
public override TValue VisitLocalReference(ILocalReferenceOperation operation, LocalDataFlowState<TValue, TContext, TValueLattice, TContextLattice> state)
173173
{
174-
return GetLocal(operation, state);
174+
return GetLocal(operation.Local, state);
175175
}
176176

177177
private TValue ProcessBinderCall(IOperation operation, string methodName, LocalDataFlowState<TValue, TContext, TValueLattice, TContextLattice> state)
@@ -199,21 +199,19 @@ public override TValue VisitDynamicMemberReference(IDynamicMemberReferenceOperat
199199
public override TValue VisitDynamicIndexerAccess(IDynamicIndexerAccessOperation operation, LocalDataFlowState<TValue, TContext, TValueLattice, TContextLattice> state)
200200
=> ProcessBinderCall(operation, operation.GetValueUsageInfo(OwningSymbol).HasFlag(ValueUsageInfo.Write) ? "SetIndex" : "GetIndex", state);
201201

202-
private bool IsReferenceToCapturedVariable(ILocalReferenceOperation localReference)
202+
private bool IsCapturedVariable(ILocalSymbol local)
203203
{
204-
var local = localReference.Local;
205-
206204
if (local.IsConst)
207205
return false;
208206
Debug.Assert(local.ContainingSymbol is IMethodSymbol or IFieldSymbol, // backing field for property initializers
209-
$"{local.ContainingSymbol.GetType()}: {localReference.Syntax.GetLocation().GetLineSpan()}");
207+
$"{local.ContainingSymbol.GetType()}: {local.Locations[0].GetLineSpan()}");
210208
return !ReferenceEquals(local.ContainingSymbol, OwningSymbol);
211209
}
212210

213-
private TValue GetLocal(ILocalReferenceOperation operation, LocalDataFlowState<TValue, TContext, TValueLattice, TContextLattice> state)
211+
private TValue GetLocal(ILocalSymbol symbol, LocalDataFlowState<TValue, TContext, TValueLattice, TContextLattice> state)
214212
{
215-
var local = new LocalKey(operation.Local);
216-
if (IsReferenceToCapturedVariable(operation))
213+
var local = new LocalKey(symbol);
214+
if (IsCapturedVariable(symbol))
217215
InterproceduralState.TrackHoistedLocal(local);
218216

219217
// Get the value from the hoisted locals, if it's tracked there.
@@ -223,10 +221,10 @@ private TValue GetLocal(ILocalReferenceOperation operation, LocalDataFlowState<T
223221
return state.Get(local);
224222
}
225223

226-
private void SetLocal(ILocalReferenceOperation operation, TValue value, LocalDataFlowState<TValue, TContext, TValueLattice, TContextLattice> state, bool merge = false)
224+
private void SetLocal(ILocalSymbol localSymbol, TValue value, LocalDataFlowState<TValue, TContext, TValueLattice, TContextLattice> state, bool merge = false)
227225
{
228-
var local = new LocalKey(operation.Local);
229-
if (IsReferenceToCapturedVariable(operation))
226+
var local = new LocalKey(localSymbol);
227+
if (IsCapturedVariable(localSymbol))
230228
InterproceduralState.TrackHoistedLocal(local);
231229

232230
// Update the value stored in the hoisted locals, if it's tracked there.
@@ -239,7 +237,23 @@ private void SetLocal(ILocalReferenceOperation operation, TValue value, LocalDat
239237
state.Set(local, newValue);
240238
}
241239

242-
private TValue ProcessSingleTargetAssignment(IOperation targetOperation, IAssignmentOperation operation, LocalDataFlowState<TValue, TContext, TValueLattice, TContextLattice> state, bool merge)
240+
private TValue ProcessSingleTargetAssignment(
241+
IOperation targetOperation,
242+
IAssignmentOperation operation,
243+
LocalDataFlowState<TValue, TContext, TValueLattice, TContextLattice> state,
244+
bool merge
245+
)
246+
{
247+
return ProcessSingleTargetAssignment(targetOperation, operation.Value, operation, state, merge);
248+
}
249+
250+
private TValue ProcessSingleTargetAssignment(
251+
IOperation targetOperation,
252+
IOperation valueOperation,
253+
IOperation assignmentOperation,
254+
LocalDataFlowState<TValue, TContext, TValueLattice, TContextLattice> state,
255+
bool merge
256+
)
243257
{
244258
switch (targetOperation)
245259
{
@@ -253,8 +267,8 @@ private TValue ProcessSingleTargetAssignment(IOperation targetOperation, IAssign
253267
IParameterReferenceOperation parameterRef => GetParameterTargetValue(parameterRef.Parameter),
254268
_ => throw new InvalidOperationException()
255269
};
256-
TValue value = Visit(operation.Value, state);
257-
HandleAssignment(value, targetValue, operation, in current.Context);
270+
TValue value = Visit(valueOperation, state);
271+
HandleAssignment(value, targetValue, assignmentOperation, in current.Context);
258272
return value;
259273
}
260274

@@ -266,7 +280,7 @@ private TValue ProcessSingleTargetAssignment(IOperation targetOperation, IAssign
266280
// correctly detect whether it is used for reading or writing inside of VisitPropertyReference.
267281
// https://github.com/dotnet/roslyn/issues/25057
268282
TValue instanceValue = Visit(propertyRef.Instance, state);
269-
TValue value = Visit(operation.Value, state);
283+
TValue value = Visit(valueOperation, state);
270284
IMethodSymbol? setMethod = propertyRef.Property.GetSetMethod();
271285

272286
if (setMethod == null ||
@@ -287,7 +301,7 @@ private TValue ProcessSingleTargetAssignment(IOperation targetOperation, IAssign
287301

288302
var current = state.Current;
289303
TValue targetValue = GetBackingFieldTargetValue(propertyRef, in current.Context);
290-
HandleAssignment(value, targetValue, operation, in current.Context);
304+
HandleAssignment(value, targetValue, assignmentOperation, in current.Context);
291305
return value;
292306
}
293307

@@ -297,7 +311,7 @@ private TValue ProcessSingleTargetAssignment(IOperation targetOperation, IAssign
297311
arguments.Add(Visit(val, state));
298312
arguments.Add(value);
299313

300-
HandleMethodCallHelper(setMethod, instanceValue, arguments.ToImmutableArray(), operation, state);
314+
HandleMethodCallHelper(setMethod, instanceValue, arguments.ToImmutableArray(), assignmentOperation, state);
301315
// The return value of a property set expression is the value,
302316
// even though a property setter has no return value.
303317
return value;
@@ -308,14 +322,14 @@ private TValue ProcessSingleTargetAssignment(IOperation targetOperation, IAssign
308322
// not a call to an event accessor method. There is no Roslyn API to access the field,
309323
// so just visit the instance and the value. https://github.com/dotnet/roslyn/issues/40103
310324
Visit(eventRef.Instance, state);
311-
return Visit(operation.Value, state);
325+
return Visit(valueOperation, state);
312326
}
313327
case IImplicitIndexerReferenceOperation indexerRef:
314328
{
315329
// An implicit reference to an indexer where the argument is a System.Index
316330
TValue instanceValue = Visit(indexerRef.Instance, state);
317331
TValue indexArgumentValue = Visit(indexerRef.Argument, state);
318-
TValue value = Visit(operation.Value, state);
332+
TValue value = Visit(valueOperation, state);
319333

320334
var property = (IPropertySymbol)indexerRef.IndexerSymbol;
321335

@@ -331,15 +345,23 @@ private TValue ProcessSingleTargetAssignment(IOperation targetOperation, IAssign
331345
break;
332346
}
333347

334-
HandleMethodCallHelper(setMethod, instanceValue, argumentsBuilder.ToImmutableArray(), operation, state);
348+
HandleMethodCallHelper(setMethod, instanceValue, argumentsBuilder.ToImmutableArray(), assignmentOperation, state);
335349
return value;
336350
}
337351

338352
// TODO: when setting a property in an attribute, target is an IPropertyReference.
339353
case ILocalReferenceOperation localRef:
340354
{
341-
TValue value = Visit(operation.Value, state);
342-
SetLocal(localRef, value, state, merge);
355+
TValue value = Visit(valueOperation, state);
356+
SetLocal(localRef.Local, value, state, merge);
357+
return value;
358+
}
359+
case IDeclarationPatternOperation declPattern:
360+
{
361+
if (declPattern.DeclaredSymbol is not ILocalSymbol declaredSymbol)
362+
break;
363+
var value = Visit(valueOperation, state);
364+
SetLocal(declaredSymbol, value, state, merge);
343365
return value;
344366
}
345367
case IArrayElementReferenceOperation arrayElementRef:
@@ -349,22 +371,22 @@ private TValue ProcessSingleTargetAssignment(IOperation targetOperation, IAssign
349371

350372
TValue arrayRef = Visit(arrayElementRef.ArrayReference, state);
351373
TValue index = Visit(arrayElementRef.Indices[0], state);
352-
TValue value = Visit(operation.Value, state);
353-
HandleArrayElementWrite(arrayRef, index, value, operation, merge: merge);
374+
TValue value = Visit(valueOperation, state);
375+
HandleArrayElementWrite(arrayRef, index, value, assignmentOperation, merge: merge);
354376
return value;
355377
}
356378
case IInlineArrayAccessOperation inlineArrayAccess:
357379
{
358380
TValue arrayRef = Visit(inlineArrayAccess.Instance, state);
359381
TValue index = Visit(inlineArrayAccess.Argument, state);
360-
TValue value = Visit(operation.Value, state);
361-
HandleArrayElementWrite(arrayRef, index, value, operation, merge: merge);
382+
TValue value = Visit(valueOperation, state);
383+
HandleArrayElementWrite(arrayRef, index, value, assignmentOperation, merge: merge);
362384
return value;
363385
}
364386
case IDiscardOperation:
365387
// Assignments like "_ = SomeMethod();" don't need dataflow tracking.
366388
// Seems like this can't happen with a flow capture operation.
367-
Debug.Assert(operation.Target is not IFlowCaptureReferenceOperation);
389+
Debug.Assert(targetOperation is not IFlowCaptureReferenceOperation);
368390
break;
369391
case IInstanceReferenceOperation:
370392
// Assignment to 'this' is not tracked currently.
@@ -389,14 +411,45 @@ private TValue ProcessSingleTargetAssignment(IOperation targetOperation, IAssign
389411
UnexpectedOperationHandler.Handle(targetOperation);
390412
break;
391413
}
392-
return Visit(operation.Value, state);
414+
return Visit(valueOperation, state);
393415
}
394416

395417
public override TValue VisitSimpleAssignment(ISimpleAssignmentOperation operation, LocalDataFlowState<TValue, TContext, TValueLattice, TContextLattice> state)
396418
{
397419
return ProcessAssignment(operation, state);
398420
}
399421

422+
public override TValue VisitIsPattern(IIsPatternOperation operation, LocalDataFlowState<TValue, TContext, TValueLattice, TContextLattice> state)
423+
{
424+
if (operation.Pattern is IDeclarationPatternOperation declarationPattern)
425+
{
426+
// A declaration pattern is like an assignment to a local
427+
return ProcessSingleTargetAssignment(
428+
declarationPattern,
429+
operation.Value,
430+
operation,
431+
state,
432+
merge: false
433+
);
434+
}
435+
return base.VisitIsPattern(operation, state);
436+
}
437+
438+
public override TValue VisitPropertySubpattern(IPropertySubpatternOperation propPattern, LocalDataFlowState<TValue, TContext, TValueLattice, TContextLattice> state)
439+
{
440+
if (propPattern.Pattern is IDeclarationPatternOperation declPattern)
441+
{
442+
return ProcessSingleTargetAssignment(
443+
declPattern,
444+
propPattern.Member,
445+
propPattern,
446+
state,
447+
merge: false
448+
);
449+
}
450+
return base.VisitPropertySubpattern(propPattern, state);
451+
}
452+
400453
public override TValue VisitCompoundAssignment(ICompoundAssignmentOperation operation, LocalDataFlowState<TValue, TContext, TValueLattice, TContextLattice> state)
401454
{
402455
return ProcessAssignment(operation, state);

src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalStateLattice.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,6 @@ public struct LocalState<TValue> : IEquatable<LocalState<TValue>>
4545
// are tracked as part of the dictionary of values, keyed by LocalKey.
4646
public DefaultValueDictionary<CaptureId, ValueSet<CapturedReferenceValue>> CapturedReferences;
4747

48-
public LocalState(TValue defaultValue)
49-
: this(new DefaultValueDictionary<LocalKey, TValue>(defaultValue),
50-
new DefaultValueDictionary<CaptureId, ValueSet<CapturedReferenceValue>>(default(ValueSet<CapturedReferenceValue>)))
51-
{
52-
}
53-
5448
public LocalState(DefaultValueDictionary<LocalKey, TValue> dictionary, DefaultValueDictionary<CaptureId, ValueSet<CapturedReferenceValue>> capturedReferences)
5549
{
5650
Dictionary = dictionary;

src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,6 @@ public Task LocalDataFlow()
294294
return RunTest(nameof(LocalDataFlow));
295295
}
296296

297-
298297
[Fact]
299298
public Task ExceptionalDataFlow()
300299
{

src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/BaseInAssemblyAttribute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ public abstract class BaseInAssemblyAttribute : BaseExpectedLinkedBehaviorAttrib
1010
/// This property can override that by setting only the platforms
1111
/// which are expected to preserve the desired behavior.
1212
/// </summary>
13-
public Tool Tool { get; set; } = Tool.TrimmerAnalyzerAndNativeAot;
13+
public Tool Tool { get; set; } = Tool.All;
1414
}
1515
}

src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/IgnoreTestCaseAttribute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ public IgnoreTestCaseAttribute(string reason)
1414
ArgumentNullException.ThrowIfNull(reason);
1515
}
1616

17-
public Tool IgnoredBy { get; set; } = Tool.TrimmerAnalyzerAndNativeAot;
17+
public Tool IgnoredBy { get; set; } = Tool.All;
1818
}
1919
}

src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptAttribute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ public class KeptAttribute : BaseExpectedLinkedBehaviorAttribute
1313
/// This property can override that by setting only the platforms
1414
/// which are expected to keep the target.
1515
/// </summary>
16-
public Tool By { get; set; } = Tool.TrimmerAnalyzerAndNativeAot;
16+
public Tool By { get; set; } = Tool.All;
1717
}
1818
}

src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/LogContainsAttribute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ public LogContainsAttribute(string message, bool regexMatch = false)
2121
/// Property used by the result checkers of trimmer and analyzers to determine whether
2222
/// the tool should have produced the specified warning on the annotated member.
2323
/// </summary>
24-
public Tool ProducedBy { get; set; } = Tool.TrimmerAnalyzerAndNativeAot;
24+
public Tool ProducedBy { get; set; } = Tool.All;
2525
}
2626
}

src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/LogDoesNotContainAttribute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ public LogDoesNotContainAttribute(string message, bool regexMatch = false)
2121
/// Property used by the result checkers of trimmer and analyzers to determine whether
2222
/// the tool should have produced the specified warning on the annotated member.
2323
/// </summary>
24-
public Tool ProducedBy { get; set; } = Tool.TrimmerAnalyzerAndNativeAot;
24+
public Tool ProducedBy { get; set; } = Tool.All;
2525
}
2626
}

src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/SkipKeptItemsValidationAttribute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ public class SkipKeptItemsValidationAttribute : BaseExpectedLinkedBehaviorAttrib
1010
{
1111
public SkipKeptItemsValidationAttribute() { }
1212

13-
public Tool By { get; set; } = Tool.TrimmerAnalyzerAndNativeAot;
13+
public Tool By { get; set; } = Tool.All;
1414
}
1515
}

src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/Tool.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ public enum Tool
1717
Trimmer = 1,
1818
Analyzer = 2,
1919
NativeAot = 4,
20-
TrimmerAnalyzerAndNativeAot = Trimmer | Analyzer | NativeAot
20+
All = Trimmer | Analyzer | NativeAot
2121
}
2222
}

0 commit comments

Comments
 (0)