summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Build.Tasks/NodeILExtensions.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Xamarin.Forms.Build.Tasks/NodeILExtensions.cs')
-rw-r--r--Xamarin.Forms.Build.Tasks/NodeILExtensions.cs480
1 files changed, 480 insertions, 0 deletions
diff --git a/Xamarin.Forms.Build.Tasks/NodeILExtensions.cs b/Xamarin.Forms.Build.Tasks/NodeILExtensions.cs
new file mode 100644
index 00000000..30b01061
--- /dev/null
+++ b/Xamarin.Forms.Build.Tasks/NodeILExtensions.cs
@@ -0,0 +1,480 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Xml;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+using Xamarin.Forms.Xaml;
+using Xamarin.Forms.Xaml.Internals;
+
+namespace Xamarin.Forms.Build.Tasks
+{
+ static class NodeILExtensions
+ {
+ public static IEnumerable<Instruction> PushConvertedValue(this ValueNode node, ILContext context,
+ TypeReference targetTypeRef, IEnumerable<ICustomAttributeProvider> attributeProviders,
+ IEnumerable<Instruction> pushServiceProvider, bool boxValueTypes, bool unboxValueTypes)
+ {
+ TypeReference typeConverter = null;
+ foreach (var attributeProvider in attributeProviders)
+ {
+ CustomAttribute typeConverterAttribute;
+ if (
+ (typeConverterAttribute =
+ attributeProvider.CustomAttributes.FirstOrDefault(
+ cad => TypeConverterAttribute.TypeConvertersType.Contains(cad.AttributeType.FullName))) != null)
+ {
+ typeConverter = typeConverterAttribute.ConstructorArguments[0].Value as TypeReference;
+ break;
+ }
+ }
+ return node.PushConvertedValue(context, targetTypeRef, typeConverter, pushServiceProvider, boxValueTypes,
+ unboxValueTypes);
+ }
+
+ public static IEnumerable<Instruction> PushConvertedValue(this ValueNode node, ILContext context, FieldReference bpRef,
+ IEnumerable<Instruction> pushServiceProvider, bool boxValueTypes, bool unboxValueTypes)
+ {
+ var module = context.Body.Method.Module;
+ var targetTypeRef = GetBPReturnType(context, bpRef, node);
+
+ TypeReference typeConverter;
+ bpRef.HasTypeConverter(module, out typeConverter);
+
+ return node.PushConvertedValue(context, targetTypeRef, typeConverter, pushServiceProvider, boxValueTypes,
+ unboxValueTypes);
+ }
+
+ public static IEnumerable<Instruction> PushConvertedValue(this ValueNode node, ILContext context,
+ TypeReference targetTypeRef, TypeReference typeConverter, IEnumerable<Instruction> pushServiceProvider,
+ bool boxValueTypes, bool unboxValueTypes)
+ {
+ var module = context.Body.Method.Module;
+ var str = (string)node.Value;
+
+ //If there's a [TypeConverter], use it
+ if (typeConverter != null)
+ {
+ var isExtendedConverter = typeConverter.ImplementsInterface(module.Import(typeof (IExtendedTypeConverter)));
+ var typeConverterCtor = typeConverter.Resolve().Methods.Single(md => md.IsConstructor && md.Parameters.Count == 0);
+ var typeConverterCtorRef = module.Import(typeConverterCtor);
+ var convertFromInvariantStringDefinition = isExtendedConverter
+ ? module.Import(typeof (IExtendedTypeConverter))
+ .Resolve()
+ .Methods.FirstOrDefault(md => md.Name == "ConvertFromInvariantString" && md.Parameters.Count == 2)
+ : typeConverter.Resolve()
+ .AllMethods()
+ .FirstOrDefault(md => md.Name == "ConvertFromInvariantString" && md.Parameters.Count == 1);
+ var convertFromInvariantStringReference = module.Import(convertFromInvariantStringDefinition);
+
+ yield return Instruction.Create(OpCodes.Newobj, typeConverterCtorRef);
+ yield return Instruction.Create(OpCodes.Ldstr, node.Value as string);
+
+ if (isExtendedConverter)
+ {
+ foreach (var instruction in pushServiceProvider)
+ yield return instruction;
+ }
+
+ yield return Instruction.Create(OpCodes.Callvirt, convertFromInvariantStringReference);
+
+ if (targetTypeRef.IsValueType && unboxValueTypes)
+ yield return Instruction.Create(OpCodes.Unbox_Any, module.Import(targetTypeRef));
+
+ //ConvertFrom returns an object, no need to Box
+ yield break;
+ }
+ var originalTypeRef = targetTypeRef;
+ var isNullable = false;
+ MethodReference nullableCtor = null;
+ if (targetTypeRef.Resolve().FullName == "System.Nullable`1")
+ {
+ targetTypeRef = ((GenericInstanceType)targetTypeRef).GenericArguments[0];
+ isNullable = true;
+ nullableCtor = originalTypeRef.GetMethods(md => md.IsConstructor && md.Parameters.Count == 1, module).Single().Item1;
+ nullableCtor = nullableCtor.MakeGeneric(targetTypeRef);
+ }
+ //Obvious Built-in conversions
+ if (targetTypeRef.Resolve().BaseType != null && targetTypeRef.Resolve().BaseType.FullName == "System.Enum")
+ yield return Instruction.Create(OpCodes.Ldc_I4, ParseEnum(targetTypeRef, str, node));
+ else if (targetTypeRef.FullName == "System.Char")
+ yield return Instruction.Create(OpCodes.Ldc_I4, Char.Parse(str));
+ else if (targetTypeRef.FullName == "System.Byte")
+ yield return Instruction.Create(OpCodes.Ldc_I4, Byte.Parse(str, CultureInfo.InvariantCulture));
+ else if (targetTypeRef.FullName == "System.Int16")
+ yield return Instruction.Create(OpCodes.Ldc_I4, Int16.Parse(str, CultureInfo.InvariantCulture));
+ else if (targetTypeRef.FullName == "System.Int32")
+ yield return Instruction.Create(OpCodes.Ldc_I4, Int32.Parse(str, CultureInfo.InvariantCulture));
+ else if (targetTypeRef.FullName == "System.Int64")
+ yield return Instruction.Create(OpCodes.Ldc_I8, Int64.Parse(str, CultureInfo.InvariantCulture));
+ else if (targetTypeRef.FullName == "System.Single")
+ yield return Instruction.Create(OpCodes.Ldc_R4, Single.Parse(str, CultureInfo.InvariantCulture));
+ else if (targetTypeRef.FullName == "System.Double")
+ yield return Instruction.Create(OpCodes.Ldc_R8, Double.Parse(str, CultureInfo.InvariantCulture));
+ else if (targetTypeRef.FullName == "System.Boolean")
+ {
+ if (Boolean.Parse(str))
+ yield return Instruction.Create(OpCodes.Ldc_I4_1);
+ else
+ yield return Instruction.Create(OpCodes.Ldc_I4_0);
+ }
+ else if (targetTypeRef.FullName == "System.TimeSpan")
+ {
+ var ts = TimeSpan.Parse(str, CultureInfo.InvariantCulture);
+ var ticks = ts.Ticks;
+ var timeSpanCtor =
+ module.Import(typeof (TimeSpan))
+ .Resolve()
+ .Methods.FirstOrDefault(md => md.IsConstructor && md.Parameters.Count == 1);
+ var timeSpanCtorRef = module.Import(timeSpanCtor);
+
+ yield return Instruction.Create(OpCodes.Ldc_I8, ticks);
+ yield return Instruction.Create(OpCodes.Newobj, timeSpanCtorRef);
+ }
+ else if (targetTypeRef.FullName == "System.DateTime")
+ {
+ var dt = DateTime.Parse(str, CultureInfo.InvariantCulture);
+ var ticks = dt.Ticks;
+ var dateTimeCtor =
+ module.Import(typeof (DateTime))
+ .Resolve()
+ .Methods.FirstOrDefault(md => md.IsConstructor && md.Parameters.Count == 1);
+ var dateTimeCtorRef = module.Import(dateTimeCtor);
+
+ yield return Instruction.Create(OpCodes.Ldc_I8, ticks);
+ yield return Instruction.Create(OpCodes.Newobj, dateTimeCtorRef);
+ }
+ else if (targetTypeRef.FullName == "System.String" && str.StartsWith("{}", StringComparison.Ordinal))
+ yield return Instruction.Create(OpCodes.Ldstr, str.Substring(2));
+ else if (targetTypeRef.FullName == "System.String")
+ yield return Instruction.Create(OpCodes.Ldstr, str);
+ else if (targetTypeRef.FullName == "System.Object")
+ yield return Instruction.Create(OpCodes.Ldstr, str);
+ else
+ yield return Instruction.Create(OpCodes.Ldnull);
+
+ if (isNullable)
+ yield return Instruction.Create(OpCodes.Newobj, module.Import(nullableCtor));
+ if (originalTypeRef.IsValueType && boxValueTypes)
+ yield return Instruction.Create(OpCodes.Box, module.Import(originalTypeRef));
+ }
+
+ static int ParseEnum(TypeReference enumRef, string value, IXmlLineInfo lineInfo)
+ {
+ var enumDef = enumRef.Resolve();
+ if (!enumDef.IsEnum)
+ throw new InvalidOperationException();
+
+ int? result = null;
+
+ foreach (var v in value.Split(','))
+ {
+ foreach (var field in enumDef.Fields)
+ {
+ if (field.Name == "value__")
+ continue;
+ if (field.Name == v.Trim())
+ result = (result ?? 0) | (int)field.Constant;
+ }
+ }
+
+ if (result.HasValue)
+ return result.Value;
+
+ throw new XamlParseException(string.Format("Enum value not found for {0}", value), lineInfo);
+ }
+
+ static bool HasTypeConverter(this FieldReference bpRef, ModuleDefinition module,
+ out TypeReference typeConverterReference)
+ {
+ typeConverterReference = null;
+
+ var declaringType = bpRef.DeclaringType;
+ var bpName = bpRef.Name;
+ var pName = bpName.EndsWith("Property", StringComparison.Ordinal) ? bpName.Substring(0, bpName.Length - 8) : bpName;
+ var property = declaringType.Resolve().Properties.FirstOrDefault(p => p.Name == pName);
+ CustomAttribute attr = null;
+
+ if (property != null)
+ {
+ if (property.HasCustomAttributes)
+ {
+ attr =
+ property.CustomAttributes.FirstOrDefault(
+ cad => TypeConverterAttribute.TypeConvertersType.Contains(cad.AttributeType.FullName));
+ }
+ if (attr == null && property.PropertyType.Resolve().HasCustomAttributes)
+ {
+ attr =
+ property.PropertyType.Resolve()
+ .CustomAttributes.FirstOrDefault(
+ cad => TypeConverterAttribute.TypeConvertersType.Contains(cad.AttributeType.FullName));
+ }
+
+ if (attr == null)
+ return false;
+
+ typeConverterReference = attr.ConstructorArguments[0].Value as TypeReference;
+ return true;
+ }
+
+ var getters = bpRef.DeclaringType.GetMethods(md => md.Name == "Get" + pName && md.IsStatic, module).SingleOrDefault();
+ if (getters != null)
+ {
+ if (getters.Item1.HasCustomAttributes)
+ {
+ attr =
+ getters.Item1.CustomAttributes.FirstOrDefault(
+ cad => TypeConverterAttribute.TypeConvertersType.Contains(cad.AttributeType.FullName));
+ }
+ else if (getters.Item1.ReturnType.Resolve().HasCustomAttributes)
+ {
+ attr =
+ getters.Item1.ReturnType.Resolve()
+ .CustomAttributes.FirstOrDefault(
+ cad => TypeConverterAttribute.TypeConvertersType.Contains(cad.AttributeType.FullName));
+ }
+
+ if (attr == null)
+ return false;
+
+ typeConverterReference = attr.ConstructorArguments[0].Value as TypeReference;
+ return true;
+ }
+
+ return false;
+ }
+
+ static TypeReference GetBPReturnType(ILContext context, FieldReference bpRef, IXmlLineInfo lineInfo)
+ {
+ //Find a property with a matching name
+ var name = bpRef.Name;
+ if (!name.EndsWith("Property", StringComparison.Ordinal))
+ return context.Body.Method.Module.TypeSystem.Object;
+ name = name.Substring(0, name.Length - 8);
+
+ //First, check for a property
+ TypeReference declaringTypeRef;
+ var property = bpRef.DeclaringType.GetProperty(pd => pd.Name == name, out declaringTypeRef);
+ if (property != null)
+ return property.PropertyType;
+
+ //Then check for getter or setter (attached BPs)
+ var getters =
+ bpRef.DeclaringType.GetMethods(md => md.Name == "Get" + name && md.IsStatic, context.Body.Method.Module)
+ .SingleOrDefault();
+ if (getters != null)
+ return getters.Item1.ReturnType;
+
+ //throws
+ throw new XamlParseException(
+ string.Format(
+ "Can not find a Property named \"{0}\" or a static method named \"Get{0}\" for BindableProperty \"{1}\"", name,
+ bpRef.Name), lineInfo);
+ }
+
+ public static IEnumerable<Instruction> PushXmlLineInfo(this INode node, ILContext context)
+ {
+ var module = context.Body.Method.Module;
+
+ var xmlLineInfo = node as IXmlLineInfo;
+ if (xmlLineInfo == null)
+ {
+ yield return Instruction.Create(OpCodes.Ldnull);
+ yield break;
+ }
+ MethodReference ctor;
+ if (xmlLineInfo.HasLineInfo())
+ {
+ yield return Instruction.Create(OpCodes.Ldc_I4, xmlLineInfo.LineNumber);
+ yield return Instruction.Create(OpCodes.Ldc_I4, xmlLineInfo.LinePosition);
+ ctor = module.Import(typeof (XmlLineInfo).GetConstructor(new[] { typeof (int), typeof (int) }));
+ }
+ else
+ ctor = module.Import(typeof (XmlLineInfo).GetConstructor(new Type[] { }));
+ yield return Instruction.Create(OpCodes.Newobj, ctor);
+ }
+
+ public static IEnumerable<Instruction> PushParentObjectsArray(this INode node, ILContext context)
+ {
+ var module = context.Body.Method.Module;
+
+ var nodes = new List<IElementNode>();
+ INode n = node.Parent;
+ while (n != null)
+ {
+ var en = n as IElementNode;
+ if (en != null && context.Variables.ContainsKey(en))
+ nodes.Add(en);
+ n = n.Parent;
+ }
+
+ if (nodes.Count == 0 && context.ParentContextValues == null)
+ {
+ yield return Instruction.Create(OpCodes.Ldnull);
+ yield break;
+ }
+
+ if (nodes.Count == 0)
+ {
+ yield return Instruction.Create(OpCodes.Ldarg_0);
+ yield return Instruction.Create(OpCodes.Ldfld, context.ParentContextValues);
+ yield break;
+ }
+
+ //Compute parent object length
+ if (context.ParentContextValues != null)
+ {
+ yield return Instruction.Create(OpCodes.Ldarg_0);
+ yield return Instruction.Create(OpCodes.Ldfld, context.ParentContextValues);
+ yield return Instruction.Create(OpCodes.Ldlen);
+ yield return Instruction.Create(OpCodes.Conv_I4);
+ }
+ else
+ yield return Instruction.Create(OpCodes.Ldc_I4_0);
+ var parentObjectLength = new VariableDefinition(module.TypeSystem.Int32);
+ context.Body.Variables.Add(parentObjectLength);
+ yield return Instruction.Create(OpCodes.Stloc, parentObjectLength);
+
+ //Create the final array
+ yield return Instruction.Create(OpCodes.Ldloc, parentObjectLength);
+ yield return Instruction.Create(OpCodes.Ldc_I4, nodes.Count);
+ yield return Instruction.Create(OpCodes.Add);
+ yield return Instruction.Create(OpCodes.Newarr, module.TypeSystem.Object);
+ var finalArray = new VariableDefinition(module.Import(typeof (object[])));
+ context.Body.Variables.Add(finalArray);
+ yield return Instruction.Create(OpCodes.Stloc, finalArray);
+
+ //Copy original array to final
+ if (context.ParentContextValues != null)
+ {
+ yield return Instruction.Create(OpCodes.Ldarg_0);
+ yield return Instruction.Create(OpCodes.Ldfld, context.ParentContextValues); //sourceArray
+ yield return Instruction.Create(OpCodes.Ldc_I4_0); //sourceIndex
+ yield return Instruction.Create(OpCodes.Ldloc, finalArray); //destinationArray
+ yield return Instruction.Create(OpCodes.Ldc_I4, nodes.Count); //destinationIndex
+ yield return Instruction.Create(OpCodes.Ldloc, parentObjectLength); //length
+ var arrayCopy =
+ module.Import(typeof (Array))
+ .Resolve()
+ .Methods.First(
+ md =>
+ md.Name == "Copy" && md.Parameters.Count == 5 &&
+ md.Parameters[1].ParameterType.FullName == module.TypeSystem.Int32.FullName);
+ yield return Instruction.Create(OpCodes.Call, module.Import(arrayCopy));
+ }
+
+ //Add nodes to array
+ yield return Instruction.Create(OpCodes.Ldloc, finalArray);
+ if (nodes.Count > 0)
+ {
+ for (var i = 0; i < nodes.Count; i++)
+ {
+ var en = nodes[i];
+ yield return Instruction.Create(OpCodes.Dup);
+ yield return Instruction.Create(OpCodes.Ldc_I4, i);
+ yield return Instruction.Create(OpCodes.Ldloc, context.Variables[en]);
+ if (context.Variables[en].VariableType.IsValueType)
+ yield return Instruction.Create(OpCodes.Box, module.Import(context.Variables[en].VariableType));
+ yield return Instruction.Create(OpCodes.Stelem_Ref);
+ }
+ }
+ }
+
+ public static IEnumerable<Instruction> PushServiceProvider(this INode node, ILContext context)
+ {
+ var module = context.Body.Method.Module;
+
+#if NOSERVICEPROVIDER
+ yield return Instruction.Create (OpCodes.Ldnull);
+ yield break;
+ #endif
+
+ var ctorinfo = typeof (XamlServiceProvider).GetConstructor(new Type[] { });
+ var ctor = module.Import(ctorinfo);
+
+ var addServiceInfo = typeof (XamlServiceProvider).GetMethod("Add", new[] { typeof (Type), typeof (object) });
+ var addService = module.Import(addServiceInfo);
+
+ var getTypeFromHandle =
+ module.Import(typeof (Type).GetMethod("GetTypeFromHandle", new[] { typeof (RuntimeTypeHandle) }));
+ var getAssembly = module.Import(typeof (Type).GetProperty("Assembly").GetMethod);
+
+ yield return Instruction.Create(OpCodes.Newobj, ctor);
+
+ //Add a SimpleValueTargetProvider
+ var pushParentIl = node.PushParentObjectsArray(context).ToList();
+ if (pushParentIl[pushParentIl.Count - 1].OpCode != OpCodes.Ldnull)
+ {
+ yield return Instruction.Create(OpCodes.Dup); //Keep the serviceProvider on the stack
+ yield return Instruction.Create(OpCodes.Ldtoken, module.Import(typeof (IProvideValueTarget)));
+ yield return Instruction.Create(OpCodes.Call, module.Import(getTypeFromHandle));
+
+ foreach (var instruction in pushParentIl)
+ yield return instruction;
+
+ var targetProviderCtor =
+ module.Import(typeof (SimpleValueTargetProvider).GetConstructor(new[] { typeof (object[]) }));
+ yield return Instruction.Create(OpCodes.Newobj, targetProviderCtor);
+ yield return Instruction.Create(OpCodes.Callvirt, addService);
+ }
+
+ //Add a NamescopeProvider
+ if (context.Scopes.ContainsKey(node))
+ {
+ yield return Instruction.Create(OpCodes.Dup); //Dupicate the serviceProvider
+ yield return Instruction.Create(OpCodes.Ldtoken, module.Import(typeof (INameScopeProvider)));
+ yield return Instruction.Create(OpCodes.Call, module.Import(getTypeFromHandle));
+ var namescopeProviderCtor = module.Import(typeof (NameScopeProvider).GetConstructor(new Type[] { }));
+ yield return Instruction.Create(OpCodes.Newobj, namescopeProviderCtor);
+ yield return Instruction.Create(OpCodes.Dup); //Duplicate the namescopeProvider
+ var setNamescope = module.Import(typeof (NameScopeProvider).GetProperty("NameScope").GetSetMethod());
+
+ yield return Instruction.Create(OpCodes.Ldloc, context.Scopes[node]);
+ yield return Instruction.Create(OpCodes.Callvirt, setNamescope);
+ yield return Instruction.Create(OpCodes.Callvirt, addService);
+ }
+
+ //Add a XamlTypeResolver
+ if (node.NamespaceResolver != null)
+ {
+ yield return Instruction.Create(OpCodes.Dup); //Dupicate the serviceProvider
+ yield return Instruction.Create(OpCodes.Ldtoken, module.Import(typeof (IXamlTypeResolver)));
+ yield return Instruction.Create(OpCodes.Call, module.Import(getTypeFromHandle));
+ var xmlNamespaceResolverCtor = module.Import(typeof (XmlNamespaceResolver).GetConstructor(new Type[] { }));
+ var addNamespace = module.Import(typeof (XmlNamespaceResolver).GetMethod("Add"));
+ yield return Instruction.Create(OpCodes.Newobj, xmlNamespaceResolverCtor);
+ foreach (var kvp in node.NamespaceResolver.GetNamespacesInScope(XmlNamespaceScope.ExcludeXml))
+ {
+ yield return Instruction.Create(OpCodes.Dup); //dup the resolver
+ yield return Instruction.Create(OpCodes.Ldstr, kvp.Key);
+ yield return Instruction.Create(OpCodes.Ldstr, kvp.Value);
+ yield return Instruction.Create(OpCodes.Callvirt, addNamespace);
+ }
+ yield return Instruction.Create(OpCodes.Ldtoken, context.Body.Method.DeclaringType);
+ yield return Instruction.Create(OpCodes.Call, module.Import(getTypeFromHandle));
+ yield return Instruction.Create(OpCodes.Callvirt, getAssembly);
+ var xtr = module.Import(typeof (XamlTypeResolver)).Resolve();
+ var xamlTypeResolverCtor = module.Import(xtr.Methods.First(md => md.IsConstructor && md.Parameters.Count == 2));
+ yield return Instruction.Create(OpCodes.Newobj, xamlTypeResolverCtor);
+ yield return Instruction.Create(OpCodes.Callvirt, addService);
+ }
+
+ if (node is IXmlLineInfo)
+ {
+ yield return Instruction.Create(OpCodes.Dup); //Dupicate the serviceProvider
+ yield return Instruction.Create(OpCodes.Ldtoken, module.Import(typeof (IXmlLineInfoProvider)));
+ yield return Instruction.Create(OpCodes.Call, module.Import(getTypeFromHandle));
+
+ foreach (var instruction in node.PushXmlLineInfo(context))
+ yield return instruction;
+
+ var lip = module.Import(typeof (XmlLineInfoProvider)).Resolve();
+ var lineInfoProviderCtor = module.Import(lip.Methods.First(md => md.IsConstructor && md.Parameters.Count == 1));
+ yield return Instruction.Create(OpCodes.Newobj, lineInfoProviderCtor);
+ yield return Instruction.Create(OpCodes.Callvirt, addService);
+ }
+ }
+ }
+} \ No newline at end of file