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.

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