diff --git a/src/linker/Linker.Dataflow/DynamicallyAccessedMembersTypeHierarchy.cs b/src/linker/Linker.Dataflow/DynamicallyAccessedMembersTypeHierarchy.cs new file mode 100644 index 000000000000..0d0edb024ae6 --- /dev/null +++ b/src/linker/Linker.Dataflow/DynamicallyAccessedMembersTypeHierarchy.cs @@ -0,0 +1,197 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Mono.Cecil; +using Mono.Linker.Steps; + +namespace Mono.Linker.Dataflow +{ + internal class DynamicallyAccessedMembersTypeHierarchy + { + readonly LinkContext _context; + readonly MarkStep _markStep; + + // Cache of DynamicallyAccessedMembers annotations applied to types and their hierarchies + // Values + // annotation - the aggregated annotation value from the entire base and interface hierarchy of the given type + // If the type has a base class with annotation a1 and an interface with annotation a2, the stored + // annotation is a1 | a2. + // applied - set to true once the annotation was applied to the type + // This only happens once the right reflection pattern is found. + // If a new type is being marked and one of its base types/interface has the applied set to true + // the new type will apply its annotation and will also set its applied to true. + // Non-interface types + // - Only marked types with non-empty annotation are put into the cache + // - Non-marked types are not stored in the cache + // - Marked types which are not in the cache don't have any annotation + // Interface types + // - All interface types accessible from marked types are stored in the cache + // - If the interface type doesn't have annotation the value None is stored here + // + // It's not possible to use the marking as a filter for interfaces in the cache + // because interfaces are marked late and in effectively random order. + // For this cache to be effective we need to be able to fill it for all base types and interfaces + // of a type which is currently being marked - at which point the interfaces are not yet marked. + readonly Dictionary _typesInDynamicallyAccessedMembersHierarchy; + + public DynamicallyAccessedMembersTypeHierarchy (LinkContext context, MarkStep markStep) + { + _context = context; + _markStep = markStep; + _typesInDynamicallyAccessedMembersHierarchy = new Dictionary (); + } + + public (DynamicallyAccessedMemberTypes annotation, bool applied) ProcessMarkedTypeForDynamicallyAccessedMembersHierarchy (TypeDefinition type) + { + // Non-interfaces must be marked already + Debug.Assert (type.IsInterface || _context.Annotations.IsMarked (type)); + + DynamicallyAccessedMemberTypes annotation = _context.Annotations.FlowAnnotations.GetTypeAnnotation (type); + bool apply = false; + + // We'll use the cache also as a way to detect and avoid recursion + // There's no possiblity to have recursion among base types, so only do this for interfaces + if (type.IsInterface) { + if (_typesInDynamicallyAccessedMembersHierarchy.TryGetValue (type, out var existingValue)) + return existingValue; + + _typesInDynamicallyAccessedMembersHierarchy.Add (type, (annotation, false)); + } + + // Base should already be marked (since we're marking its derived type now) + // so we should already have its cached values filled. + TypeDefinition baseType = type.BaseType?.Resolve (); + Debug.Assert (baseType == null || _context.Annotations.IsMarked (baseType)); + if (baseType != null && _typesInDynamicallyAccessedMembersHierarchy.TryGetValue (baseType, out var baseValue)) { + annotation |= baseValue.annotation; + apply |= baseValue.applied; + } + + // For the purposes of the DynamicallyAccessedMembers type hierarchies + // we consider interfaces of marked types to be also "marked" in that + // their annotations will be applied to the type regardless if later on + // we decide to remove the interface. This is to keep the complexity of the implementation + // relatively low. In the future it could be possibly optimized. + if (type.HasInterfaces) { + foreach (InterfaceImplementation iface in type.Interfaces) { + var interfaceType = iface.InterfaceType.Resolve (); + if (interfaceType != null) { + var interfaceValue = ProcessMarkedTypeForDynamicallyAccessedMembersHierarchy (interfaceType); + annotation |= interfaceValue.annotation; + apply |= interfaceValue.applied; + } + } + } + + Debug.Assert (!apply || annotation != DynamicallyAccessedMemberTypes.None); + + if (apply) { + // One of the base/interface types is already marked as having the annotation applied + // so we need to apply the annotation to this type as well + var reflectionMethodBodyScanner = new ReflectionMethodBodyScanner (_context, _markStep); + var reflectionPatternContext = new ReflectionPatternContext (_context, true, type, type); + reflectionMethodBodyScanner.ApplyDynamicallyAccessedMembersToType (ref reflectionPatternContext, type, annotation); + } + + // Store the results in the cache + // Don't store empty annotations for non-interface types - we can use the presence of the row + // in the cache as indication of it instead. + // This doesn't work for interfaces, since we can't rely on them being marked (and thus have the cache + // already filled), so we need to always store the row (even if empty) for interfaces. + if (annotation != DynamicallyAccessedMemberTypes.None || type.IsInterface) { + _typesInDynamicallyAccessedMembersHierarchy[type] = (annotation, apply); + } + + return (annotation, apply); + } + + public DynamicallyAccessedMemberTypes ApplyDynamicallyAccessedMembersToTypeHierarchy ( + ReflectionMethodBodyScanner reflectionMethodBodyScanner, + ref ReflectionPatternContext reflectionPatternContext, + TypeDefinition type) + { + Debug.Assert (_context.Annotations.IsMarked (type)); + + // The type should be in our cache already + (var annotation, var applied) = GetCachedInfoForTypeInHierarchy (type); + + // If the annotation was already applied to this type, there's no reason to repeat the operation, the result will + // be no change. + if (applied || annotation == DynamicallyAccessedMemberTypes.None) + return annotation; + + // Apply the effective annotation for the type + reflectionMethodBodyScanner.ApplyDynamicallyAccessedMembersToType (ref reflectionPatternContext, type, annotation); + + // Mark it as applied in the cache + _typesInDynamicallyAccessedMembersHierarchy[type] = (annotation, true); + + // Propagate the newly applied annotation to all derived/implementation types + // Since we don't have a data structure which would allow us to enumerate all derived/implementation types + // walk all of the types in the cache. These are good candidates as types not in the cache don't apply. + foreach (var candidate in _typesInDynamicallyAccessedMembersHierarchy) { + if (candidate.Value.annotation == DynamicallyAccessedMemberTypes.None) + continue; + + ApplyDynamicallyAccessedMembersToTypeHierarchyInner (reflectionMethodBodyScanner, ref reflectionPatternContext, candidate.Key); + } + + return annotation; + } + + bool ApplyDynamicallyAccessedMembersToTypeHierarchyInner ( + ReflectionMethodBodyScanner reflectionMethodBodyScanner, + ref ReflectionPatternContext reflectionPatternContext, + TypeDefinition type) + { + (var annotation, var applied) = GetCachedInfoForTypeInHierarchy (type); + + if (annotation == DynamicallyAccessedMemberTypes.None) + return false; + + if (applied) + return true; + + TypeDefinition baseType = type.BaseType?.Resolve (); + if (baseType != null) + applied = ApplyDynamicallyAccessedMembersToTypeHierarchyInner (reflectionMethodBodyScanner, ref reflectionPatternContext, baseType); + + if (!applied && type.HasInterfaces) { + foreach (InterfaceImplementation iface in type.Interfaces) { + var interfaceType = iface.InterfaceType.Resolve (); + if (interfaceType != null) { + if (ApplyDynamicallyAccessedMembersToTypeHierarchyInner (reflectionMethodBodyScanner, ref reflectionPatternContext, interfaceType)) { + applied = true; + break; + } + } + } + } + + if (applied) { + reflectionMethodBodyScanner.ApplyDynamicallyAccessedMembersToType (ref reflectionPatternContext, type, annotation); + _typesInDynamicallyAccessedMembersHierarchy[type] = (annotation, true); + } + + return applied; + } + + (DynamicallyAccessedMemberTypes annotation, bool applied) GetCachedInfoForTypeInHierarchy (TypeDefinition type) + { + Debug.Assert (type.IsInterface || _context.Annotations.IsMarked (type)); + + // The type should be in our cache already + if (!_typesInDynamicallyAccessedMembersHierarchy.TryGetValue (type, out var existingValue)) { + // If it's not in the cache it should be a non-interface type in which case it means there were no annotations + Debug.Assert (!type.IsInterface); + return (DynamicallyAccessedMemberTypes.None, false); + } + + return existingValue; + } + } +} diff --git a/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs b/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs index 7d52fcbb5e53..a86469f48ab0 100644 --- a/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs +++ b/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs @@ -107,6 +107,14 @@ public void ProcessAttributeDataflow (IMemberDefinition source, FieldDefinition RequireDynamicallyAccessedMembers (ref reflectionContext, annotation, valueNode, field); } + public void ApplyDynamicallyAccessedMembersToType (ref ReflectionPatternContext reflectionPatternContext, TypeDefinition type, DynamicallyAccessedMemberTypes annotation) + { + Debug.Assert (annotation != DynamicallyAccessedMemberTypes.None); + + reflectionPatternContext.AnalyzingPattern (); + MarkTypeForDynamicallyAccessedMembers (ref reflectionPatternContext, type, annotation); + } + static ValueNode GetValueNodeForCustomAttributeArgument (CustomAttributeArgument argument) { ValueNode valueNode; @@ -929,11 +937,16 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c // - On the other hand this sort of "violates" the annotation on the type methodReturnValue = MergePointValue.MergeValues (methodReturnValue, new SystemTypeValue (staticType)); } else { - DynamicallyAccessedMemberTypes annotation = GetMemberTypesForTypeHierarchy (staticType); + reflectionContext.AnalyzingPattern (); - if (annotation != DynamicallyAccessedMemberTypes.None) { - // TODO: Apply the annotation to staticType and all its derived types - } + // 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 + MarkType (ref reflectionContext, staticType); + + var annotation = _markStep.DynamicallyAccessedMembersTypeHierarchy + .ApplyDynamicallyAccessedMembersToTypeHierarchy (this, ref reflectionContext, staticType); + + reflectionContext.RecordHandledPattern (); // 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 @@ -1755,34 +1768,6 @@ void ProcessGetMethodByName ( methodReturnValue = MergePointValue.MergeValues (methodReturnValue, NullValue.Instance); } - DynamicallyAccessedMemberTypes GetMemberTypesForTypeHierarchy (TypeDefinition targetType) - { - HashSet visitedTypes = new HashSet (); - return GetMemberTypesForTypeHierarchyInternal (targetType, visitedTypes); - } - - DynamicallyAccessedMemberTypes GetMemberTypesForTypeHierarchyInternal (TypeDefinition targetType, HashSet visitedTypes) - { - DynamicallyAccessedMemberTypes memberTypes = _context.Annotations.FlowAnnotations.GetTypeAnnotation (targetType); - - if (!visitedTypes.Add (targetType)) - return memberTypes; - - TypeDefinition baseType = targetType.BaseType?.Resolve (); - if (baseType != null) - memberTypes |= GetMemberTypesForTypeHierarchyInternal (baseType, visitedTypes); - - if (targetType.HasInterfaces) { - foreach (InterfaceImplementation iface in targetType.Interfaces) { - var interfaceType = iface.InterfaceType.Resolve (); - if (interfaceType != null) - memberTypes |= GetMemberTypesForTypeHierarchyInternal (interfaceType, visitedTypes); - } - } - - return memberTypes; - } - void RequireDynamicallyAccessedMembers (ref ReflectionPatternContext reflectionContext, DynamicallyAccessedMemberTypes requiredMemberTypes, ValueNode value, IMetadataTokenProvider targetContext) { foreach (var uniqueValue in value.UniqueValues ()) { diff --git a/src/linker/Linker.Steps/MarkStep.cs b/src/linker/Linker.Steps/MarkStep.cs index c4e1e6fa89dc..a25d7d2f62c3 100644 --- a/src/linker/Linker.Steps/MarkStep.cs +++ b/src/linker/Linker.Steps/MarkStep.cs @@ -59,6 +59,11 @@ public partial class MarkStep : IStep UnreachableBlocksOptimizer _unreachableBlocksOptimizer; MarkStepContext _markContext; readonly HashSet _entireTypesMarked; + DynamicallyAccessedMembersTypeHierarchy _dynamicallyAccessedMembersTypeHierarchy; + + internal DynamicallyAccessedMembersTypeHierarchy DynamicallyAccessedMembersTypeHierarchy { + get => _dynamicallyAccessedMembersTypeHierarchy; + } #if DEBUG static readonly DependencyKind[] _entireTypeReasons = new DependencyKind[] { @@ -185,7 +190,7 @@ public MarkStep () _dynamicInterfaceCastableImplementationTypes = new List (); _unreachableBodies = new List (); _pending_isinst_instr = new List<(TypeDefinition, MethodBody, Instruction)> (); - _entireTypesMarked = new HashSet (); ; + _entireTypesMarked = new HashSet (); } public AnnotationStore Annotations => _context.Annotations; @@ -196,6 +201,7 @@ public virtual void Process (LinkContext context) _context = context; _unreachableBlocksOptimizer = new UnreachableBlocksOptimizer (_context); _markContext = new MarkStepContext (); + _dynamicallyAccessedMembersTypeHierarchy = new DynamicallyAccessedMembersTypeHierarchy (_context, this); Initialize (); Process (); @@ -1653,6 +1659,12 @@ protected internal virtual TypeDefinition MarkType (TypeReference reference, Dep handleMarkType (type); MarkType (type.BaseType, new DependencyInfo (DependencyKind.BaseType, type), type); + + // The DynamicallyAccessedMembers hiearchy processing must be done after the base type was marked + // (to avoid inconsistencies in the cache), but before anything else as work done below + // might need the results of the processing here. + _dynamicallyAccessedMembersTypeHierarchy.ProcessMarkedTypeForDynamicallyAccessedMembersHierarchy (type); + if (type.DeclaringType != null) MarkType (type.DeclaringType, new DependencyInfo (DependencyKind.DeclaringType, type), type); MarkCustomAttributes (type, new DependencyInfo (DependencyKind.CustomAttribute, type), type); diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/TypeDataflow.cs b/test/Mono.Linker.Tests.Cases/DataFlow/TypeDataflow.cs index 752ec8894dbe..47a608f6a53f 100644 --- a/test/Mono.Linker.Tests.Cases/DataFlow/TypeDataflow.cs +++ b/test/Mono.Linker.Tests.Cases/DataFlow/TypeDataflow.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Helpers; using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.DataFlow @@ -41,6 +42,10 @@ public static void CallUnannotatedTypeInstance (UnannotatedTypeInstance instance [RecognizedReflectionAccessPattern] public static void CallAnnotatedTypeInstance (AnnotatedViaXmlTypeInstance instance) { + // Just add a direct dependency on the derived type, so that it's kept + // its methods should be kept as a result of the derived type marking + typeof (DerivedFromAnnotated).RequiresNone (); + instance.GetType ().GetMethod ("Foo"); } @@ -84,12 +89,24 @@ public void Foo () { } [Kept] [KeptMember (".ctor()")] + // [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] public class AnnotatedViaXmlTypeInstance { + [Kept] public void Foo () { } + protected int field; } + [Kept] + [KeptBaseType (typeof (AnnotatedViaXmlTypeInstance))] + public class DerivedFromAnnotated : AnnotatedViaXmlTypeInstance + { + [Kept] + public void DerivedPublicMethod () { } + } + + // [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] public interface InterfaceWithAnnotation { public void Foo () { } @@ -97,8 +114,11 @@ public void Foo () { } [Kept] [KeptMember (".ctor()")] + // [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] public class ImplementsInterfaceWithAnnotationAndHasDifferentAnnotation : InterfaceWithAnnotation { + // In this case the interfac will be removed, but its annotation still applies + [Kept] public void Foo () { } }