Skip to content
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

Implement marking of type hierarchies #4

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 197 additions & 0 deletions src/linker/Linker.Dataflow/DynamicallyAccessedMembersTypeHierarchy.cs
Original file line number Diff line number Diff line change
@@ -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<TypeDefinition, (DynamicallyAccessedMemberTypes annotation, bool applied)> _typesInDynamicallyAccessedMembersHierarchy;

public DynamicallyAccessedMembersTypeHierarchy (LinkContext context, MarkStep markStep)
{
_context = context;
_markStep = markStep;
_typesInDynamicallyAccessedMembersHierarchy = new Dictionary<TypeDefinition, (DynamicallyAccessedMemberTypes, bool)> ();
}

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;
}
}
}
49 changes: 17 additions & 32 deletions src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1755,34 +1768,6 @@ void ProcessGetMethodByName (
methodReturnValue = MergePointValue.MergeValues (methodReturnValue, NullValue.Instance);
}

DynamicallyAccessedMemberTypes GetMemberTypesForTypeHierarchy (TypeDefinition targetType)
{
HashSet<TypeDefinition> visitedTypes = new HashSet<TypeDefinition> ();
return GetMemberTypesForTypeHierarchyInternal (targetType, visitedTypes);
}

DynamicallyAccessedMemberTypes GetMemberTypesForTypeHierarchyInternal (TypeDefinition targetType, HashSet<TypeDefinition> 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 ()) {
Expand Down
14 changes: 13 additions & 1 deletion src/linker/Linker.Steps/MarkStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ public partial class MarkStep : IStep
UnreachableBlocksOptimizer _unreachableBlocksOptimizer;
MarkStepContext _markContext;
readonly HashSet<TypeDefinition> _entireTypesMarked;
DynamicallyAccessedMembersTypeHierarchy _dynamicallyAccessedMembersTypeHierarchy;

internal DynamicallyAccessedMembersTypeHierarchy DynamicallyAccessedMembersTypeHierarchy {
get => _dynamicallyAccessedMembersTypeHierarchy;
}

#if DEBUG
static readonly DependencyKind[] _entireTypeReasons = new DependencyKind[] {
Expand Down Expand Up @@ -185,7 +190,7 @@ public MarkStep ()
_dynamicInterfaceCastableImplementationTypes = new List<TypeDefinition> ();
_unreachableBodies = new List<MethodBody> ();
_pending_isinst_instr = new List<(TypeDefinition, MethodBody, Instruction)> ();
_entireTypesMarked = new HashSet<TypeDefinition> (); ;
_entireTypesMarked = new HashSet<TypeDefinition> ();
}

public AnnotationStore Annotations => _context.Annotations;
Expand All @@ -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 ();
Expand Down Expand Up @@ -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);
Expand Down
20 changes: 20 additions & 0 deletions test/Mono.Linker.Tests.Cases/DataFlow/TypeDataflow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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");
}

Expand Down Expand Up @@ -84,21 +89,36 @@ 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 () { }
}

[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 () { }
}

Expand Down