diff --git a/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs b/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs index 00f534dd84bd..3fa1f16cdbb2 100644 --- a/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs +++ b/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs @@ -66,7 +66,9 @@ public void Transfer (BlockProxy block, LocalDataFlowState new GenericParameterValue ((ITypeParameterSymbol) type), - // Technically this should be a new value node type as it's not a System.Type instance representation, but just the generic parameter - // That said we only use it to perform the dynamically accessed members checks and for that purpose treating it as System.Type is perfectly valid. - SymbolKind.NamedType => new SystemTypeValue (new TypeProxy ((INamedTypeSymbol) type)), - SymbolKind.ErrorType => UnknownValue.Instance, - SymbolKind.ArrayType => new SystemTypeValue (new TypeProxy (context.Compilation.GetTypeByMetadataName ("System.Array")!)), - // What about things like PointerType and so on. Linker treats these as "named types" since it can resolve them to concrete type - _ => throw new NotImplementedException () - }; - } - static IEnumerable GetDynamicallyAccessedMembersDiagnostics (SingleValue sourceValue, SingleValue targetValue, Location location) { // The target should always be an annotated value, but the visitor design currently prevents diff --git a/src/ILLink.RoslynAnalyzer/TrimAnalysis/ArrayValue.cs b/src/ILLink.RoslynAnalyzer/TrimAnalysis/ArrayValue.cs index b3d5d0b4e44a..449316370849 100644 --- a/src/ILLink.RoslynAnalyzer/TrimAnalysis/ArrayValue.cs +++ b/src/ILLink.RoslynAnalyzer/TrimAnalysis/ArrayValue.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; +using System.Collections.Generic; using ILLink.Shared.DataFlow; using MultiValue = ILLink.Shared.DataFlow.ValueSet; @@ -9,10 +9,63 @@ namespace ILLink.Shared.TrimAnalysis { partial record ArrayValue { - public ArrayValue (SingleValue size) => Size = size; + public readonly Dictionary IndexValues; -#pragma warning disable IDE0060 // Remove unused parameter - public partial bool TryGetValueByIndex (int index, out MultiValue value) => throw new NotImplementedException (); -#pragma warning restore IDE0060 // Remove unused parameter + public static MultiValue Create (MultiValue size) + { + MultiValue result = MultiValueLattice.Top; + foreach (var sizeValue in size) { + result = MultiValueLattice.Meet (result, new MultiValue (new ArrayValue (sizeValue))); + } + + return result; + } + + ArrayValue (SingleValue size) + { + Size = size; + IndexValues = new Dictionary (); + } + + public partial bool TryGetValueByIndex (int index, out MultiValue value) + { + if (IndexValues.TryGetValue (index, out value)) + return true; + + value = default; + return false; + } + + public override int GetHashCode () + { + return HashUtils.Combine (GetType ().GetHashCode (), Size); + } + + public bool Equals (ArrayValue? otherArr) + { + if (otherArr == null) + return false; + + bool equals = Size.Equals (otherArr.Size); + equals &= IndexValues.Count == otherArr.IndexValues.Count; + if (!equals) + return false; + + // If both sets T and O are the same size and "T intersect O" is empty, then T == O. + HashSet> thisValueSet = new (IndexValues); + thisValueSet.ExceptWith (otherArr.IndexValues); + return thisValueSet.Count == 0; + } + + // Lattice Meet() is supposed to copy values, so we need to make a deep copy since ArrayValue is mutable through IndexValues + public override SingleValue DeepCopy () + { + var newArray = new ArrayValue (Size); + foreach (var kvp in IndexValues) { + newArray.IndexValues.Add (kvp.Key, kvp.Value.Clone ()); + } + + return newArray; + } } -} +} \ No newline at end of file diff --git a/src/ILLink.RoslynAnalyzer/TrimAnalysis/FieldValue.cs b/src/ILLink.RoslynAnalyzer/TrimAnalysis/FieldValue.cs index fd01536fa3f3..57d9ab81bd26 100644 --- a/src/ILLink.RoslynAnalyzer/TrimAnalysis/FieldValue.cs +++ b/src/ILLink.RoslynAnalyzer/TrimAnalysis/FieldValue.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using ILLink.RoslynAnalyzer; +using ILLink.Shared.DataFlow; using Microsoft.CodeAnalysis; namespace ILLink.Shared.TrimAnalysis @@ -19,6 +20,8 @@ partial record FieldValue public override IEnumerable GetDiagnosticArgumentsForAnnotationMismatch () => new string[] { FieldSymbol.GetDisplayName () }; + public override SingleValue DeepCopy () => this; // This value is immutable + public override string ToString () => this.ValueToString (FieldSymbol, DynamicallyAccessedMemberTypes); } } diff --git a/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericParameterValue.cs b/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericParameterValue.cs index 290a1dfd77bb..0d8a4119e96e 100644 --- a/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericParameterValue.cs +++ b/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericParameterValue.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using ILLink.RoslynAnalyzer; +using ILLink.Shared.DataFlow; using Microsoft.CodeAnalysis; namespace ILLink.Shared.TrimAnalysis @@ -26,6 +27,8 @@ public partial bool HasDefaultConstructorConstraint () => public override IEnumerable GetDiagnosticArgumentsForAnnotationMismatch () => new string[] { GenericParameter.TypeParameterSymbol.Name, GenericParameter.TypeParameterSymbol.ContainingSymbol.GetDisplayName () }; + public override SingleValue DeepCopy () => this; // This value is immutable + public override string ToString () => this.ValueToString (GenericParameter.TypeParameterSymbol, DynamicallyAccessedMemberTypes); } diff --git a/src/ILLink.RoslynAnalyzer/TrimAnalysis/MethodParameterValue.cs b/src/ILLink.RoslynAnalyzer/TrimAnalysis/MethodParameterValue.cs index 0aa1abbcf036..426cb0155fb6 100644 --- a/src/ILLink.RoslynAnalyzer/TrimAnalysis/MethodParameterValue.cs +++ b/src/ILLink.RoslynAnalyzer/TrimAnalysis/MethodParameterValue.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using ILLink.RoslynAnalyzer; using ILLink.RoslynAnalyzer.TrimAnalysis; +using ILLink.Shared.DataFlow; using Microsoft.CodeAnalysis; namespace ILLink.Shared.TrimAnalysis @@ -24,6 +25,8 @@ public MethodParameterValue (IParameterSymbol parameterSymbol, DynamicallyAccess public override IEnumerable GetDiagnosticArgumentsForAnnotationMismatch () => new string[] { ParameterSymbol.GetDisplayName (), ParameterSymbol.ContainingSymbol.GetDisplayName () }; + public override SingleValue DeepCopy () => this; // This value is immutable + public override string ToString () => this.ValueToString (ParameterSymbol, DynamicallyAccessedMemberTypes); } diff --git a/src/ILLink.RoslynAnalyzer/TrimAnalysis/MethodReturnValue.cs b/src/ILLink.RoslynAnalyzer/TrimAnalysis/MethodReturnValue.cs index 5f551a8eff72..46ae00bd2bd5 100644 --- a/src/ILLink.RoslynAnalyzer/TrimAnalysis/MethodReturnValue.cs +++ b/src/ILLink.RoslynAnalyzer/TrimAnalysis/MethodReturnValue.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using ILLink.RoslynAnalyzer; using ILLink.RoslynAnalyzer.TrimAnalysis; +using ILLink.Shared.DataFlow; using Microsoft.CodeAnalysis; namespace ILLink.Shared.TrimAnalysis @@ -30,6 +31,8 @@ public MethodReturnValue (IMethodSymbol methodSymbol, DynamicallyAccessedMemberT public override IEnumerable GetDiagnosticArgumentsForAnnotationMismatch () => new string[] { MethodSymbol.GetDisplayName () }; + public override SingleValue DeepCopy () => this; // This value is immutable + public override string ToString () => this.ValueToString (MethodSymbol, DynamicallyAccessedMemberTypes); } } diff --git a/src/ILLink.RoslynAnalyzer/TrimAnalysis/MethodThisParameterValue.cs b/src/ILLink.RoslynAnalyzer/TrimAnalysis/MethodThisParameterValue.cs index 36742afe8a31..80d510e13eb1 100644 --- a/src/ILLink.RoslynAnalyzer/TrimAnalysis/MethodThisParameterValue.cs +++ b/src/ILLink.RoslynAnalyzer/TrimAnalysis/MethodThisParameterValue.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using ILLink.RoslynAnalyzer; +using ILLink.Shared.DataFlow; using Microsoft.CodeAnalysis; namespace ILLink.Shared.TrimAnalysis @@ -23,6 +24,8 @@ public MethodThisParameterValue (IMethodSymbol methodSymbol, DynamicallyAccessed public override IEnumerable GetDiagnosticArgumentsForAnnotationMismatch () => new string[] { MethodSymbol.GetDisplayName () }; + public override SingleValue DeepCopy () => this; // This value is immutable + public override string ToString () => this.ValueToString (MethodSymbol, DynamicallyAccessedMemberTypes); } } diff --git a/src/ILLink.RoslynAnalyzer/TrimAnalysis/RuntimeMethodHandleValue.cs b/src/ILLink.RoslynAnalyzer/TrimAnalysis/RuntimeMethodHandleValue.cs new file mode 100644 index 000000000000..02ab79315584 --- /dev/null +++ b/src/ILLink.RoslynAnalyzer/TrimAnalysis/RuntimeMethodHandleValue.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using ILLink.Shared.DataFlow; + +namespace ILLink.Shared.TrimAnalysis +{ + partial record RuntimeMethodHandleValue + { + public override SingleValue DeepCopy () => this; // immutable value + + public override string ToString () => this.ValueToString (); + } +} diff --git a/src/ILLink.RoslynAnalyzer/TrimAnalysis/SingleValueExtensions.cs b/src/ILLink.RoslynAnalyzer/TrimAnalysis/SingleValueExtensions.cs new file mode 100644 index 000000000000..dd0c647e410e --- /dev/null +++ b/src/ILLink.RoslynAnalyzer/TrimAnalysis/SingleValueExtensions.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Linq; +using ILLink.Shared.DataFlow; +using ILLink.Shared.TrimAnalysis; +using ILLink.Shared.TypeSystemProxy; +using Microsoft.CodeAnalysis; + +namespace ILLink.RoslynAnalyzer.TrimAnalysis +{ + public static class SingleValueExtensions + { + public static SingleValue? FromTypeSymbol (ITypeSymbol type) + { + if (type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) { + var underlyingType = (type as INamedTypeSymbol)?.TypeArguments.FirstOrDefault (); + return underlyingType?.TypeKind switch { + TypeKind.TypeParameter => + new NullableValueWithDynamicallyAccessedMembers (new TypeProxy (type), + new GenericParameterValue ((ITypeParameterSymbol) underlyingType)), + // typeof(Nullable<>) + TypeKind.Error => new SystemTypeValue (new TypeProxy (type)), + TypeKind.Class or TypeKind.Struct or TypeKind.Interface => + new NullableSystemTypeValue (new TypeProxy (type), new SystemTypeValue (new TypeProxy (underlyingType))), + _ => UnknownValue.Instance + }; + } + return type.Kind switch { + SymbolKind.TypeParameter => new GenericParameterValue ((ITypeParameterSymbol) type), + SymbolKind.NamedType => new SystemTypeValue (new TypeProxy (type)), + // If the symbol is an Array type, the BaseType is System.Array + SymbolKind.ArrayType => new SystemTypeValue (new TypeProxy (type.BaseType!)), + SymbolKind.ErrorType => UnknownValue.Instance, + _ => null + }; + + } + } +} diff --git a/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs b/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs index 04b3367e09f8..c6e78bb8a3ad 100644 --- a/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs +++ b/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Immutable; +using System.Linq; using ILLink.RoslynAnalyzer.DataFlow; using ILLink.Shared.DataFlow; using ILLink.Shared.TrimAnalysis; @@ -24,6 +25,11 @@ public class TrimAnalysisVisitor : LocalDataFlowVisitor _multiValueLattice; + // Limit tracking array values to 32 values for performance reasons. + // There are many arrays much longer than 32 elements in .NET, + // but the interesting ones for the linker are nearly always less than 32 elements. + private const int MaxTrackedArrayValues = 32; + public TrimAnalysisVisitor ( LocalStateLattice> lattice, OperationBlockAnalysisContext context @@ -52,13 +58,38 @@ public override MultiValue Visit (IOperation? operation, StateValue argument) return NullValue.Instance; else if (operation.Type?.SpecialType == SpecialType.System_String && constantValue is string stringConstantValue) return new KnownStringValue (stringConstantValue); - else if (operation.Type?.TypeKind == TypeKind.Enum && constantValue is int intConstantValue) + else if (operation.Type?.TypeKind == TypeKind.Enum && constantValue is int enumConstantValue) + return new ConstIntValue (enumConstantValue); + else if (operation.Type?.SpecialType == SpecialType.System_Int32 && constantValue is int intConstantValue) return new ConstIntValue (intConstantValue); } return returnValue; } + public override MultiValue VisitArrayCreation (IArrayCreationOperation operation, StateValue state) + { + var value = base.VisitArrayCreation (operation, state); + + // Don't track multi-dimensional arrays + if (operation.DimensionSizes.Length != 1) + return TopValue; + + // Don't track large arrays for performance reasons + if (operation.Initializer?.ElementValues.Length >= MaxTrackedArrayValues) + return TopValue; + + var arrayValue = ArrayValue.Create (Visit (operation.DimensionSizes[0], state)); + var elements = operation.Initializer?.ElementValues.Select (val => Visit (val, state)).ToArray () ?? System.Array.Empty (); + foreach (var array in arrayValue.Cast ()) { + for (int i = 0; i < elements.Length; i++) { + array.IndexValues.Add (i, elements[i]); + } + } + + return arrayValue; + } + public override MultiValue VisitConversion (IConversionOperation operation, StateValue state) { var value = base.VisitConversion (operation, state); @@ -106,12 +137,7 @@ public override MultiValue VisitFieldReference (IFieldReferenceOperation fieldRe public override MultiValue VisitTypeOf (ITypeOfOperation typeOfOperation, StateValue state) { - if (typeOfOperation.TypeOperand is ITypeParameterSymbol typeParameter) - return new GenericParameterValue (typeParameter); - else if (typeOfOperation.TypeOperand is INamedTypeSymbol namedType) - return new SystemTypeValue (new TypeProxy (namedType)); - - return TopValue; + return SingleValueExtensions.FromTypeSymbol (typeOfOperation.TypeOperand) ?? TopValue; } public override MultiValue VisitBinaryOperator (IBinaryOperation operation, StateValue argument) @@ -162,11 +188,39 @@ public override void HandleAssignment (MultiValue source, MultiValue target, IOp ); } - public override MultiValue HandleArrayElementAccess (IOperation arrayReferene) + public override MultiValue HandleArrayElementRead (MultiValue arrayValue, MultiValue indexValue, IOperation operation) { + if (arrayValue.AsSingleValue () is not ArrayValue arr) + return UnknownValue.Instance; + + if (indexValue.AsConstInt () is not int index) + return UnknownValue.Instance; + + if (arr.TryGetValueByIndex (index, out var elementValue)) + return elementValue; + return UnknownValue.Instance; } + public override void HandleArrayElementWrite (MultiValue arrayValue, MultiValue indexValue, MultiValue valueToWrite, IOperation operation) + { + int? index = indexValue.AsConstInt (); + foreach (var arraySingleValue in arrayValue) { + if (arraySingleValue is ArrayValue arr) { + if (index == null) { + // Reset the array to all unknowns - since we don't know which index is being assigned + arr.IndexValues.Clear (); + } else { + if (arr.IndexValues.TryGetValue (index.Value, out _)) { + arr.IndexValues[index.Value] = valueToWrite; + } else if (arr.IndexValues.Count < MaxTrackedArrayValues) { + arr.IndexValues[index.Value] = valueToWrite; + } + } + } + } + } + public override MultiValue HandleMethodCall (IMethodSymbol calledMethod, MultiValue instance, ImmutableArray arguments, IOperation operation) { // For .ctors: @@ -195,6 +249,13 @@ public override MultiValue HandleMethodCall (IMethodSymbol calledMethod, MultiVa operation, Context.OwningSymbol)); + foreach (var argument in arguments) { + foreach (var argumentValue in argument) { + if (argumentValue is ArrayValue arrayValue) + arrayValue.IndexValues.Clear (); + } + } + return methodReturnValue; } @@ -211,4 +272,4 @@ public override void HandleReturnValue (MultiValue returnValue, IOperation opera } } } -} \ No newline at end of file +} diff --git a/src/ILLink.RoslynAnalyzer/TrimAnalysis/TypeProxy.cs b/src/ILLink.RoslynAnalyzer/TrimAnalysis/TypeProxy.cs index 19c6e877185d..d66ed455b645 100644 --- a/src/ILLink.RoslynAnalyzer/TrimAnalysis/TypeProxy.cs +++ b/src/ILLink.RoslynAnalyzer/TrimAnalysis/TypeProxy.cs @@ -12,10 +12,14 @@ internal readonly partial struct TypeProxy public readonly ITypeSymbol Type; - public string Name { get => Type.Name; } + public string Name { get => Type.MetadataName; } + + public string Namespace { get => Type.ContainingNamespace.Name; } public string GetDisplayName () => Type.GetDisplayName (); public override string ToString () => Type.ToString (); + + public bool IsTypeOf (string @namespace, string name) => Namespace == @namespace && Name == name; } } diff --git a/src/ILLink.Shared/Annotations.cs b/src/ILLink.Shared/Annotations.cs index 42acc8b7ad71..46ab7251c278 100644 --- a/src/ILLink.Shared/Annotations.cs +++ b/src/ILLink.Shared/Annotations.cs @@ -116,6 +116,12 @@ public static (DiagnosticId Id, string[] Arguments) GetDiagnosticForAnnotationMi (GenericParameterValue, FieldValue) => DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsField, (GenericParameterValue, MethodThisParameterValue) => DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsThisParameter, (GenericParameterValue, GenericParameterValue) => DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsGenericParameter, + (NullableValueWithDynamicallyAccessedMembers, MethodParameterValue) => DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsParameter, + (NullableValueWithDynamicallyAccessedMembers, MethodReturnValue) => DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsMethodReturnType, + (NullableValueWithDynamicallyAccessedMembers, FieldValue) => DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsField, + (NullableValueWithDynamicallyAccessedMembers, MethodThisParameterValue) => DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsThisParameter, + (NullableValueWithDynamicallyAccessedMembers, GenericParameterValue) => DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsGenericParameter, + _ => throw new NotImplementedException ($"Unsupported source context {source} or target context {target}.") }; diff --git a/src/ILLink.Shared/DataFlow/IDeepCopyValue.cs b/src/ILLink.Shared/DataFlow/IDeepCopyValue.cs new file mode 100644 index 000000000000..abceedf61227 --- /dev/null +++ b/src/ILLink.Shared/DataFlow/IDeepCopyValue.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace ILLink.Shared.DataFlow +{ + // Adds ability to deep copy a value + public interface IDeepCopyValue + { + public TSingleValue DeepCopy (); + } +} \ No newline at end of file diff --git a/src/ILLink.Shared/DataFlow/SingleValue.cs b/src/ILLink.Shared/DataFlow/SingleValue.cs index 03e9edf9143b..2c4019e3295d 100644 --- a/src/ILLink.Shared/DataFlow/SingleValue.cs +++ b/src/ILLink.Shared/DataFlow/SingleValue.cs @@ -9,5 +9,13 @@ namespace ILLink.Shared.DataFlow // - known strings // - known integers - public abstract record SingleValue; + public abstract record SingleValue : IDeepCopyValue + { + // All values must explicitely declare their ability to deep copy itself. + // If the value is immutable, it can "return this" as an optimization. + // Note: Since immutability is relatively tricky to determine, we require all values + // to explicitly implement the DeepCopy, even though the expectation is that + // most values will just "return this". + public abstract SingleValue DeepCopy (); + } } \ No newline at end of file diff --git a/src/ILLink.Shared/DataFlow/ValueSet.cs b/src/ILLink.Shared/DataFlow/ValueSet.cs index 5f03fd9b31e6..349d0e8dafa5 100644 --- a/src/ILLink.Shared/DataFlow/ValueSet.cs +++ b/src/ILLink.Shared/DataFlow/ValueSet.cs @@ -4,7 +4,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Text; @@ -105,15 +104,12 @@ public bool Equals (ValueSet other) return false; if (_values is EnumerableValues enumerableValues) { - Debug.Assert (enumerableValues.Count > 1); if (other._values is EnumerableValues otherValuesSet) { - Debug.Assert (otherValuesSet.Count > 1); return enumerableValues.SetEquals (otherValuesSet); } else return false; } else { - if (other._values is EnumerableValues otherEnumerableValues) { - Debug.Assert (otherEnumerableValues.Count > 1); + if (other._values is EnumerableValues) { return false; } @@ -147,18 +143,18 @@ public bool Contains (TValue value) => _values is null internal static ValueSet Meet (ValueSet left, ValueSet right) { if (left._values == null) - return right; + return right.Clone (); if (right._values == null) - return left; + return left.Clone (); if (left._values is not EnumerableValues && right.Contains ((TValue) left._values)) - return right; + return right.Clone (); if (right._values is not EnumerableValues && left.Contains ((TValue) right._values)) - return left; + return left.Clone (); - var values = new EnumerableValues (left); - values.UnionWith (right); + var values = new EnumerableValues (left.Clone ()); + values.UnionWith (right.Clone ()); return new ValueSet (values); } @@ -172,5 +168,23 @@ public override string ToString () sb.Append ("}"); return sb.ToString (); } + + // Meet should copy the values, but most SingleValues are immutable. + // Clone returns `this` if there are no mutable SingleValues (SingleValues that implement IDeepCopyValue), otherwise creates a new ValueSet with copies of the copiable Values + public ValueSet Clone () + { + if (_values is null) + return this; + + // Optimize for the most common case with only a single value + if (_values is not EnumerableValues) { + if (_values is IDeepCopyValue copyValue) + return new ValueSet (copyValue.DeepCopy ()); + else + return this; + } + + return new ValueSet (this.Select (value => value is IDeepCopyValue copyValue ? copyValue.DeepCopy () : value)); + } } } \ No newline at end of file diff --git a/src/ILLink.Shared/ILLink.Shared.projitems b/src/ILLink.Shared/ILLink.Shared.projitems index d4927ef103be..838bfaff28b4 100644 --- a/src/ILLink.Shared/ILLink.Shared.projitems +++ b/src/ILLink.Shared/ILLink.Shared.projitems @@ -24,4 +24,4 @@ - \ No newline at end of file + diff --git a/src/ILLink.Shared/TrimAnalysis/ArrayValue.cs b/src/ILLink.Shared/TrimAnalysis/ArrayValue.cs index f20be2f8edb4..5e9111fc48f2 100644 --- a/src/ILLink.Shared/TrimAnalysis/ArrayValue.cs +++ b/src/ILLink.Shared/TrimAnalysis/ArrayValue.cs @@ -8,6 +8,8 @@ namespace ILLink.Shared.TrimAnalysis { sealed partial record ArrayValue : SingleValue { + static ValueSetLattice MultiValueLattice => default; + public readonly SingleValue Size; public partial bool TryGetValueByIndex (int index, out MultiValue value); diff --git a/src/ILLink.Shared/TrimAnalysis/ConstIntValue.cs b/src/ILLink.Shared/TrimAnalysis/ConstIntValue.cs index 476f3ef99661..69cd934ddd46 100644 --- a/src/ILLink.Shared/TrimAnalysis/ConstIntValue.cs +++ b/src/ILLink.Shared/TrimAnalysis/ConstIntValue.cs @@ -14,6 +14,8 @@ sealed record ConstIntValue : SingleValue public readonly int Value; + public override SingleValue DeepCopy () => this; // This value is immutable + public override string ToString () => this.ValueToString (Value); } } diff --git a/src/ILLink.Shared/TrimAnalysis/HandleCallAction.cs b/src/ILLink.Shared/TrimAnalysis/HandleCallAction.cs index c20e3b6f0c7a..ca33184a3a47 100644 --- a/src/ILLink.Shared/TrimAnalysis/HandleCallAction.cs +++ b/src/ILLink.Shared/TrimAnalysis/HandleCallAction.cs @@ -64,12 +64,22 @@ public bool Invoke (MethodProxy calledMethod, MultiValue instanceValue, IReadOnl } foreach (var value in argumentValues[0]) { - if (value is RuntimeTypeHandleValue typeHandle) - AddReturnValue (new SystemTypeValue (typeHandle.RepresentedType)); - else if (value is RuntimeTypeHandleForGenericParameterValue typeHandleForGenericParameter) - AddReturnValue (GetGenericParameterValue (typeHandleForGenericParameter.GenericParameter)); - else - AddReturnValue (GetMethodReturnValue (calledMethod, returnValueDynamicallyAccessedMemberTypes)); + AddReturnValue (value switch { + RuntimeTypeHandleForNullableSystemTypeValue nullableSystemType + => new NullableSystemTypeValue (nullableSystemType.NullableType, nullableSystemType.UnderlyingTypeValue), + // When generating type handles from IL, the GenericParameterValue with DAM annotations is not available. + // Once we convert it to a Value with annotations here, there is no need to convert it back in get_TypeHandle + RuntimeTypeHandleForNullableValueWithDynamicallyAccessedMembers nullableDamType when nullableDamType.UnderlyingTypeValue is RuntimeTypeHandleForGenericParameterValue underlyingGenericParameter + => new NullableValueWithDynamicallyAccessedMembers (nullableDamType.NullableType, GetGenericParameterValue (underlyingGenericParameter.GenericParameter)), + // This should only happen if the code does something like typeof(Nullable<>).MakeGenericType(methodParameter).TypeHandle + RuntimeTypeHandleForNullableValueWithDynamicallyAccessedMembers nullableDamType when nullableDamType.UnderlyingTypeValue is ValueWithDynamicallyAccessedMembers underlyingTypeValue + => new NullableValueWithDynamicallyAccessedMembers (nullableDamType.NullableType, underlyingTypeValue), + RuntimeTypeHandleValue typeHandle + => new SystemTypeValue (typeHandle.RepresentedType), + RuntimeTypeHandleForGenericParameterValue genericParam + => GetGenericParameterValue (genericParam.GenericParameter), + _ => GetMethodReturnValue (calledMethod, returnValueDynamicallyAccessedMemberTypes) + }); } break; @@ -80,15 +90,22 @@ public bool Invoke (MethodProxy calledMethod, MultiValue instanceValue, IReadOnl } foreach (var value in instanceValue) { - if (value is SystemTypeValue typeValue) - AddReturnValue (new RuntimeTypeHandleValue (typeValue.RepresentedType)); - else if (value is GenericParameterValue genericParameterValue) - AddReturnValue (new RuntimeTypeHandleForGenericParameterValue (genericParameterValue.GenericParameter)); - else if (value == NullValue.Instance) { - // Throws if the input is null, so no return value. + if (value != NullValue.Instance) + AddReturnValue (value switch { + NullableSystemTypeValue nullableSystemType + => new RuntimeTypeHandleForNullableSystemTypeValue (nullableSystemType.NullableType, nullableSystemType.UnderlyingTypeValue), + NullableValueWithDynamicallyAccessedMembers nullableDamType when nullableDamType.UnderlyingTypeValue is GenericParameterValue genericParam + => new RuntimeTypeHandleForNullableValueWithDynamicallyAccessedMembers (nullableDamType.NullableType, new RuntimeTypeHandleForGenericParameterValue (genericParam.GenericParameter)), + NullableValueWithDynamicallyAccessedMembers nullableDamType + => new RuntimeTypeHandleForNullableValueWithDynamicallyAccessedMembers (nullableDamType.NullableType, nullableDamType.UnderlyingTypeValue), + SystemTypeValue typeHandle + => new RuntimeTypeHandleValue (typeHandle.RepresentedType), + GenericParameterValue genericParam + => new RuntimeTypeHandleForGenericParameterValue (genericParam.GenericParameter), + _ => GetMethodReturnValue (calledMethod, returnValueDynamicallyAccessedMemberTypes) + }); + else returnValue ??= MultiValueLattice.Top; - } else - AddReturnValue (GetMethodReturnValue (calledMethod, returnValueDynamicallyAccessedMemberTypes)); } break; @@ -540,6 +557,59 @@ public bool Invoke (MethodProxy calledMethod, MultiValue instanceValue, IReadOnl } break; + case IntrinsicId.Nullable_GetUnderlyingType: + foreach (var singlevalue in argumentValues[0].AsEnumerable ()) { + AddReturnValue (singlevalue switch { + SystemTypeValue systemType => systemType, + NullableSystemTypeValue nullableSystemType => nullableSystemType.UnderlyingTypeValue, + NullableValueWithDynamicallyAccessedMembers nullableDamValue => nullableDamValue.UnderlyingTypeValue, + ValueWithDynamicallyAccessedMembers damValue => damValue, + _ => GetMethodReturnValue (calledMethod, returnValueDynamicallyAccessedMemberTypes) + }); + } + break; + + case IntrinsicId.Type_MakeGenericType: + // Contains part of the analysis done in the Linker, but is not complete + // instanceValue here is like argumentValues[0] in linker + foreach (var value in instanceValue) { + // Nullables without a type argument are considered SystemTypeValues + if (!(value is SystemTypeValue typeValue && typeValue.RepresentedType.IsTypeOf ("System", "Nullable`1"))) { + // We still don't want to lose track of the type if it is not Nullable + AddReturnValue (value); + continue; + } + // We have a nullable + foreach (var argumentValue in argumentValues[0]) { + if ((argumentValue as ArrayValue)?.TryGetValueByIndex (0, out var underlyingMultiValue) != true) { + continue; + } + foreach (var underlyingValue in underlyingMultiValue) { + switch (underlyingValue) { + // Don't warn on these types - it will throw instead + case NullableValueWithDynamicallyAccessedMembers: + case NullableSystemTypeValue: + case SystemTypeValue maybeArrayValue when maybeArrayValue.RepresentedType.IsTypeOf ("System", "Array"): + AddReturnValue (MultiValueLattice.Top); + break; + case SystemTypeValue systemTypeValue: + AddReturnValue (new NullableSystemTypeValue (typeValue.RepresentedType, new SystemTypeValue (systemTypeValue.RepresentedType))); + break; + // Generic Parameters and method parameters with annotations + case ValueWithDynamicallyAccessedMembers damValue: + AddReturnValue (new NullableValueWithDynamicallyAccessedMembers (typeValue.RepresentedType, damValue)); + break; + // Everything else assume it has no annotations + default: + AddReturnValue (GetMethodReturnValue (calledMethod, returnValueDynamicallyAccessedMemberTypes)); + break; + } + } + // We haven't found any generic parameters with annotations, so there's nothing to validate. + } + } + break; + // // Type.BaseType // diff --git a/src/ILLink.Shared/TrimAnalysis/IntrinsicId.cs b/src/ILLink.Shared/TrimAnalysis/IntrinsicId.cs index 0946298be65b..4550580e0c36 100644 --- a/src/ILLink.Shared/TrimAnalysis/IntrinsicId.cs +++ b/src/ILLink.Shared/TrimAnalysis/IntrinsicId.cs @@ -57,5 +57,6 @@ enum IntrinsicId RuntimeReflectionExtensions_GetRuntimeProperty, RuntimeHelpers_RunClassConstructor, MethodInfo_MakeGenericMethod, + Nullable_GetUnderlyingType } } diff --git a/src/ILLink.Shared/TrimAnalysis/Intrinsics.cs b/src/ILLink.Shared/TrimAnalysis/Intrinsics.cs index a21fd07bac06..deb92e52a9d5 100644 --- a/src/ILLink.Shared/TrimAnalysis/Intrinsics.cs +++ b/src/ILLink.Shared/TrimAnalysis/Intrinsics.cs @@ -332,6 +332,10 @@ public static IntrinsicId GetIntrinsicIdForMethod (MethodProxy calledMethod) && calledMethod.HasParametersCount (1) => IntrinsicId.MethodInfo_MakeGenericMethod, + "GetUnderlyingType" when calledMethod.IsDeclaredOnType ("System.Nullable") + && calledMethod.HasParameterOfType (0, "System.Type") + && calledMethod.IsStatic () + => IntrinsicId.Nullable_GetUnderlyingType, _ => IntrinsicId.None, }; } diff --git a/src/ILLink.Shared/TrimAnalysis/KnownStringValue.cs b/src/ILLink.Shared/TrimAnalysis/KnownStringValue.cs index 4ebd2d2394e1..b551d42b8a75 100644 --- a/src/ILLink.Shared/TrimAnalysis/KnownStringValue.cs +++ b/src/ILLink.Shared/TrimAnalysis/KnownStringValue.cs @@ -14,6 +14,8 @@ sealed partial record KnownStringValue : SingleValue public readonly string Contents; + public override SingleValue DeepCopy () => this; // This value is immutable + public override string ToString () => this.ValueToString ("\"" + Contents + "\""); } } diff --git a/src/ILLink.Shared/TrimAnalysis/NullValue.cs b/src/ILLink.Shared/TrimAnalysis/NullValue.cs index d2bb63976423..2497c9863937 100644 --- a/src/ILLink.Shared/TrimAnalysis/NullValue.cs +++ b/src/ILLink.Shared/TrimAnalysis/NullValue.cs @@ -13,6 +13,8 @@ private NullValue () public static NullValue Instance { get; } = new NullValue (); + public override SingleValue DeepCopy () => this; // This value is immutable + public override string ToString () => this.ValueToString (); } } diff --git a/src/ILLink.Shared/TrimAnalysis/NullableSystemTypeValue.cs b/src/ILLink.Shared/TrimAnalysis/NullableSystemTypeValue.cs new file mode 100644 index 000000000000..10d47aca3b01 --- /dev/null +++ b/src/ILLink.Shared/TrimAnalysis/NullableSystemTypeValue.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using ILLink.Shared.DataFlow; +using ILLink.Shared.TypeSystemProxy; + +namespace ILLink.Shared.TrimAnalysis +{ + /// + /// This represents a Nullable where T is a known SystemTypeValue. + /// It is necessary to track the underlying type to propagate DynamicallyAccessedMembers annotations to the underlying type when applied to a Nullable. + /// + sealed record NullableSystemTypeValue : SingleValue + { + public NullableSystemTypeValue (in TypeProxy nullableType, in SystemTypeValue underlyingTypeValue) + { + Debug.Assert (nullableType.IsTypeOf ("System", "Nullable`1")); + UnderlyingTypeValue = underlyingTypeValue; + NullableType = nullableType; + } + public readonly TypeProxy NullableType; + + public readonly SystemTypeValue UnderlyingTypeValue; + + public override SingleValue DeepCopy () => this; // This value is immutable + + public override string ToString () => this.ValueToString (UnderlyingTypeValue, NullableType); + } +} diff --git a/src/ILLink.Shared/TrimAnalysis/NullableValueWithDynamicallyAccessedMembers.cs b/src/ILLink.Shared/TrimAnalysis/NullableValueWithDynamicallyAccessedMembers.cs new file mode 100644 index 000000000000..d9402b993ee3 --- /dev/null +++ b/src/ILLink.Shared/TrimAnalysis/NullableValueWithDynamicallyAccessedMembers.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using ILLink.Shared.DataFlow; +using ILLink.Shared.TypeSystemProxy; + +namespace ILLink.Shared.TrimAnalysis +{ + /// + /// This represents a Nullable where T is an unknown value with DynamicallyAccessedMembers annotations. + /// It is necessary to track the underlying type to ensure DynamicallyAccessedMembers annotations on the underlying type match the target parameters where the Nullable is used. + /// + sealed record NullableValueWithDynamicallyAccessedMembers : ValueWithDynamicallyAccessedMembers + { + public NullableValueWithDynamicallyAccessedMembers (in TypeProxy nullableType, in ValueWithDynamicallyAccessedMembers underlyingTypeValue) + { + Debug.Assert (nullableType.IsTypeOf ("System", "Nullable`1")); + NullableType = nullableType; + UnderlyingTypeValue = underlyingTypeValue; + } + + public readonly TypeProxy NullableType; + public readonly ValueWithDynamicallyAccessedMembers UnderlyingTypeValue; + + public override DynamicallyAccessedMemberTypes DynamicallyAccessedMemberTypes => UnderlyingTypeValue.DynamicallyAccessedMemberTypes; + public override IEnumerable GetDiagnosticArgumentsForAnnotationMismatch () + => UnderlyingTypeValue.GetDiagnosticArgumentsForAnnotationMismatch (); + + public override SingleValue DeepCopy () => this; // This value is immutable + + public override string ToString () => this.ValueToString (UnderlyingTypeValue, NullableType); + } +} diff --git a/src/ILLink.Shared/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs b/src/ILLink.Shared/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs index 846790cd5aca..c4a783080833 100644 --- a/src/ILLink.Shared/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs +++ b/src/ILLink.Shared/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs @@ -26,6 +26,9 @@ public void Invoke (in MultiValue value, ValueWithDynamicallyAccessedMembers tar && genericParam.HasDefaultConstructorConstraint ()) { // We allow a new() constraint on a generic parameter to satisfy DynamicallyAccessedMemberTypes.PublicParameterlessConstructor } else if (uniqueValue is ValueWithDynamicallyAccessedMembers valueWithDynamicallyAccessedMembers) { + if (uniqueValue is NullableValueWithDynamicallyAccessedMembers nullableValue) { + MarkTypeForDynamicallyAccessedMembers (nullableValue.NullableType, nullableValue.DynamicallyAccessedMemberTypes); + } var availableMemberTypes = valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes; if (!Annotations.SourceHasRequiredAnnotations (availableMemberTypes, targetValue.DynamicallyAccessedMemberTypes, out var missingMemberTypes)) { (var diagnosticId, var diagnosticArguments) = Annotations.GetDiagnosticForAnnotationMismatch (valueWithDynamicallyAccessedMembers, targetValue, missingMemberTypes); @@ -39,6 +42,9 @@ public void Invoke (in MultiValue value, ValueWithDynamicallyAccessedMembers tar } else { MarkTypeForDynamicallyAccessedMembers (foundType, targetValue.DynamicallyAccessedMemberTypes); } + } else if (uniqueValue is NullableSystemTypeValue nullableSystemTypeValue) { + MarkTypeForDynamicallyAccessedMembers (nullableSystemTypeValue.NullableType, targetValue.DynamicallyAccessedMemberTypes); + MarkTypeForDynamicallyAccessedMembers (nullableSystemTypeValue.UnderlyingTypeValue.RepresentedType, targetValue.DynamicallyAccessedMemberTypes); } else if (uniqueValue == NullValue.Instance) { // Ignore - probably unreachable path as it would fail at runtime anyway. } else { diff --git a/src/ILLink.Shared/TrimAnalysis/RuntimeTypeHandleForGenericParameterValue.cs b/src/ILLink.Shared/TrimAnalysis/RuntimeTypeHandleForGenericParameterValue.cs index 48ca7577991d..195c3ec63662 100644 --- a/src/ILLink.Shared/TrimAnalysis/RuntimeTypeHandleForGenericParameterValue.cs +++ b/src/ILLink.Shared/TrimAnalysis/RuntimeTypeHandleForGenericParameterValue.cs @@ -15,6 +15,8 @@ sealed record RuntimeTypeHandleForGenericParameterValue : SingleValue public RuntimeTypeHandleForGenericParameterValue (GenericParameterProxy genericParameter) => GenericParameter = genericParameter; + public override SingleValue DeepCopy () => this; // This value is immutable + public override string ToString () => this.ValueToString (GenericParameter); } } diff --git a/src/ILLink.Shared/TrimAnalysis/RuntimeTypeHandleForNullableSystemTypeValue.cs b/src/ILLink.Shared/TrimAnalysis/RuntimeTypeHandleForNullableSystemTypeValue.cs new file mode 100644 index 000000000000..92c275d645df --- /dev/null +++ b/src/ILLink.Shared/TrimAnalysis/RuntimeTypeHandleForNullableSystemTypeValue.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using ILLink.Shared.DataFlow; +using ILLink.Shared.TypeSystemProxy; + +namespace ILLink.Shared.TrimAnalysis +{ + /// + /// This represents a type handle Nullable where T is a known SystemTypeValue. + /// It is necessary to track the underlying type to propagate DynamicallyAccessedMembers annotations to the underlying type when applied to a Nullable. + /// + sealed record RuntimeTypeHandleForNullableSystemTypeValue : SingleValue + { + public RuntimeTypeHandleForNullableSystemTypeValue (in TypeProxy nullableType, in SystemTypeValue underlyingTypeValue) + { + Debug.Assert (nullableType.IsTypeOf ("System", "Nullable`1")); + UnderlyingTypeValue = underlyingTypeValue; + NullableType = nullableType; + } + public readonly TypeProxy NullableType; + + public readonly SystemTypeValue UnderlyingTypeValue; + + public override SingleValue DeepCopy () => this; // This value is immutable + + public override string ToString () => this.ValueToString (UnderlyingTypeValue, NullableType); + } +} diff --git a/src/ILLink.Shared/TrimAnalysis/RuntimeTypeHandleForNullableValueWithDynamicallyAccessedMembers.cs b/src/ILLink.Shared/TrimAnalysis/RuntimeTypeHandleForNullableValueWithDynamicallyAccessedMembers.cs new file mode 100644 index 000000000000..f3e606fe07d2 --- /dev/null +++ b/src/ILLink.Shared/TrimAnalysis/RuntimeTypeHandleForNullableValueWithDynamicallyAccessedMembers.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using ILLink.Shared.DataFlow; +using ILLink.Shared.TypeSystemProxy; + +namespace ILLink.Shared.TrimAnalysis +{ + /// + /// This represents a type handle of a Nullable where T is an unknown value with DynamicallyAccessedMembers annotations. + /// It is necessary to track the underlying type to ensure DynamicallyAccessedMembers annotations on the underlying type match the target parameters where the Nullable is used. + /// + sealed record RuntimeTypeHandleForNullableValueWithDynamicallyAccessedMembers : SingleValue + { + public RuntimeTypeHandleForNullableValueWithDynamicallyAccessedMembers (in TypeProxy nullableType, in SingleValue underlyingTypeValue) + { + Debug.Assert (nullableType.IsTypeOf ("System", "Nullable`1")); + NullableType = nullableType; + UnderlyingTypeValue = underlyingTypeValue; + } + + public readonly TypeProxy NullableType; + public readonly SingleValue UnderlyingTypeValue; + + public override SingleValue DeepCopy () => this; // This value is immutable } + + public override string ToString () => this.ValueToString (UnderlyingTypeValue, NullableType); + } +} \ No newline at end of file diff --git a/src/ILLink.Shared/TrimAnalysis/RuntimeTypeHandleValue.cs b/src/ILLink.Shared/TrimAnalysis/RuntimeTypeHandleValue.cs index 4b3a07ee846a..1ce18389b029 100644 --- a/src/ILLink.Shared/TrimAnalysis/RuntimeTypeHandleValue.cs +++ b/src/ILLink.Shared/TrimAnalysis/RuntimeTypeHandleValue.cs @@ -11,10 +11,15 @@ namespace ILLink.Shared.TrimAnalysis /// sealed record RuntimeTypeHandleValue : SingleValue { - public RuntimeTypeHandleValue (in TypeProxy representedType) => RepresentedType = representedType; + public RuntimeTypeHandleValue (in TypeProxy representedType) + { + RepresentedType = representedType; + } public readonly TypeProxy RepresentedType; + public override SingleValue DeepCopy () => this; // This value is immutable + public override string ToString () => this.ValueToString (RepresentedType); } } diff --git a/src/ILLink.Shared/TrimAnalysis/SystemReflectionMethodBaseValue.cs b/src/ILLink.Shared/TrimAnalysis/SystemReflectionMethodBaseValue.cs index a32d52fea3dd..f29dac38537d 100644 --- a/src/ILLink.Shared/TrimAnalysis/SystemReflectionMethodBaseValue.cs +++ b/src/ILLink.Shared/TrimAnalysis/SystemReflectionMethodBaseValue.cs @@ -15,6 +15,8 @@ sealed partial record SystemReflectionMethodBaseValue : SingleValue public readonly MethodProxy MethodRepresented; + public override SingleValue DeepCopy () => this; // This value is immutable + public override string ToString () => this.ValueToString (MethodRepresented); } } diff --git a/src/ILLink.Shared/TrimAnalysis/SystemTypeValue.cs b/src/ILLink.Shared/TrimAnalysis/SystemTypeValue.cs index 44d5011a060b..1bdff7c942de 100644 --- a/src/ILLink.Shared/TrimAnalysis/SystemTypeValue.cs +++ b/src/ILLink.Shared/TrimAnalysis/SystemTypeValue.cs @@ -11,10 +11,15 @@ namespace ILLink.Shared.TrimAnalysis /// sealed record SystemTypeValue : SingleValue { - public SystemTypeValue (in TypeProxy representedType) => RepresentedType = representedType; + public SystemTypeValue (in TypeProxy representedType) + { + RepresentedType = representedType; + } public readonly TypeProxy RepresentedType; + public override SingleValue DeepCopy () => this; // This value is immutable + public override string ToString () => this.ValueToString (RepresentedType); } } diff --git a/src/ILLink.Shared/TrimAnalysis/UnknownValue.cs b/src/ILLink.Shared/TrimAnalysis/UnknownValue.cs index 675d952afa6b..3af419ca17af 100644 --- a/src/ILLink.Shared/TrimAnalysis/UnknownValue.cs +++ b/src/ILLink.Shared/TrimAnalysis/UnknownValue.cs @@ -13,6 +13,8 @@ private UnknownValue () public static UnknownValue Instance { get; } = new UnknownValue (); + public override SingleValue DeepCopy () => this; // This value is immutable + public override string ToString () => this.ValueToString (); } } diff --git a/src/linker/Linker.Dataflow/MethodBodyScanner.cs b/src/linker/Linker.Dataflow/MethodBodyScanner.cs index 4fb6c65bfcd1..8f94eb40cf39 100644 --- a/src/linker/Linker.Dataflow/MethodBodyScanner.cs +++ b/src/linker/Linker.Dataflow/MethodBodyScanner.cs @@ -6,10 +6,10 @@ using System.Linq; using ILLink.Shared.DataFlow; using ILLink.Shared.TrimAnalysis; +using ILLink.Shared.TypeSystemProxy; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Collections.Generic; - using MultiValue = ILLink.Shared.DataFlow.ValueSet; namespace Mono.Linker.Dataflow @@ -188,25 +188,22 @@ private static void StoreMethodLocalValue ( int? maxTrackedValues = null) where KeyType : notnull { - ValueBasicBlockPair newValue = new ValueBasicBlockPair { BasicBlockIndex = curBasicBlock }; - - ValueBasicBlockPair existingValue; - if (valueCollection.TryGetValue (collectionKey, out existingValue)) { + if (valueCollection.TryGetValue (collectionKey, out ValueBasicBlockPair existingValue)) { + MultiValue value; if (existingValue.BasicBlockIndex == curBasicBlock) { // If the previous value was stored in the current basic block, then we can safely // overwrite the previous value with the new one. - newValue.Value = valueToStore; + value = valueToStore; } else { // If the previous value came from a previous basic block, then some other use of // the local could see the previous value, so we must merge the new value with the // old value. - newValue.Value = MultiValueLattice.Meet (existingValue.Value, valueToStore); + value = MultiValueLattice.Meet (existingValue.Value, valueToStore); } - valueCollection[collectionKey] = newValue; + valueCollection[collectionKey] = new ValueBasicBlockPair (value, curBasicBlock); } else if (maxTrackedValues == null || valueCollection.Count < maxTrackedValues) { // We're not currently tracking a value a this index, so store the value now. - newValue.Value = valueToStore; - valueCollection[collectionKey] = newValue; + valueCollection[collectionKey] = new ValueBasicBlockPair (valueToStore, curBasicBlock); } } @@ -725,29 +722,41 @@ private void ScanLdloc ( void ScanLdtoken (Instruction operation, Stack currentStack) { - if (operation.Operand is GenericParameter genericParameter) { - StackSlot slot = new StackSlot (new RuntimeTypeHandleForGenericParameterValue (genericParameter)); - currentStack.Push (slot); + switch (operation.Operand) { + case GenericParameter genericParameter: + var param = new RuntimeTypeHandleForGenericParameterValue (genericParameter); + currentStack.Push (new StackSlot (param)); return; - } - - if (operation.Operand is TypeReference typeReference) { - var resolvedReference = ResolveToTypeDefinition (typeReference); - if (resolvedReference != null) { - StackSlot slot = new StackSlot (new RuntimeTypeHandleValue (resolvedReference)); - currentStack.Push (slot); - return; - } - } else if (operation.Operand is MethodReference methodReference) { - var resolvedMethod = _context.TryResolve (methodReference); - if (resolvedMethod != null) { - StackSlot slot = new StackSlot (new RuntimeMethodHandleValue (resolvedMethod)); - currentStack.Push (slot); + case TypeReference typeReference when ResolveToTypeDefinition (typeReference) is TypeDefinition resolvedDefinition: + // Note that Nullable types without a generic argument (i.e. Nullable<>) will be RuntimeTypeHandleValue / SystemTypeValue + if (typeReference is IGenericInstance instance && resolvedDefinition.IsTypeOf ("System", "Nullable`1")) { + switch (instance.GenericArguments[0]) { + case GenericParameter genericParam: + var nullableDam = new RuntimeTypeHandleForNullableValueWithDynamicallyAccessedMembers (new TypeProxy (resolvedDefinition), + new RuntimeTypeHandleForGenericParameterValue (genericParam)); + currentStack.Push (new StackSlot (nullableDam)); + return; + case TypeReference underlyingTypeReference when ResolveToTypeDefinition (underlyingTypeReference) is TypeDefinition underlyingType: + var nullableType = new RuntimeTypeHandleForNullableSystemTypeValue (new TypeProxy (resolvedDefinition), new SystemTypeValue (underlyingType)); + currentStack.Push (new StackSlot (nullableType)); + return; + default: + PushUnknown (currentStack); + return; + } + } else { + var typeHandle = new RuntimeTypeHandleValue (new TypeProxy (resolvedDefinition)); + currentStack.Push (new StackSlot (typeHandle)); return; } + case MethodReference methodReference when _context.TryResolve (methodReference) is MethodDefinition resolvedMethod: + var method = new RuntimeMethodHandleValue (resolvedMethod); + currentStack.Push (new StackSlot (method)); + return; + default: + PushUnknown (currentStack); + return; } - - PushUnknown (currentStack); } private void ScanStloc ( @@ -997,7 +1006,7 @@ private void ScanLdelem ( { StackSlot indexToLoadFrom = PopUnknown (currentStack, 1, methodBody, operation.Offset); StackSlot arrayToLoadFrom = PopUnknown (currentStack, 1, methodBody, operation.Offset); - if (arrayToLoadFrom.Value.Count () != 1 || arrayToLoadFrom.Value.Single () is not ArrayValue arr) { + if (arrayToLoadFrom.Value.AsSingleValue () is not ArrayValue arr) { PushUnknown (currentStack); return; } @@ -1012,8 +1021,10 @@ private void ScanLdelem ( return; } - arr.IndexValues.TryGetValue (index.Value, out ValueBasicBlockPair arrayIndexValue); - currentStack.Push (new StackSlot (arrayIndexValue.Value, isByRef)); + if (arr.IndexValues.TryGetValue (index.Value, out ValueBasicBlockPair arrayIndexValue)) + currentStack.Push (new StackSlot (arrayIndexValue.Value, isByRef)); + else + PushUnknown (currentStack); } } } diff --git a/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs b/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs index 8531974cc702..fdc39bc02de9 100644 --- a/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs +++ b/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs @@ -145,16 +145,25 @@ MultiValue GetTypeValueNodeFromGenericArgument (TypeReference genericArgument) // Technically this should be a new value node type as it's not a System.Type instance representation, but just the generic parameter // That said we only use it to perform the dynamically accessed members checks and for that purpose treating it as System.Type is perfectly valid. return new GenericParameterValue (inputGenericParameter, _context.Annotations.FlowAnnotations.GetGenericParameterAnnotation (inputGenericParameter)); - } else { - TypeDefinition? genericArgumentTypeDef = ResolveToTypeDefinition (genericArgument); - if (genericArgumentTypeDef != null) { - return new SystemTypeValue (genericArgumentTypeDef); - } else { - // If we can't resolve the generic argument, it means we can't apply potential requirements on it - // so track it as unknown value. If we later on hit this unknown value as being used somewhere - // where we need to apply requirements on it, it will generate a warning. - return UnknownValue.Instance; + } else if (ResolveToTypeDefinition (genericArgument) is TypeDefinition genericArgumentType) { + if (genericArgumentType.IsTypeOf ("System", "Nullable`1")) { + var innerGenericArgument = (genericArgument as IGenericInstance)?.GenericArguments.FirstOrDefault (); + switch (innerGenericArgument) { + case GenericParameter gp: + return new NullableValueWithDynamicallyAccessedMembers (genericArgumentType, + new GenericParameterValue (gp, _context.Annotations.FlowAnnotations.GetGenericParameterAnnotation (gp))); + + case TypeReference underlyingType: + if (ResolveToTypeDefinition (underlyingType) is TypeDefinition underlyingTypeDefinition) + return new NullableSystemTypeValue (genericArgumentType, new SystemTypeValue (underlyingTypeDefinition)); + else + return UnknownValue.Instance; + } } + // All values except for Nullable, including Nullable<> (with no type arguments) + return new SystemTypeValue (genericArgumentType); + } else { + return UnknownValue.Instance; } } @@ -238,6 +247,7 @@ protected override void HandleStoreParameter (MethodDefinition method, MethodPar public override bool HandleCall (MethodBody callingMethodBody, MethodReference calledMethod, Instruction operation, ValueNodeList methodParams, out MultiValue methodReturnValue) { methodReturnValue = new (); + MultiValue? maybeMethodReturnValue = null; var reflectionProcessed = _markStep.ProcessReflectionDependency (callingMethodBody, operation); if (reflectionProcessed) @@ -288,6 +298,7 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c case IntrinsicId.Type_GetMember: case IntrinsicId.Type_GetMethod: case IntrinsicId.Type_GetNestedType: + case IntrinsicId.Nullable_GetUnderlyingType: case IntrinsicId.Expression_Property when calledMethod.HasParameterOfType (1, "System.Reflection.MethodInfo"): case var fieldOrPropertyInstrinsic when fieldOrPropertyInstrinsic == IntrinsicId.Expression_Field || fieldOrPropertyInstrinsic == IntrinsicId.Expression_Property: case IntrinsicId.Type_get_BaseType: { @@ -303,12 +314,12 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c case IntrinsicId.TypeDelegator_Ctor: { // This is an identity function for analysis purposes if (operation.OpCode == OpCodes.Newobj) - methodReturnValue = methodParams[1]; + AddReturnValue (methodParams[1]); } break; case IntrinsicId.Array_Empty: { - methodReturnValue = ArrayValue.Create (0, ((GenericInstanceMethod) calledMethod).GenericArguments[0]); + AddReturnValue (ArrayValue.Create (0, ((GenericInstanceMethod) calledMethod).GenericArguments[0])); } break; @@ -317,7 +328,7 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c case IntrinsicId.MethodBase_GetMethodFromHandle: { // Infrastructure piece to support "ldtoken method -> GetMethodFromHandle" if (methodParams[0].AsSingleValue () is RuntimeMethodHandleValue methodHandle) - methodReturnValue = new SystemReflectionMethodBaseValue (methodHandle.MethodRepresented); + AddReturnValue (new SystemReflectionMethodBaseValue (methodHandle.MethodRepresented)); } break; @@ -327,6 +338,8 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c // Type MakeGenericType (params Type[] typeArguments) // case IntrinsicId.Type_MakeGenericType: { + // Shared HandleCallAction doesn't cover all the same functionality as this does + // We don't yet handle the case where you can create a nullable type with typeof(Nullable<>).MakeGenericType(T) foreach (var value in methodParams[0]) { if (value is SystemTypeValue typeValue) { if (!AnalyzeGenericInstantiationTypeArray (analysisContext, methodParams[1], calledMethodDefinition, typeValue.RepresentedType.Type.GenericParameters)) { @@ -349,6 +362,36 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c } } + // Nullables without a type argument are considered SystemTypeValues + if (typeValue.RepresentedType.IsTypeOf ("System", "Nullable`1")) { + foreach (var argumentValue in methodParams[1]) { + if ((argumentValue as ArrayValue)?.TryGetValueByIndex (0, out var underlyingMultiValue) == true) { + foreach (var underlyingValue in underlyingMultiValue) { + switch (underlyingValue) { + // Don't warn on these types - it will throw instead + case NullableValueWithDynamicallyAccessedMembers: + case NullableSystemTypeValue: + case SystemTypeValue maybeArrayValue when maybeArrayValue.RepresentedType.IsTypeOf ("System", "Array"): + AddReturnValue (MultiValueLattice.Top); + break; + case SystemTypeValue systemTypeValue: + AddReturnValue (new NullableSystemTypeValue (typeValue.RepresentedType, new SystemTypeValue (systemTypeValue.RepresentedType))); + break; + // Generic Parameters and method parameters with annotations + case ValueWithDynamicallyAccessedMembers damValue: + AddReturnValue (new NullableValueWithDynamicallyAccessedMembers (typeValue.RepresentedType, damValue)); + break; + // Everything else assume it has no annotations + default: + AddReturnValue (GetMethodReturnValue (calledMethodDefinition, returnValueDynamicallyAccessedMemberTypes)); + break; + } + } + } + } + // We want to skip adding the `value` to the return Value because we have already added Nullable + continue; + } // We haven't found any generic parameters with annotations, so there's nothing to validate. } else if (value == NullValue.Instance) { // Do nothing - null value is valid and should not cause warnings nor marking @@ -356,11 +399,10 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c // We have no way to "include more" to fix this if we don't know, so we have to warn analysisContext.ReportWarning (DiagnosticId.MakeGenericType, calledMethodDefinition.GetDisplayName ()); } + // We don't want to lose track of the type + // in case this is e.g. Activator.CreateInstance(typeof(Foo<>).MakeGenericType(...)); + AddReturnValue (value); } - - // We don't want to lose track of the type - // in case this is e.g. Activator.CreateInstance(typeof(Foo<>).MakeGenericType(...)); - methodReturnValue = methodParams[0]; } break; @@ -457,7 +499,7 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c TypeDefinition? staticType = (valueNode as IValueWithStaticType)?.StaticType; if (staticType is null) { // We don't know anything about the type GetType was called on. Track this as a usual result of a method call without any annotations - methodReturnValue = MultiValueLattice.Meet (methodReturnValue, GetMethodReturnValue (calledMethodDefinition)); + AddReturnValue (GetMethodReturnValue (calledMethodDefinition)); } else if (staticType.IsSealed || staticType.IsTypeOf ("System", "Delegate")) { // We can treat this one the same as if it was a typeof() expression @@ -472,7 +514,7 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c // This can be seen a little bit as a violation of the annotation, but we already have similar cases // where a parameter is annotated and if something in the method sets a specific known type to it // we will also make it just work, even if the annotation doesn't match the usage. - methodReturnValue = MultiValueLattice.Meet (methodReturnValue, new SystemTypeValue (staticType)); + AddReturnValue (new SystemTypeValue (staticType)); } else { // Make sure the type is marked (this will mark it as used via reflection, which is sort of true) // This should already be true for most cases (method params, fields, ...), but just in case @@ -484,7 +526,7 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c // Return a value which is "unknown type" with annotation. For now we'll use the return value node // for the method, which means we're loosing the information about which staticType this // started with. For now we don't need it, but we can add it later on. - methodReturnValue = MultiValueLattice.Meet (methodReturnValue, GetMethodReturnValue (calledMethodDefinition, annotation)); + AddReturnValue (GetMethodReturnValue (calledMethodDefinition, annotation)); } } } @@ -514,7 +556,7 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c // Intentionally ignore - it's not wrong for code to call Type.GetType on non-existing name, the code might expect null/exception back. } else { _markStep.MarkTypeVisibleToReflection (foundTypeRef, foundType, new DependencyInfo (DependencyKind.AccessedViaReflection, callingMethodDefinition)); - methodReturnValue = MultiValueLattice.Meet (methodReturnValue, new SystemTypeValue (foundType)); + AddReturnValue (new SystemTypeValue (foundType)); _context.MarkingHelpers.MarkMatchingExportedType (foundType, typeAssembly, new DependencyInfo (DependencyKind.AccessedViaReflection, foundType), analysisContext.Origin); } } else if (typeNameValue == NullValue.Instance) { @@ -522,7 +564,7 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c } else if (typeNameValue is ValueWithDynamicallyAccessedMembers valueWithDynamicallyAccessedMembers && valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes != 0) { // Propagate the annotation from the type name to the return value. Annotation on a string value will be fullfilled whenever a value is assigned to the string with annotation. // So while we don't know which type it is, we can guarantee that it will fulfill the annotation. - methodReturnValue = MultiValueLattice.Meet (methodReturnValue, GetMethodReturnValue (calledMethodDefinition, valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes)); + AddReturnValue (GetMethodReturnValue (calledMethodDefinition, valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes)); } else { analysisContext.ReportWarning (DiagnosticId.UnrecognizedTypeNameInTypeGetType, calledMethod.GetDisplayName ()); } @@ -756,7 +798,7 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c } // MakeGenericMethod doesn't change the identity of the MethodBase we're tracking so propagate to the return value - methodReturnValue = methodParams[0]; + AddReturnValue (methodParams[0]); } break; @@ -800,11 +842,10 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c // If we get here, we handled this as an intrinsic. As a convenience, if the code above // didn't set the return value (and the method has a return value), we will set it to be an // unknown value with the return type of the method. - if (methodReturnValue.IsEmpty ()) { - if (GetReturnTypeWithoutModifiers (calledMethod.ReturnType).MetadataType != MetadataType.Void) { - methodReturnValue = GetMethodReturnValue (calledMethodDefinition, returnValueDynamicallyAccessedMemberTypes); - } - } + bool returnsVoid = GetReturnTypeWithoutModifiers (calledMethod.ReturnType).MetadataType == MetadataType.Void; + methodReturnValue = maybeMethodReturnValue ?? (returnsVoid ? + MultiValueLattice.Top : + GetMethodReturnValue (calledMethodDefinition, returnValueDynamicallyAccessedMemberTypes)); // Validate that the return value has the correct annotations as per the method return value annotations if (returnValueDynamicallyAccessedMemberTypes != 0) { @@ -822,6 +863,11 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c } return true; + + void AddReturnValue (MultiValue value) + { + maybeMethodReturnValue = (maybeMethodReturnValue is null) ? value : MultiValueLattice.Meet ((MultiValue) maybeMethodReturnValue, value); + } } bool IsComInterop (IMarshalInfoProvider marshalInfoProvider, TypeReference parameterType) diff --git a/src/linker/Linker.Dataflow/TypeProxy.cs b/src/linker/Linker.Dataflow/TypeProxy.cs index 910a56ecebfd..9fa5f5c09533 100644 --- a/src/linker/Linker.Dataflow/TypeProxy.cs +++ b/src/linker/Linker.Dataflow/TypeProxy.cs @@ -16,8 +16,12 @@ internal readonly partial struct TypeProxy public string Name { get => Type.Name; } + public string Namespace { get => Type.Namespace; } + public string GetDisplayName () => Type.GetDisplayName (); public override string ToString () => Type.ToString (); + + public bool IsTypeOf (string @namespace, string name) => Type.IsTypeOf (@namespace, name); } } diff --git a/src/linker/Linker.Dataflow/ValueNode.cs b/src/linker/Linker.Dataflow/ValueNode.cs index a079e777f8d0..51085efebc71 100644 --- a/src/linker/Linker.Dataflow/ValueNode.cs +++ b/src/linker/Linker.Dataflow/ValueNode.cs @@ -76,6 +76,14 @@ public static bool DetectCycle (this SingleValue node, HashSet seen } break; + case RuntimeTypeHandleForNullableValueWithDynamicallyAccessedMembers value: + foundCycle = value.UnderlyingTypeValue.DetectCycle (seenNodes, allNodesSeen); + break; + + case NullableValueWithDynamicallyAccessedMembers value: + foundCycle = value.UnderlyingTypeValue.DetectCycle (seenNodes, allNodesSeen); + break; + default: throw new Exception (String.Format ("Unknown node type: {0}", node.GetType ().Name)); } @@ -104,6 +112,8 @@ public GenericParameterValue (GenericParameter genericParameter, DynamicallyAcce public override IEnumerable GetDiagnosticArgumentsForAnnotationMismatch () => new string[] { GenericParameter.GenericParameter.Name, DiagnosticUtilities.GetGenericParameterDeclaringMemberDisplayName (GenericParameter.GenericParameter) }; + public override SingleValue DeepCopy () => this; // This value is immutable + public override string ToString () => this.ValueToString (GenericParameter, DynamicallyAccessedMemberTypes); } @@ -116,6 +126,8 @@ partial record RuntimeMethodHandleValue public readonly MethodDefinition MethodRepresented; + public override SingleValue DeepCopy () => this; // This value is immutable + public override string ToString () => this.ValueToString (MethodRepresented); } @@ -149,6 +161,8 @@ public override IEnumerable GetDiagnosticArgumentsForAnnotationMismatch public TypeDefinition? StaticType { get; } + public override SingleValue DeepCopy () => this; // This value is immutable + public override string ToString () => this.ValueToString (Method, ParameterIndex, DynamicallyAccessedMemberTypes); } @@ -172,6 +186,8 @@ public override IEnumerable GetDiagnosticArgumentsForAnnotationMismatch public TypeDefinition? StaticType => Method.DeclaringType; + public override SingleValue DeepCopy () => this; // This value is immutable + public override string ToString () => this.ValueToString (Method, DynamicallyAccessedMemberTypes); } @@ -196,6 +212,8 @@ public override IEnumerable GetDiagnosticArgumentsForAnnotationMismatch public TypeDefinition? StaticType { get; } + public override SingleValue DeepCopy () => this; // This value is immutable + public override string ToString () => this.ValueToString (Method, DynamicallyAccessedMemberTypes); } @@ -220,13 +238,13 @@ public override IEnumerable GetDiagnosticArgumentsForAnnotationMismatch public TypeDefinition? StaticType { get; } + public override SingleValue DeepCopy () => this; // This value is immutable + public override string ToString () => this.ValueToString (Field, DynamicallyAccessedMemberTypes); } partial record ArrayValue { - static ValueSetLattice MultiValueLattice => default; - public static MultiValue Create (MultiValue size, TypeReference elementType) { MultiValue result = MultiValueLattice.Top; @@ -288,6 +306,16 @@ public bool Equals (ArrayValue? otherArr) return thisValueSet.Count == 0; } + public override SingleValue DeepCopy () + { + var newValue = new ArrayValue (Size.DeepCopy (), ElementType); + foreach (var kvp in IndexValues) { + newValue.IndexValues.Add (kvp.Key, new ValueBasicBlockPair (kvp.Value.Value.Clone (), kvp.Value.BasicBlockIndex)); + } + + return newValue; + } + public override string ToString () { StringBuilder result = new (); @@ -367,7 +395,13 @@ public override bool Equals (object? other) public struct ValueBasicBlockPair { - public MultiValue Value; - public int BasicBlockIndex; + public ValueBasicBlockPair (MultiValue value, int basicBlockIndex) + { + Value = value; + BasicBlockIndex = basicBlockIndex; + } + + public MultiValue Value { get; } + public int BasicBlockIndex { get; } } } diff --git a/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs b/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs index cd90ee2b7009..923ed4482673 100644 --- a/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs +++ b/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs @@ -25,6 +25,12 @@ public Task AssemblyQualifiedNameDataflow () return RunTest (nameof (AssemblyQualifiedNameDataflow)); } + [Fact] + public Task ArrayDataFlow () + { + return RunTest (); + } + [Fact] public Task AttributeConstructorDataflow () { @@ -186,6 +192,12 @@ public Task MethodThisDataFlow () return RunTest (nameof (MethodThisDataFlow)); } + [Fact] + public Task NullableAnnotations () + { + return RunTest (); + } + [Fact] public Task PropertyDataFlow () { diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/ArrayDataFlow.cs b/test/Mono.Linker.Tests.Cases/DataFlow/ArrayDataFlow.cs new file mode 100644 index 000000000000..75829c1f89b1 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/DataFlow/ArrayDataFlow.cs @@ -0,0 +1,452 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics.CodeAnalysis; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Helpers; + +namespace Mono.Linker.Tests.Cases.DataFlow +{ + [ExpectedNoWarnings] + [SkipKeptItemsValidation] + public class ArrayDataFlow + { + public static void Main () + { + TestArrayWithInitializerOneElementStaticType (); + TestArrayWithInitializerOneElementParameter (typeof (TestType)); + TestArrayWithInitializerMultipleElementsStaticType (); + TestArrayWithInitializerMultipleElementsMix (typeof (TestType)); + + TestArraySetElementOneElementStaticType (); + TestArraySetElementOneElementParameter (typeof (TestType)); + TestArraySetElementMultipleElementsStaticType (); + TestArraySetElementMultipleElementsMix (typeof (TestType)); + + TestArraySetElementAndInitializerMultipleElementsMix (typeof (TestType)); + + TestGetElementAtUnknownIndex (); + + // Array reset - certain operations on array are not tracked fully (or impossible due to unknown inputs) + // and sometimes the only valid thing to do is to reset the array to all unknowns as it's impossible + // to determine what the operation did to the array. So after the reset, everything in the array + // should be treated as unknown value. + TestArrayResetStoreUnknownIndex (); + TestArrayResetGetElementOnByRefArray (); + TestArrayResetAfterCall (); + TestArrayResetAfterAssignment (); + TestMultiDimensionalArray.Test (); + } + + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicMethods))] + static void TestArrayWithInitializerOneElementStaticType () + { + Type[] arr = new Type[] { typeof (TestType) }; + arr[0].RequiresAll (); + arr[1].RequiresPublicMethods (); // Should warn - unknown value at this index + } + + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicMethods))] + static void TestArrayWithInitializerOneElementParameter ([DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] Type type) + { + Type[] arr = new Type[] { type }; + arr[0].RequiresAll (); + arr[1].RequiresPublicMethods (); // Should warn - unknown value at this index + } + + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicMethods))] + static void TestArrayWithInitializerMultipleElementsStaticType () + { + Type[] arr = new Type[] { typeof (TestType), typeof (TestType), typeof (TestType) }; + arr[0].RequiresAll (); + arr[1].RequiresAll (); + arr[2].RequiresAll (); + arr[3].RequiresPublicMethods (); // Should warn - unknown value at this index + } + + [ExpectedWarning ("IL2087", nameof (DataFlowTypeExtensions.RequiresPublicFields))] + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicMethods))] + static void TestArrayWithInitializerMultipleElementsMix<[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicProperties)] TProperties> ( + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] Type typeAll) + { + Type[] arr = new Type[] { typeof (TestType), typeof (TProperties), typeAll }; + arr[0].RequiresAll (); + arr[1].RequiresPublicProperties (); + arr[1].RequiresPublicFields (); // Should warn + arr[2].RequiresAll (); + arr[3].RequiresPublicMethods (); // Should warn - unknown value at this index + } + + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicMethods))] + static void TestArraySetElementOneElementStaticType () + { + Type[] arr = new Type[1]; + arr[0] = typeof (TestType); + arr[0].RequiresAll (); + arr[1].RequiresPublicMethods (); // Should warn - unknown value at this index + } + + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicMethods))] + static void TestArraySetElementOneElementParameter ([DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] Type type) + { + Type[] arr = new Type[1]; + arr[0] = type; + arr[0].RequiresAll (); + arr[1].RequiresPublicMethods (); // Should warn - unknown value at this index + } + + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicMethods))] + static void TestArraySetElementMultipleElementsStaticType () + { + Type[] arr = new Type[3]; + arr[0] = typeof (TestType); + arr[1] = typeof (TestType); + arr[2] = typeof (TestType); + arr[0].RequiresAll (); + arr[1].RequiresAll (); + arr[2].RequiresAll (); + arr[3].RequiresPublicMethods (); // Should warn - unknown value at this index + } + + [ExpectedWarning ("IL2087", nameof (DataFlowTypeExtensions.RequiresPublicFields))] + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicMethods))] + static void TestArraySetElementMultipleElementsMix<[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicProperties)] TProperties> ( + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] Type typeAll) + { + Type[] arr = new Type[3]; + arr[0] = typeof (TestType); + arr[1] = typeof (TProperties); + arr[2] = typeAll; + arr[0].RequiresAll (); + + arr[1].RequiresPublicProperties (); + arr[1].RequiresPublicFields (); // Should warn + arr[2].RequiresAll (); + arr[3].RequiresPublicMethods (); // Should warn - unknown value at this index + } + + [ExpectedWarning ("IL2087", nameof (DataFlowTypeExtensions.RequiresPublicFields))] + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicMethods))] + static void TestArraySetElementAndInitializerMultipleElementsMix<[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicProperties)] TProperties> ( + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] Type typeAll) + { + Type[] arr = new Type[] { typeof (TestType), null, null }; + arr[1] = typeof (TProperties); + arr[2] = typeAll; + arr[0].RequiresAll (); + arr[1].RequiresPublicProperties (); + arr[1].RequiresPublicFields (); // Should warn + arr[2].RequiresAll (); + arr[3].RequiresPublicMethods (); // Should warn - unknown value at this index + } + + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicFields))] + static void TestGetElementAtUnknownIndex (int i = 0) + { + Type[] arr = new Type[] { typeof (TestType) }; + arr[i].RequiresPublicFields (); + } + + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicFields))] + static void TestArrayResetStoreUnknownIndex (int i = 0) + { + Type[] arr = new Type[] { typeof (TestType) }; + arr[0].RequiresPublicProperties (); + + arr[i] = typeof (TestType); // Unknown index - we reset array to all unknowns + + arr[0].RequiresPublicFields (); // Warns + } + + // https://github.com/dotnet/linker/issues/2680 - analyzer doesn't reset array in this case + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicFields), ProducedBy = ProducedBy.Trimmer)] + static void TestArrayResetGetElementOnByRefArray (int i = 0) + { + Type[] arr = new Type[] { typeof (TestType) }; + arr[0].RequiresPublicProperties (); + + TakesTypeByRef (ref arr[0]); // No reset - known index + arr[0].RequiresPublicMethods (); // Doesn't warn + + TakesTypeByRef (ref arr[i]); // Reset - unknown index + arr[0].RequiresPublicFields (); // Warns + } + + static void TakesTypeByRef (ref Type type) { } + + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicFields))] + static void TestArrayResetAfterCall () + { + Type[] arr = new Type[] { typeof (TestType) }; + arr[0].RequiresPublicProperties (); + + // Calling a method and passing the array will reset the array after the call + // This is necessary since the array is passed by referenced and its unknown + // what the method will do to the array + TakesTypesArray (arr); + arr[0].RequiresPublicFields (); // Warns + } + + static void TakesTypesArray (Type[] types) { } + + // https://github.com/dotnet/linker/issues/2680 + // [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicFields))] + static void TestArrayResetAfterAssignment () + { + Type[] arr = new Type[] { typeof (TestType) }; + arr[0].RequiresPublicProperties (); + + // Assigning the array out of the method means that others can modify it - for non-method-calls it's not very likely to cause problems + // because the only meaningful way this could work in the program is if some other thread accessed and modified the array + // but it's still better to be safe in this case. + _externalArray = arr; + + arr[0].RequiresPublicFields (); // Should warn + } + + static Type[] _externalArray; + + static class TestMultiDimensionalArray + { + public static void Test () + { + TestArrayWithInitializerOneElementStaticType (); + TestArrayWithInitializerOneElementParameter (typeof (TestType)); + TestArrayWithInitializerMultipleElementsStaticType (); + TestArrayWithInitializerMultipleElementsMix (typeof (TestType)); + + TestArraySetElementOneElementStaticType (); + TestArraySetElementOneElementParameter (typeof (TestType)); + TestArraySetElementMultipleElementsStaticType (); + TestArraySetElementMultipleElementsMix (typeof (TestType)); + + TestArraySetElementAndInitializerMultipleElementsMix (typeof (TestType)); + + TestGetElementAtUnknownIndex (); + + // Array reset - certain operations on array are not tracked fully (or impossible due to unknown inputs) + // and sometimes the only valid thing to do is to reset the array to all unknowns as it's impossible + // to determine what the operation did to the array. So after the reset, everything in the array + // should be treated as unknown value. + TestArrayResetStoreUnknownIndex (); + TestArrayResetGetElementOnByRefArray (); + TestArrayResetAfterCall (); + TestArrayResetAfterAssignment (); + } + + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicMethods), ProducedBy = ProducedBy.Trimmer)] + // Multidimensional Arrays not handled -- assumed to be UnknownValue + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Trimmer)] + static void TestArrayWithInitializerOneElementStaticType () + { + Type[,] arr = new Type[,] { { typeof (TestType) } }; + arr[0, 0].RequiresAll (); + arr[0, 1].RequiresPublicMethods (); // Should warn - unknown value at this index + } + + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicMethods), ProducedBy = ProducedBy.Trimmer)] + // Multidimensional Arrays not handled -- assumed to be UnknownValue + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Trimmer)] + static void TestArrayWithInitializerOneElementParameter ([DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] Type type) + { + Type[,] arr = new Type[,] { { type } }; + arr[0, 0].RequiresAll (); + arr[0, 1].RequiresPublicMethods (); // Should warn - unknown value at this index + } + + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicMethods), ProducedBy = ProducedBy.Trimmer)] + // Below are because we do not handle Multi dimensional arrays + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Trimmer)] + static void TestArrayWithInitializerMultipleElementsStaticType () + { + Type[,] arr = new Type[,] { { typeof (TestType), typeof (TestType), typeof (TestType) } }; + arr[0, 0].RequiresAll (); + arr[0, 1].RequiresAll (); + arr[0, 2].RequiresAll (); + arr[0, 3].RequiresPublicMethods (); // Should warn - unknown value at this index + } + + // Bug + // [ExpectedWarning ("IL2087", nameof (DataFlowTypeExtensions.RequiresPublicFields), ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicMethods), ProducedBy = ProducedBy.Trimmer)] + // Below are because we do not handle Multi dimensional arrays + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicProperties), ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicFields), ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Trimmer)] + static void TestArrayWithInitializerMultipleElementsMix<[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicProperties)] TProperties> ( + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] Type typeAll) + { + Type[,] arr = new Type[,] { { typeof (TestType), typeof (TProperties), typeAll } }; + arr[0, 0].RequiresAll (); + arr[0, 1].RequiresPublicProperties (); + arr[0, 1].RequiresPublicFields (); // Should warn + arr[0, 2].RequiresAll (); + arr[0, 3].RequiresPublicMethods (); // Should warn - unknown value at this index + } + + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicMethods), ProducedBy = ProducedBy.Trimmer)] + // Multidimensional Arrays not handled -- assumed to be UnknownValue + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Trimmer)] + static void TestArraySetElementOneElementStaticType () + { + Type[,] arr = new Type[1, 1]; + arr[0, 0] = typeof (TestType); + arr[0, 0].RequiresAll (); + arr[0, 1].RequiresPublicMethods (); // Should warn - unknown value at this index + } + + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicMethods), ProducedBy = ProducedBy.Trimmer)] + // Multidimensional Arrays not handled -- assumed to be UnknownValue + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Trimmer)] + static void TestArraySetElementOneElementParameter ([DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] Type type) + { + Type[,] arr = new Type[1, 1]; + arr[0, 0] = type; + arr[0, 0].RequiresAll (); + arr[0, 1].RequiresPublicMethods (); // Should warn - unknown value at this index + } + + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicMethods), ProducedBy = ProducedBy.Trimmer)] + // Below are because we do not handle Multi dimensional arrays + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Trimmer)] + static void TestArraySetElementMultipleElementsStaticType () + { + Type[,] arr = new Type[1, 3]; + arr[0, 0] = typeof (TestType); + arr[0, 1] = typeof (TestType); + arr[0, 2] = typeof (TestType); + arr[0, 0].RequiresAll (); + arr[0, 1].RequiresAll (); + arr[0, 2].RequiresAll (); + arr[0, 3].RequiresPublicMethods (); // Should warn - unknown value at this index + } + + // Bug + // [ExpectedWarning ("IL2087", nameof (DataFlowTypeExtensions.RequiresPublicFields), ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicMethods), ProducedBy = ProducedBy.Trimmer)] + // Below are because we do not handle Multi dimensional arrays + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicFields), ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicProperties), ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Trimmer)] + static void TestArraySetElementMultipleElementsMix<[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicProperties)] TProperties> ( + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] Type typeAll) + { + Type[,] arr = new Type[1, 3]; + arr[0, 0] = typeof (TestType); + arr[0, 1] = typeof (TProperties); + arr[0, 2] = typeAll; + arr[0, 0].RequiresAll (); + arr[0, 1].RequiresPublicProperties (); + arr[0, 1].RequiresPublicFields (); // Should warn + arr[0, 2].RequiresAll (); + arr[0, 3].RequiresPublicMethods (); // Should warn - unknown value at this index + } + + // Bug + // [ExpectedWarning ("IL2087", nameof (DataFlowTypeExtensions.RequiresPublicFields), ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicMethods), ProducedBy = ProducedBy.Trimmer)] + // Below are because we do not handle Multi dimensional arrays + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicFields), ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicProperties), ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Trimmer)] + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Trimmer)] + static void TestArraySetElementAndInitializerMultipleElementsMix<[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicProperties)] TProperties> ( + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] Type typeAll) + { + Type[,] arr = new Type[,] { { typeof (TestType), null, null } }; + arr[0, 1] = typeof (TProperties); + arr[0, 2] = typeAll; + arr[0, 0].RequiresAll (); + arr[0, 1].RequiresPublicProperties (); + arr[0, 1].RequiresPublicFields (); // Should warn + arr[0, 2].RequiresAll (); + arr[0, 3].RequiresPublicMethods (); // Should warn - unknown value at this index + } + + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicFields), ProducedBy = ProducedBy.Trimmer)] + static void TestGetElementAtUnknownIndex (int i = 0) + { + Type[,] arr = new Type[,] { { typeof (TestType) } }; + arr[0, i].RequiresPublicFields (); + } + + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicFields), ProducedBy = ProducedBy.Trimmer)] + // Multidimensional Arrays not handled -- assumed to be UnknownValue + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicProperties), ProducedBy = ProducedBy.Trimmer)] + static void TestArrayResetStoreUnknownIndex (int i = 0) + { + Type[,] arr = new Type[,] { { typeof (TestType) } }; + arr[0, 0].RequiresPublicProperties (); + + arr[0, i] = typeof (TestType); // Unknown index - we reset array to all unknowns + + arr[0, 0].RequiresPublicFields (); // Warns + } + + // https://github.com/dotnet/linker/issues/2680 - analyzer doesn't reset array in this case + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicFields), ProducedBy = ProducedBy.Trimmer)] + // Multidimensional Arrays not handled -- assumed to be UnknownValue + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicProperties), ProducedBy = ProducedBy.Trimmer)] + // Multidimensional Arrays not handled -- assumed to be UnknownValue + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicMethods), ProducedBy = ProducedBy.Trimmer)] + static void TestArrayResetGetElementOnByRefArray (int i = 0) + { + Type[,] arr = new Type[,] { { typeof (TestType) } }; + arr[0, 0].RequiresPublicProperties (); + + TakesTypeByRef (ref arr[0, 0]); // No reset - known index + arr[0, 0].RequiresPublicMethods (); // Doesn't warn + + TakesTypeByRef (ref arr[0, i]); // Reset - unknown index + arr[0, 0].RequiresPublicFields (); // Warns + } + + static void TakesTypeByRef (ref Type type) { } + + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicFields), ProducedBy = ProducedBy.Trimmer)] + // Multidimensional Arrays not handled -- assumed to be UnknownValue + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicProperties), ProducedBy = ProducedBy.Trimmer)] + static void TestArrayResetAfterCall () + { + Type[,] arr = new Type[,] { { typeof (TestType) } }; + arr[0, 0].RequiresPublicProperties (); + + // Calling a method and passing the array will reset the array after the call + // This is necessary since the array is passed by referenced and its unknown + // what the method will do to the array + TakesTypesArray (arr); + arr[0, 0].RequiresPublicFields (); // Warns + } + + static void TakesTypesArray (Type[,] types) { } + + // https://github.com/dotnet/linker/issues/2680 + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicFields), ProducedBy = ProducedBy.Trimmer)] + // Multidimensional Arrays not handled -- assumed to be UnknownValue + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresPublicProperties), ProducedBy = ProducedBy.Trimmer)] + static void TestArrayResetAfterAssignment () + { + Type[,] arr = new Type[,] { { typeof (TestType) } }; + arr[0, 0].RequiresPublicProperties (); + + // Assigning the array out of the method means that others can modify it - for non-method-calls it's not very likely to cause problems + // because the only meaningful way this could work in the program is if some other thread accessed and modified the array + // but it's still better to be safe in this case. + _externalArray = arr; + + arr[0, 0].RequiresPublicFields (); // Should warn + } + + static Type[,] _externalArray; + } + + public class TestType { } + } +} diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/NullableAnnotations.cs b/test/Mono.Linker.Tests.Cases/DataFlow/NullableAnnotations.cs new file mode 100644 index 000000000000..c1a602fb5828 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/DataFlow/NullableAnnotations.cs @@ -0,0 +1,270 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics.CodeAnalysis; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Helpers; +using DAM = System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute; +using DAMT = System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes; + +namespace Mono.Linker.Tests.Cases.DataFlow +{ + [ExpectedNoWarnings] + class NullableAnnotations + { + [Kept] + struct TestType + { + } + + // This only gets annotations through Nullable + [Kept] + struct TestStructPassedInsideNullable + { + [Kept] + [KeptBackingField] + public string FirstName { [Kept] get; [Kept] set; } + [Kept] + [KeptBackingField] + public string LastName { [Kept] get; [Kept] set; } + } + + + [Kept] + struct TestStructWithRucMethod + { + [Kept] + [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] + [RequiresUnreferencedCode ("message")] + void MethodWithRuc () { } + } + + [Kept] + public static void Main () + { + NullableOfAnnotatedGenericParameterRequiresPublicProperties (); + Type _ = ReturnUnderlyingTypeThatRequiresProperties> (new ()); + TestRequireRucMethodThroughNullable (); + + DamOnNullableKeepsUnderlyingMembers (); + UnderlyingTypeOfCreatedNullableOfAnnotatedTRequiresPublicProperties (); + RequirePublicFieldsOnGenericParam> (); + NullableOfUnannotatedGenericParamPassedAsGenericParamRequiresPublicFields (); + NullableOfAnnotatedGenericParamPassedAsGenericParamRequiresPublicFields (); + + TestGetUnderlyingTypeOnStructs (); + TestAnnotationsOnNullableKeepsMembersOnUnderlyingType (); + TestGetUnderlyingTypeOfCreatedNullableOnStructs (); + ImproperMakeGenericTypeDoesntWarn (); + MakeGenericTypeWithUnknownValue (new object[2] { 1, 2 }); + } + + [Kept] + static void ImproperMakeGenericTypeDoesntWarn () + { + typeof (Nullable<>).MakeGenericType (typeof (Nullable)).GetProperties (); // No warning - we treat the cases where reflection throws as "no value". + typeof (Nullable<>).MakeGenericType (typeof (int[])).GetProperties (); // No warning - we treat the cases where reflection throws as "no value". + } + + [Kept] + [ExpectedWarning ("IL2026", "message")] + static void RequireAllFromUnderlyingTypeWithMethodWithRUC () + { + var T = typeof (Nullable); + var uT = Nullable.GetUnderlyingType (T); + uT.RequiresAll (); + } + + [Kept] + [ExpectedWarning ("IL2026", "message")] + static void RequireAllFromNullableOfTypeWithMethodWithRuc () + { + typeof (Nullable).RequiresAll (); + } + + [Kept] + [ExpectedWarning ("IL2026", "message")] + static void RequireAllFromMadeGenericNullableOfTypeWithMethodWithRuc () + { + typeof (Nullable<>).MakeGenericType (typeof (TestStructWithRucMethod)).RequiresAll (); + } + + [Kept] + static void TestRequireRucMethodThroughNullable () + { + RequireAllFromUnderlyingTypeWithMethodWithRUC (); + RequireAllFromNullableOfTypeWithMethodWithRuc (); + RequireAllFromMadeGenericNullableOfTypeWithMethodWithRuc (); + } + + [Kept] + static void UnderlyingTypeOfAnnotatedGenericParameterRequiresPublicProperties<[KeptAttributeAttribute (typeof (DAM))][DAM (DAMT.PublicProperties)] TNullable> () + { + Nullable.GetUnderlyingType (typeof (TNullable)).RequiresPublicProperties (); + } + + [Kept] + static void UnderlyingTypeOfAnnotatedParameterRequiresPublicProperties ([KeptAttributeAttribute (typeof (DAM))][DAM (DAMT.PublicProperties)] Type tNullable) + { + Nullable.GetUnderlyingType (tNullable).RequiresPublicProperties (); + } + + [Kept] + [ExpectedWarning ("IL2067")] + static void UnderlyingTypeOfUnannotatedParameterRequiresPublicProperties (Type tNullable) + { + Nullable.GetUnderlyingType (tNullable).RequiresPublicProperties (); + } + + [Kept] + [ExpectedWarning ("IL2087")] + static void UnderlyingTypeOfUnannotatedGenericParameterRequiresProperties () + { + Nullable.GetUnderlyingType (typeof (TNullable)).RequiresPublicProperties (); + } + + [Kept] + static void NullableOfAnnotatedGenericParameterRequiresPublicProperties<[KeptAttributeAttribute (typeof (DAM))][DAM (DAMT.PublicProperties)] T> () where T : struct + { + Nullable.GetUnderlyingType (typeof (Nullable)).RequiresPublicProperties (); + } + + [Kept] + [ExpectedWarning ("IL2087")] + static void NullableOfUnannotatedGenericParameterRequiresPublicProperties () where T : struct + { + Nullable.GetUnderlyingType (typeof (Nullable)).RequiresPublicProperties (); + } + + [Kept] + static void MakeGenericNullableOfAnnotatedParameterRequiresPublicProperties ([KeptAttributeAttribute (typeof (DAM))][DAM (DAMT.PublicProperties)] Type t) + { + Nullable.GetUnderlyingType (typeof (Nullable<>).MakeGenericType (t)).RequiresPublicProperties (); + } + + [Kept] + [ExpectedWarning ("IL2067")] + static void MakeGenericNullableOfUnannotatedParameterRequiresPublicProperties (Type t) + { + Nullable.GetUnderlyingType (typeof (Nullable<>).MakeGenericType (t)).RequiresPublicProperties (); + } + + [Kept] + static void MakeGenericNullableOfAnnotatedGenericParameterRequiresPublicProperties<[KeptAttributeAttribute (typeof (DAM))][DAM (DAMT.PublicProperties)] T> () + { + Nullable.GetUnderlyingType (typeof (Nullable<>).MakeGenericType (typeof (T))).RequiresPublicProperties (); + } + + [Kept] + [ExpectedWarning ("IL2087")] + static void MakeGenericNullableOfUnannotatedGenericParameterRequiresPublicProperties () + { + Nullable.GetUnderlyingType (typeof (Nullable<>).MakeGenericType (typeof (T))).RequiresPublicProperties (); + } + + [Kept] + static void TestGetUnderlyingTypeOnStructs () + { + UnderlyingTypeOfAnnotatedParameterRequiresPublicProperties (typeof (TestType)); + UnderlyingTypeOfAnnotatedGenericParameterRequiresPublicProperties (); + UnderlyingTypeOfUnannotatedParameterRequiresPublicProperties (typeof (TestType)); + UnderlyingTypeOfUnannotatedGenericParameterRequiresProperties (); + } + + [Kept] + static void TestAnnotationsOnNullableKeepsMembersOnUnderlyingType () + { + UnderlyingTypeOfAnnotatedParameterRequiresPublicProperties (typeof (Nullable)); + UnderlyingTypeOfAnnotatedGenericParameterRequiresPublicProperties> (); + UnderlyingTypeOfUnannotatedParameterRequiresPublicProperties (typeof (Nullable)); + UnderlyingTypeOfUnannotatedGenericParameterRequiresProperties> (); + } + + [Kept] + static void TestGetUnderlyingTypeOfCreatedNullableOnStructs () + { + MakeGenericNullableOfAnnotatedParameterRequiresPublicProperties (typeof (TestType)); + MakeGenericNullableOfAnnotatedGenericParameterRequiresPublicProperties (); + NullableOfUnannotatedGenericParameterRequiresPublicProperties (); + MakeGenericNullableOfUnannotatedParameterRequiresPublicProperties (typeof (TestType)); + MakeGenericNullableOfUnannotatedGenericParameterRequiresPublicProperties (); + NullableOfUnannotatedGenericParameterRequiresPublicProperties (); + } + + + [Kept] + [return: DynamicallyAccessedMembers (DAMT.PublicProperties)] + [return: KeptAttributeAttribute (typeof (DAM))] + static Type ReturnUnderlyingTypeThatRequiresProperties<[KeptAttributeAttribute (typeof (DAM))][DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicProperties)] T> (T instance) + { + Type type = Nullable.GetUnderlyingType (typeof (T)) ?? typeof (T); + return type; + } + + [Kept] + struct StructWithUnreferencedFields + { + [Kept] + public int field1; + + [Kept] + public StructReferencedThroughDam s; + + [KeptBackingField] + public int prop { get; set; } + } + + [Kept] + struct StructReferencedThroughDam { } + + [Kept] + static void DamOnNullableKeepsUnderlyingMembers () + { + typeof (Nullable).RequiresPublicFields (); + } + + [Kept] + static void UnderlyingTypeOfCreatedNullableOfAnnotatedTRequiresPublicProperties<[KeptAttributeAttribute (typeof (DAM))][DAM (DAMT.PublicProperties)] T> () where T : struct + { + Type t = typeof (Nullable); + t = Nullable.GetUnderlyingType (t); + t.RequiresPublicProperties (); + } + + [Kept] + struct StructWithFieldsReferencedThroughDamOnNullable + { + [Kept] + public int field; + public int method () { return 0; } + } + + [Kept] + static void RequirePublicFieldsOnGenericParam<[KeptAttributeAttribute (typeof (DAM))][DAM (DAMT.PublicFields)] T> () + { + } + + [Kept] + [ExpectedWarning ("IL2091")] + static void NullableOfUnannotatedGenericParamPassedAsGenericParamRequiresPublicFields () where T : struct + { + RequirePublicFieldsOnGenericParam> (); + } + + [Kept] + static void NullableOfAnnotatedGenericParamPassedAsGenericParamRequiresPublicFields<[KeptAttributeAttribute (typeof (DAM))][DAM (DAMT.PublicFields)] T> () where T : struct + { + RequirePublicFieldsOnGenericParam> (); + } + + [Kept] + [ExpectedWarning ("IL2075")] + static void MakeGenericTypeWithUnknownValue (object[] maybetypes) + { + Type[] types = new Type[] { maybetypes[0] as Type }; // Roundabout way to get UnknownValue - it is getting tricky to do that reliably + Type nullable = typeof (Nullable<>).MakeGenericType (types); + nullable.GetProperties (); // Must WARN + } + } +} diff --git a/test/Mono.Linker.Tests.Cases/Reflection/MethodsUsedViaReflection.cs b/test/Mono.Linker.Tests.Cases/Reflection/MethodsUsedViaReflection.cs index 99ec8d44ef44..23ae33cacc5b 100644 --- a/test/Mono.Linker.Tests.Cases/Reflection/MethodsUsedViaReflection.cs +++ b/test/Mono.Linker.Tests.Cases/Reflection/MethodsUsedViaReflection.cs @@ -362,8 +362,6 @@ private static void PrivateMethodWithRUC () { } } [Kept] - // https://github.com/dotnet/linker/issues/2638 - [ExpectedWarning ("IL2026", ProducedBy = ProducedBy.Analyzer)] public static void Test () { typeof (TestClassWithRUCMethods).GetMethods ((BindingFlags) 24); diff --git a/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs b/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs index fcd8572fa4d7..4181e95d120a 100644 --- a/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs +++ b/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs @@ -755,6 +755,7 @@ protected virtual IEnumerable FilterLinkedAttributes (ICustomAttributePr switch (attr.AttributeType.FullName) { case "System.Runtime.CompilerServices.RuntimeCompatibilityAttribute": case "System.Runtime.CompilerServices.CompilerGeneratedAttribute": + case "System.Runtime.CompilerServices.IsReadOnlyAttribute": continue; // When mcs is used to compile the test cases, backing fields end up with this attribute on them