File: Linker\MethodBodyScanner.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.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
 
namespace Mono.Linker
{
	public static class MethodBodyScanner
	{
		public static bool IsWorthConvertingToThrow (MethodIL body)
		{
			// Some bodies are cheaper size wise to leave alone than to convert to a throw
			Instruction? previousMeaningful = null;
			int meaningfulCount = 0;
			foreach (var ins in body.Instructions) {
				// Handle ignoring noops because (1) it's a valid case to ignore
				// and (2) When running the tests on .net core roslyn tosses in no ops
				// and that leads to a difference in test results between mcs and .net framework csc.
				if (ins.OpCode.Code == Code.Nop)
					continue;
 
				meaningfulCount++;
 
				if (meaningfulCount == 1 && ins.OpCode.Code == Code.Ret)
					return false;
 
				if (meaningfulCount == 2 && ins.OpCode.Code == Code.Ret && previousMeaningful != null) {
					if (previousMeaningful.OpCode.StackBehaviourPop == StackBehaviour.Pop0) {
						switch (previousMeaningful.OpCode.StackBehaviourPush) {
						case StackBehaviour.Pushi:
						case StackBehaviour.Pushi8:
						case StackBehaviour.Pushr4:
						case StackBehaviour.Pushr8:
							return false;
						}
 
						switch (previousMeaningful.OpCode.Code) {
						case Code.Ldnull:
							return false;
						}
					}
				}
 
				if (meaningfulCount >= 2)
					return true;
 
				previousMeaningful = ins;
			}
 
			return true;
		}
	}
	readonly struct InterfacesOnStackScanner
	{
		readonly LinkContext context;
 
		public InterfacesOnStackScanner (LinkContext context)
		{
			this.context = context;
		}
 
		public IEnumerable<(InterfaceImplementation, TypeDefinition)>? GetReferencedInterfaces (MethodIL methodIL)
		{
			var possibleStackTypes = AllPossibleStackTypes (methodIL);
			if (possibleStackTypes.Count == 0)
				return null;
 
			var interfaceTypes = possibleStackTypes.Where (t => t.IsInterface).ToArray ();
			if (interfaceTypes.Length == 0)
				return null;
 
			var interfaceImplementations = new HashSet<(InterfaceImplementation, TypeDefinition)> ();
 
			// If a type could be on the stack in the body and an interface it implements could be on the stack on the body
			// then we need to mark that interface implementation.  When this occurs it is not safe to remove the interface implementation from the type
			// even if the type is never instantiated
			foreach (var type in possibleStackTypes) {
				// We only sweep interfaces on classes so that's why we only care about classes
				if (!type.IsClass)
					continue;
 
				TypeDefinition? currentType = type;
				while (currentType?.BaseType != null) // Checking BaseType != null to skip System.Object
				{
					AddMatchingInterfaces (interfaceImplementations, currentType, interfaceTypes);
					currentType = context.TryResolve (currentType.BaseType);
				}
			}
 
			return interfaceImplementations;
		}
 
		HashSet<TypeDefinition> AllPossibleStackTypes (MethodIL methodIL)
		{
			var types = new HashSet<TypeDefinition> ();
 
			foreach (VariableDefinition var in methodIL.Variables)
				AddIfResolved (types, var.VariableType);
 
			foreach (var param in methodIL.Method.GetParameters ())
				AddIfResolved (types, param.ParameterType);
 
			foreach (ExceptionHandler eh in methodIL.ExceptionHandlers) {
				if (eh.HandlerType == ExceptionHandlerType.Catch) {
					AddIfResolved (types, eh.CatchType);
				}
			}
 
			foreach (Instruction instruction in methodIL.Instructions) {
				if (instruction.Operand is FieldReference fieldReference) {
					if (context.TryResolve (fieldReference)?.FieldType is TypeReference fieldType)
						AddIfResolved (types, fieldType);
				} else if (instruction.Operand is MethodReference methodReference) {
					if (methodReference is GenericInstanceMethod genericInstanceMethod)
						AddFromGenericInstance (types, genericInstanceMethod);
 
					if (methodReference.DeclaringType is GenericInstanceType genericInstanceType)
						AddFromGenericInstance (types, genericInstanceType);
 
					var resolvedMethod = context.TryResolve (methodReference);
					if (resolvedMethod != null) {
						if (resolvedMethod.HasMetadataParameters ()) {
							foreach (var param in resolvedMethod.GetParameters ())
								AddIfResolved (types, param.ParameterType);
						}
 
						AddFromGenericParameterProvider (types, resolvedMethod);
						AddFromGenericParameterProvider (types, resolvedMethod.DeclaringType);
						AddIfResolved (types, resolvedMethod.ReturnType);
					}
				}
			}
 
 
			return types;
		}
 
		void AddMatchingInterfaces (HashSet<(InterfaceImplementation, TypeDefinition)> results, TypeDefinition type, TypeDefinition[] interfaceTypes)
		{
			if (!type.HasInterfaces)
				return;
 
			foreach (var interfaceType in interfaceTypes) {
				if (HasInterface (type, interfaceType, out InterfaceImplementation? implementation))
					results.Add ((implementation, type));
			}
		}
 
		bool HasInterface (TypeDefinition type, TypeDefinition interfaceType, [NotNullWhen (true)] out InterfaceImplementation? implementation)
		{
			implementation = null;
			if (!type.HasInterfaces)
				return false;
 
			foreach (var iface in type.Interfaces) {
				if (context.TryResolve (iface.InterfaceType) == interfaceType) {
					implementation = iface;
					return true;
				}
			}
 
			return false;
		}
 
		void AddFromGenericInstance (HashSet<TypeDefinition> set, IGenericInstance instance)
		{
			if (!instance.HasGenericArguments)
				return;
 
			foreach (var genericArgument in instance.GenericArguments)
				AddIfResolved (set, genericArgument);
		}
 
		void AddFromGenericParameterProvider (HashSet<TypeDefinition> set, IGenericParameterProvider provider)
		{
			if (!provider.HasGenericParameters)
				return;
 
			foreach (var genericParameter in provider.GenericParameters) {
				foreach (var constraint in genericParameter.Constraints)
					AddIfResolved (set, constraint.ConstraintType);
			}
		}
 
		void AddIfResolved (HashSet<TypeDefinition> set, TypeReference item)
		{
			var resolved = context.TryResolve (item);
			if (resolved == null)
				return;
 
			set.Add (resolved);
		}
	}
}