MethodCompatiblity.cs 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. #if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
  2. using System;
  3. using System.Reflection;
  4. using SingularityGroup.HotReload.MonoMod.Utils;
  5. namespace SingularityGroup.HotReload {
  6. static class MethodCompatiblity {
  7. internal static string CheckCompatibility(MethodBase previousMethod, MethodBase patchMethod) {
  8. var previousConstructor = previousMethod as ConstructorInfo;
  9. var patchConstructor = patchMethod as ConstructorInfo;
  10. if(previousConstructor != null && !ReferenceEquals(patchConstructor, null)) {
  11. return AreConstructorsCompatible(previousConstructor, patchConstructor);
  12. }
  13. var previousMethodInfo = previousMethod as MethodInfo;
  14. var patchMethodInfo = patchMethod as MethodInfo;
  15. if(!ReferenceEquals(previousMethodInfo, null) && !ReferenceEquals(patchMethodInfo, null)) {
  16. return AreMethodInfosCompatible(previousMethodInfo, patchMethodInfo);
  17. }
  18. return "unknown issue";
  19. }
  20. static string AreMethodBasesCompatible(MethodBase previousMethod, MethodBase patchMethod) {
  21. if(previousMethod.Name != patchMethod.Name) {
  22. return "Method name mismatch";
  23. }
  24. //Declaring type of patch method is different from the target method but their full name (namespace + name) is equal
  25. bool isDeclaringTypeCompatible = false;
  26. var declaringType = patchMethod.DeclaringType;
  27. while (declaringType != null) {
  28. if(previousMethod.DeclaringType?.FullName == declaringType.FullName) {
  29. isDeclaringTypeCompatible = true;
  30. break;
  31. }
  32. declaringType = declaringType.BaseType;
  33. }
  34. if (!isDeclaringTypeCompatible) {
  35. return "Declaring type name mismatch";
  36. }
  37. //Check in case type parameter overloads to distinguish between: void M<T>() { } <-> void M() { }
  38. if(previousMethod.IsGenericMethodDefinition != patchMethod.IsGenericMethodDefinition) {
  39. return "IsGenericMethodDefinition mismatch";
  40. }
  41. var prevParams = previousMethod.GetParameters();
  42. var patchParams = patchMethod.GetParameters();
  43. ArraySegment<ParameterInfo> patchParamsSegment;
  44. bool patchMethodHasExplicitThis;
  45. if(previousMethod.IsStatic || previousMethod.Name.Contains("<") && !patchMethod.IsStatic) {
  46. patchMethodHasExplicitThis = false;
  47. } else {
  48. patchMethodHasExplicitThis = true;
  49. }
  50. if(LikelyHasExplicitThis(prevParams, patchParams, previousMethod)) {
  51. patchMethodHasExplicitThis = true;
  52. }
  53. //Special edge case: User added static keyword to method. No explicit this will be generated in that case
  54. if(!previousMethod.IsStatic && patchMethod.IsStatic && !LikelyHasExplicitThis(prevParams, patchParams, previousMethod)) {
  55. patchMethodHasExplicitThis = false;
  56. }
  57. if(patchMethodHasExplicitThis) {
  58. //Special case: patch method for an instance method is static and has an explicit this parameter.
  59. //If the patch method doesn't have any parameters it is not compatible.
  60. if(patchParams.Length == 0) {
  61. return "missing this parameter";
  62. }
  63. //this parameter has to be the declaring type
  64. if(!ParamTypeMatches(patchParams[0].ParameterType, previousMethod.DeclaringType)) {
  65. return "this parameter type mismatch";
  66. }
  67. //Ignore the this parameter and compare the remaining ones.
  68. patchParamsSegment = new ArraySegment<ParameterInfo>(patchParams, 1, patchParams.Length - 1);
  69. } else {
  70. patchParamsSegment = new ArraySegment<ParameterInfo>(patchParams);
  71. }
  72. return CompareParameters(new ArraySegment<ParameterInfo>(prevParams), patchParamsSegment);
  73. }
  74. static bool LikelyHasExplicitThis(ParameterInfo[] prevParams, ParameterInfo[] patchParams, MethodBase previousMethod) {
  75. if (patchParams.Length != prevParams.Length + 1) {
  76. return false;
  77. }
  78. var patchT = patchParams[0].ParameterType;
  79. if (!ParamTypeMatches(patchT, previousMethod.DeclaringType)) {
  80. return false;
  81. }
  82. return patchParams[0].Name == "this";
  83. }
  84. static bool ParamTypeMatches(Type patchT, Type originalT) {
  85. return patchT == originalT || patchT.IsByRef && patchT.GetElementType() == originalT;
  86. }
  87. static string CompareParameters(ArraySegment<ParameterInfo> x, ArraySegment<ParameterInfo> y) {
  88. if(x.Count != y.Count) {
  89. return "parameter count mismatch";
  90. }
  91. for (var i = 0; i < x.Count; i++) {
  92. if(x.Array[i + x.Offset].ParameterType != y.Array[i + y.Offset].ParameterType) {
  93. return "parameter type mismatch";
  94. }
  95. }
  96. return null;
  97. }
  98. static string AreConstructorsCompatible(ConstructorInfo x, ConstructorInfo y) {
  99. return AreMethodBasesCompatible(x, y);
  100. }
  101. static string AreMethodInfosCompatible(MethodInfo x, MethodInfo y) {
  102. return AreMethodBasesCompatible(x, y) ?? (x.ReturnType == y.ReturnType ? null : "Return type mismatch");
  103. }
  104. }
  105. }
  106. #endif