File: Linker.Steps\DescriptorMarker.cs
Web Access
Project: src\src\tools\illink\src\linker\Mono.Linker.csproj (illink)
// 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));
			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;
		}
	}
}