|
// 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;
using System.Collections.Generic;
using Mono.Cecil;
namespace Mono.Linker
{
// This recursively marks members of types for serialization. This is not supposed to be complete; it is just
// here as a heuristic to enable some serialization scenarios.
//
// Xamarin-android had some heuristics for serialization which behaved as follows:
//
// Discover members in the "link" assemblies with certain attributes:
// for XMLSerializer: Xml*Attribute, except XmlIgnoreAttribute
// for DataContractSerializer: DataContractAttribute or DataMemberAttribute
// These members are considered "roots" for serialization.
//
// For each "root":
// in an SDK assembly, set TypePreserve.All for types, or conditionally preserve methods or property methods
// event methods were not preserved.
// in a non-SDK assembly, mark types, fields, methods, property methods, and event methods
// recursively scan types of properties and fields (including generic arguments)
// For each recursive type:
// conditionally preserve the default ctor
//
// We want to match the above behavior in a more correct way, even if this means not marking some members
// which used to be marked. We also would like to avoid serializer-specific logic in the recursive marking.
//
// Instead of conditionally preserving things, we will just mark them, and we will do so consistently for every
// type discovered as part of the type graph reachable from the discovered roots. We also do not distinguish between
// SDK and non-SDK assemblies.
//
// The behavior is as follows:
//
// Discover attributed "roots" by looking for the same attributes, but only on marked types.
//
// For each "root":
// recursively scan types of public properties and fields, and the base type (including generic arguments)
// For each recursive type:
// mark the type and its public instance {fields, properties, and parameterless constructors}
[Flags]
public enum SerializerKind
{
None = 0,
XmlSerializer = 1,
DataContractSerializer = 2,
}
public class SerializationMarker
{
readonly LinkContext _context;
SerializerKind ActiveSerializers { get; set; }
Dictionary<SerializerKind, HashSet<ICustomAttributeProvider>>? _trackedRoots;
Dictionary<SerializerKind, HashSet<ICustomAttributeProvider>> TrackedRoots
{
get
{
_trackedRoots ??= new Dictionary<SerializerKind, HashSet<ICustomAttributeProvider>>();
return _trackedRoots;
}
}
HashSet<TypeDefinition>? _recursiveTypes;
HashSet<TypeDefinition> RecursiveTypes
{
get
{
_recursiveTypes ??= new HashSet<TypeDefinition>();
return _recursiveTypes;
}
}
public SerializationMarker(LinkContext context)
{
_context = context;
}
public bool IsActive(SerializerKind serializerKind) => ActiveSerializers.HasFlag(serializerKind);
static DependencyKind ToDependencyKind(SerializerKind serializerKind) => serializerKind switch
{
SerializerKind.DataContractSerializer => DependencyKind.DataContractSerialized,
SerializerKind.XmlSerializer => DependencyKind.XmlSerialized,
_ => throw new ArgumentException(nameof(SerializerKind))
};
public void TrackForSerialization(ICustomAttributeProvider provider, SerializerKind serializerKind)
{
if (ActiveSerializers.HasFlag(serializerKind))
{
MarkRecursiveMembers(provider, serializerKind);
return;
}
if (!TrackedRoots.TryGetValue(serializerKind, out var roots))
{
roots = new HashSet<ICustomAttributeProvider>();
TrackedRoots.Add(serializerKind, roots);
}
roots.Add(provider);
}
public void Activate(SerializerKind serializerKind)
{
if (!Enum.IsDefined<SerializerKind>(serializerKind) || serializerKind == SerializerKind.None)
throw new ArgumentException($"Unexpected serializer kind {nameof(serializerKind)}");
if (ActiveSerializers.HasFlag(serializerKind))
return;
ActiveSerializers |= serializerKind;
if (!TrackedRoots.TryGetValue(serializerKind, out var roots))
return;
foreach (var provider in roots)
MarkRecursiveMembers(provider, serializerKind);
TrackedRoots.Remove(serializerKind);
}
public void MarkRecursiveMembers(ICustomAttributeProvider provider, SerializerKind serializerKind)
{
TypeDefinition type;
var reason = new DependencyInfo(ToDependencyKind(serializerKind), provider);
var origin = new MessageOrigin(provider);
// Mark field and property types up-front in case the root field/property is
// not discovered recursively from the declaring type (for example, it may be private).
// Also mark the root members because the recursive logic doesn't mark all member types.
switch (provider)
{
case TypeDefinition td:
type = td;
break;
case FieldDefinition field:
type = field.DeclaringType;
MarkRecursiveMembersInternal(field.FieldType, reason);
_context.Annotations.Mark(field, reason, origin);
break;
case PropertyDefinition property:
type = property.DeclaringType;
MarkRecursiveMembersInternal(property.PropertyType, reason);
if (property.GetMethod != null)
_context.Annotations.Mark(property.GetMethod, reason, origin);
if (property.SetMethod != null)
_context.Annotations.Mark(property.SetMethod, reason, origin);
break;
case MethodDefinition method:
type = method.DeclaringType;
_context.Annotations.Mark(method, reason, origin);
break;
case EventDefinition @event:
type = @event.DeclaringType;
if (@event.AddMethod != null)
_context.Annotations.Mark(@event.AddMethod, reason, origin);
if (@event.InvokeMethod != null)
_context.Annotations.Mark(@event.InvokeMethod, reason, origin);
if (@event.RemoveMethod != null)
_context.Annotations.Mark(@event.RemoveMethod, reason, origin);
break;
default:
throw new ArgumentException($"{nameof(provider)} has invalid provider type {provider.GetType()}");
}
MarkRecursiveMembersInternal(type, reason);
}
void MarkRecursiveMembersInternal(TypeReference typeRef, in DependencyInfo reason)
{
if (typeRef == null)
return;
DependencyInfo typeReason = reason;
while (typeRef is GenericInstanceType git)
{
if (git.HasGenericArguments)
{
foreach (var argType in git.GenericArguments)
MarkRecursiveMembersInternal(argType, new DependencyInfo(DependencyKind.GenericArgumentType, typeRef));
}
_context.Tracer.AddDirectDependency(typeRef, typeReason, marked: false);
typeReason = new DependencyInfo(DependencyKind.ElementType, typeRef);
typeRef = git.ElementType;
}
// This doesn't handle other TypeSpecs. We are only matching what xamarin-android used to do.
// Arrays will still work because Resolve returns the array element type.
TypeDefinition? type = _context.TryResolve(typeRef);
if (type == null)
return;
_context.Annotations.Mark(type, typeReason, new MessageOrigin(reason.Source as ICustomAttributeProvider));
if (!RecursiveTypes.Add(type))
return;
// Unlike xamarin-android, don't preserve all members.
// Unlike xamarin-android, we preserve base type members recursively.
MarkRecursiveMembersInternal(type.BaseType, new DependencyInfo(DependencyKind.SerializedRecursiveType, type));
if (type.HasFields)
{
foreach (var field in type.Fields)
{
// Unlike xamarin-android, don't preserve non-public or static fields.
if (!field.IsPublic || field.IsStatic)
continue;
MarkRecursiveMembersInternal(field.FieldType, new DependencyInfo(DependencyKind.SerializedRecursiveType, type));
_context.Annotations.Mark(field, new DependencyInfo(DependencyKind.SerializedMember, type), new MessageOrigin(type));
}
}
if (type.HasProperties)
{
foreach (var property in type.Properties)
{
// Unlike xamarin-android, don't preserve non-public or static properties.
var get = property.GetMethod;
var set = property.SetMethod;
if ((get == null || !get.IsPublic || get.IsStatic) &&
(set == null || !set.IsPublic || set.IsStatic))
continue;
MarkRecursiveMembersInternal(property.PropertyType, new DependencyInfo(DependencyKind.SerializedRecursiveType, type));
if (get != null)
_context.Annotations.Mark(get, new DependencyInfo(DependencyKind.SerializedMember, type), new MessageOrigin(type));
if (set != null)
_context.Annotations.Mark(set, new DependencyInfo(DependencyKind.SerializedMember, type), new MessageOrigin(type));
// The property will be marked as a consequence of marking the getter/setter.
}
}
if (type.HasMethods)
{
foreach (var method in type.Methods)
{
// Unlike xamarin-android, don't preserve non-public, static, or parameterless constructors.
if (!method.IsPublic || !method.IsDefaultConstructor())
continue;
_context.Annotations.Mark(method, new DependencyInfo(DependencyKind.SerializedMember, type), new MessageOrigin(type));
}
}
}
}
}
|