-
Notifications
You must be signed in to change notification settings - Fork 128
Trim static interfaces #2741
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Trim static interfaces #2741
Changes from 25 commits
0388e48
39ffb9f
e5b3788
61115f6
c4ebd45
d7cc881
3a8b76d
1c04ad2
be37029
7ad5afc
07143dd
a7c7e13
64692a0
4ea2991
578c686
798e87f
3fe5a52
9908282
3b03b77
4ae7888
7df4117
0b51131
e61826c
31a2447
6e1aead
52c4bb4
854d08b
14f71b9
8edb5ac
0c79f06
ca0e253
c32a329
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -573,6 +573,10 @@ void ProcessVirtualMethods () | |
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Does extra handling of marked types that have interfaces when it's necessary to know what types are marked or instantiated. | ||
| /// e.g. Marks the "implements interface" annotations and removes override annotations for static interface methods. | ||
| /// </summary> | ||
| void ProcessMarkedTypesWithInterfaces () | ||
| { | ||
| // We may mark an interface type later on. Which means we need to reprocess any time with one or more interface implementations that have not been marked | ||
|
|
@@ -582,6 +586,9 @@ void ProcessMarkedTypesWithInterfaces () | |
| var typesWithInterfaces = _typesWithInterfaces.ToArray (); | ||
|
|
||
| foreach ((var type, var scope) in typesWithInterfaces) { | ||
| // Regardless of the optimizations or whether or not the type is instantiated, we should | ||
| // remove unneeded override annotations for static interface method implementations | ||
| RemoveStaticInterfaceOverrideAnnotations (type); | ||
| // Exception, types that have not been flagged as instantiated yet. These types may not need their interfaces even if the | ||
| // interface type is marked | ||
| // UnusedInterfaces optimization is turned off mark all interface implementations | ||
|
|
@@ -2265,6 +2272,26 @@ void MarkInterfaceImplementations (TypeDefinition type) | |
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Removes the 'override' annotation for implementations of static interface methods if the interface method is not marked. | ||
| /// </summary> | ||
| void RemoveStaticInterfaceOverrideAnnotations (TypeDefinition type) | ||
| { | ||
| foreach (var method in type.Methods) { | ||
| if (!Annotations.IsMarked (method)) | ||
| continue; | ||
| // Modify overrides in place | ||
| for (int i = 0; i < method.Overrides.Count;) { | ||
| if (Context.Resolve (method.Overrides[i].DeclaringType)?.IsInterface == true | ||
| && Context.Resolve (method.Overrides[i])?.IsStatic == true | ||
| && !Annotations.IsMarked (method.Overrides[i])) | ||
| method.Overrides.RemoveAt (i); | ||
| else | ||
| i++; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void MarkGenericParameterProvider (IGenericParameterProvider provider) | ||
| { | ||
| if (!provider.HasGenericParameters) | ||
|
|
@@ -3049,8 +3076,15 @@ protected virtual void ProcessMethod (MethodDefinition method, in DependencyInfo | |
| } | ||
| } | ||
|
|
||
| // Mark overrides except for static interface methods | ||
|
||
| if (method.HasOverrides) { | ||
| foreach (MethodReference ov in method.Overrides) { | ||
| // Don't mark overrides for static interface methods. | ||
| // Since they can only be called on a concrete type and not the interface, these methods can safely be removed in some cases | ||
|
||
| if (Context.Resolve (ov)?.IsStatic == true | ||
| && Context.Resolve (ov.DeclaringType)?.IsInterface == true) { | ||
| continue; | ||
| } | ||
| MarkMethod (ov, new DependencyInfo (DependencyKind.MethodImplOverride, method)); | ||
| MarkExplicitInterfaceImplementation (method, ov); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| // 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.Method, AllowMultiple = true, Inherited = false)] | ||
| /// <Summary> | ||
| /// Used to ensure that a method should keep an 'override' annotation for a method in the supplied base type | ||
| /// Fails in tests if the method doesn't have the override method in the original or linked assembly | ||
|
||
| /// </Summary> | ||
| public class KeptOverrideAttribute : KeptAttribute | ||
| { | ||
| public Type TypeWithOverriddenMethodDeclaration; | ||
|
|
||
| public KeptOverrideAttribute (Type typeWithOverriddenMethod) | ||
| { | ||
| if (typeWithOverriddenMethod == null) | ||
| throw new ArgumentNullException (nameof (typeWithOverriddenMethod)); | ||
| TypeWithOverriddenMethodDeclaration = typeWithOverriddenMethod; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| // 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 | ||
| { | ||
| /// <Summary> | ||
| /// Used to ensure that a method should remove an 'override' annotation for a method in the supplied base type. | ||
| /// Fails in tests if the method has the override method in the linked assembly, | ||
| /// or if the override is not found in the original assembly | ||
| /// </Summary> | ||
| [AttributeUsage (AttributeTargets.Method, AllowMultiple = true, Inherited = false)] | ||
| public class RemovedOverrideAttribute : BaseInAssemblyAttribute | ||
| { | ||
| public Type TypeWithOverriddenMethodDeclaration; | ||
| public RemovedOverrideAttribute (Type typeWithOverriddenMethod) | ||
| { | ||
| if (typeWithOverriddenMethod == null) | ||
| throw new ArgumentException ("Value cannot be null or empty.", nameof (typeWithOverriddenMethod)); | ||
| TypeWithOverriddenMethodDeclaration = typeWithOverriddenMethod; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,29 +20,63 @@ public static void Main () | |
| t = typeof (UninstantiatedPublicClassWithImplicitlyImplementedInterface); | ||
| t = typeof (UninstantiatedPublicClassWithPrivateInterface); | ||
|
|
||
| UninstantiatedPublicClassWithInterface.InternalStaticInterfaceMethodUsed (); | ||
| InstantiatedClassWithInterfaces.InternalStaticInterfaceMethodUsed (); | ||
|
|
||
| UninstantiatedPublicClassWithInterface.InternalStaticInterfaceMethodUsedThroughImplementation (); | ||
|
||
| InstantiatedClassWithInterfaces.InternalStaticInterfaceMethodUsedThroughImplementation (); | ||
| GenericMethodThatCallsInternalStaticInterfaceMethod | ||
| <ImplementsInternalStaticInterfaceWithInterfaceDefinitionUsedThroughGeneric> (); | ||
| typeof (InterfaceVariants).GetMethod ("GenericMethodThatCallsInternalStaticInterfaceMethod") | ||
| .MakeGenericMethod (typeof (ImplementsInternalStaticInterfaceWithInterfaceDefinitionNotUsedThroughGeneric)) | ||
| .Invoke (null, null); | ||
| // Use all public interfaces - they're marked as public only to denote them as "used" | ||
| typeof (IPublicInterface).RequiresPublicMethods (); | ||
| typeof (IPublicStaticInterface).RequiresPublicMethods (); | ||
|
|
||
| var a = new InstantiatedClassWithInterfaces (); | ||
| } | ||
|
|
||
| [Kept] | ||
| internal static void GenericMethodThatCallsInternalStaticInterfaceMethod<T> () where T : IInternalStaticInterfaceWithInterfaceDefinitionUsed | ||
|
||
| { | ||
| T.InternalStaticInterfaceUsedThroughInterface (); | ||
| } | ||
|
|
||
| [Kept] | ||
| [KeptInterface (typeof (IInternalStaticInterfaceWithInterfaceDefinitionUsed))] | ||
| internal class ImplementsInternalStaticInterfaceWithInterfaceDefinitionUsedThroughGeneric : IInternalStaticInterfaceWithInterfaceDefinitionUsed | ||
| { | ||
| [Kept] | ||
| [KeptOverride (typeof (IInternalStaticInterfaceWithInterfaceDefinitionUsed))] | ||
| public static void InternalStaticInterfaceUsedThroughInterface () | ||
| { | ||
| } | ||
| public static void UnusedMethod () { } | ||
| } | ||
|
|
||
| [Kept] | ||
| [KeptInterface (typeof (IInternalStaticInterfaceWithInterfaceDefinitionUsed))] | ||
| internal class ImplementsInternalStaticInterfaceWithInterfaceDefinitionNotUsedThroughGeneric : IInternalStaticInterfaceWithInterfaceDefinitionUsed | ||
|
||
| { | ||
| [Kept] | ||
| [KeptOverride (typeof (IInternalStaticInterfaceWithInterfaceDefinitionUsed))] | ||
| public static void InternalStaticInterfaceUsedThroughInterface () | ||
| { | ||
| } | ||
|
|
||
| public static void UnusedMethod () { } | ||
| } | ||
|
|
||
| [Kept] | ||
| [KeptInterface (typeof (IEnumerator))] | ||
| [KeptInterface (typeof (IPublicInterface))] | ||
| [KeptInterface (typeof (IPublicStaticInterface))] | ||
| [KeptInterface (typeof (IInternalStaticInterfaceWithUsedMethod))] // https://github.com/dotnet/linker/issues/2733 | ||
| [KeptInterface (typeof (ICopyLibraryInterface))] | ||
| [KeptInterface (typeof (ICopyLibraryStaticInterface))] | ||
| public class UninstantiatedPublicClassWithInterface : | ||
| IPublicInterface, | ||
| IPublicStaticInterface, | ||
| IInternalInterface, | ||
| IInternalStaticInterface, | ||
| IInternalStaticInterfaceWithUsedMethod, | ||
| IInternalStaticInterfaceWithUsedMethodImplementation, | ||
| IEnumerator, | ||
| ICopyLibraryInterface, | ||
| ICopyLibraryStaticInterface | ||
|
|
@@ -70,7 +104,8 @@ public static void InternalStaticInterfaceMethod () { } | |
| static void IInternalStaticInterface.ExplicitImplementationInternalStaticInterfaceMethod () { } | ||
|
|
||
| [Kept] | ||
| public static void InternalStaticInterfaceMethodUsed () { } | ||
| [RemovedOverride (typeof (IInternalStaticInterfaceWithUsedMethodImplementation))] | ||
| public static void InternalStaticInterfaceMethodUsedThroughImplementation () { } | ||
|
|
||
| [Kept] | ||
| [ExpectBodyModified] | ||
|
|
@@ -122,15 +157,14 @@ public string ToString (string format, IFormatProvider formatProvider) | |
| [KeptInterface (typeof (IEnumerator))] | ||
| [KeptInterface (typeof (IPublicInterface))] | ||
| [KeptInterface (typeof (IPublicStaticInterface))] | ||
| [KeptInterface (typeof (IInternalStaticInterfaceWithUsedMethod))] // https://github.com/dotnet/linker/issues/2733 | ||
| [KeptInterface (typeof (ICopyLibraryInterface))] | ||
| [KeptInterface (typeof (ICopyLibraryStaticInterface))] | ||
| public class InstantiatedClassWithInterfaces : | ||
| IPublicInterface, | ||
| IPublicStaticInterface, | ||
| IInternalInterface, | ||
| IInternalStaticInterface, | ||
| IInternalStaticInterfaceWithUsedMethod, | ||
| IInternalStaticInterfaceWithUsedMethodImplementation, | ||
| IEnumerator, | ||
| ICopyLibraryInterface, | ||
| ICopyLibraryStaticInterface | ||
|
|
@@ -159,7 +193,8 @@ public static void InternalStaticInterfaceMethod () { } | |
| static void IInternalStaticInterface.ExplicitImplementationInternalStaticInterfaceMethod () { } | ||
|
|
||
| [Kept] | ||
| public static void InternalStaticInterfaceMethodUsed () { } | ||
| [RemovedOverride (typeof (IInternalStaticInterfaceWithUsedMethodImplementation))] | ||
| public static void InternalStaticInterfaceMethodUsedThroughImplementation () { } | ||
|
|
||
| [Kept] | ||
| bool IEnumerator.MoveNext () { throw new PlatformNotSupportedException (); } | ||
|
|
@@ -225,13 +260,20 @@ internal interface IInternalStaticInterface | |
| static abstract void ExplicitImplementationInternalStaticInterfaceMethod (); | ||
| } | ||
|
|
||
| // The interface methods themselves are not used, but the implentation of these methods is | ||
| // https://github.com/dotnet/linker/issues/2733 | ||
| // The interface methods themselves are not used, but the implementation of these methods is | ||
| internal interface IInternalStaticInterfaceWithUsedMethodImplementation | ||
| { | ||
| static abstract void InternalStaticInterfaceMethodUsedThroughImplementation (); | ||
| } | ||
|
|
||
| // The interface methods themselves are used through the interface | ||
| [Kept] | ||
| internal interface IInternalStaticInterfaceWithUsedMethod | ||
| internal interface IInternalStaticInterfaceWithInterfaceDefinitionUsed | ||
| { | ||
| [Kept] // https://github.com/dotnet/linker/issues/2733 | ||
| static abstract void InternalStaticInterfaceMethodUsed (); | ||
| [Kept] | ||
| static abstract void InternalStaticInterfaceUsedThroughInterface (); | ||
|
|
||
| static abstract void UnusedMethod (); | ||
| } | ||
|
|
||
| private interface IPrivateInterface | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to write a test for this?
I don't know if the test infra actually verifies the "overrides" (maybe it should at least as an opt-in)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't look like there's anything in the test infra yet, but I'll look into it more and add a
KeptOverride/NoOverrideattribute to check for thatThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a couple attribute to check for the presence/removal of the override annotations. They only perform a check if there is a
KeptOverrideorRemovedOverride, so it doesn't affect any other tests.