From 00b6ad5a647dfdcc3174a9fd86a69d5be3690cc4 Mon Sep 17 00:00:00 2001 From: Anairkoen Schno Date: Tue, 24 Mar 2020 23:20:26 -0500 Subject: [PATCH] Added most of implicit value type serialization --- .../Config/Providers/JsonConfigProvider.cs | 6 +- .../GeneratedStoreImpl/Deserialization.cs | 101 +++++++++++++++--- .../Stores/GeneratedStoreImpl/MakeCreator.cs | 29 ++--- .../GeneratedStoreImpl/ObjectStructure.cs | 25 +++-- .../GeneratedStoreImpl/Serialization.cs | 34 +++++- .../Stores/GeneratedStoreImpl/Utility.cs | 13 ++- 6 files changed, 152 insertions(+), 56 deletions(-) diff --git a/IPA.Loader/Config/Providers/JsonConfigProvider.cs b/IPA.Loader/Config/Providers/JsonConfigProvider.cs index 34c7d0d3..250918cc 100644 --- a/IPA.Loader/Config/Providers/JsonConfigProvider.cs +++ b/IPA.Loader/Config/Providers/JsonConfigProvider.cs @@ -24,10 +24,10 @@ namespace IPA.Config.Providers public Value Load(FileInfo file) { - if (!file.Exists) return Value.Null(); - + if (!file.Exists) return Value.Null(); + try - { + { JToken jtok; using (var sreader = new StreamReader(file.OpenRead())) { diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs index ec969610..0dc3d5c5 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Deserialization.cs @@ -1,4 +1,5 @@ using IPA.Config.Data; +using IPA.Logging; using System; using System.Collections; using System.Collections.Generic; @@ -40,7 +41,8 @@ namespace IPA.Config.Stores return typeof(Map); // default for various objects } - private static void EmitDeserializeGeneratedValue(ILGenerator il, SerializedMemberInfo member, Type srcType, GetLocal GetLocal) + 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)); @@ -48,12 +50,12 @@ namespace IPA.Config.Stores var noCreate = il.DefineLabel(); il.Emit(OpCodes.Stloc, valuel); - EmitLoad(il, member, il => il.Emit(OpCodes.Ldarg_0)); + 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); + EmitCreateChildGenerated(il, member.Type, parentobj); il.MarkLabel(noCreate); il.Emit(OpCodes.Dup); @@ -61,15 +63,19 @@ namespace IPA.Config.Stores il.Emit(OpCodes.Callvirt, IGeneratedStore_Deserialize); } - private static void EmitDeserializeNullable(ILGenerator il, SerializedMemberInfo member, Type expected, GetLocal GetLocal) + private static void EmitDeserializeNullable(ILGenerator il, SerializedMemberInfo member, Type expected, GetLocal GetLocal, + Action thisarg, Action parentobj) { - EmitDeserializeValue(il, member, member.NullableWrappedType, expected, GetLocal); + 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) + 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 @@ -103,7 +109,66 @@ namespace IPA.Config.Stores } // 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)) { - EmitDeserializeGeneratedValue(il, member, expected, GetLocal); + 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 { @@ -112,7 +177,8 @@ namespace IPA.Config.Stores } } - private static void EmitDeserializeConverter(ILGenerator il, SerializedMemberInfo member, Label nextLabel, GetLocal GetLocal) + 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); @@ -121,7 +187,7 @@ namespace IPA.Config.Stores il.BeginExceptionBlock(); il.Emit(OpCodes.Ldsfld, member.ConverterField); il.Emit(OpCodes.Ldloc, stlocal); - il.Emit(OpCodes.Ldarg_0); + parentobj(il); if (member.IsGenericConverter) { @@ -151,13 +217,14 @@ namespace IPA.Config.Stores } // 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) + 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.IsNullable ? member.NullableWrappedType : member.Type); + var expectType = GetExpectedValueTypeForType(member.ConversionType); il.Emit(OpCodes.Dup); il.Emit(OpCodes.Brtrue_S, implLabel); // null check @@ -175,13 +242,13 @@ namespace IPA.Config.Stores 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)); + 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)); + EmitStore(il, member, il => il.Emit(OpCodes.Ldnull), thisobj); il.Emit(OpCodes.Br, nextLabel); } @@ -244,11 +311,11 @@ namespace IPA.Config.Stores il.MarkLabel(passedTypeCheck); var local = GetLocal(member.Type, 0); - if (member.HasConverter) EmitDeserializeConverter(il, member, nextLabel, GetLocal); - else if (member.IsNullable) EmitDeserializeNullable(il, member, expectType, GetLocal); - else EmitDeserializeValue(il, member, member.Type, expectType, GetLocal); + 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)); + EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, local), thisobj); } } } diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/MakeCreator.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/MakeCreator.cs index f47233db..c39fbcbc 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/MakeCreator.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/MakeCreator.cs @@ -1,5 +1,6 @@ using IPA.Config.Data; using IPA.Config.Stores.Attributes; +using IPA.Logging; using System; using System.Collections.Generic; using System.ComponentModel; @@ -17,6 +18,7 @@ namespace IPA.Config.Stores { internal delegate IConfigStore GeneratedStoreCreator(IGeneratedStore parent); + private static void GetMethodThis(ILGenerator il) => il.Emit(OpCodes.Ldarg_0); private static (GeneratedStoreCreator ctor, Type type) MakeCreator(Type type) { // note that this does not and should not use converters by default for everything @@ -44,7 +46,9 @@ namespace IPA.Config.Stores var isINotifyPropertyChanged = type.FindInterfaces((i, t) => i == (Type)t, typeof(INotifyPropertyChanged)).Length != 0; var hasNotifyAttribute = type.GetCustomAttribute() != null; - var structure = ReadObjectMembers(type, throwOnPrivateProperty: false, throwOnPrivateField: false); + var structure = ReadObjectMembers(type); + if (!structure.Any()) + Logger.config.Warn($"Custom type {type.FullName} has no accessible members"); #endregion var typeBuilder = Module.DefineType($"{type.FullName}", @@ -118,9 +122,9 @@ namespace IPA.Config.Stores { EmitStore(il, member, il => { - EmitLoad(il, member); // load the member - EmitCorrectMember(il, member, false, true, GetLocal); // correct it - }); + EmitLoad(il, member, GetMethodThis); // load the member + EmitCorrectMember(il, member, false, true, GetLocal, GetMethodThis, GetMethodThis); // correct it + }, GetMethodThis); } il.Emit(OpCodes.Pop); @@ -437,7 +441,7 @@ namespace IPA.Config.Stores il.MarkLabel(endErrorLabel); il.Emit(OpCodes.Ldloc_S, valueLocal); - EmitDeserializeMember(il, member, nextLabel, il => il.Emit(OpCodes.Ldloc_S, valueLocal), GetLocal); + EmitDeserializeMember(il, member, nextLabel, il => il.Emit(OpCodes.Ldloc_S, valueLocal), GetLocal, GetMethodThis, GetMethodThis); } il.MarkLabel(nextLabel); @@ -610,8 +614,8 @@ namespace IPA.Config.Stores EmitStore(il, member, il => { EmitLoad(il, member, il => il.Emit(OpCodes.Ldarg_1)); - EmitCorrectMember(il, member, false, false, GetLocal); - }); + EmitCorrectMember(il, member, false, false, GetLocal, GetMethodThis, GetMethodThis); + }, GetMethodThis); il.BeginCatchBlock(typeof(Exception)); @@ -713,7 +717,7 @@ namespace IPA.Config.Stores propBuilder.SetSetMethod(propSet); typeBuilder.DefineMethodOverride(propSet, set); - { // TODO: decide if i want to correct the value before or after i take the write lock + { var il = propSet.GetILGenerator(); var transactionLocal = il.DeclareLocal(IDisposable_t); @@ -727,7 +731,7 @@ namespace IPA.Config.Stores il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); - EmitCorrectMember(il, member, false, false, GetLocal); + EmitCorrectMember(il, member, false, false, GetLocal, GetMethodThis, GetMethodThis); il.Emit(OpCodes.Call, set); il.BeginFinallyBlock(); @@ -773,7 +777,8 @@ namespace IPA.Config.Stores } // expects start value on stack, exits with final value on stack - private static void EmitCorrectMember(ILGenerator il, SerializedMemberInfo member, bool shouldLock, bool alwaysNew, GetLocal GetLocal) + private static void EmitCorrectMember(ILGenerator il, SerializedMemberInfo member, bool shouldLock, bool alwaysNew, GetLocal GetLocal, + Action thisobj, Action parentobj) { if (!NeedsCorrection(member)) return; // this will never be called for a custom value type @@ -804,13 +809,13 @@ namespace IPA.Config.Stores il.Emit(OpCodes.Stloc, valLocal); if (!alwaysNew) { - EmitLoad(il, member, il => il.Emit(OpCodes.Ldarg_0)); + EmitLoad(il, member, thisobj); il.Emit(OpCodes.Dup); il.Emit(OpCodes.Isinst, typeof(IGeneratedStore)); il.Emit(OpCodes.Brtrue_S, noCreate); il.Emit(OpCodes.Pop); } - EmitCreateChildGenerated(il, member.Type); + EmitCreateChildGenerated(il, member.Type, parentobj); il.MarkLabel(noCreate); il.Emit(OpCodes.Dup); diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs index 3e689820..d8dcb42a 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs @@ -119,7 +119,16 @@ namespace IPA.Config.Stores return true; } - private static IEnumerable ReadObjectMembers(Type type, bool throwOnPrivateField = false, bool throwOnPrivateProperty = false) + private static readonly Dictionary objectStructureCache = new Dictionary(); + + private static IEnumerable ReadObjectMembers(Type type) + { + if (!objectStructureCache.TryGetValue(type, out var structure)) + objectStructureCache.Add(type, structure = ReadObjectMembersInternal(type).ToArray()); + return structure; + } + + private static IEnumerable ReadObjectMembersInternal(Type type) { var structure = new List(); @@ -132,12 +141,7 @@ namespace IPA.Config.Stores } if (prop.GetGetMethod(true)?.IsPrivate ?? true) { // we enter this block if the getter is inacessible or doesn't exist - //continue; // ignore props without getter - // TODO: is this what I want to do for private properties? - if (throwOnPrivateProperty) - throw new InvalidOperationException($"Cannot compile serializer for type {type} with private property {prop.Name}"); - else - continue; + continue; // ignore props without getter } var smi = new SerializedMemberInfo @@ -158,12 +162,7 @@ namespace IPA.Config.Stores foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { if (field.IsPrivate) - { - if (throwOnPrivateField && !field.Name.Contains(">k__BackingField")) // don't throw for private fields that are just backing fields - throw new InvalidOperationException($"Cannot compile serializer for type {type} with private field {field.Name}"); - else - continue; - } + continue; var smi = new SerializedMemberInfo { diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs index 170f46b6..584d0656 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Serialization.cs @@ -1,4 +1,5 @@ using IPA.Config.Data; +using IPA.Logging; using System; using System.Collections.Generic; using System.Linq; @@ -13,9 +14,9 @@ namespace IPA.Config.Stores internal static partial class GeneratedStoreImpl { // emit takes no args, leaves Value at top of stack - private static void EmitSerializeMember(ILGenerator il, SerializedMemberInfo member, GetLocal GetLocal) + private static void EmitSerializeMember(ILGenerator il, SerializedMemberInfo member, GetLocal GetLocal, Action thisarg = null) { - EmitLoad(il, member); + EmitLoad(il, member, thisarg); var endSerialize = il.DefineLabel(); @@ -131,21 +132,46 @@ namespace IPA.Config.Stores il.Emit(OpCodes.Isinst, typeof(IGeneratedStore)); il.Emit(OpCodes.Brtrue_S, noCreate); il.Emit(OpCodes.Stloc, stlocal); - EmitCreateChildGenerated(il, member.Type); + EmitCreateChildGenerated(il, member.Type, GetMethodThis); il.Emit(OpCodes.Dup); il.Emit(OpCodes.Ldloc, stlocal); il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Callvirt, IGeneratedStoreT_CopyFrom); il.Emit(OpCodes.Dup); il.Emit(OpCodes.Stloc, stlocal); - EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, stlocal)); + EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, stlocal), thisarg); il.MarkLabel(noCreate); } il.Emit(OpCodes.Callvirt, IGeneratedStore_Serialize); } else { // generate serialization for value types + var MapCreate = typeof(Value).GetMethod(nameof(Value.Map)); + var MapAdd = typeof(Map).GetMethod(nameof(Map.Add)); + var valueLocal = GetLocal(memberConversionType); + + var structure = ReadObjectMembers(memberConversionType); + if (!structure.Any()) + { + Logger.config.Warn($"Custom value type {memberConversionType.FullName} (when compiling serialization of" + + $" {member.Name} on {member.Member.DeclaringType.FullName}) has no accessible members"); + il.Emit(OpCodes.Pop); + } + else + { + il.Emit(OpCodes.Stloc, valueLocal); + } + + il.Emit(OpCodes.Call, MapCreate); + + foreach (var mem in structure) + { + il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Ldstr, mem.Name); + EmitSerializeMember(il, mem, GetLocal, il => il.Emit(OpCodes.Ldloca, valueLocal)); + il.Emit(OpCodes.Call, MapAdd); + } } } diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Utility.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Utility.cs index 22e8f46c..c0da244e 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/Utility.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/Utility.cs @@ -49,10 +49,9 @@ namespace IPA.Config.Stores return GetLocal; } - private static void EmitLoad(ILGenerator il, SerializedMemberInfo member, Action thisarg = null) + private static void EmitLoad(ILGenerator il, SerializedMemberInfo member, Action thisarg) { - if (thisarg == null) - thisarg = il => il.Emit(OpCodes.Ldarg_0); + thisarg ??= il => il.Emit(OpCodes.Ldarg_0); thisarg(il); // load this @@ -68,9 +67,9 @@ namespace IPA.Config.Stores } } - private static void EmitStore(ILGenerator il, SerializedMemberInfo member, Action value) + private static void EmitStore(ILGenerator il, SerializedMemberInfo member, Action value, Action thisobj) { - il.Emit(OpCodes.Ldarg_0); // load this + thisobj(il); value(il); if (member.IsField) @@ -196,10 +195,10 @@ namespace IPA.Config.Stores } } - private static void EmitCreateChildGenerated(ILGenerator il, Type childType) + private static void EmitCreateChildGenerated(ILGenerator il, Type childType, Action parentobj) { var method = CreateGParent.MakeGenericMethod(childType); - il.Emit(OpCodes.Ldarg_0); + parentobj(il); il.Emit(OpCodes.Call, method); } }