You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

302 lines
13 KiB

  1. using IPA.Config.Data;
  2. using IPA.Logging;
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using System.Reflection;
  8. using System.Reflection.Emit;
  9. using System.Text;
  10. using System.Threading.Tasks;
  11. using Boolean = IPA.Config.Data.Boolean;
  12. #if NET3
  13. using Net3_Proxy;
  14. using Array = Net3_Proxy.Array;
  15. #endif
  16. namespace IPA.Config.Stores
  17. {
  18. internal static partial class GeneratedStoreImpl
  19. {
  20. private static void EmitDeserializeGeneratedValue(ILGenerator il, SerializedMemberInfo member, Type srcType, LocalAllocator GetLocal,
  21. Action<ILGenerator> thisarg, Action<ILGenerator> parentobj)
  22. {
  23. var IGeneratedStore_Deserialize = typeof(IGeneratedStore).GetMethod(nameof(IGeneratedStore.Deserialize));
  24. using var valuel = GetLocal.Allocate(srcType);
  25. var noCreate = il.DefineLabel();
  26. il.Emit(OpCodes.Stloc, valuel);
  27. EmitLoad(il, member, thisarg);
  28. il.Emit(OpCodes.Dup);
  29. il.Emit(OpCodes.Isinst, typeof(IGeneratedStore));
  30. il.Emit(OpCodes.Brtrue_S, noCreate);
  31. il.Emit(OpCodes.Pop);
  32. EmitCreateChildGenerated(il, member.Type, parentobj);
  33. il.MarkLabel(noCreate);
  34. il.Emit(OpCodes.Dup);
  35. il.Emit(OpCodes.Ldloc, valuel);
  36. il.Emit(OpCodes.Callvirt, IGeneratedStore_Deserialize);
  37. }
  38. private static void EmitDeserializeNullable(ILGenerator il, SerializedMemberInfo member, Type expected, LocalAllocator GetLocal,
  39. Action<ILGenerator> thisarg, Action<ILGenerator> parentobj)
  40. {
  41. thisarg ??= il => il.Emit(OpCodes.Ldarg_0);
  42. parentobj ??= thisarg;
  43. EmitDeserializeValue(il, member, member.NullableWrappedType, expected, GetLocal, thisarg, parentobj);
  44. il.Emit(OpCodes.Newobj, member.Nullable_Construct);
  45. }
  46. // top of stack is the Value to deserialize; the type will be as returned from GetExpectedValueTypeForType
  47. // after, top of stack will be thing to write to field
  48. private static void EmitDeserializeValue(ILGenerator il, SerializedMemberInfo member, Type targetType, Type expected, LocalAllocator GetLocal,
  49. Action<ILGenerator> thisarg, Action<ILGenerator> parentobj)
  50. {
  51. if (typeof(Value).IsAssignableFrom(targetType)) return; // do nothing
  52. if (expected == typeof(Text))
  53. {
  54. var getter = expected.GetProperty(nameof(Text.Value)).GetGetMethod();
  55. il.Emit(OpCodes.Call, getter);
  56. if (targetType == typeof(char))
  57. {
  58. var strIndex = typeof(string).GetProperty("Chars").GetGetMethod(); // string's indexer is specially named Chars
  59. il.Emit(OpCodes.Ldc_I4_0);
  60. il.Emit(OpCodes.Call, strIndex);
  61. }
  62. }
  63. else if (expected == typeof(Boolean))
  64. {
  65. var getter = expected.GetProperty(nameof(Boolean.Value)).GetGetMethod();
  66. il.Emit(OpCodes.Call, getter);
  67. }
  68. else if (expected == typeof(Integer))
  69. {
  70. var getter = expected.GetProperty(nameof(Integer.Value)).GetGetMethod();
  71. il.Emit(OpCodes.Call, getter);
  72. EmitNumberConvertTo(il, targetType, getter.ReturnType);
  73. }
  74. else if (expected == typeof(FloatingPoint))
  75. {
  76. var getter = expected.GetProperty(nameof(FloatingPoint.Value)).GetGetMethod();
  77. il.Emit(OpCodes.Call, getter);
  78. EmitNumberConvertTo(il, targetType, getter.ReturnType);
  79. } // TODO: implement stuff for lists and maps of various types (probably call out somewhere else to figure out what to do)
  80. else if (expected == typeof(Map))
  81. {
  82. if (!targetType.IsValueType)
  83. {
  84. EmitDeserializeGeneratedValue(il, member, expected, GetLocal, thisarg, parentobj);
  85. }
  86. else
  87. {
  88. using var mapLocal = GetLocal.Allocate(typeof(Map));
  89. using var resultLocal = GetLocal.Allocate(targetType);
  90. using var valueLocal = GetLocal.Allocate(typeof(Value));
  91. var structure = ReadObjectMembers(targetType);
  92. if (!structure.Any())
  93. {
  94. Logger.config.Warn($"Custom value type {targetType.FullName} (when compiling serialization of" +
  95. $" {member.Name} on {member.Member.DeclaringType.FullName}) has no accessible members");
  96. il.Emit(OpCodes.Pop);
  97. il.Emit(OpCodes.Ldloca, resultLocal);
  98. il.Emit(OpCodes.Initobj, targetType);
  99. }
  100. else
  101. {
  102. il.Emit(OpCodes.Stloc, mapLocal);
  103. EmitLoad(il, member, thisarg);
  104. il.Emit(OpCodes.Stloc, resultLocal);
  105. EmitDeserializeStructure(il, structure, mapLocal, valueLocal, GetLocal, il => il.Emit(OpCodes.Ldloca, resultLocal), parentobj);
  106. }
  107. il.Emit(OpCodes.Ldloc, resultLocal);
  108. }
  109. }
  110. else
  111. {
  112. Logger.config.Warn($"Implicit conversions to {expected} are not currently implemented");
  113. il.Emit(OpCodes.Pop);
  114. il.Emit(OpCodes.Ldnull);
  115. }
  116. }
  117. private static void EmitDeserializeStructure(ILGenerator il, IEnumerable<SerializedMemberInfo> structure,
  118. LocalBuilder mapLocal, LocalBuilder valueLocal,
  119. LocalAllocator GetLocal, Action<ILGenerator> thisobj, Action<ILGenerator> parentobj)
  120. {
  121. var Map_TryGetValue = typeof(Map).GetMethod(nameof(Map.TryGetValue));
  122. // head of stack is Map instance
  123. foreach (var mem in structure)
  124. {
  125. var nextLabel = il.DefineLabel();
  126. var endErrorLabel = il.DefineLabel();
  127. il.Emit(OpCodes.Ldloc, mapLocal);
  128. il.Emit(OpCodes.Ldstr, mem.Name);
  129. il.Emit(OpCodes.Ldloca_S, valueLocal);
  130. il.Emit(OpCodes.Call, Map_TryGetValue);
  131. il.Emit(OpCodes.Brtrue_S, endErrorLabel);
  132. EmitLogError(il, $"Missing key {mem.Name}", tailcall: false);
  133. il.Emit(OpCodes.Br, nextLabel);
  134. il.MarkLabel(endErrorLabel);
  135. il.Emit(OpCodes.Ldloc_S, valueLocal);
  136. EmitDeserializeMember(il, mem, nextLabel, il => il.Emit(OpCodes.Ldloc_S, valueLocal), GetLocal, thisobj, parentobj);
  137. il.MarkLabel(nextLabel);
  138. }
  139. }
  140. private static void EmitDeserializeConverter(ILGenerator il, SerializedMemberInfo member, Label nextLabel, LocalAllocator GetLocal,
  141. Action<ILGenerator> thisobj, Action<ILGenerator> parentobj)
  142. {
  143. using var stlocal = GetLocal.Allocate(typeof(Value));
  144. using var valLocal = GetLocal.Allocate(member.Type);
  145. il.Emit(OpCodes.Stloc, stlocal);
  146. il.BeginExceptionBlock();
  147. il.Emit(OpCodes.Ldsfld, member.ConverterField);
  148. il.Emit(OpCodes.Ldloc, stlocal);
  149. parentobj(il);
  150. if (member.IsGenericConverter)
  151. {
  152. var fromValueBase = member.ConverterBase.GetMethod(nameof(ValueConverter<int>.FromValue),
  153. new[] { typeof(Value), typeof(object) });
  154. var fromValue = member.Converter.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
  155. .FirstOrDefault(m => m.GetBaseDefinition() == fromValueBase) ?? fromValueBase;
  156. il.Emit(OpCodes.Call, fromValue);
  157. }
  158. else
  159. {
  160. var fromValueBase = typeof(IValueConverter).GetMethod(nameof(IValueConverter.FromValue),
  161. new[] { typeof(Value), typeof(object) });
  162. var fromValue = member.Converter.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
  163. .FirstOrDefault(m => m.GetBaseDefinition() == fromValueBase) ?? fromValueBase;
  164. il.Emit(OpCodes.Call, fromValue);
  165. if (member.Type.IsValueType)
  166. il.Emit(OpCodes.Unbox);
  167. }
  168. il.Emit(OpCodes.Stloc, valLocal);
  169. il.BeginCatchBlock(typeof(Exception));
  170. EmitWarnException(il, "Error occurred while deserializing");
  171. il.Emit(OpCodes.Leave, nextLabel);
  172. il.EndExceptionBlock();
  173. il.Emit(OpCodes.Ldloc, valLocal);
  174. }
  175. // emit takes the value being deserialized, logs on error, leaves nothing on stack
  176. private static void EmitDeserializeMember(ILGenerator il, SerializedMemberInfo member, Label nextLabel, Action<ILGenerator> getValue, LocalAllocator GetLocal,
  177. Action<ILGenerator> thisobj, Action<ILGenerator> parentobj)
  178. {
  179. var Object_GetType = typeof(object).GetMethod(nameof(Object.GetType));
  180. var implLabel = il.DefineLabel();
  181. var passedTypeCheck = il.DefineLabel();
  182. var expectType = GetExpectedValueTypeForType(member.ConversionType);
  183. il.Emit(OpCodes.Dup);
  184. il.Emit(OpCodes.Brtrue_S, implLabel); // null check
  185. if (!member.AllowNull)
  186. {
  187. il.Emit(OpCodes.Pop);
  188. EmitLogError(il, $"Member {member.Name} ({member.Type}) not nullable", tailcall: false,
  189. expected: il => EmitTypeof(il, expectType));
  190. il.Emit(OpCodes.Br, nextLabel);
  191. }
  192. else if (member.IsNullable)
  193. {
  194. il.Emit(OpCodes.Pop);
  195. using var valTLocal = GetLocal.Allocate(member.Type);
  196. il.Emit(OpCodes.Ldloca, valTLocal);
  197. il.Emit(OpCodes.Initobj, member.Type);
  198. EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, valTLocal), thisobj);
  199. il.Emit(OpCodes.Br, nextLabel);
  200. }
  201. else
  202. {
  203. il.Emit(OpCodes.Pop);
  204. EmitStore(il, member, il => il.Emit(OpCodes.Ldnull), thisobj);
  205. il.Emit(OpCodes.Br, nextLabel);
  206. }
  207. if (!member.HasConverter)
  208. {
  209. il.MarkLabel(implLabel);
  210. il.Emit(OpCodes.Isinst, expectType); //replaces on stack
  211. il.Emit(OpCodes.Dup); // duplicate cloned value
  212. il.Emit(OpCodes.Brtrue, passedTypeCheck); // null check
  213. }
  214. var errorHandle = il.DefineLabel();
  215. // special cases to handle coersion between Float and Int
  216. if (member.HasConverter)
  217. il.MarkLabel(implLabel);
  218. else if (expectType == typeof(FloatingPoint))
  219. {
  220. var specialTypeCheck = il.DefineLabel();
  221. il.Emit(OpCodes.Pop);
  222. getValue(il);
  223. il.Emit(OpCodes.Isinst, typeof(Integer)); //replaces on stack
  224. il.Emit(OpCodes.Dup); // duplicate cloned value
  225. il.Emit(OpCodes.Brfalse, errorHandle); // null check
  226. var Integer_CoerceToFloat = typeof(Integer).GetMethod(nameof(Integer.AsFloat));
  227. il.Emit(OpCodes.Call, Integer_CoerceToFloat);
  228. il.Emit(OpCodes.Br, passedTypeCheck);
  229. }
  230. else if (expectType == typeof(Integer))
  231. {
  232. var specialTypeCheck = il.DefineLabel();
  233. il.Emit(OpCodes.Pop);
  234. getValue(il);
  235. il.Emit(OpCodes.Isinst, typeof(FloatingPoint)); //replaces on stack
  236. il.Emit(OpCodes.Dup); // duplicate cloned value
  237. il.Emit(OpCodes.Brfalse, errorHandle); // null check
  238. var Float_CoerceToInt = typeof(FloatingPoint).GetMethod(nameof(FloatingPoint.AsInteger));
  239. il.Emit(OpCodes.Call, Float_CoerceToInt);
  240. il.Emit(OpCodes.Br, passedTypeCheck);
  241. }
  242. if (!member.HasConverter)
  243. {
  244. il.MarkLabel(errorHandle);
  245. il.Emit(OpCodes.Pop);
  246. EmitLogError(il, $"Unexpected type deserializing {member.Name}", tailcall: false,
  247. expected: il => EmitTypeof(il, expectType), found: il =>
  248. {
  249. getValue(il);
  250. il.Emit(OpCodes.Callvirt, Object_GetType);
  251. });
  252. il.Emit(OpCodes.Br, nextLabel);
  253. }
  254. il.MarkLabel(passedTypeCheck);
  255. using var local = GetLocal.Allocate(member.Type);
  256. if (member.HasConverter) EmitDeserializeConverter(il, member, nextLabel, GetLocal, thisobj, parentobj);
  257. else if (member.IsNullable) EmitDeserializeNullable(il, member, expectType, GetLocal, thisobj, parentobj);
  258. else EmitDeserializeValue(il, member, member.Type, expectType, GetLocal, thisobj, parentobj);
  259. il.Emit(OpCodes.Stloc, local);
  260. EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, local), thisobj);
  261. }
  262. }
  263. }