|
| 1 | +// Copyright 2017 Xamarin Inc. |
| 2 | + |
| 3 | +using System.Linq; |
| 4 | + |
| 5 | +using Mono.Cecil; |
| 6 | +using Mono.Linker; |
| 7 | +using Mono.Linker.Steps; |
| 8 | +using Mono.Tuner; |
| 9 | + |
| 10 | +using Xamarin.Bundler; |
| 11 | +using Xamarin.Tuner; |
| 12 | + |
| 13 | +#nullable enable |
| 14 | + |
| 15 | +namespace Xamarin.Linker.Steps { |
| 16 | + public class PreserveSmartEnumConversionsStep : AssemblyModifierStep { |
| 17 | + protected override string Name { get; } = "Smart Enum Conversion Preserver"; |
| 18 | + protected override int ErrorCode { get; } = 2200; |
| 19 | + |
| 20 | + PreserveSmartEnumConversion? preserver; |
| 21 | + PreserveSmartEnumConversion Preserver { |
| 22 | + get { |
| 23 | + if (preserver is null) |
| 24 | + preserver = new PreserveSmartEnumConversion (DerivedLinkContext, Preserve); |
| 25 | + return preserver; |
| 26 | + } |
| 27 | + } |
| 28 | + |
| 29 | + protected override bool IsActiveFor (AssemblyDefinition assembly) |
| 30 | + { |
| 31 | + // We have to process assemblies that aren't linked, because type A from the unlinked assembly X |
| 32 | + // might reference the smart enum B from the linked assembly Y, |
| 33 | + // and we need to make sure that smart enum B's conversion methods aren't trimmed away - which |
| 34 | + // means adding dynamic dependency attributes to the methods in the unlinked assembly X, |
| 35 | + // which means we need to process the unlinked assembly X. |
| 36 | + |
| 37 | + // Hot Reload: we can't modify user assemblies when Hot Reload is enabled (otherwise Hot Reload won't work), |
| 38 | + // so we'll have to come up with a different solution (emit xml definition instead maybe?) |
| 39 | + |
| 40 | + // Unless an assembly is or references our platform assembly, then it won't have anything we need to preserve |
| 41 | + if (!Configuration.Profile.IsOrReferencesProductAssembly (assembly)) |
| 42 | + return false; |
| 43 | + |
| 44 | + return true; |
| 45 | + } |
| 46 | + |
| 47 | + bool Preserve (Tuple<MethodDefinition, MethodDefinition> pair, bool alreadyProcessed, params MethodDefinition? [] conditions) |
| 48 | + { |
| 49 | + var conds = conditions.Where (v => v is not null).Cast<MethodDefinition> ().ToArray (); |
| 50 | + if (conds.Length == 0) |
| 51 | + return false; |
| 52 | + |
| 53 | + var modified = false; |
| 54 | + foreach (var condition in conds) { |
| 55 | + modified |= abr.AddDynamicDependencyAttribute (condition, pair.Item1); |
| 56 | + modified |= abr.AddDynamicDependencyAttribute (condition, pair.Item2); |
| 57 | + } |
| 58 | + |
| 59 | + return modified; |
| 60 | + } |
| 61 | + |
| 62 | + protected override bool ProcessType (TypeDefinition type) |
| 63 | + { |
| 64 | + var modified = false; |
| 65 | + |
| 66 | + if (type.HasNestedTypes) { |
| 67 | + foreach (var nested in type.NestedTypes) |
| 68 | + modified |= ProcessType (nested); |
| 69 | + } |
| 70 | + |
| 71 | + if (!type.HasMethods) |
| 72 | + return modified; |
| 73 | + |
| 74 | + foreach (var method in type.Methods) |
| 75 | + modified |= ProcessMethod (method); |
| 76 | + |
| 77 | + return modified; |
| 78 | + } |
| 79 | + |
| 80 | + bool ProcessMethod (MethodDefinition method) |
| 81 | + { |
| 82 | + static bool IsPropertyMethod (MethodDefinition method) |
| 83 | + { |
| 84 | + return method.IsGetter || method.IsSetter; |
| 85 | + } |
| 86 | + |
| 87 | + var modified = false; |
| 88 | + modified |= Preserver.ProcessAttributeProvider (method, method); |
| 89 | + modified |= Preserver.ProcessAttributeProvider (method.MethodReturnType, method); |
| 90 | + |
| 91 | + if (method.HasParameters) { |
| 92 | + foreach (var p in method.Parameters) |
| 93 | + modified |= Preserver.ProcessAttributeProvider (p, method); |
| 94 | + } |
| 95 | + if (IsPropertyMethod (method)) { |
| 96 | + foreach (var property in method.DeclaringType.Properties) |
| 97 | + if (property.GetMethod == method || property.SetMethod == method) { |
| 98 | + modified |= Preserver.ProcessAttributeProvider (property, property.GetMethod, property.SetMethod); |
| 99 | + break; |
| 100 | + } |
| 101 | + } |
| 102 | + return modified; |
| 103 | + } |
| 104 | + } |
| 105 | + |
| 106 | + class PreserveSmartEnumConversion { |
| 107 | + Dictionary<TypeDefinition, Tuple<MethodDefinition, MethodDefinition>> cache = new (); |
| 108 | + |
| 109 | + public DerivedLinkContext LinkContext { get; private set; } |
| 110 | + |
| 111 | + public Func<Tuple<MethodDefinition, MethodDefinition>, bool, MethodDefinition? [], bool> preserve { get; set; } |
| 112 | + |
| 113 | + public PreserveSmartEnumConversion (DerivedLinkContext linkContext, Func<Tuple<MethodDefinition, MethodDefinition>, bool, MethodDefinition? [], bool> preserve) |
| 114 | + { |
| 115 | + LinkContext = linkContext; |
| 116 | + this.preserve = preserve; |
| 117 | + } |
| 118 | + |
| 119 | + bool Preserve (Tuple<MethodDefinition, MethodDefinition> pair, bool alreadyProcessed, params MethodDefinition? [] conditions) |
| 120 | + { |
| 121 | + return preserve (pair, alreadyProcessed, conditions); |
| 122 | + } |
| 123 | + |
| 124 | + public bool ProcessAttributeProvider (ICustomAttributeProvider provider, params MethodDefinition [] conditions) |
| 125 | + { |
| 126 | + var modified = false; |
| 127 | + |
| 128 | + if (provider?.HasCustomAttributes != true) |
| 129 | + return modified; |
| 130 | + |
| 131 | + foreach (var ca in provider.CustomAttributes) { |
| 132 | + var tr = ca.Constructor.DeclaringType; |
| 133 | + |
| 134 | + if (!tr.Is ("ObjCRuntime", "BindAsAttribute")) |
| 135 | + continue; |
| 136 | + |
| 137 | + if (ca.ConstructorArguments.Count != 1) { |
| 138 | + ErrorHelper.Show (ErrorHelper.CreateWarning (LinkContext.App, 4124, provider, Errors.MT4124_E, provider.AsString (), ca.ConstructorArguments.Count)); |
| 139 | + continue; |
| 140 | + } |
| 141 | + |
| 142 | + var managedType = ca.ConstructorArguments [0].Value as TypeReference; |
| 143 | + var managedEnumType = managedType?.GetElementType ().Resolve (); |
| 144 | + if (managedEnumType is null) { |
| 145 | + ErrorHelper.Show (ErrorHelper.CreateWarning (LinkContext.App, 4124, provider, Errors.MT4124_H, provider.AsString (), managedType?.FullName ?? "(null)")); |
| 146 | + continue; |
| 147 | + } |
| 148 | + |
| 149 | + // We only care about enums, BindAs attributes can be used for other types too. |
| 150 | + if (!managedEnumType.IsEnum) |
| 151 | + continue; |
| 152 | + |
| 153 | + if (cache.TryGetValue (managedEnumType, out var pair)) { |
| 154 | + // The pair was already marked if it was cached. |
| 155 | + Preserve (pair, true, conditions); |
| 156 | + continue; |
| 157 | + } |
| 158 | + |
| 159 | + // Find the Extension type |
| 160 | + TypeDefinition? extensionType = null; |
| 161 | + var extensionName = managedEnumType.Name + "Extensions"; |
| 162 | + foreach (var type in managedEnumType.Module.Types) { |
| 163 | + if (type.Namespace != managedEnumType.Namespace) |
| 164 | + continue; |
| 165 | + if (type.Name != extensionName) |
| 166 | + continue; |
| 167 | + extensionType = type; |
| 168 | + break; |
| 169 | + } |
| 170 | + if (extensionType is null) { |
| 171 | + 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."); |
| 172 | + continue; |
| 173 | + } |
| 174 | + |
| 175 | + // Find the GetConstant/GetValue methods |
| 176 | + MethodDefinition? getConstant = null; |
| 177 | + MethodDefinition? getValue = null; |
| 178 | + |
| 179 | + foreach (var method in extensionType.Methods) { |
| 180 | + if (!method.IsStatic) |
| 181 | + continue; |
| 182 | + if (!method.HasParameters || method.Parameters.Count != 1) |
| 183 | + continue; |
| 184 | + if (method.Name == "GetConstant") { |
| 185 | + if (!method.ReturnType.Is ("Foundation", "NSString")) |
| 186 | + continue; |
| 187 | + if (method.Parameters [0].ParameterType != managedEnumType) |
| 188 | + continue; |
| 189 | + getConstant = method; |
| 190 | + } else if (method.Name == "GetValue") { |
| 191 | + if (!method.Parameters [0].ParameterType.Is ("Foundation", "NSString")) |
| 192 | + continue; |
| 193 | + if (method.ReturnType != managedEnumType) |
| 194 | + continue; |
| 195 | + getValue = method; |
| 196 | + } |
| 197 | + } |
| 198 | + |
| 199 | + if (getConstant is null) { |
| 200 | + 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."); |
| 201 | + continue; |
| 202 | + } |
| 203 | + |
| 204 | + if (getValue is null) { |
| 205 | + 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."); |
| 206 | + continue; |
| 207 | + } |
| 208 | + |
| 209 | + pair = new Tuple<MethodDefinition, MethodDefinition> (getConstant, getValue); |
| 210 | + cache.Add (managedEnumType, pair); |
| 211 | + modified |= Preserve (pair, false, conditions); |
| 212 | + } |
| 213 | + |
| 214 | + return modified; |
| 215 | + } |
| 216 | + } |
| 217 | +} |
0 commit comments