diff --git a/docs/website/mtouch-errors.md b/docs/website/mtouch-errors.md index dcd98fe183..a95f0b5954 100644 --- a/docs/website/mtouch-errors.md +++ b/docs/website/mtouch-errors.md @@ -1201,6 +1201,10 @@ Something unexpected occured when trying to mark `NSObject` subclasses from the Something unexpected occured when trying to inline code from the application. The assembly causing the issue is named in the error message. In order to fix this issue the assembly will need to be provided in a [bug report](https://bugzilla.xamarin.com) along with a complete build log with verbosity enabled (i.e. `-v -v -v -v` in the **Additional mtouch arguments**). +### MT2100: Smart Enum Conversion Preserver failed processing `...`. + +Something unexpected occured when trying to mark the conversion methods for smart enums from the application. The assembly causing the issue is named in the error message. In order to fix this issue the assembly will need to be provided in a [bug report](https://bugzilla.xamarin.com) along with a complete build log with verbosity enabled (i.e. `-v -v -v -v` in the **Additional mtouch arguments**). + diff --git a/tests/linker-ios/link all/PreserveTest.cs b/tests/linker-ios/link all/PreserveTest.cs index f83addf096..2eac93a15a 100644 --- a/tests/linker-ios/link all/PreserveTest.cs +++ b/tests/linker-ios/link all/PreserveTest.cs @@ -164,5 +164,60 @@ namespace LinkAll.Attributes { var method = klass.GetMethod ("GetHandle", BindingFlags.Public | BindingFlags.Static); Assert.NotNull (method, "GetHandle"); } + + [Test] + public void SmartEnumTest () + { + var consumer = GetType ().Assembly.GetType ("LinkAll.Attributes.SmartConsumer"); + Assert.NotNull (consumer, "SmartConsumer"); + Assert.NotNull (consumer.GetMethod ("GetSmartEnumValue"), "GetSmartEnumValue"); + Assert.NotNull (consumer.GetMethod ("SetSmartEnumValue"), "SetSmartEnumValue"); + var smartEnum = GetType ().Assembly.GetType ("LinkAll.Attributes.SmartEnum"); + Assert.NotNull (smartEnum, "SmartEnum"); + var smartExtensions = GetType ().Assembly.GetType ("LinkAll.Attributes.SmartEnumExtensions"); + Assert.NotNull (smartExtensions, "SmartEnumExtensions"); + Assert.NotNull (smartExtensions.GetMethod ("GetConstant"), "GetConstant"); + Assert.NotNull (smartExtensions.GetMethod ("GetValue"), "GetValue"); + + // Unused smart enums and their extensions should be linked away + Assert.IsNull (typeof (NSObject).Assembly.GetType ("AVFoundation.AVMediaTypes"), "AVMediaTypes"); + Assert.IsNull (typeof (NSObject).Assembly.GetType ("AVFoundation.AVMediaTypesExtensions"), "AVMediaTypesExtensions"); + } } + + [Preserve (AllMembers = true)] + class SmartConsumer : NSObject + { + // The Smart Get/Set methods should not be linked away, and neither should the Smart enums + extensions + [Export ("getSmartEnumValue")] + [return: BindAs (typeof (SmartEnum), OriginalType = typeof (NSString))] + public SmartEnum GetSmartEnumValue () + { + return SmartEnum.Smart; + } + + [Export ("setSmartEnumValue:")] + public void SetSmartEnumValue ([BindAs (typeof (SmartEnum), OriginalType = typeof (NSString))] SmartEnum value) + { + } + } + + public enum SmartEnum : int + { + Smart = 0, + } + + public static class SmartEnumExtensions + { + public static NSString GetConstant (this SmartEnum self) + { + return (NSString) "Smart"; + } + + public static SmartEnum GetValue (NSString constant) + { + return SmartEnum.Smart; + } + } + } \ No newline at end of file diff --git a/tools/linker/MonoTouch.Tuner/PreserveSmartEnumConversionsSubStep.cs b/tools/linker/MonoTouch.Tuner/PreserveSmartEnumConversionsSubStep.cs new file mode 100644 index 0000000000..aef98062bf --- /dev/null +++ b/tools/linker/MonoTouch.Tuner/PreserveSmartEnumConversionsSubStep.cs @@ -0,0 +1,146 @@ +// Copyright 2017 Xamarin Inc. + +using System; +using System.Collections.Generic; + +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Linker; +using Mono.Tuner; +using MonoTouch.Tuner; + +namespace Xamarin.Linker.Steps +{ + public class PreserveSmartEnumConversionsSubStep : ExceptionalSubStep + { + Dictionary> cache; + protected override string Name { get; } = "Smart Enum Conversion Preserver"; + protected override int ErrorCode { get; } = 2100; + + public override SubStepTargets Targets { + get { + return SubStepTargets.Method | SubStepTargets.Property; + } + } + + public override bool IsActiveFor (AssemblyDefinition assembly) + { + // we need to process all assemblies, because the functions we want to + // preserve are not necessarily in the assembly we're processing. + return true; + } + + void Preserve (Tuple 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); + } + } + + void ProcessAttributeProvider (ICustomAttributeProvider provider, MethodDefinition conditionA, MethodDefinition conditionB = null) + { + if (provider?.HasCustomAttributes != true) + return; + + foreach (var ca in provider.CustomAttributes) { + var tr = ca.Constructor.DeclaringType; + + if (!tr.IsPlatformType ("ObjCRuntime", "BindAsAttribute")) + continue; + + if (ca.ConstructorArguments.Count != 1) { + Console.WriteLine ("WARNING"); + continue; + } + + var managedType = ca.ConstructorArguments [0].Value as TypeReference; + var managedEnumType = managedType?.GetElementType ().Resolve (); + if (managedEnumType == null) { + Console.WriteLine ("WARNING"); + continue; + } + + Tuple pair; + if (cache != null && cache.TryGetValue (managedEnumType, out pair)) { + Preserve (pair, conditionA, conditionB); + continue; + } + + Console.WriteLine (managedEnumType); + // 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) { + Console.WriteLine ("WARNING"); + 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.IsPlatformType ("Foundation", "NSString")) + continue; + if (method.Parameters [0].ParameterType != managedEnumType) + continue; + getConstant = method; + } else if (method.Name == "GetValue") { + if (!method.Parameters [0].ParameterType.IsPlatformType ("Foundation", "NSString")) + continue; + if (method.ReturnType != managedEnumType) + continue; + getValue = method; + } + } + if (getConstant == null || getValue == null) { + Console.WriteLine ("WARNING"); + continue; + } + + pair = new Tuple (getConstant, getValue); + if (cache == null) + cache = new Dictionary> (); + cache.Add (managedEnumType, pair); + Preserve (pair, conditionA, conditionB); + } + } + + protected override void Process (MethodDefinition method) + { + ProcessAttributeProvider (method, method); + ProcessAttributeProvider (method.MethodReturnType, method); + if (method.HasParameters) { + foreach (var p in method.Parameters) + ProcessAttributeProvider (p, method); + } + } + + 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); + } + } +} diff --git a/tools/mmp/Makefile b/tools/mmp/Makefile index ffe6ac8093..594f1f9709 100644 --- a/tools/mmp/Makefile +++ b/tools/mmp/Makefile @@ -94,6 +94,7 @@ tuner_sources = \ ../linker/MonoTouch.Tuner/Extensions.cs \ ../linker/MonoTouch.Tuner/ListExportedSymbols.cs \ ../linker/MonoTouch.Tuner/ProcessExportedFields.cs \ + ../linker/MonoTouch.Tuner/PreserveSmartEnumConversionsSubStep.cs \ ../../src/build/mac/Constants.cs linker_resources = \ diff --git a/tools/mmp/mmp.csproj b/tools/mmp/mmp.csproj index 5638b23f9c..e8e0239ca9 100644 --- a/tools/mmp/mmp.csproj +++ b/tools/mmp/mmp.csproj @@ -265,6 +265,9 @@ MonoTouch.Tuner\ProcessExportedFields.cs + + MonoTouch.Tuner\PreserveSmartEnumConversionsSubStep.cs + external\Constants.cs diff --git a/tools/mtouch/Makefile b/tools/mtouch/Makefile index 5f7565d54b..86472ff2c3 100644 --- a/tools/mtouch/Makefile +++ b/tools/mtouch/Makefile @@ -71,6 +71,7 @@ LINKER_SOURCES = \ $(LINKER_DIR)/MonoTouch.Tuner/RemoveAttributes.cs \ $(LINKER_DIR)/MonoTouch.Tuner/RemoveCode.cs \ $(LINKER_DIR)/MonoTouch.Tuner/SealerSubStep.cs \ + $(LINKER_DIR)/MonoTouch.Tuner/PreserveSmartEnumConversionsSubStep.cs \ $(TOP)/tools/linker/ApplyPreserveAttribute.cs \ $(TOP)/tools/linker/BaseProfile.cs \ $(TOP)/tools/linker/CoreHttpMessageHandler.cs \ diff --git a/tools/mtouch/Tuning.cs b/tools/mtouch/Tuning.cs index 0686fd1c67..72b78729d9 100644 --- a/tools/mtouch/Tuning.cs +++ b/tools/mtouch/Tuning.cs @@ -157,6 +157,7 @@ namespace MonoTouch.Tuner { sub.Add (new PreserveSoapHttpClients ()); sub.Add (new CoreHttpMessageHandler (options)); sub.Add (new InlinerSubStep ()); + sub.Add (new PreserveSmartEnumConversionsSubStep ()); return sub; } diff --git a/tools/mtouch/mtouch.csproj b/tools/mtouch/mtouch.csproj index 62d0b69f75..312f775fec 100644 --- a/tools/mtouch/mtouch.csproj +++ b/tools/mtouch/mtouch.csproj @@ -335,6 +335,9 @@ MonoTouch.Tuner\InlinerSubStep.cs + + MonoTouch.Tuner\PreserveSmartEnumConversionsSubStep.cs + common\BuildTasks.cs