using IPA.Config.Data; using IPA.Logging; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Text; using System.Threading.Tasks; using Boolean = IPA.Config.Data.Boolean; #if NET3 using Net3_Proxy; using Array = Net3_Proxy.Array; #endif namespace IPA.Config.Stores { internal static partial class GeneratedStoreImpl { private static Type GetExpectedValueTypeForType(Type valT) { if (typeof(Value).IsAssignableFrom(valT)) // this is a Value subtype return valT; if (valT == typeof(string) || valT == typeof(char)) return typeof(Text); if (valT == typeof(bool)) return typeof(Boolean); if (valT == typeof(byte) || valT == typeof(sbyte) || valT == typeof(short) || valT == typeof(ushort) || valT == typeof(int) || valT == typeof(uint) || valT == typeof(long) || valT == typeof(IntPtr)) return typeof(Integer); if (valT == typeof(float) || valT == typeof(double) || valT == typeof(decimal) || valT == typeof(ulong) // ulong gets put into this, because decimal can hold it || valT == typeof(UIntPtr)) return typeof(FloatingPoint); if (typeof(IEnumerable).IsAssignableFrom(valT)) return typeof(List); // TODO: fill this out the rest of the way return typeof(Map); // default for various objects } private static void EmitDeserializeGeneratedValue(ILGenerator il, SerializedMemberInfo member, Type srcType, GetLocal GetLocal, Action thisarg, Action parentobj) { var IGeneratedStore_Deserialize = typeof(IGeneratedStore).GetMethod(nameof(IGeneratedStore.Deserialize)); var valuel = GetLocal(srcType, 0); var noCreate = il.DefineLabel(); il.Emit(OpCodes.Stloc, valuel); EmitLoad(il, member, thisarg); il.Emit(OpCodes.Dup); il.Emit(OpCodes.Isinst, typeof(IGeneratedStore)); il.Emit(OpCodes.Brtrue_S, noCreate); il.Emit(OpCodes.Pop); EmitCreateChildGenerated(il, member.Type, parentobj); il.MarkLabel(noCreate); il.Emit(OpCodes.Dup); il.Emit(OpCodes.Ldloc, valuel); il.Emit(OpCodes.Callvirt, IGeneratedStore_Deserialize); } private static void EmitDeserializeNullable(ILGenerator il, SerializedMemberInfo member, Type expected, GetLocal GetLocal, Action thisarg, Action parentobj) { thisarg ??= il => il.Emit(OpCodes.Ldarg_0); parentobj ??= thisarg; EmitDeserializeValue(il, member, member.NullableWrappedType, expected, GetLocal, thisarg, parentobj); il.Emit(OpCodes.Newobj, member.Nullable_Construct); } // top of stack is the Value to deserialize; the type will be as returned from GetExpectedValueTypeForType // after, top of stack will be thing to write to field private static void EmitDeserializeValue(ILGenerator il, SerializedMemberInfo member, Type targetType, Type expected, GetLocal GetLocal, Action thisarg, Action parentobj) { if (typeof(Value).IsAssignableFrom(targetType)) return; // do nothing if (expected == typeof(Text)) { var getter = expected.GetProperty(nameof(Text.Value)).GetGetMethod(); il.Emit(OpCodes.Call, getter); if (targetType == typeof(char)) { var strIndex = typeof(string).GetProperty("Chars").GetGetMethod(); // string's indexer is specially named Chars il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Call, strIndex); } } else if (expected == typeof(Boolean)) { var getter = expected.GetProperty(nameof(Boolean.Value)).GetGetMethod(); il.Emit(OpCodes.Call, getter); } else if (expected == typeof(Integer)) { var getter = expected.GetProperty(nameof(Integer.Value)).GetGetMethod(); il.Emit(OpCodes.Call, getter); EmitNumberConvertTo(il, targetType, getter.ReturnType); } else if (expected == typeof(FloatingPoint)) { var getter = expected.GetProperty(nameof(FloatingPoint.Value)).GetGetMethod(); il.Emit(OpCodes.Call, getter); EmitNumberConvertTo(il, targetType, getter.ReturnType); } // TODO: implement stuff for lists and maps of various types (probably call out somewhere else to figure out what to do) else if (expected == typeof(Map)) { if (!targetType.IsValueType) { EmitDeserializeGeneratedValue(il, member, expected, GetLocal, thisarg, parentobj); } else { var Map_TryGetValue = typeof(Map).GetMethod(nameof(Map.TryGetValue)); var mapLocal = GetLocal(typeof(Map)); var resultLocal = GetLocal(targetType, 1); var valueLocal = GetLocal(typeof(Value)); var structure = ReadObjectMembers(targetType); if (!structure.Any()) { Logger.config.Warn($"Custom value type {targetType.FullName} (when compiling serialization of" + $" {member.Name} on {member.Member.DeclaringType.FullName}) has no accessible members"); il.Emit(OpCodes.Pop); il.Emit(OpCodes.Ldloca, resultLocal); il.Emit(OpCodes.Initobj, targetType); } else { il.Emit(OpCodes.Stloc, mapLocal); EmitLoad(il, member, thisarg); il.Emit(OpCodes.Stloc, resultLocal); // TODO: pull this and the MakeCreator version out into another function var nextLabel = il.DefineLabel(); // head of stack is Map instance foreach (var mem in structure) { il.MarkLabel(nextLabel); nextLabel = il.DefineLabel(); var endErrorLabel = il.DefineLabel(); il.Emit(OpCodes.Ldloc, mapLocal); il.Emit(OpCodes.Ldstr, mem.Name); il.Emit(OpCodes.Ldloca_S, valueLocal); il.Emit(OpCodes.Call, Map_TryGetValue); il.Emit(OpCodes.Brtrue_S, endErrorLabel); EmitLogError(il, $"Missing key {mem.Name}", tailcall: false); il.Emit(OpCodes.Br, nextLabel); il.MarkLabel(endErrorLabel); il.Emit(OpCodes.Ldloc_S, valueLocal); EmitDeserializeMember(il, mem, nextLabel, il => il.Emit(OpCodes.Ldloc_S, valueLocal), GetLocal, il => il.Emit(OpCodes.Ldloca, resultLocal), parentobj); } il.MarkLabel(nextLabel); } il.Emit(OpCodes.Ldloc, resultLocal); /*il.Emit(OpCodes.Ldloca, resultLocal); il.Emit(OpCodes.Ldobj, targetType);*/ } } else { il.Emit(OpCodes.Pop); il.Emit(OpCodes.Ldnull); } } private static void EmitDeserializeConverter(ILGenerator il, SerializedMemberInfo member, Label nextLabel, GetLocal GetLocal, Action thisobj, Action parentobj) { var stlocal = GetLocal(typeof(Value)); var valLocal = GetLocal(member.Type); il.Emit(OpCodes.Stloc, stlocal); il.BeginExceptionBlock(); il.Emit(OpCodes.Ldsfld, member.ConverterField); il.Emit(OpCodes.Ldloc, stlocal); parentobj(il); if (member.IsGenericConverter) { var fromValueBase = member.ConverterBase.GetMethod(nameof(ValueConverter.FromValue), new[] { typeof(Value), typeof(object) }); var fromValue = member.Converter.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .FirstOrDefault(m => m.GetBaseDefinition() == fromValueBase) ?? fromValueBase; il.Emit(OpCodes.Call, fromValue); } else { var fromValueBase = typeof(IValueConverter).GetMethod(nameof(IValueConverter.FromValue), new[] { typeof(Value), typeof(object) }); var fromValue = member.Converter.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .FirstOrDefault(m => m.GetBaseDefinition() == fromValueBase) ?? fromValueBase; il.Emit(OpCodes.Call, fromValue); if (member.Type.IsValueType) il.Emit(OpCodes.Unbox); } il.Emit(OpCodes.Stloc, valLocal); il.BeginCatchBlock(typeof(Exception)); EmitWarnException(il, "Error occurred while deserializing"); il.Emit(OpCodes.Leave, nextLabel); il.EndExceptionBlock(); il.Emit(OpCodes.Ldloc, valLocal); } // emit takes the value being deserialized, logs on error, leaves nothing on stack private static void EmitDeserializeMember(ILGenerator il, SerializedMemberInfo member, Label nextLabel, Action getValue, GetLocal GetLocal, Action thisobj, Action parentobj) { var Object_GetType = typeof(object).GetMethod(nameof(Object.GetType)); var implLabel = il.DefineLabel(); var passedTypeCheck = il.DefineLabel(); var expectType = GetExpectedValueTypeForType(member.ConversionType); il.Emit(OpCodes.Dup); il.Emit(OpCodes.Brtrue_S, implLabel); // null check if (!member.AllowNull) { il.Emit(OpCodes.Pop); EmitLogError(il, $"Member {member.Name} ({member.Type}) not nullable", tailcall: false, expected: il => EmitTypeof(il, expectType)); il.Emit(OpCodes.Br, nextLabel); } else if (member.IsNullable) { il.Emit(OpCodes.Pop); var valTLocal = GetLocal(member.Type, 0); il.Emit(OpCodes.Ldloca, valTLocal); il.Emit(OpCodes.Initobj, member.Type); EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, valTLocal), thisobj); il.Emit(OpCodes.Br, nextLabel); } else { il.Emit(OpCodes.Pop); EmitStore(il, member, il => il.Emit(OpCodes.Ldnull), thisobj); il.Emit(OpCodes.Br, nextLabel); } if (!member.HasConverter) { il.MarkLabel(implLabel); il.Emit(OpCodes.Isinst, expectType); //replaces on stack il.Emit(OpCodes.Dup); // duplicate cloned value il.Emit(OpCodes.Brtrue, passedTypeCheck); // null check } var errorHandle = il.DefineLabel(); // special cases to handle coersion between Float and Int if (member.HasConverter) il.MarkLabel(implLabel); else if (expectType == typeof(FloatingPoint)) { var specialTypeCheck = il.DefineLabel(); il.Emit(OpCodes.Pop); getValue(il); il.Emit(OpCodes.Isinst, typeof(Integer)); //replaces on stack il.Emit(OpCodes.Dup); // duplicate cloned value il.Emit(OpCodes.Brfalse, errorHandle); // null check var Integer_CoerceToFloat = typeof(Integer).GetMethod(nameof(Integer.AsFloat)); il.Emit(OpCodes.Call, Integer_CoerceToFloat); il.Emit(OpCodes.Br, passedTypeCheck); } else if (expectType == typeof(Integer)) { var specialTypeCheck = il.DefineLabel(); il.Emit(OpCodes.Pop); getValue(il); il.Emit(OpCodes.Isinst, typeof(FloatingPoint)); //replaces on stack il.Emit(OpCodes.Dup); // duplicate cloned value il.Emit(OpCodes.Brfalse, errorHandle); // null check var Float_CoerceToInt = typeof(FloatingPoint).GetMethod(nameof(FloatingPoint.AsInteger)); il.Emit(OpCodes.Call, Float_CoerceToInt); il.Emit(OpCodes.Br, passedTypeCheck); } if (!member.HasConverter) { il.MarkLabel(errorHandle); il.Emit(OpCodes.Pop); EmitLogError(il, $"Unexpected type deserializing {member.Name}", tailcall: false, expected: il => EmitTypeof(il, expectType), found: il => { getValue(il); il.Emit(OpCodes.Callvirt, Object_GetType); }); il.Emit(OpCodes.Br, nextLabel); } il.MarkLabel(passedTypeCheck); var local = GetLocal(member.Type, 0); if (member.HasConverter) EmitDeserializeConverter(il, member, nextLabel, GetLocal, thisobj, parentobj); else if (member.IsNullable) EmitDeserializeNullable(il, member, expectType, GetLocal, thisobj, parentobj); else EmitDeserializeValue(il, member, member.Type, expectType, GetLocal, thisobj, parentobj); il.Emit(OpCodes.Stloc, local); EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, local), thisobj); } } }