diff options
23 files changed, 263 insertions, 61 deletions
diff --git a/eng/Versions.props b/eng/Versions.props index 285436be0..f3a76f0c0 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -21,7 +21,7 @@ <MicrosoftDotNetApiCompatVersion>7.0.0-beta.22313.1</MicrosoftDotNetApiCompatVersion> <MicrosoftDotNetCodeAnalysisVersion>6.0.0-beta.21271.1</MicrosoftDotNetCodeAnalysisVersion> <MicrosoftCodeAnalysisCSharpCodeStyleVersion>3.10.0-2.final</MicrosoftCodeAnalysisCSharpCodeStyleVersion> - <MicrosoftCodeAnalysisVersion>4.0.1</MicrosoftCodeAnalysisVersion> + <MicrosoftCodeAnalysisVersion>4.3.0-1.22206.2</MicrosoftCodeAnalysisVersion> <MicrosoftCodeAnalysisCSharpAnalyzerTestingXunitVersion>1.0.1-beta1.*</MicrosoftCodeAnalysisCSharpAnalyzerTestingXunitVersion> <MicrosoftCodeAnalysisBannedApiAnalyzersVersion>3.3.2</MicrosoftCodeAnalysisBannedApiAnalyzersVersion> <!-- This controls the version of the cecil package, or the version of cecil in the project graph diff --git a/src/ILLink.RoslynAnalyzer/DataFlow/CapturedReferenceValue.cs b/src/ILLink.RoslynAnalyzer/DataFlow/CapturedReferenceValue.cs index 0b2e725ff..c9fc77bc7 100644 --- a/src/ILLink.RoslynAnalyzer/DataFlow/CapturedReferenceValue.cs +++ b/src/ILLink.RoslynAnalyzer/DataFlow/CapturedReferenceValue.cs @@ -19,6 +19,7 @@ namespace ILLink.RoslynAnalyzer.DataFlow case OperationKind.FieldReference: case OperationKind.ParameterReference: case OperationKind.ArrayElementReference: + case OperationKind.ImplicitIndexerReference: break; case OperationKind.None: case OperationKind.InstanceReference: diff --git a/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs b/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs index 7ad261191..819653eeb 100644 --- a/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs +++ b/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs @@ -136,12 +136,9 @@ namespace ILLink.RoslynAnalyzer.DataFlow // Avoid visiting the property reference because for captured properties, we can't // correctly detect whether it is used for reading or writing inside of VisitPropertyReference. // https://github.com/dotnet/roslyn/issues/25057 - IPropertySymbol? property = propertyRef.Property; - IMethodSymbol? setMethod; - while ((setMethod = property.SetMethod) == null) { - if ((property = property.OverriddenProperty) == null) - break; - } + TValue instanceValue = Visit (propertyRef.Instance, state); + TValue value = Visit (operation.Value, state); + IMethodSymbol? setMethod = propertyRef.Property.GetSetMethod (); if (setMethod == null) { // This can happen in a constructor - there it is possible to assign to a property // without a setter. This turns into an assignment to the compiler-generated backing field. @@ -149,17 +146,34 @@ namespace ILLink.RoslynAnalyzer.DataFlow // For now, just don't warn. https://github.com/dotnet/linker/issues/2731 break; } - TValue instanceValue = Visit (propertyRef.Instance, state); - TValue value = Visit (operation.Value, state); - HandleMethodCall ( - setMethod, - instanceValue, - ImmutableArray.Create (value), - operation); + HandleMethodCall (setMethod, instanceValue, ImmutableArray.Create (value), operation); // The return value of a property set expression is the value, // even though a property setter has no return value. return value; } + case IImplicitIndexerReferenceOperation indexerRef: { + // An implicit reference to an indexer where the argument is a System.Index + TValue instanceValue = Visit (indexerRef.Instance, state); + TValue indexArgumentValue = Visit (indexerRef.Argument, state); + TValue value = Visit (operation.Value, state); + + var property = (IPropertySymbol) indexerRef.IndexerSymbol; + + var argumentsBuilder = ImmutableArray.CreateBuilder<TValue> (); + argumentsBuilder.Add (indexArgumentValue); + argumentsBuilder.Add (value); + + IMethodSymbol? setMethod = property.GetSetMethod (); + if (setMethod == null) { + // It might actually be a call to a ref-returning get method, + // like Span<T>.this[int].get. We don't handle ref returns yet. + break; + } + + HandleMethodCall (setMethod, instanceValue, argumentsBuilder.ToImmutableArray (), operation); + return value; + } + // TODO: when setting a property in an attribute, target is an IPropertyReference. case ILocalReferenceOperation localRef: { TValue value = Visit (operation.Value, state); @@ -208,7 +222,7 @@ namespace ILLink.RoslynAnalyzer.DataFlow // (don't have specific I*Operation types), such as pointer dereferences. if (targetOperation.Kind is OperationKind.None) break; - throw new NotImplementedException (targetOperation.GetType ().ToString ()); + throw new NotImplementedException ($"{targetOperation.GetType ().ToString ()}: {targetOperation.Syntax.GetLocation ().GetLineSpan ()}"); } return Visit (operation.Value, state); } @@ -272,17 +286,26 @@ namespace ILLink.RoslynAnalyzer.DataFlow // Accessing property for reading is really a call to the getter // The setter case is handled in assignment operation since here we don't have access to the value to pass to the setter TValue instanceValue = Visit (operation.Instance, state); - IPropertySymbol? property = operation.Property; - IMethodSymbol? getMethod; - while ((getMethod = property.GetMethod) == null) { - if ((property = property.OverriddenProperty) == null) - break; + IMethodSymbol? getMethod = operation.Property.GetGetMethod (); + return HandleMethodCall (getMethod!, instanceValue, ImmutableArray<TValue>.Empty, operation); + } + + public override TValue VisitImplicitIndexerReference (IImplicitIndexerReferenceOperation operation, LocalDataFlowState<TValue, TValueLattice> state) + { + if (!operation.GetValueUsageInfo (Context.OwningSymbol).HasFlag (ValueUsageInfo.Read)) + return TopValue; + + TValue instanceValue = Visit (operation.Instance, state); + TValue indexArgumentValue = Visit (operation.Argument, state); + + if (operation.IndexerSymbol is not IPropertySymbol indexerProperty) { + // For example, System.Span<T>.Slice(int, int). + // Don't try to handle it for now. + return TopValue; } - return HandleMethodCall ( - getMethod!, - instanceValue, - ImmutableArray<TValue>.Empty, - operation); + + IMethodSymbol getMethod = indexerProperty.GetGetMethod ()!; + return HandleMethodCall (getMethod, instanceValue, ImmutableArray.Create (indexArgumentValue), operation); } public override TValue VisitArrayElementReference (IArrayElementReferenceOperation operation, LocalDataFlowState<TValue, TValueLattice> state) diff --git a/src/ILLink.RoslynAnalyzer/DataFlow/OperationWalker.cs b/src/ILLink.RoslynAnalyzer/DataFlow/OperationWalker.cs index 32811a763..4adf9be0f 100644 --- a/src/ILLink.RoslynAnalyzer/DataFlow/OperationWalker.cs +++ b/src/ILLink.RoslynAnalyzer/DataFlow/OperationWalker.cs @@ -13,8 +13,7 @@ namespace ILLink.RoslynAnalyzer.DataFlow private void VisitChildOperations (IOperation operation, TArgument argument) { - // https://github.com/dotnet/roslyn/issues/49475 would let us use ChildOperations, a struct enumerable. - foreach (var child in operation.Children) + foreach (var child in operation.ChildOperations) Visit (child, argument); } diff --git a/src/ILLink.RoslynAnalyzer/IPropertySymbolExtensions.cs b/src/ILLink.RoslynAnalyzer/IPropertySymbolExtensions.cs new file mode 100644 index 000000000..24cb4566c --- /dev/null +++ b/src/ILLink.RoslynAnalyzer/IPropertySymbolExtensions.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; + +namespace ILLink.RoslynAnalyzer +{ + public static class IPropertySymbolExtensions + { + public static IMethodSymbol? GetGetMethod (this IPropertySymbol property) + { + IPropertySymbol? declaringProperty = property; + IMethodSymbol? getMethod; + while ((getMethod = declaringProperty.GetMethod) == null) { + if ((declaringProperty = declaringProperty.OverriddenProperty) == null) + break; + } + return getMethod; + } + + public static IMethodSymbol? GetSetMethod (this IPropertySymbol property) + { + IPropertySymbol? declaringProperty = property; + IMethodSymbol? setMethod; + while ((setMethod = declaringProperty.SetMethod) == null) { + if ((declaringProperty = declaringProperty.OverriddenProperty) == null) + break; + } + return setMethod; + } + } +}
\ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptDelegateCacheFieldAttribute.cs b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptDelegateCacheFieldAttribute.cs index 52fe49618..b3638b01a 100644 --- a/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptDelegateCacheFieldAttribute.cs +++ b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptDelegateCacheFieldAttribute.cs @@ -8,10 +8,12 @@ namespace Mono.Linker.Tests.Cases.Expectations.Assertions [AttributeUsage (AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = true)] public class KeptDelegateCacheFieldAttribute : KeptAttribute { - public KeptDelegateCacheFieldAttribute (string uniquePartOfName) + public KeptDelegateCacheFieldAttribute (string classIndex, string fieldName) { - if (string.IsNullOrEmpty (uniquePartOfName)) - throw new ArgumentNullException (nameof (uniquePartOfName)); + if (string.IsNullOrEmpty (classIndex)) + throw new ArgumentNullException (nameof (classIndex)); + if (string.IsNullOrEmpty (fieldName)) + throw new ArgumentNullException (nameof (fieldName)); } } } diff --git a/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptPrivateImplementationDetails.cs b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptPrivateImplementationDetails.cs new file mode 100644 index 000000000..a410eb2f5 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptPrivateImplementationDetails.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace Mono.Linker.Tests.Cases.Expectations.Assertions +{ + [AttributeUsage (AttributeTargets.Class)] + public sealed class KeptPrivateImplementationDetailsAttribute : KeptAttribute + { + public KeptPrivateImplementationDetailsAttribute (string methodName) + { + if (string.IsNullOrEmpty (methodName)) + throw new ArgumentException ("Value cannot be null or empty.", nameof (methodName)); + } + } +}
\ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributeCtorOnEvent.cs b/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributeCtorOnEvent.cs index 6b9c5ff8e..eaf9a4866 100644 --- a/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributeCtorOnEvent.cs +++ b/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributeCtorOnEvent.cs @@ -14,7 +14,7 @@ namespace Mono.Linker.Tests.Cases.Attributes.Csc [KeptTypeInAssembly ("LibraryWithType.dll", typeof (TypeDefinedInReference))] [RemovedMemberInAssembly ("LibraryWithType.dll", typeof (TypeDefinedInReference), "Unused()")] [KeptMemberInAssembly ("LibraryWithAttribute.dll", typeof (AttributeDefinedInReference), ".ctor(System.Type)")] - [KeptDelegateCacheField ("0")] + [KeptDelegateCacheField ("0", nameof (FooOnMyEvent))] public class OnlyTypeUsedInAssemblyIsTypeOnAttributeCtorOnEvent { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributeFieldOnEvent.cs b/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributeFieldOnEvent.cs index 9026e6048..d069585c0 100644 --- a/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributeFieldOnEvent.cs +++ b/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributeFieldOnEvent.cs @@ -15,7 +15,7 @@ namespace Mono.Linker.Tests.Cases.Attributes.Csc [RemovedMemberInAssembly ("LibraryWithType.dll", typeof (TypeDefinedInReference), "Unused()")] [KeptMemberInAssembly ("LibraryWithAttribute.dll", typeof (AttributeDefinedInReference), ".ctor()")] [KeptMemberInAssembly ("LibraryWithAttribute.dll", typeof (AttributeDefinedInReference), "FieldType")] - [KeptDelegateCacheField ("0")] + [KeptDelegateCacheField ("0", nameof (FooOnMyEvent))] public class OnlyTypeUsedInAssemblyIsTypeOnAttributeFieldOnEvent { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributePropertyOnEvent.cs b/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributePropertyOnEvent.cs index 5f1791948..83466e847 100644 --- a/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributePropertyOnEvent.cs +++ b/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributePropertyOnEvent.cs @@ -15,7 +15,7 @@ namespace Mono.Linker.Tests.Cases.Attributes.Csc [RemovedMemberInAssembly ("LibraryWithType.dll", typeof (TypeDefinedInReference), "Unused()")] [KeptMemberInAssembly ("LibraryWithAttribute.dll", typeof (AttributeDefinedInReference), ".ctor()")] [KeptMemberInAssembly ("LibraryWithAttribute.dll", typeof (AttributeDefinedInReference), "set_PropertyType(System.Type)")] - [KeptDelegateCacheField ("0")] + [KeptDelegateCacheField ("0", nameof (FooOnMyEvent))] public class OnlyTypeUsedInAssemblyIsTypeOnAttributePropertyOnEvent { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Attributes/OnlyKeepUsed/UnusedAttributeTypeOnEventIsRemoved.cs b/test/Mono.Linker.Tests.Cases/Attributes/OnlyKeepUsed/UnusedAttributeTypeOnEventIsRemoved.cs index ff8775974..c05e9ad30 100644 --- a/test/Mono.Linker.Tests.Cases/Attributes/OnlyKeepUsed/UnusedAttributeTypeOnEventIsRemoved.cs +++ b/test/Mono.Linker.Tests.Cases/Attributes/OnlyKeepUsed/UnusedAttributeTypeOnEventIsRemoved.cs @@ -5,7 +5,7 @@ using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.Attributes.OnlyKeepUsed { [SetupLinkerArgument ("--used-attrs-only", "true")] - [KeptDelegateCacheField ("0")] + [KeptDelegateCacheField ("0", nameof (Tmp_Something))] class UnusedAttributeTypeOnEventIsRemoved { static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Attributes/OnlyKeepUsed/UsedAttributeTypeOnEventIsKept.cs b/test/Mono.Linker.Tests.Cases/Attributes/OnlyKeepUsed/UsedAttributeTypeOnEventIsKept.cs index 27d50d3cd..0a5e3cfe3 100644 --- a/test/Mono.Linker.Tests.Cases/Attributes/OnlyKeepUsed/UsedAttributeTypeOnEventIsKept.cs +++ b/test/Mono.Linker.Tests.Cases/Attributes/OnlyKeepUsed/UsedAttributeTypeOnEventIsKept.cs @@ -5,7 +5,7 @@ using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.Attributes.OnlyKeepUsed { [SetupLinkerArgument ("--used-attrs-only", "true")] - [KeptDelegateCacheField ("0")] + [KeptDelegateCacheField ("0", nameof (Tmp_Something))] class UsedAttributeTypeOnEventIsKept { static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Basic/DelegateBeginInvokeEndInvokePair.cs b/test/Mono.Linker.Tests.Cases/Basic/DelegateBeginInvokeEndInvokePair.cs index d39ab89d9..99e8e90ae 100644 --- a/test/Mono.Linker.Tests.Cases/Basic/DelegateBeginInvokeEndInvokePair.cs +++ b/test/Mono.Linker.Tests.Cases/Basic/DelegateBeginInvokeEndInvokePair.cs @@ -5,6 +5,9 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; namespace Mono.Linker.Tests.Cases.Basic { + [KeptDelegateCacheField ("0", nameof (Method))] + [KeptDelegateCacheField ("1", nameof (Method))] + [KeptDelegateCacheField ("2", nameof (Method))] class DelegateBeginInvokeEndInvokePair { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Basic/UsedEventIsKept.cs b/test/Mono.Linker.Tests.Cases/Basic/UsedEventIsKept.cs index 8453f1624..490ffa348 100644 --- a/test/Mono.Linker.Tests.Cases/Basic/UsedEventIsKept.cs +++ b/test/Mono.Linker.Tests.Cases/Basic/UsedEventIsKept.cs @@ -3,8 +3,7 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; namespace Mono.Linker.Tests.Cases.Basic { - [KeptDelegateCacheField ("0")] - [KeptDelegateCacheField ("1")] + [KeptDelegateCacheField ("0", nameof (Tmp_Bar))] class UsedEventIsKept { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Basic/UsedEventOnInterfaceIsKept.cs b/test/Mono.Linker.Tests.Cases/Basic/UsedEventOnInterfaceIsKept.cs index 099b92ab2..47e45e75e 100644 --- a/test/Mono.Linker.Tests.Cases/Basic/UsedEventOnInterfaceIsKept.cs +++ b/test/Mono.Linker.Tests.Cases/Basic/UsedEventOnInterfaceIsKept.cs @@ -3,7 +3,7 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; namespace Mono.Linker.Tests.Cases.Basic { - [KeptDelegateCacheField ("0")] + [KeptDelegateCacheField ("0", nameof (Bar_Ping))] class UsedEventOnInterfaceIsKept { static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Basic/UsedEventOnInterfaceIsRemovedWhenUsedFromClass.cs b/test/Mono.Linker.Tests.Cases/Basic/UsedEventOnInterfaceIsRemovedWhenUsedFromClass.cs index 464db7008..10555f041 100644 --- a/test/Mono.Linker.Tests.Cases/Basic/UsedEventOnInterfaceIsRemovedWhenUsedFromClass.cs +++ b/test/Mono.Linker.Tests.Cases/Basic/UsedEventOnInterfaceIsRemovedWhenUsedFromClass.cs @@ -3,7 +3,7 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; namespace Mono.Linker.Tests.Cases.Basic { - [KeptDelegateCacheField ("0")] + [KeptDelegateCacheField ("0", nameof (Bar_Ping))] class UsedEventOnInterfaceIsRemovedWhenUsedFromClass { static void Main () diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/NullableAnnotations.cs b/test/Mono.Linker.Tests.Cases/DataFlow/NullableAnnotations.cs index d19a1fb82..90c24fd9f 100644 --- a/test/Mono.Linker.Tests.Cases/DataFlow/NullableAnnotations.cs +++ b/test/Mono.Linker.Tests.Cases/DataFlow/NullableAnnotations.cs @@ -11,6 +11,7 @@ using DAMT = System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes; namespace Mono.Linker.Tests.Cases.DataFlow { [ExpectedNoWarnings] + [KeptPrivateImplementationDetails ("ThrowSwitchExpressionException")] class NullableAnnotations { [Kept] diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs b/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs index 2128b8e41..15508baca 100644 --- a/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs +++ b/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs @@ -45,6 +45,9 @@ namespace Mono.Linker.Tests.Cases.DataFlow BasePropertyAccess.Test (); AccessReturnedInstanceProperty.Test (); + + ExplicitIndexerAccess.Test (); + ImplicitIndexerAccess.Test (); } [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors)] @@ -653,6 +656,85 @@ namespace Mono.Linker.Tests.Cases.DataFlow } } + class ExplicitIndexerAccess + { + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] + Type this[Index idx] { + get => throw new NotImplementedException (); + set => throw new NotImplementedException (); + } + + [ExpectedWarning ("IL2072", "this[Index].get", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL2072", "Item.get", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Trimmer)] + static void TestRead (ExplicitIndexerAccess instance = null) + { + instance[new Index (1)].RequiresAll (); + } + + [ExpectedWarning ("IL2072", nameof (GetTypeWithPublicConstructors), "this[Index].set", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL2072", nameof (GetTypeWithPublicConstructors), "Item.set", ProducedBy = ProducedBy.Trimmer)] + static void TestWrite (ExplicitIndexerAccess instance = null) + { + instance[^1] = GetTypeWithPublicConstructors (); + } + + public static void Test () + { + TestRead (); + TestWrite (); + } + } + + class ImplicitIndexerAccess + { + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] + Type this[int idx] { + get => throw new NotImplementedException (); + set => throw new NotImplementedException (); + } + + int Length => throw new NotImplementedException (); + + [ExpectedWarning ("IL2072", "this[Int32].get", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL2072", "Item.get", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Trimmer)] + static void TestRead (ImplicitIndexerAccess instance = null) + { + instance[new Index (1)].RequiresAll (); + } + + [ExpectedWarning ("IL2072", nameof (GetTypeWithPublicConstructors), "this[Int32].set", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL2072", nameof (GetTypeWithPublicConstructors), "Item.set", ProducedBy = ProducedBy.Trimmer)] + static void TestWrite (ImplicitIndexerAccess instance = null) + { + instance[^1] = GetTypeWithPublicConstructors (); + } + + [ExpectedWarning ("IL2072", nameof (GetUnknownType), "this[Int32].set", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL2072", nameof (GetUnknownType), "Item.set", ProducedBy = ProducedBy.Trimmer)] + static void TestNullCoalescingAssignment (ImplicitIndexerAccess instance = null) + { + instance[new Index (1)] ??= GetUnknownType (); + } + + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll))] + static void TestSpanIndexerAccess (int start = 0, int end = 3) + { + Span<byte> bytes = stackalloc byte[4] { 1, 2, 3, 4 }; + bytes[^4] = 0; // This calls the get indexer which has a ref return. + int index = bytes[0]; + Type[] types = new Type[] { GetUnknownType () }; + types[index].RequiresAll (); + } + + public static void Test () + { + TestRead (); + TestWrite (); + TestNullCoalescingAssignment (); + TestSpanIndexerAccess (); + } + } + [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] private static Type GetTypeWithPublicParameterlessConstructor () { diff --git a/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/GenericInterfaceWithEvent.cs b/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/GenericInterfaceWithEvent.cs index e8856be3b..ec4a75670 100644 --- a/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/GenericInterfaceWithEvent.cs +++ b/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/GenericInterfaceWithEvent.cs @@ -2,7 +2,7 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.OnReferenceType.BaseProvidesInterfaceMember { - [KeptDelegateCacheField ("0")] + [KeptDelegateCacheField ("0", nameof (EventMethod))] public class GenericInterfaceWithEvent { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/SimpleEvent.cs b/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/SimpleEvent.cs index 11e5860a6..98597a4b2 100644 --- a/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/SimpleEvent.cs +++ b/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/SimpleEvent.cs @@ -2,7 +2,7 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.OnReferenceType.BaseProvidesInterfaceMember { - [KeptDelegateCacheField ("0")] + [KeptDelegateCacheField ("0", nameof (EventMethod))] public class SimpleEvent { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Reflection/ExpressionCallString.cs b/test/Mono.Linker.Tests.Cases/Reflection/ExpressionCallString.cs index 10ac90435..5489497a8 100644 --- a/test/Mono.Linker.Tests.Cases/Reflection/ExpressionCallString.cs +++ b/test/Mono.Linker.Tests.Cases/Reflection/ExpressionCallString.cs @@ -10,6 +10,7 @@ namespace Mono.Linker.Tests.Cases.Reflection [SetupCSharpCompilerToUse ("csc")] [Reference ("System.Core.dll")] [ExpectedNoWarnings] + [KeptPrivateImplementationDetails ("ThrowSwitchExpressionException")] public class ExpressionCallString { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs b/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs index 906f56ab4..5bec60d50 100644 --- a/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs +++ b/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs @@ -9,6 +9,8 @@ namespace Mono.Linker.Tests.Cases.Reflection { [KeptMember (".cctor()")] [ExpectedNoWarnings ()] + [KeptDelegateCacheField ("0", nameof (AssemblyResolver))] + [KeptDelegateCacheField ("1", nameof (GetTypeFromAssembly))] public class TypeUsedViaReflection { public static void Main () diff --git a/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs b/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs index f7e6df662..96c037778 100644 --- a/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs +++ b/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs @@ -146,6 +146,10 @@ namespace Mono.Linker.Tests.TestCasesRunner VerifyFixedBufferFields (original, linked); + // Need to check delegate cache fields before the normal field check + VerifyDelegateBackingFields (original, linked); + VerifyPrivateImplementationDetails (original, linked); + foreach (var td in original.NestedTypes) { VerifyTypeDefinition (td, linked?.NestedTypes.FirstOrDefault (l => td.FullName == l.FullName)); linkedMembers.Remove (td.FullName); @@ -162,9 +166,6 @@ namespace Mono.Linker.Tests.TestCasesRunner linkedMembers.Remove (e.FullName); } - // Need to check delegate cache fields before the normal field check - VerifyDelegateBackingFields (original, linked); - foreach (var f in original.Fields) { if (verifiedGeneratedFields.Contains (f.FullName)) continue; @@ -712,6 +713,41 @@ namespace Mono.Linker.Tests.TestCasesRunner Assert.That (linkedAttrs, Is.EquivalentTo (expectedAttrs), $"Security attributes on `{src}' are not matching"); } + void VerifyPrivateImplementationDetails (TypeDefinition original, TypeDefinition linked) + { + var expectedImplementationDetailsMethods = GetCustomAttributeCtorValues<string> (original, nameof (KeptPrivateImplementationDetailsAttribute)) + .Select (attr => attr.ToString ()) + .ToList (); + + if (expectedImplementationDetailsMethods.Count == 0) + return; + + VerifyPrivateImplementationDetailsType (original.Module, linked.Module, out TypeDefinition srcImplementationDetails, out TypeDefinition linkedImplementationDetails); + foreach (var methodName in expectedImplementationDetailsMethods) { + var originalMethod = srcImplementationDetails.Methods.FirstOrDefault (m => m.Name == methodName); + if (originalMethod == null) + Assert.Fail ($"Could not locate original private implementation details method {methodName}"); + + var linkedMethod = linkedImplementationDetails.Methods.FirstOrDefault (m => m.Name == methodName); + VerifyMethodKept (originalMethod, linkedMethod); + linkedMembers.Remove (linkedMethod.FullName); + } + verifiedGeneratedTypes.Add (srcImplementationDetails.FullName); + } + + static void VerifyPrivateImplementationDetailsType (ModuleDefinition src, ModuleDefinition linked, out TypeDefinition srcImplementationDetails, out TypeDefinition linkedImplementationDetails) + { + srcImplementationDetails = src.Types.FirstOrDefault (t => string.IsNullOrEmpty (t.Namespace) && t.Name.StartsWith ("<PrivateImplementationDetails>")); + + if (srcImplementationDetails == null) + Assert.Fail ("Could not locate <PrivateImplementationDetails> in the original assembly. Does your test use initializers?"); + + linkedImplementationDetails = linked.Types.FirstOrDefault (t => string.IsNullOrEmpty (t.Namespace) && t.Name.StartsWith ("<PrivateImplementationDetails>")); + + if (linkedImplementationDetails == null) + Assert.Fail ("Could not locate <PrivateImplementationDetails> in the linked assembly"); + } + protected virtual void VerifyArrayInitializers (MethodDefinition src, MethodDefinition linked) { var expectedIndicies = GetCustomAttributeCtorValues<object> (src, nameof (KeptInitializerData)) @@ -726,15 +762,7 @@ namespace Mono.Linker.Tests.TestCasesRunner if (!src.HasBody) Assert.Fail ($"`{nameof (KeptInitializerData)}` cannot be used on methods that don't have bodies"); - var srcImplementationDetails = src.Module.Types.FirstOrDefault (t => string.IsNullOrEmpty (t.Namespace) && t.Name.StartsWith ("<PrivateImplementationDetails>")); - - if (srcImplementationDetails == null) - Assert.Fail ("Could not locate <PrivateImplementationDetails> in the original assembly. Does your test use initializers?"); - - var linkedImplementationDetails = linked.Module.Types.FirstOrDefault (t => string.IsNullOrEmpty (t.Namespace) && t.Name.StartsWith ("<PrivateImplementationDetails>")); - - if (linkedImplementationDetails == null) - Assert.Fail ("Could not locate <PrivateImplementationDetails> in the linked assembly"); + VerifyPrivateImplementationDetailsType (src.Module, linked.Module, out TypeDefinition srcImplementationDetails, out TypeDefinition linkedImplementationDetails); var possibleInitializerFields = src.Body.Instructions .Where (ins => IsLdtokenOnPrivateImplementationDetails (srcImplementationDetails, ins)) @@ -874,21 +902,33 @@ namespace Mono.Linker.Tests.TestCasesRunner void VerifyDelegateBackingFields (TypeDefinition src, TypeDefinition linked) { - var expectedFieldNames = GetCustomAttributeCtorValues<string> (src, nameof (KeptDelegateCacheFieldAttribute)) - .Select (unique => $"<>f__mg$cache{unique}") + var expectedFieldNames = src.CustomAttributes + .Where (a => a.AttributeType.Name == nameof (KeptDelegateCacheFieldAttribute)) + .Select (a => (a.ConstructorArguments[0].Value as string, a.ConstructorArguments[1].Value as string)) + .Select (indexAndField => $"<{indexAndField.Item1}>__{indexAndField.Item2}") .ToList (); if (expectedFieldNames.Count == 0) return; - foreach (var srcField in src.Fields) { - if (!expectedFieldNames.Contains (srcField.Name)) + foreach (var nestedType in src.NestedTypes) { + if (nestedType.Name != "<>O") continue; - var linkedField = linked?.Fields.FirstOrDefault (l => l.Name == srcField.Name); - VerifyFieldKept (srcField, linkedField); - verifiedGeneratedFields.Add (srcField.FullName); - linkedMembers.Remove (srcField.FullName); + var linkedNestedType = linked.NestedTypes.FirstOrDefault (t => t.Name == nestedType.Name); + foreach (var expectedFieldName in expectedFieldNames) { + var originalField = nestedType.Fields.FirstOrDefault (f => f.Name == expectedFieldName); + if (originalField is null) + Assert.Fail ($"Invalid expected delegate backing field {expectedFieldName} in {src}. This member was not in the unlinked assembly"); + + var linkedField = linkedNestedType?.Fields.FirstOrDefault (f => f.Name == expectedFieldName); + VerifyFieldKept (originalField, linkedField); + verifiedGeneratedFields.Add (linkedField.FullName); + linkedMembers.Remove (linkedField.FullName); + } + + VerifyTypeDefinitionKept (nestedType, linkedNestedType); + verifiedGeneratedTypes.Add (linkedNestedType.FullName); } } |