Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/linker.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSven Boemer <sbomer@gmail.com>2022-04-20 21:37:22 +0300
committerGitHub <noreply@github.com>2022-04-20 21:37:22 +0300
commitfb5b3396eda1fb41765f04cad8b2cbd34d1fd53c (patch)
treef19f1ba18b1ac7f1d5a0b4793ed60a11eb3511ca
parent36b61348be2b93f2add8886174f19c0ed4aacd36 (diff)
Support assignment to flow capture references (#2730)
* Support assignment to flow capture references This uses code copied from Roslyn to detect whether a flow capture is used as an r-value or l-value (or both). L-value captures are tracked in a separate dictionary which is used to look up captured values when assigning to l-value capture references. The capture references are not first-class dataflow values under SingleValue, in order to keep the general dataflow logic separated from the trim-analysis-specific values.
-rw-r--r--src/ILLink.RoslynAnalyzer/DataFlow/BasicBlockExtensions.cs27
-rw-r--r--src/ILLink.RoslynAnalyzer/DataFlow/CapturedReferenceValue.cs50
-rw-r--r--src/ILLink.RoslynAnalyzer/DataFlow/ControlFlowGraphExtensions.cs36
-rw-r--r--src/ILLink.RoslynAnalyzer/DataFlow/ControlFlowGraphProxy.cs2
-rw-r--r--src/ILLink.RoslynAnalyzer/DataFlow/FlowCaptureKind.cs30
-rw-r--r--src/ILLink.RoslynAnalyzer/DataFlow/LValueFlowCaptureProvider.cs80
-rw-r--r--src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs196
-rw-r--r--src/ILLink.RoslynAnalyzer/DataFlow/LocalStateLattice.cs26
-rw-r--r--src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs6
-rw-r--r--src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimDataFlowAnalysis.cs3
-rw-r--r--test/Mono.Linker.Tests.Cases/DataFlow/ArrayDataFlow.cs93
-rw-r--r--test/Mono.Linker.Tests.Cases/DataFlow/FieldDataFlow.cs35
-rw-r--r--test/Mono.Linker.Tests.Cases/DataFlow/LocalDataFlow.cs70
-rw-r--r--test/Mono.Linker.Tests.Cases/DataFlow/MethodParametersDataFlow.cs43
-rw-r--r--test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs106
15 files changed, 682 insertions, 121 deletions
diff --git a/src/ILLink.RoslynAnalyzer/DataFlow/BasicBlockExtensions.cs b/src/ILLink.RoslynAnalyzer/DataFlow/BasicBlockExtensions.cs
new file mode 100644
index 000000000..f4f1c85d6
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/DataFlow/BasicBlockExtensions.cs
@@ -0,0 +1,27 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Generic;
+using Microsoft.CodeAnalysis.Operations;
+
+namespace Microsoft.CodeAnalysis.FlowAnalysis
+{
+ // Copied from https://github.com/dotnet/roslyn/blob/fdd40b21d59c13e8fa6c718c7aaf9d50634da754/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/BasicBlockExtensions.cs
+ internal static partial class BasicBlockExtensions
+ {
+ public static IEnumerable<IOperation> DescendantOperations (this BasicBlock basicBlock)
+ {
+ foreach (var statement in basicBlock.Operations) {
+ foreach (var operation in statement.DescendantsAndSelf ()) {
+ yield return operation;
+ }
+ }
+
+ if (basicBlock.BranchValue != null) {
+ foreach (var operation in basicBlock.BranchValue.DescendantsAndSelf ()) {
+ yield return operation;
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/ILLink.RoslynAnalyzer/DataFlow/CapturedReferenceValue.cs b/src/ILLink.RoslynAnalyzer/DataFlow/CapturedReferenceValue.cs
new file mode 100644
index 000000000..3bb47df6c
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/DataFlow/CapturedReferenceValue.cs
@@ -0,0 +1,50 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using ILLink.Shared.DataFlow;
+using Microsoft.CodeAnalysis;
+
+namespace ILLink.RoslynAnalyzer.DataFlow
+{
+ public struct CapturedReferenceValue : IEquatable<CapturedReferenceValue>
+ {
+ public readonly IOperation? Reference;
+
+ public CapturedReferenceValue (IOperation operation)
+ {
+ switch (operation.Kind) {
+ case OperationKind.PropertyReference:
+ case OperationKind.LocalReference:
+ case OperationKind.FieldReference:
+ case OperationKind.ParameterReference:
+ case OperationKind.ArrayElementReference:
+ break;
+ default:
+ throw new NotImplementedException (operation.Kind.ToString ());
+ }
+ Reference = operation;
+ }
+
+ public bool Equals (CapturedReferenceValue other) => Reference == other.Reference;
+ }
+
+
+ public struct CapturedReferenceLattice : ILattice<CapturedReferenceValue>
+ {
+ public CapturedReferenceValue Top => new CapturedReferenceValue ();
+
+ public CapturedReferenceValue Meet (CapturedReferenceValue left, CapturedReferenceValue right)
+ {
+ if (left.Equals (right))
+ return left;
+ if (left.Reference == null)
+ return right;
+ if (right.Reference == null)
+ return left;
+ // Both non-null and different shouldn't happen.
+ // We assume that a flow capture can capture only a single property.
+ throw new InvalidOperationException ();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/ILLink.RoslynAnalyzer/DataFlow/ControlFlowGraphExtensions.cs b/src/ILLink.RoslynAnalyzer/DataFlow/ControlFlowGraphExtensions.cs
new file mode 100644
index 000000000..6f188c53a
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/DataFlow/ControlFlowGraphExtensions.cs
@@ -0,0 +1,36 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.FlowAnalysis;
+
+namespace ILLink.RoslynAnalyzer.DataFlow
+{
+ // Copied from https://github.com/dotnet/roslyn/blob/fdd40b21d59c13e8fa6c718c7aaf9d50634da754/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ControlFlowGraphExtensions.cs
+ internal static partial class ControlFlowGraphExtensions
+ {
+ public static BasicBlock EntryBlock (this ControlFlowGraph cfg)
+ {
+ var firstBlock = cfg.Blocks[0];
+ Debug.Assert (firstBlock.Kind == BasicBlockKind.Entry);
+ return firstBlock;
+ }
+
+ public static BasicBlock ExitBlock (this ControlFlowGraph cfg)
+ {
+ var lastBlock = cfg.Blocks.Last ();
+ Debug.Assert (lastBlock.Kind == BasicBlockKind.Exit);
+ return lastBlock;
+ }
+
+ public static IEnumerable<IOperation> DescendantOperations (this ControlFlowGraph cfg)
+ => cfg.Blocks.SelectMany (b => b.DescendantOperations ());
+
+ public static IEnumerable<T> DescendantOperations<T> (this ControlFlowGraph cfg, OperationKind operationKind)
+ where T : IOperation
+ => cfg.DescendantOperations ().Where (d => d?.Kind == operationKind).Cast<T> ();
+ }
+}
diff --git a/src/ILLink.RoslynAnalyzer/DataFlow/ControlFlowGraphProxy.cs b/src/ILLink.RoslynAnalyzer/DataFlow/ControlFlowGraphProxy.cs
index 8eb5befd2..d5182c531 100644
--- a/src/ILLink.RoslynAnalyzer/DataFlow/ControlFlowGraphProxy.cs
+++ b/src/ILLink.RoslynAnalyzer/DataFlow/ControlFlowGraphProxy.cs
@@ -49,7 +49,7 @@ namespace ILLink.RoslynAnalyzer.DataFlow
}
}
- public BlockProxy Entry => new BlockProxy (ControlFlowGraph.Blocks[0]);
+ public BlockProxy Entry => new BlockProxy (ControlFlowGraph.EntryBlock ());
// This is implemented by getting predecessors of the underlying Roslyn BasicBlock.
// This is fine as long as the blocks come from the correct control-flow graph.
diff --git a/src/ILLink.RoslynAnalyzer/DataFlow/FlowCaptureKind.cs b/src/ILLink.RoslynAnalyzer/DataFlow/FlowCaptureKind.cs
new file mode 100644
index 000000000..78feb6f8b
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/DataFlow/FlowCaptureKind.cs
@@ -0,0 +1,30 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#nullable disable
+
+namespace ILLink.RoslynAnalyzer.DataFlow
+{
+ // Copied from https://github.com/dotnet/roslyn/blob/c8ebc8682889b395fcb84c85bf4ff54577377d26/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/FlowCaptureKind.cs
+ /// <summary>
+ /// Indicates the kind of flow capture in an <see cref="IFlowCaptureOperation"/>.
+ /// </summary>
+ public enum FlowCaptureKind
+ {
+ /// <summary>
+ /// Indicates an R-Value flow capture, i.e. capture of a symbol's value.
+ /// </summary>
+ RValueCapture,
+
+ /// <summary>
+ /// Indicates an L-Value flow capture, i.e. captures of a symbol's location/address.
+ /// </summary>
+ LValueCapture,
+
+ /// <summary>
+ /// Indicates both an R-Value and an L-Value flow capture, i.e. captures of a symbol's value and location/address.
+ /// These are generated for left of a compound assignment operation, such that there is conditional code on the right side of the compound assignment.
+ /// </summary>
+ LValueAndRValueCapture
+ }
+}
diff --git a/src/ILLink.RoslynAnalyzer/DataFlow/LValueFlowCaptureProvider.cs b/src/ILLink.RoslynAnalyzer/DataFlow/LValueFlowCaptureProvider.cs
new file mode 100644
index 000000000..610a4cfd4
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/DataFlow/LValueFlowCaptureProvider.cs
@@ -0,0 +1,80 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#nullable disable
+
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Operations;
+using Microsoft.CodeAnalysis.FlowAnalysis;
+
+#if DEBUG
+using System.Diagnostics;
+#endif
+
+namespace ILLink.RoslynAnalyzer.DataFlow
+{
+ // Copied from https://github.com/dotnet/roslyn/blob/c8ebc8682889b395fcb84c85bf4ff54577377d26/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/LValueFlowCaptureProvider.cs
+ /// <summary>
+ /// Helper class to detect <see cref="IFlowCaptureOperation"/>s that are l-value captures.
+ /// L-value captures are essentially captures of a symbol's location/address.
+ /// Corresponding <see cref="IFlowCaptureReferenceOperation"/>s which share the same
+ /// <see cref="CaptureId"/> as this flow capture, dereferences and writes to this location
+ /// subsequently in the flow graph.
+ /// For example, consider the below code:
+ /// a[i] = x ?? a[j];
+ /// The control flow graph contains an initial flow capture of "a[i]" to capture the l-value
+ /// of this array element:
+ /// FC0 (a[i])
+ /// Then it evaluates the right hand side, which can have different
+ /// values on different control flow paths, and the resultant value is then written
+ /// to the captured location:
+ /// FCR0 = result
+ /// </summary>
+ /// <remarks>
+ /// NOTE: This type is a workaround for https://github.com/dotnet/roslyn/issues/31007
+ /// and it can be deleted once that feature is implemented.
+ /// </remarks>
+ internal static class LValueFlowCapturesProvider
+ {
+ public static ImmutableDictionary<CaptureId, FlowCaptureKind> CreateLValueFlowCaptures (ControlFlowGraph cfg)
+ {
+ // This method identifies flow capture reference operations that are target of an assignment
+ // and marks them as lvalue flow captures.
+ // Control flow graph can also contain flow captures
+ // that are r-value captures at some point and l-values captures at other point in
+ // the flow graph. Specifically, for an ICoalesceOperation a flow capture acts
+ // as both an r-value and l-value flow capture.
+
+ ImmutableDictionary<CaptureId, FlowCaptureKind>.Builder lvalueFlowCaptureIdBuilder = null;
+ var rvalueFlowCaptureIds = new HashSet<CaptureId> ();
+
+ foreach (var flowCaptureReference in cfg.DescendantOperations<IFlowCaptureReferenceOperation> (OperationKind.FlowCaptureReference)) {
+ if (flowCaptureReference.Parent is IAssignmentOperation assignment &&
+ assignment.Target == flowCaptureReference ||
+ flowCaptureReference.IsInLeftOfDeconstructionAssignment (out _)) {
+ lvalueFlowCaptureIdBuilder ??= ImmutableDictionary.CreateBuilder<CaptureId, FlowCaptureKind> ();
+ var captureKind = flowCaptureReference.Parent.IsAnyCompoundAssignment () || rvalueFlowCaptureIds.Contains (flowCaptureReference.Id)
+ ? FlowCaptureKind.LValueAndRValueCapture
+ : FlowCaptureKind.LValueCapture;
+ lvalueFlowCaptureIdBuilder.Add (flowCaptureReference.Id, captureKind);
+ } else {
+ rvalueFlowCaptureIds.Add (flowCaptureReference.Id);
+ }
+ }
+
+#if DEBUG
+ if (lvalueFlowCaptureIdBuilder != null) {
+ foreach (var kvp in lvalueFlowCaptureIdBuilder) {
+ var captureId = kvp.Key;
+ var kind = kvp.Value;
+ Debug.Assert (kind == FlowCaptureKind.LValueAndRValueCapture || !rvalueFlowCaptureIds.Contains (captureId), "Flow capture used as both an r-value and an l-value, but with incorrect flow capture kind");
+ }
+ }
+#endif
+
+ return lvalueFlowCaptureIdBuilder != null ? lvalueFlowCaptureIdBuilder.ToImmutable () : ImmutableDictionary<CaptureId, FlowCaptureKind>.Empty;
+ }
+ }
+}
diff --git a/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs b/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs
index 48c3132d9..1f3c48323 100644
--- a/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs
+++ b/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs
@@ -30,8 +30,16 @@ namespace ILLink.RoslynAnalyzer.DataFlow
protected TValue TopValue => LocalStateLattice.Lattice.ValueLattice.Top;
- public LocalDataFlowVisitor (LocalStateLattice<TValue, TValueLattice> lattice, OperationBlockAnalysisContext context) =>
- (LocalStateLattice, Context) = (lattice, context);
+ private readonly ImmutableDictionary<CaptureId, FlowCaptureKind> lValueFlowCaptures;
+
+ bool IsLValueFlowCapture (CaptureId captureId)
+ => lValueFlowCaptures.ContainsKey (captureId);
+
+ bool IsRValueFlowCapture (CaptureId captureId)
+ => !lValueFlowCaptures.TryGetValue (captureId, out var captureKind) || captureKind != FlowCaptureKind.LValueCapture;
+
+ public LocalDataFlowVisitor (LocalStateLattice<TValue, TValueLattice> lattice, OperationBlockAnalysisContext context, ImmutableDictionary<CaptureId, FlowCaptureKind> lValueFlowCaptures) =>
+ (LocalStateLattice, Context, this.lValueFlowCaptures) = (lattice, context, lValueFlowCaptures);
public void Transfer (BlockProxy block, LocalDataFlowState<TValue, TValueLattice> state)
{
@@ -89,60 +97,100 @@ namespace ILLink.RoslynAnalyzer.DataFlow
public override TValue VisitSimpleAssignment (ISimpleAssignmentOperation operation, LocalDataFlowState<TValue, TValueLattice> state)
{
+ // Visiting the target operation is a no-op except for operations which produce
+ // dataflow values that have the same representation as LValues or RValues.
+ // For example, field references.
var targetValue = Visit (operation.Target, state);
- var value = Visit (operation.Value, state);
- switch (operation.Target) {
- case ILocalReferenceOperation localRef:
- state.Set (new LocalKey (localRef.Local), value);
- break;
+
+ var targetOperation = operation.Target;
+ if (targetOperation is IFlowCaptureReferenceOperation flowCaptureReference) {
+ Debug.Assert (IsLValueFlowCapture (flowCaptureReference.Id));
+ var capturedReference = state.Current.CapturedReferences.Get (flowCaptureReference.Id).Reference;
+ targetOperation = capturedReference;
+ if (targetOperation == null)
+ throw new InvalidOperationException ();
+
+ targetValue = Visit (targetOperation, state);
+ // Note: technically we should avoid visiting the target operation when assigning to a flow capture reference,
+ // because this should be done when the capture is created. For example, a flow capture used as both an LValue and a RValue
+ // should only evaluate the expression that computes the object instance of a property reference once.
+ // However, we just visit the instance again below for simplicity. This could be generalized if we encounter a dataflow
+ // behavior where this makes a difference.
+ }
+
+ switch (targetOperation) {
case IFieldReferenceOperation:
- case IParameterReferenceOperation:
- // Extension point for assignments to "interesting" targets.
- // Doesn't get called for assignments to locals, which are handled above.
- HandleAssignment (value, targetValue, operation);
- break;
- case IPropertyReferenceOperation propertyRef:
- // A property assignment is really a call to the property setter.
- var setMethod = propertyRef.Property.SetMethod;
- if (setMethod == null) {
- // This can happen in a constructor - there it is possible to assign to a property
- // without a setter. This turns into an assignment to the compiler-generated backing field.
- // To match the linker, this should warn about the compiler-generated backing field.
- // For now, just don't warn.
- break;
+ case IParameterReferenceOperation: {
+ // These assignment targets have the same dataflow representation for LValues and RValues,
+ // so we can use the target value that we already computed. This simplification is specific
+ // to the trim analysis, so would ideally be moved into TrimAnalysisVisitor.
+ TValue value = Visit (operation.Value, state);
+ HandleAssignment (value, targetValue, operation);
+ return value;
}
- TValue instanceValue = Visit (propertyRef.Instance, state);
- // The return value of a property set expression is the value,
- // even though a property setter has no return value.
- HandleMethodCall (
- setMethod,
- instanceValue,
- ImmutableArray.Create (value),
- operation);
- break;
- // TODO: when setting a property in an attribute, target is an IPropertyReference.
- case IArrayElementReferenceOperation arrayElementRef:
- if (arrayElementRef.Indices.Length != 1)
- break;
- HandleArrayElementWrite (Visit (arrayElementRef.ArrayReference, state), Visit (arrayElementRef.Indices[0], state), value, operation);
- break;
+ // The remaining cases don't have a dataflow value that represents LValues, so we need
+ // to handle the LHS specially.
+ case IPropertyReferenceOperation propertyRef: {
+ var setMethod = propertyRef.Property.SetMethod;
+ if (setMethod == null) {
+ // This can happen in a constructor - there it is possible to assign to a property
+ // without a setter. This turns into an assignment to the compiler-generated backing field.
+ // To match the linker, this should warn about the compiler-generated backing field.
+ // For now, just don't warn. https://github.com/dotnet/linker/issues/2731
+ break;
+ }
+ TValue instanceValue = Visit (propertyRef.Instance, state);
+ TValue value = Visit (operation.Value, state);
+ HandleMethodCall (
+ setMethod,
+ instanceValue,
+ ImmutableArray.Create (value),
+ operation);
+ // The return value of a property set expression is the value,
+ // even though a property setter has no return value.
+ return value;
+ }
+ // TODO: when setting a property in an attribute, target is an IPropertyReference.
+ case ILocalReferenceOperation localRef: {
+ TValue value = Visit (operation.Value, state);
+ state.Set (new LocalKey (localRef.Local), value);
+ return value;
+ }
+ case IArrayElementReferenceOperation arrayElementRef: {
+ if (arrayElementRef.Indices.Length != 1)
+ break;
+
+ TValue arrayRef = Visit (arrayElementRef.ArrayReference, state);
+ TValue index = Visit (arrayElementRef.Indices[0], state);
+ TValue value = Visit (operation.Value, state);
+ HandleArrayElementWrite (arrayRef, index, value, operation);
+ return value;
+ }
case IDiscardOperation:
// Assignments like "_ = SomeMethod();" don't need dataflow tracking.
+ // Seems like this can't happen with a flow capture operation.
+ Debug.Assert (operation.Target is not IFlowCaptureReferenceOperation);
break;
case IInvalidOperation:
// This can happen for a field assignment in an attribute instance.
// TODO: validate against the field attributes.
break;
default:
- throw new NotImplementedException (operation.Target.GetType ().ToString ());
+ throw new NotImplementedException (targetOperation.GetType ().ToString ());
}
- return value;
+ return Visit (operation.Value, state);
}
// Similar to VisitLocalReference
public override TValue VisitFlowCaptureReference (IFlowCaptureReferenceOperation operation, LocalDataFlowState<TValue, TValueLattice> state)
{
+ if (!operation.GetValueUsageInfo (Context.OwningSymbol).HasFlag (ValueUsageInfo.Read)) {
+ Debug.Assert (IsLValueFlowCapture (operation.Id));
+ return TopValue;
+ }
+
+ Debug.Assert (IsRValueFlowCapture (operation.Id));
return state.Get (new LocalKey (operation.Id));
}
@@ -152,8 +200,18 @@ namespace ILLink.RoslynAnalyzer.DataFlow
public override TValue VisitFlowCapture (IFlowCaptureOperation operation, LocalDataFlowState<TValue, TValueLattice> state)
{
TValue value = Visit (operation.Value, state);
- state.Set (new LocalKey (operation.Id), value);
- return value;
+ if (IsLValueFlowCapture (operation.Id)) {
+ // Note: technically we should save some information about the value for LValue flow captures
+ // (for example, the object instance of a property reference) and avoid re-computing it when
+ // assigning to the FlowCaptureReference.
+ var currentState = state.Current;
+ currentState.CapturedReferences.Set (operation.Id, new CapturedReferenceValue (operation.Value));
+ state.Current = currentState;
+ }
+ if (IsRValueFlowCapture (operation.Id))
+ state.Set (new LocalKey (operation.Id), value);
+
+ return TopValue;
}
public override TValue VisitExpressionStatement (IExpressionStatementOperation operation, LocalDataFlowState<TValue, TValueLattice> state)
@@ -165,56 +223,34 @@ namespace ILLink.RoslynAnalyzer.DataFlow
public override TValue VisitInvocation (IInvocationOperation operation, LocalDataFlowState<TValue, TValueLattice> state)
=> ProcessMethodCall (operation, operation.TargetMethod, operation.Instance, operation.Arguments, state);
- public static IMethodSymbol GetPropertyMethod (IPropertyReferenceOperation operation)
- {
- // The IPropertyReferenceOperation doesn't tell us whether this reference is to the getter or setter.
- // For this we need to look at the containing operation.
- var parent = operation.Parent;
- if (parent?.Kind == OperationKind.SimpleAssignment) {
- var assignment = (ISimpleAssignmentOperation) parent;
- if (assignment.Target == operation) {
- var setMethod = operation.Property.SetMethod;
- Debug.Assert (setMethod != null);
- return setMethod!;
- }
- Debug.Assert (assignment.Value == operation);
- }
-
- var getMethod = operation.Property.GetMethod;
- Debug.Assert (getMethod != null);
- return getMethod!;
- }
-
public override TValue VisitPropertyReference (IPropertyReferenceOperation operation, LocalDataFlowState<TValue, TValueLattice> state)
{
- if (operation.GetValueUsageInfo (Context.OwningSymbol).HasFlag (ValueUsageInfo.Read)) {
- // Accessing property for reading is really a call to the getter
- // The setter case is handled in assignment operation since here we don't have access to the value to pass to the setter
- TValue instanceValue = Visit (operation.Instance, state);
- return HandleMethodCall (
- operation.Property.GetMethod!,
- instanceValue,
- ImmutableArray<TValue>.Empty,
- operation);
- }
+ if (!operation.GetValueUsageInfo (Context.OwningSymbol).HasFlag (ValueUsageInfo.Read))
+ return TopValue;
- return TopValue;
+ // Accessing property for reading is really a call to the getter
+ // The setter case is handled in assignment operation since here we don't have access to the value to pass to the setter
+ TValue instanceValue = Visit (operation.Instance, state);
+ return HandleMethodCall (
+ operation.Property.GetMethod!,
+ instanceValue,
+ ImmutableArray<TValue>.Empty,
+ operation);
}
public override TValue VisitArrayElementReference (IArrayElementReferenceOperation operation, LocalDataFlowState<TValue, TValueLattice> state)
{
- if (operation.GetValueUsageInfo (Context.OwningSymbol).HasFlag (ValueUsageInfo.Read)) {
- // Accessing an array element for reading is a call to the indexer
- // or a plain array access. Just handle plain array access for now.
+ if (!operation.GetValueUsageInfo (Context.OwningSymbol).HasFlag (ValueUsageInfo.Read))
+ return TopValue;
- // Only handle simple index access
- if (operation.Indices.Length != 1)
- return TopValue;
+ // Accessing an array element for reading is a call to the indexer
+ // or a plain array access. Just handle plain array access for now.
- return HandleArrayElementRead (Visit (operation.ArrayReference, state), Visit (operation.Indices[0], state), operation);
- }
+ // Only handle simple index access
+ if (operation.Indices.Length != 1)
+ return TopValue;
- return TopValue;
+ return HandleArrayElementRead (Visit (operation.ArrayReference, state), Visit (operation.Indices[0], state), operation);
}
public override TValue VisitArgument (IArgumentOperation operation, LocalDataFlowState<TValue, TValueLattice> state)
diff --git a/src/ILLink.RoslynAnalyzer/DataFlow/LocalStateLattice.cs b/src/ILLink.RoslynAnalyzer/DataFlow/LocalStateLattice.cs
index 09b0a649e..5792e55c6 100644
--- a/src/ILLink.RoslynAnalyzer/DataFlow/LocalStateLattice.cs
+++ b/src/ILLink.RoslynAnalyzer/DataFlow/LocalStateLattice.cs
@@ -29,13 +29,26 @@ namespace ILLink.RoslynAnalyzer.DataFlow
}
}
- // Wrapper struct exists purely to substitute a concrete LocalKey for TKey of DefaultValueDictionary
public struct LocalState<TValue> : IEquatable<LocalState<TValue>>
where TValue : IEquatable<TValue>
{
public DefaultValueDictionary<LocalKey, TValue> Dictionary;
- public LocalState (DefaultValueDictionary<LocalKey, TValue> dictionary) => Dictionary = dictionary;
+ // Stores any operations which are captured by reference in a FlowCaptureOperation.
+ // Only stores captures which are assigned through. Captures of the values of operations
+ // are tracked as part of the dictionary of values, keyed by LocalKey.
+ public DefaultValueDictionary<CaptureId, CapturedReferenceValue> CapturedReferences;
+
+ public LocalState (DefaultValueDictionary<LocalKey, TValue> dictionary, DefaultValueDictionary<CaptureId, CapturedReferenceValue> capturedReferences)
+ {
+ Dictionary = dictionary;
+ CapturedReferences = capturedReferences;
+ }
+
+ public LocalState (DefaultValueDictionary<LocalKey, TValue> dictionary)
+ : this (dictionary, new DefaultValueDictionary<CaptureId, CapturedReferenceValue> (new CapturedReferenceValue ()))
+ {
+ }
public bool Equals (LocalState<TValue> other) => Dictionary.Equals (other.Dictionary);
@@ -52,15 +65,22 @@ namespace ILLink.RoslynAnalyzer.DataFlow
where TValueLattice : ILattice<TValue>
{
public readonly DictionaryLattice<LocalKey, TValue, TValueLattice> Lattice;
+ public readonly DictionaryLattice<CaptureId, CapturedReferenceValue, CapturedReferenceLattice> CapturedReferenceLattice;
public LocalStateLattice (TValueLattice valueLattice)
{
Lattice = new DictionaryLattice<LocalKey, TValue, TValueLattice> (valueLattice);
+ CapturedReferenceLattice = new DictionaryLattice<CaptureId, CapturedReferenceValue, CapturedReferenceLattice> (new CapturedReferenceLattice ());
Top = new (Lattice.Top);
}
public LocalState<TValue> Top { get; }
- public LocalState<TValue> Meet (LocalState<TValue> left, LocalState<TValue> right) => new (Lattice.Meet (left.Dictionary, right.Dictionary));
+ public LocalState<TValue> Meet (LocalState<TValue> left, LocalState<TValue> right)
+ {
+ var dictionary = Lattice.Meet (left.Dictionary, right.Dictionary);
+ var capturedProperties = CapturedReferenceLattice.Meet (left.CapturedReferences, right.CapturedReferences);
+ return new LocalState<TValue> (dictionary, capturedProperties);
+ }
}
}
diff --git a/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs b/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs
index 13fc5ca6c..f1992f492 100644
--- a/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs
+++ b/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs
@@ -9,6 +9,7 @@ using ILLink.Shared.TrimAnalysis;
using ILLink.Shared.TypeSystemProxy;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.FlowAnalysis;
using Microsoft.CodeAnalysis.Operations;
using MultiValue = ILLink.Shared.DataFlow.ValueSet<ILLink.Shared.DataFlow.SingleValue>;
@@ -32,8 +33,9 @@ namespace ILLink.RoslynAnalyzer.TrimAnalysis
public TrimAnalysisVisitor (
LocalStateLattice<MultiValue, ValueSetLattice<SingleValue>> lattice,
- OperationBlockAnalysisContext context
- ) : base (lattice, context)
+ OperationBlockAnalysisContext context,
+ ImmutableDictionary<CaptureId, FlowCaptureKind> lValueFlowCaptures
+ ) : base (lattice, context, lValueFlowCaptures)
{
_multiValueLattice = lattice.Lattice.ValueLattice;
TrimAnalysisPatterns = new TrimAnalysisPatternStore (_multiValueLattice);
diff --git a/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimDataFlowAnalysis.cs b/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimDataFlowAnalysis.cs
index 05ddd21c7..f688b782d 100644
--- a/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimDataFlowAnalysis.cs
+++ b/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimDataFlowAnalysis.cs
@@ -39,7 +39,8 @@ namespace ILLink.RoslynAnalyzer.TrimAnalysis
public TrimAnalysisPatternStore ComputeTrimAnalysisPatterns ()
{
- var visitor = new TrimAnalysisVisitor (Lattice, Context);
+ var lValueFlowCaptures = LValueFlowCapturesProvider.CreateLValueFlowCaptures (ControlFlowGraph.ControlFlowGraph);
+ var visitor = new TrimAnalysisVisitor (Lattice, Context, lValueFlowCaptures);
Fixpoint (ControlFlowGraph, Lattice, visitor);
return visitor.TrimAnalysisPatterns;
}
diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/ArrayDataFlow.cs b/test/Mono.Linker.Tests.Cases/DataFlow/ArrayDataFlow.cs
index 161889350..fb40089e4 100644
--- a/test/Mono.Linker.Tests.Cases/DataFlow/ArrayDataFlow.cs
+++ b/test/Mono.Linker.Tests.Cases/DataFlow/ArrayDataFlow.cs
@@ -20,6 +20,8 @@ namespace Mono.Linker.Tests.Cases.DataFlow
TestArrayWithInitializerMultipleElementsMix<TestType> (typeof (TestType));
TestArraySetElementOneElementStaticType ();
+ TestArraySetElementOneElementMix ();
+ TestArraySetElementOneElementMerged ();
TestArraySetElementOneElementParameter (typeof (TestType));
TestArraySetElementMultipleElementsStaticType ();
TestMergedArrayElement (1);
@@ -39,6 +41,8 @@ namespace Mono.Linker.Tests.Cases.DataFlow
TestArrayResetAfterCall ();
TestArrayResetAfterAssignment ();
TestMultiDimensionalArray.Test ();
+
+ WriteCapturedArrayElement.Test ();
}
[ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicMethods))]
@@ -89,6 +93,37 @@ namespace Mono.Linker.Tests.Cases.DataFlow
arr[1].RequiresPublicMethods (); // Should warn - unknown value at this index
}
+ [ExpectedWarning ("IL2072", nameof (GetUnknownType), nameof (DataFlowTypeExtensions.RequiresAll),
+ ProducedBy = ProducedBy.Trimmer)]
+ [ExpectedWarning ("IL2072", nameof (GetTypeWithPublicConstructors), nameof (DataFlowTypeExtensions.RequiresAll),
+ ProducedBy = ProducedBy.Trimmer)]
+ // https://github.com/dotnet/linker/issues/2736
+ [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll),
+ ProducedBy = ProducedBy.Analyzer)]
+ static void TestArraySetElementOneElementMix ()
+ {
+ Type[] arr = new Type[1];
+ if (string.Empty.Length == 0)
+ arr[0] = GetUnknownType ();
+ else
+ arr[0] = GetTypeWithPublicConstructors ();
+ arr[0].RequiresAll ();
+ }
+
+ [ExpectedWarning ("IL2072", nameof (GetUnknownType), nameof (DataFlowTypeExtensions.RequiresAll),
+ ProducedBy = ProducedBy.Analyzer)]
+ [ExpectedWarning ("IL2072", nameof (GetTypeWithPublicConstructors), nameof (DataFlowTypeExtensions.RequiresAll),
+ ProducedBy = ProducedBy.Analyzer)]
+ // https://github.com/dotnet/linker/issues/2737
+ [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll),
+ ProducedBy = ProducedBy.Trimmer)]
+ static void TestArraySetElementOneElementMerged ()
+ {
+ Type[] arr = new Type[1];
+ arr[0] = string.Empty.Length == 0 ? GetUnknownType () : GetTypeWithPublicConstructors ();
+ arr[0].RequiresAll ();
+ }
+
[ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicMethods))]
static void TestArraySetElementOneElementParameter ([DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] Type type)
{
@@ -483,6 +518,64 @@ namespace Mono.Linker.Tests.Cases.DataFlow
static Type[,] _externalArray;
}
+ class WriteCapturedArrayElement
+ {
+ [ExpectedWarning ("IL2072", nameof (GetUnknownType), nameof (DataFlowTypeExtensions.RequiresAll),
+ ProducedBy = ProducedBy.Analyzer)]
+ [ExpectedWarning ("IL2072", nameof (GetTypeWithPublicConstructors), nameof (DataFlowTypeExtensions.RequiresAll),
+ ProducedBy = ProducedBy.Analyzer)]
+ // https://github.com/dotnet/linker/issues/2737
+ [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll),
+ ProducedBy = ProducedBy.Trimmer)]
+ static void TestNullCoalesce ()
+ {
+ Type[] arr = new Type[1];
+ arr[0] = GetUnknownType () ?? GetTypeWithPublicConstructors ();
+ arr[0].RequiresAll ();
+ }
+
+ [ExpectedWarning ("IL2072", nameof (GetUnknownType), nameof (DataFlowTypeExtensions.RequiresAll),
+ ProducedBy = ProducedBy.Trimmer)]
+ // https://github.com/dotnet/linker/issues/2736
+ [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll),
+ ProducedBy = ProducedBy.Analyzer)]
+ static void TestNullCoalescingAssignment ()
+ {
+ Type[] arr = new Type[1];
+ arr[0] ??= GetUnknownType ();
+ arr[0].RequiresAll ();
+ }
+
+ // Both linker and analyzer get this wrong. They should produce IL2072 instead.
+ // https://github.com/dotnet/linker/issues/2736
+ // https://github.com/dotnet/linker/issues/2737
+ [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll))]
+ static void TestNullCoalescingAssignmentComplex ()
+ {
+ Type[] arr = new Type[1];
+ arr[0] ??= (GetUnknownType () ?? GetTypeWithPublicConstructors ());
+ arr[0].RequiresAll ();
+ }
+
+ public static void Test ()
+ {
+ TestNullCoalesce ();
+ TestNullCoalescingAssignment ();
+ TestNullCoalescingAssignmentComplex ();
+ }
+ }
+
+ [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors)]
+ private static Type GetTypeWithPublicConstructors ()
+ {
+ return null;
+ }
+
+ private static Type GetUnknownType ()
+ {
+ return null;
+ }
+
public class TestType { }
}
}
diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/FieldDataFlow.cs b/test/Mono.Linker.Tests.Cases/DataFlow/FieldDataFlow.cs
index 3809faa57..e432c6cc2 100644
--- a/test/Mono.Linker.Tests.Cases/DataFlow/FieldDataFlow.cs
+++ b/test/Mono.Linker.Tests.Cases/DataFlow/FieldDataFlow.cs
@@ -33,6 +33,8 @@ namespace Mono.Linker.Tests.Cases.DataFlow
instance.WriteUnknownValue ();
+ WriteCapturedField.Test ();
+
_ = _annotationOnWrongType;
TestStringEmpty ();
@@ -155,6 +157,39 @@ namespace Mono.Linker.Tests.Cases.DataFlow
RequirePublicMethods (string.Empty);
}
+ class WriteCapturedField
+ {
+ [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
+ static Type field;
+
+ [ExpectedWarning ("IL2074", nameof (GetUnknownType), nameof (field))]
+ [ExpectedWarning ("IL2074", nameof (GetTypeWithPublicConstructors), nameof (field))]
+ static void TestNullCoalesce ()
+ {
+ field = GetUnknownType () ?? GetTypeWithPublicConstructors ();
+ }
+
+ [ExpectedWarning ("IL2074", nameof (GetUnknownType), nameof (field))]
+ static void TestNullCoalescingAssignment ()
+ {
+ field ??= GetUnknownType ();
+ }
+
+ [ExpectedWarning ("IL2074", nameof (GetUnknownType), nameof (field))]
+ [ExpectedWarning ("IL2074", nameof (GetTypeWithPublicConstructors), nameof (field))]
+ static void TestNullCoalescingAssignmentComplex ()
+ {
+ field ??= GetUnknownType () ?? GetTypeWithPublicConstructors ();
+ }
+
+ public static void Test ()
+ {
+ TestNullCoalesce ();
+ TestNullCoalescingAssignment ();
+ TestNullCoalescingAssignmentComplex ();
+ }
+ }
+
private static void RequirePublicMethods (
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
string s)
diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/LocalDataFlow.cs b/test/Mono.Linker.Tests.Cases/DataFlow/LocalDataFlow.cs
index 1f6347d3b..b864be13e 100644
--- a/test/Mono.Linker.Tests.Cases/DataFlow/LocalDataFlow.cs
+++ b/test/Mono.Linker.Tests.Cases/DataFlow/LocalDataFlow.cs
@@ -19,6 +19,10 @@ namespace Mono.Linker.Tests.Cases.DataFlow
// These behave as expected
TestBranchMergeGoto ();
TestBranchMergeIf ();
+ TestBranchMergeNullCoalesce ();
+ TestBranchMergeNullCoalescingAssignment ();
+ TestBranchMergeNullCoalescingAssignmentComplex ();
+ TestBranchMergeDiscardNullCoalesce ();
TestBranchMergeIfElse ();
TestBranchMergeSwitch ();
TestBranchMergeTry ();
@@ -83,6 +87,43 @@ namespace Mono.Linker.Tests.Cases.DataFlow
str.RequiresPublicMethods (); // warns for GetWithPublicFields
}
+ [ExpectedWarning ("IL2072", nameof (GetWithPublicMethods), nameof (DataFlowStringExtensions.RequiresAll))]
+ [ExpectedWarning ("IL2072", nameof (GetWithPublicFields), nameof (DataFlowStringExtensions.RequiresAll))]
+ public static void TestBranchMergeNullCoalesce ()
+ {
+ string str = GetWithPublicMethods () ?? GetWithPublicFields ();
+
+ str.RequiresAll ();
+ }
+
+ [ExpectedWarning ("IL2072", nameof (GetWithPublicMethods), nameof (DataFlowStringExtensions.RequiresAll))]
+ [ExpectedWarning ("IL2072", nameof (GetWithPublicFields), nameof (DataFlowStringExtensions.RequiresAll))]
+ public static void TestBranchMergeNullCoalescingAssignment ()
+ {
+ string str = GetWithPublicMethods ();
+ str ??= GetWithPublicFields ();
+
+ str.RequiresAll ();
+ }
+
+ [ExpectedWarning ("IL2072", nameof (GetWithPublicMethods), nameof (DataFlowStringExtensions.RequiresAll))]
+ [ExpectedWarning ("IL2072", nameof (GetWithPublicFields), nameof (DataFlowStringExtensions.RequiresAll))]
+ [ExpectedWarning ("IL2072", nameof (GetWithPublicConstructors), nameof (DataFlowStringExtensions.RequiresAll))]
+ public static void TestBranchMergeNullCoalescingAssignmentComplex ()
+ {
+ string str = GetWithPublicMethods ();
+ str ??= GetWithPublicFields () ?? GetWithPublicConstructors ();
+
+ str.RequiresAll ();
+ }
+
+ [ExpectedWarning ("IL2072", nameof (GetWithPublicMethods), nameof (DataFlowStringExtensions.RequiresAll))]
+ [ExpectedWarning ("IL2072", nameof (GetWithPublicFields), nameof (DataFlowStringExtensions.RequiresAll))]
+ public static void TestBranchMergeDiscardNullCoalesce ()
+ {
+ (_ = GetWithPublicMethods () ?? GetWithPublicFields ()).RequiresAll ();
+ }
+
[ExpectedWarning ("IL2072",
"Mono.Linker.Tests.Cases.DataFlow.LocalDataFlow.GetWithPublicMethods()",
nameof (DataFlowStringExtensions) + "." + nameof (DataFlowStringExtensions.RequiresPublicFields) + "(String)")]
@@ -411,35 +452,6 @@ namespace Mono.Linker.Tests.Cases.DataFlow
return resultType;
}
- public static void RequireAll (
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
- string type)
- {
- }
-
- public static void RequirePublicFields (
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)]
- string type)
- {
- }
-
- public static void RequireNonPublicMethods (
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicMethods)]
- string type)
- {
- }
- public static void RequirePublicMethods (
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
- string type)
- {
- }
-
- public static void RequirePublicConstructors (
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
- string type)
- {
- }
-
[return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicFields)]
public static string GetWithPublicFields ()
{
diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/MethodParametersDataFlow.cs b/test/Mono.Linker.Tests.Cases/DataFlow/MethodParametersDataFlow.cs
index 7c4a0bdd3..77de9327a 100644
--- a/test/Mono.Linker.Tests.Cases/DataFlow/MethodParametersDataFlow.cs
+++ b/test/Mono.Linker.Tests.Cases/DataFlow/MethodParametersDataFlow.cs
@@ -42,6 +42,8 @@ namespace Mono.Linker.Tests.Cases.DataFlow
ParametersPassedToInstanceCtor (typeof (TestType), typeof (TestType));
TestParameterOverwrite (typeof (TestType));
+
+ WriteCapturedParameter.Test ();
}
// Validate the error message when annotated parameter is passed to another annotated parameter
@@ -235,11 +237,52 @@ namespace Mono.Linker.Tests.Cases.DataFlow
type.GetFields ();
}
+ class WriteCapturedParameter
+ {
+ [ExpectedWarning ("IL2072", nameof (GetUnknownType), "parameter")]
+ [ExpectedWarning ("IL2072", nameof (GetTypeWithPublicConstructors), "parameter")]
+ static void TestNullCoalesce ([DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] Type parameter = null)
+ {
+ parameter = GetUnknownType () ?? GetTypeWithPublicConstructors ();
+ }
+
+ [ExpectedWarning ("IL2072", nameof (GetUnknownType), "parameter")]
+ static void TestNullCoalescingAssignment ([DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] Type parameter = null)
+ {
+ parameter ??= GetUnknownType ();
+ }
+
+ [ExpectedWarning ("IL2072", nameof (GetUnknownType), "parameter")]
+ [ExpectedWarning ("IL2072", nameof (GetTypeWithPublicConstructors), "parameter")]
+ static void TestNullCoalescingAssignmentComplex ([DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] Type parameter = null)
+ {
+ parameter ??= (GetUnknownType () ?? GetTypeWithPublicConstructors ());
+ }
+
+ public static void Test ()
+ {
+ TestNullCoalesce ();
+ TestNullCoalescingAssignment ();
+ TestNullCoalescingAssignmentComplex ();
+ }
+ }
+
class TestType
{
public TestType () { }
public TestType (int arg) { }
private TestType (int arg1, int arg2) { }
}
+
+ [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors)]
+ private static Type GetTypeWithPublicConstructors ()
+ {
+ return null;
+ }
+
+ private static Type GetUnknownType ()
+ {
+ return null;
+ }
}
}
diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs b/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs
index 424992e9d..188bc8672 100644
--- a/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs
+++ b/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs
@@ -35,8 +35,12 @@ namespace Mono.Linker.Tests.Cases.DataFlow
TestAutomaticPropagation ();
+ WriteCapturedProperty.Test ();
+ WriteCapturedGetOnlyProperty.Test ();
+ ReadCapturedProperty.Test ();
+
PropertyWithAttributeMarkingItself.Test ();
- new TestWriteToGetOnlyProperty ();
+ WriteToGetOnlyProperty.Test ();
}
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors)]
@@ -437,18 +441,110 @@ namespace Mono.Linker.Tests.Cases.DataFlow
}
}
- class TestWriteToGetOnlyProperty
+
+ class WriteToGetOnlyProperty
{
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
public Type GetOnlyProperty { get; }
- // Analyzer doesn't warn about compiler-generated backing field of property
- [ExpectedWarning ("IL2074", nameof (TestWriteToGetOnlyProperty), nameof (GetUnknownType),
+ // Analyzer doesn't warn about compiler-generated backing field of property: https://github.com/dotnet/linker/issues/2731
+ [ExpectedWarning ("IL2074", nameof (WriteToGetOnlyProperty), nameof (GetUnknownType),
ProducedBy = ProducedBy.Trimmer)]
- public TestWriteToGetOnlyProperty ()
+ public WriteToGetOnlyProperty ()
{
GetOnlyProperty = GetUnknownType ();
}
+
+ public static void Test ()
+ {
+ new WriteToGetOnlyProperty ();
+ }
+ }
+
+ class WriteCapturedProperty
+ {
+ [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
+ static Type Property { get; set; }
+
+ [ExpectedWarning ("IL2072", nameof (GetUnknownType), nameof (Property))]
+ [ExpectedWarning ("IL2072", nameof (GetTypeWithPublicConstructors), nameof (Property))]
+ static void TestNullCoalesce ()
+ {
+ Property = GetUnknownType () ?? GetTypeWithPublicConstructors ();
+ }
+
+ [ExpectedWarning ("IL2072", nameof (GetUnknownType), nameof (Property))]
+ static void TestNullCoalescingAssignment ()
+ {
+ Property ??= GetUnknownType ();
+ }
+
+ class NestedType
+ {
+ [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
+ public Type Property { get; set; }
+ }
+
+ NestedType NestedTypeProperty { get; set; }
+
+ [ExpectedWarning ("IL2072", nameof (GetUnknownType), nameof (Property))]
+ [ExpectedWarning ("IL2072", nameof (GetTypeWithPublicConstructors), nameof (Property))]
+ void TestNestedNullCoalescingAssignment ()
+ {
+ NestedTypeProperty.Property = GetUnknownType () ?? GetTypeWithPublicConstructors ();
+ }
+
+ [ExpectedWarning ("IL2072", nameof (GetUnknownType), nameof (Property))]
+ [ExpectedWarning ("IL2072", nameof (GetTypeWithPublicConstructors), nameof (Property))]
+ static void TestNullCoalescingAssignmentComplex ()
+ {
+ Property ??= (GetUnknownType () ?? GetTypeWithPublicConstructors ());
+ }
+
+ public static void Test ()
+ {
+ TestNullCoalesce ();
+ TestNullCoalescingAssignment ();
+ TestNullCoalescingAssignmentComplex ();
+ new WriteCapturedProperty ().TestNestedNullCoalescingAssignment ();
+ }
+ }
+
+ class WriteCapturedGetOnlyProperty
+ {
+ [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
+ Type GetOnlyProperty { get; }
+
+ // Analyzer doesn't warn about compiler-generated backing field of property: https://github.com/dotnet/linker/issues/2731
+ [ExpectedWarning ("IL2074", nameof (WriteCapturedGetOnlyProperty), nameof (GetUnknownType),
+ ProducedBy = ProducedBy.Trimmer)]
+ [ExpectedWarning ("IL2074", nameof (WriteCapturedGetOnlyProperty), nameof (GetTypeWithPublicConstructors),
+ ProducedBy = ProducedBy.Trimmer)]
+ public WriteCapturedGetOnlyProperty ()
+ {
+ GetOnlyProperty = GetUnknownType () ?? GetTypeWithPublicConstructors ();
+ }
+
+ public static void Test ()
+ {
+ new WriteCapturedGetOnlyProperty ();
+ }
+ }
+
+ class ReadCapturedProperty
+ {
+ [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
+ static Type PublicMethods { get; }
+
+ [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicFields)]
+ static Type PublicFields { get; }
+
+ [ExpectedWarning ("IL2072", nameof (PublicMethods), nameof (DataFlowTypeExtensions.RequiresAll))]
+ [ExpectedWarning ("IL2072", nameof (PublicFields), nameof (DataFlowTypeExtensions.RequiresAll))]
+ public static void Test ()
+ {
+ (PublicMethods ?? PublicFields).RequiresAll ();
+ }
}
[return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]