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

github.com/mono/linker.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSven Boemer <sbomer@gmail.com>2022-06-15 19:28:50 +0300
committerGitHub <noreply@github.com>2022-06-15 19:28:50 +0300
commit226107b9117b9d166e38dd00e30f9ab54399e658 (patch)
tree410de8da2b0f1555d977b78938f7ced082a32c72
parent2abcee1070e38d2994a25777023dfa61615ea498 (diff)
Analyze implicit indexer reference operations (#2839)
Adds support for `IImplicitIndexerReferenceOperation`, which represents an implicit access to an indexer that uses `System.Index`. Implicit means that there is no `System.Index` accessor in IL, but the compiler supports `System.Index` access via an existing indexer (for example one that takes int) for types that satisfy certain criteria. See https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/ranges#implicit-index-support for details. This operation only showed up in the CFG with a more recent version of the Roslyn APIs, so this includes an update to match the one used in dotnet/runtime. This resulted in a few changes to the generated code that required tweaking some of the test validation: - Delegate cache fields are emitted. This reuses an existing attribute originally designed for mcs, but it has been updated to check for these compiler-generated fields. - `<PrivateImplementationDetails>.ThrowSwitchExpressionException` is emitted for an implicit unhandled case in switch expressions. This change includes a new attribute to check that this is kept in a few of the tests.
-rw-r--r--eng/Versions.props2
-rw-r--r--src/ILLink.RoslynAnalyzer/DataFlow/CapturedReferenceValue.cs1
-rw-r--r--src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs71
-rw-r--r--src/ILLink.RoslynAnalyzer/DataFlow/OperationWalker.cs3
-rw-r--r--src/ILLink.RoslynAnalyzer/IPropertySymbolExtensions.cs32
-rw-r--r--test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptDelegateCacheFieldAttribute.cs8
-rw-r--r--test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptPrivateImplementationDetails.cs17
-rw-r--r--test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributeCtorOnEvent.cs2
-rw-r--r--test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributeFieldOnEvent.cs2
-rw-r--r--test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributePropertyOnEvent.cs2
-rw-r--r--test/Mono.Linker.Tests.Cases/Attributes/OnlyKeepUsed/UnusedAttributeTypeOnEventIsRemoved.cs2
-rw-r--r--test/Mono.Linker.Tests.Cases/Attributes/OnlyKeepUsed/UsedAttributeTypeOnEventIsKept.cs2
-rw-r--r--test/Mono.Linker.Tests.Cases/Basic/DelegateBeginInvokeEndInvokePair.cs3
-rw-r--r--test/Mono.Linker.Tests.Cases/Basic/UsedEventIsKept.cs3
-rw-r--r--test/Mono.Linker.Tests.Cases/Basic/UsedEventOnInterfaceIsKept.cs2
-rw-r--r--test/Mono.Linker.Tests.Cases/Basic/UsedEventOnInterfaceIsRemovedWhenUsedFromClass.cs2
-rw-r--r--test/Mono.Linker.Tests.Cases/DataFlow/NullableAnnotations.cs1
-rw-r--r--test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs82
-rw-r--r--test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/GenericInterfaceWithEvent.cs2
-rw-r--r--test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/SimpleEvent.cs2
-rw-r--r--test/Mono.Linker.Tests.Cases/Reflection/ExpressionCallString.cs1
-rw-r--r--test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs2
-rw-r--r--test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs80
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);
}
}