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.

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