|
// 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 System.Diagnostics;
using System.IO;
using System.Text;
using System.Xml.XPath;
using ILLink.Shared;
using Mono.Cecil;
namespace Mono.Linker.Steps
{
public class DescriptorMarker : ProcessLinkerXmlBase
{
const string NamespaceElementName = "namespace";
const string _required = "required";
const string _preserve = "preserve";
const string _accessors = "accessors";
static readonly string[] _accessorsAll = new string[] { "all" };
static readonly char[] _accessorsSep = new char[] { ';' };
protected readonly HashSet<object> _preservedMembers;
public DescriptorMarker(LinkContext context, Stream documentStream, string xmlDocumentLocation)
: base(context, documentStream, xmlDocumentLocation)
{
_preservedMembers = new();
}
public DescriptorMarker(LinkContext context, Stream documentStream, EmbeddedResource resource, AssemblyDefinition resourceAssembly, string xmlDocumentLocation = "<unspecified>")
: base(context, documentStream, resource, resourceAssembly, xmlDocumentLocation)
{
_preservedMembers = new();
}
protected void LogDuplicatePreserve(string memberName, XPathNavigator duplicatePosition)
{
var origin = GetMessageOriginForPosition(duplicatePosition);
_context.LogMessage(MessageContainer.CreateInfoMessage(origin, $"Duplicate preserve of '{memberName}'"));
}
public void Mark()
{
bool stripDescriptors = _context.IsOptimizationEnabled(CodeOptimizations.RemoveDescriptors, _resource?.Assembly);
ProcessXml(stripDescriptors, _context.IgnoreDescriptors);
}
protected override AllowedAssemblies AllowedAssemblySelector { get => AllowedAssemblies.AnyAssembly; }
protected override void ProcessAssembly(AssemblyDefinition assembly, XPathNavigator nav, bool warnOnUnresolvedTypes)
{
if (GetTypePreserve(nav) == TypePreserve.All)
{
foreach (var type in assembly.MainModule.Types)
MarkAndPreserveAll(type, nav);
foreach (var exportedType in assembly.MainModule.ExportedTypes)
_context.MarkingHelpers.MarkExportedType(exportedType, assembly.MainModule, new DependencyInfo(DependencyKind.XmlDescriptor, assembly.MainModule), GetMessageOriginForPosition(nav));
}
else
{
ProcessTypes(assembly, nav, warnOnUnresolvedTypes);
ProcessNamespaces(assembly, nav);
}
}
void ProcessNamespaces(AssemblyDefinition assembly, XPathNavigator nav)
{
foreach (XPathNavigator namespaceNav in nav.SelectChildren(NamespaceElementName, XmlNamespace))
{
if (!ShouldProcessElement(namespaceNav))
continue;
string fullname = GetFullName(namespaceNav);
bool foundMatch = false;
foreach (TypeDefinition type in assembly.MainModule.Types)
{
if (type.Namespace != fullname)
continue;
foundMatch = true;
MarkAndPreserveAll(type, nav);
}
if (!foundMatch)
{
LogWarning(namespaceNav, DiagnosticId.XmlCouldNotFindAnyTypeInNamespace, fullname);
}
}
}
void MarkAndPreserveAll(TypeDefinition type, XPathNavigator nav)
{
_context.Annotations.Mark(type, new DependencyInfo(DependencyKind.XmlDescriptor, _xmlDocumentLocation), GetMessageOriginForPosition(nav));
_context.Annotations.SetPreserve(type, TypePreserve.All);
if (!type.HasNestedTypes)
return;
foreach (TypeDefinition nested in type.NestedTypes)
MarkAndPreserveAll(nested, nav);
}
protected override TypeDefinition? ProcessExportedType(ExportedType exported, AssemblyDefinition assembly, XPathNavigator nav)
{
_context.MarkingHelpers.MarkExportedType(exported, assembly.MainModule, new DependencyInfo(DependencyKind.XmlDescriptor, _xmlDocumentLocation), GetMessageOriginForPosition(nav));
// If a nested exported type is marked, then the declaring type must also be marked otherwise cecil will write out an invalid exported type table
// and anything that tries to read the assembly with cecil will crash
if (exported.DeclaringType != null)
{
var currentType = exported.DeclaringType;
while (currentType != null)
{
var parent = currentType.DeclaringType;
_context.MarkingHelpers.MarkExportedType(currentType, assembly.MainModule, new DependencyInfo(DependencyKind.DeclaringType, currentType), GetMessageOriginForPosition(nav));
currentType = parent;
}
}
return base.ProcessExportedType(exported, assembly, nav);
}
protected override void ProcessType(TypeDefinition type, XPathNavigator nav)
{
Debug.Assert(ShouldProcessElement(nav));
TypePreserve preserve = GetTypePreserve(nav);
switch (preserve)
{
case TypePreserve.Fields when !type.HasFields:
LogWarning(nav, DiagnosticId.TypeHasNoFieldsToPreserve, type.GetDisplayName());
break;
case TypePreserve.Methods when !type.HasMethods:
LogWarning(nav, DiagnosticId.TypeHasNoMethodsToPreserve, type.GetDisplayName());
break;
case TypePreserve.Fields:
case TypePreserve.Methods:
case TypePreserve.All:
_context.Annotations.SetPreserve(type, preserve);
break;
}
bool required = IsRequired(nav);
ProcessTypeChildren(type, nav, required);
if (!required)
return;
_context.Annotations.Mark(type, new DependencyInfo(DependencyKind.XmlDescriptor, _xmlDocumentLocation), GetMessageOriginForPosition(nav));
if (type.IsNested)
{
var currentType = type;
while (currentType.IsNested)
{
var parent = currentType.DeclaringType;
_context.Annotations.Mark(parent, new DependencyInfo(DependencyKind.DeclaringType, currentType), GetMessageOriginForPosition(nav));
currentType = parent;
}
}
}
protected static TypePreserve GetTypePreserve(XPathNavigator nav)
{
string attribute = GetAttribute(nav, _preserve);
if (string.IsNullOrEmpty(attribute))
return nav.HasChildren ? TypePreserve.Nothing : TypePreserve.All;
if (Enum.TryParse(attribute, true, out TypePreserve result))
return result;
return TypePreserve.Nothing;
}
protected override void ProcessField(TypeDefinition type, FieldDefinition field, XPathNavigator nav)
{
if (!_preservedMembers.Add(field))
LogDuplicatePreserve(field.FullName, nav);
_context.Annotations.Mark(field, new DependencyInfo(DependencyKind.XmlDescriptor, _xmlDocumentLocation), GetMessageOriginForPosition(nav));
}
protected override void ProcessMethod(TypeDefinition type, MethodDefinition method, XPathNavigator nav, object? customData)
{
if (!_preservedMembers.Add(method))
LogDuplicatePreserve(method.GetDisplayName(), nav);
_context.Annotations.MarkIndirectlyCalledMethod(method);
_context.Annotations.SetAction(method, MethodAction.Parse);
if (customData is bool required && !required)
{
_context.Annotations.AddPreservedMethod(type, method);
}
else
{
_context.Annotations.Mark(method, new DependencyInfo(DependencyKind.XmlDescriptor, _xmlDocumentLocation), GetMessageOriginForPosition(nav));
}
}
void ProcessMethodIfNotNull(TypeDefinition type, MethodDefinition method, XPathNavigator nav, object? customData)
{
if (method == null)
return;
ProcessMethod(type, method, nav, customData);
}
protected override MethodDefinition? GetMethod(TypeDefinition type, string signature)
{
if (type.HasMethods)
foreach (MethodDefinition meth in type.Methods)
if (signature == GetMethodSignature(meth, false))
return meth;
return null;
}
public static string GetMethodSignature(MethodDefinition meth, bool includeGenericParameters)
{
StringBuilder sb = new StringBuilder();
sb.Append(meth.ReturnType.FullName);
sb.Append(' ');
sb.Append(meth.Name);
if (includeGenericParameters && meth.HasGenericParameters)
{
sb.Append('`');
sb.Append(meth.GenericParameters.Count);
}
sb.Append('(');
if (meth.HasMetadataParameters())
{
int i = 0;
foreach (var p in meth.GetMetadataParameters())
{
if (i++ > 0)
sb.Append(',');
sb.Append(p.ParameterType.FullName);
}
}
sb.Append(')');
return sb.ToString();
}
protected override void ProcessEvent(TypeDefinition type, EventDefinition @event, XPathNavigator nav, object? customData)
{
if (!_preservedMembers.Add(@event))
LogDuplicatePreserve(@event.FullName, nav);
ProcessMethod(type, @event.AddMethod, nav, customData);
ProcessMethod(type, @event.RemoveMethod, nav, customData);
ProcessMethodIfNotNull(type, @event.InvokeMethod, nav, customData);
}
protected override void ProcessProperty(TypeDefinition type, PropertyDefinition property, XPathNavigator nav, object? customData, bool fromSignature)
{
string[] accessors = fromSignature ? GetAccessors(nav) : _accessorsAll;
if (!_preservedMembers.Add(property))
LogDuplicatePreserve(property.FullName, nav);
if (Array.IndexOf(accessors, "all") >= 0)
{
ProcessMethodIfNotNull(type, property.GetMethod, nav, customData);
ProcessMethodIfNotNull(type, property.SetMethod, nav, customData);
return;
}
if (property.GetMethod != null && Array.IndexOf(accessors, "get") >= 0)
ProcessMethod(type, property.GetMethod, nav, customData);
else if (property.GetMethod == null)
LogWarning(nav, DiagnosticId.XmlCouldNotFindGetAccesorOfPropertyOnType, property.Name, type.FullName);
if (property.SetMethod != null && Array.IndexOf(accessors, "set") >= 0)
ProcessMethod(type, property.SetMethod, nav, customData);
else if (property.SetMethod == null)
LogWarning(nav, DiagnosticId.XmlCouldNotFindSetAccesorOfPropertyOnType, property.Name, type.FullName);
}
static bool IsRequired(XPathNavigator nav)
{
string attribute = GetAttribute(nav, _required);
if (attribute == null || attribute.Length == 0)
return true;
return bool.TryParse(attribute, out bool result) && result;
}
protected static string[] GetAccessors(XPathNavigator nav)
{
string accessorsValue = GetAttribute(nav, _accessors);
if (accessorsValue != null)
{
string[] accessors = accessorsValue.Split(
_accessorsSep, StringSplitOptions.RemoveEmptyEntries);
if (accessors.Length > 0)
{
for (int i = 0; i < accessors.Length; ++i)
accessors[i] = accessors[i].ToLowerInvariant();
return accessors;
}
}
return _accessorsAll;
}
}
}
|