xamarin-macios/tools/linker/MonoTouch.Tuner/PreserveSmartEnumConversion...

228 строки
7.0 KiB
C#

// Copyright 2017 Xamarin Inc.
using System;
using System.Collections.Generic;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Linker;
using Mono.Tuner;
#if NET
using Mono.Linker.Steps;
#else
using MonoTouch.Tuner;
#endif
using Xamarin.Bundler;
namespace Xamarin.Linker.Steps {
#if NET
public class PreserveSmartEnumConversionsHandler : ExceptionalMarkHandler
#else
public class PreserveSmartEnumConversionsSubStep : ExceptionalSubStep
#endif
{
Dictionary<TypeDefinition, Tuple<MethodDefinition, MethodDefinition>> cache;
protected override string Name { get; } = "Smart Enum Conversion Preserver";
protected override int ErrorCode { get; } = 2200;
#if NET
public override void Initialize (LinkContext context, MarkContext markContext)
{
base.Initialize (context);
markContext.RegisterMarkMethodAction (ProcessMethod);
}
#else
public override SubStepTargets Targets {
get {
return SubStepTargets.Method | SubStepTargets.Property;
}
}
#endif
#if NET
bool IsActiveFor (AssemblyDefinition assembly)
#else
public override bool IsActiveFor (AssemblyDefinition assembly)
#endif
{
if (Profile.IsProductAssembly (assembly))
return true;
// We don't need to process assemblies that don't reference ObjCRuntime.BindAsAttribute.
foreach (var tr in assembly.MainModule.GetTypeReferences ()) {
if (tr.Is ("ObjCRuntime", "BindAsAttribute"))
return true;
}
return false;
}
#if NET
void Mark (Tuple<MethodDefinition, MethodDefinition> pair)
{
context.Annotations.Mark (pair.Item1);
context.Annotations.Mark (pair.Item2);
}
#else
void Preserve (Tuple<MethodDefinition, MethodDefinition> pair, MethodDefinition conditionA, MethodDefinition conditionB = null)
{
if (conditionA != null) {
context.Annotations.AddPreservedMethod (conditionA, pair.Item1);
context.Annotations.AddPreservedMethod (conditionA, pair.Item2);
}
if (conditionB != null) {
context.Annotations.AddPreservedMethod (conditionB, pair.Item1);
context.Annotations.AddPreservedMethod (conditionB, pair.Item2);
}
}
#endif
#if NET
void ProcessAttributeProvider (ICustomAttributeProvider provider)
#else
void ProcessAttributeProvider (ICustomAttributeProvider provider, MethodDefinition conditionA, MethodDefinition conditionB = null)
#endif
{
if (provider?.HasCustomAttributes != true)
return;
foreach (var ca in provider.CustomAttributes) {
var tr = ca.Constructor.DeclaringType;
if (!tr.Is ("ObjCRuntime", "BindAsAttribute"))
continue;
if (ca.ConstructorArguments.Count != 1) {
ErrorHelper.Show (ErrorHelper.CreateWarning (LinkContext.Target.App, 4124, provider, Errors.MT4124_E, provider.AsString (), ca.ConstructorArguments.Count));
continue;
}
var managedType = ca.ConstructorArguments [0].Value as TypeReference;
var managedEnumType = managedType?.GetElementType ().Resolve ();
if (managedEnumType == null) {
ErrorHelper.Show (ErrorHelper.CreateWarning (LinkContext.Target.App, 4124, provider, Errors.MT4124_H, provider.AsString (), managedType?.FullName));
continue;
}
// We only care about enums, BindAs attributes can be used for other types too.
if (!managedEnumType.IsEnum)
continue;
Tuple<MethodDefinition, MethodDefinition> pair;
if (cache != null && cache.TryGetValue (managedEnumType, out pair)) {
#if NET
// The pair was already marked if it was cached.
#else
Preserve (pair, conditionA, conditionB);
#endif
continue;
}
// Find the Extension type
TypeDefinition extensionType = null;
var extensionName = managedEnumType.Name + "Extensions";
foreach (var type in managedEnumType.Module.Types) {
if (type.Namespace != managedEnumType.Namespace)
continue;
if (type.Name != extensionName)
continue;
extensionType = type;
break;
}
if (extensionType == null) {
Driver.Log (1, $"Could not find a smart extension type for the enum {managedEnumType.FullName} (due to BindAs attribute on {provider.AsString ()}): most likely this is because the enum isn't a smart enum.");
continue;
}
// Find the GetConstant/GetValue methods
MethodDefinition getConstant = null;
MethodDefinition getValue = null;
foreach (var method in extensionType.Methods) {
if (!method.IsStatic)
continue;
if (!method.HasParameters || method.Parameters.Count != 1)
continue;
if (method.Name == "GetConstant") {
if (!method.ReturnType.Is ("Foundation", "NSString"))
continue;
if (method.Parameters [0].ParameterType != managedEnumType)
continue;
getConstant = method;
} else if (method.Name == "GetValue") {
if (!method.Parameters [0].ParameterType.Is ("Foundation", "NSString"))
continue;
if (method.ReturnType != managedEnumType)
continue;
getValue = method;
}
}
if (getConstant == null) {
Driver.Log (1, $"Could not find the GetConstant method on the supposedly smart extension type {extensionType.FullName} for the enum {managedEnumType.FullName} (due to BindAs attribute on {provider.AsString ()}): most likely this is because the enum isn't a smart enum.");
continue;
}
if (getValue == null) {
Driver.Log (1, $"Could not find the GetValue method on the supposedly smart extension type {extensionType.FullName} for the enum {managedEnumType.FullName} (due to BindAs attribute on {provider.AsString ()}): most likely this is because the enum isn't a smart enum.");
continue;
}
pair = new Tuple<MethodDefinition, MethodDefinition> (getConstant, getValue);
if (cache == null)
cache = new Dictionary<TypeDefinition, Tuple<MethodDefinition, MethodDefinition>> ();
cache.Add (managedEnumType, pair);
#if NET
Mark (pair);
#else
Preserve (pair, conditionA, conditionB);
#endif
}
}
protected override void Process (MethodDefinition method)
{
#if NET
static bool IsPropertyMethod (MethodDefinition method)
{
return method.IsGetter || method.IsSetter;
}
ProcessAttributeProvider (method);
ProcessAttributeProvider (method.MethodReturnType);
if (method.HasParameters) {
foreach (var p in method.Parameters)
ProcessAttributeProvider (p);
}
if (IsPropertyMethod (method)) {
foreach (PropertyDefinition property in method.DeclaringType.Properties)
if (property.GetMethod == method || property.SetMethod == method) {
ProcessAttributeProvider (property);
break;
}
}
#else
ProcessAttributeProvider (method, method);
ProcessAttributeProvider (method.MethodReturnType, method);
if (method.HasParameters) {
foreach (var p in method.Parameters)
ProcessAttributeProvider (p, method);
}
#endif
}
#if !NET
protected override void Process (PropertyDefinition property)
{
ProcessAttributeProvider (property, property.GetMethod, property.SetMethod);
if (property.GetMethod != null)
Process (property.GetMethod);
if (property.SetMethod != null)
Process (property.SetMethod);
}
#endif
}
}