DiagnosticsExtensions.cs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Globalization;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Reflection;
  9. using System.Runtime.CompilerServices;
  10. using System.Security;
  11. using System.Text;
  12. using System.Text.RegularExpressions;
  13. using System.Threading.Tasks;
  14. using UnityEngine;
  15. namespace Cysharp.Threading.Tasks.Internal
  16. {
  17. internal static class DiagnosticsExtensions
  18. {
  19. static bool displayFilenames = true;
  20. static readonly Regex typeBeautifyRegex = new Regex("`.+$", RegexOptions.Compiled);
  21. static readonly Dictionary<Type, string> builtInTypeNames = new Dictionary<Type, string>
  22. {
  23. { typeof(void), "void" },
  24. { typeof(bool), "bool" },
  25. { typeof(byte), "byte" },
  26. { typeof(char), "char" },
  27. { typeof(decimal), "decimal" },
  28. { typeof(double), "double" },
  29. { typeof(float), "float" },
  30. { typeof(int), "int" },
  31. { typeof(long), "long" },
  32. { typeof(object), "object" },
  33. { typeof(sbyte), "sbyte" },
  34. { typeof(short), "short" },
  35. { typeof(string), "string" },
  36. { typeof(uint), "uint" },
  37. { typeof(ulong), "ulong" },
  38. { typeof(ushort), "ushort" },
  39. { typeof(Task), "Task" },
  40. { typeof(UniTask), "UniTask" },
  41. { typeof(UniTaskVoid), "UniTaskVoid" }
  42. };
  43. public static string CleanupAsyncStackTrace(this StackTrace stackTrace)
  44. {
  45. if (stackTrace == null) return "";
  46. var sb = new StringBuilder();
  47. for (int i = 0; i < stackTrace.FrameCount; i++)
  48. {
  49. var sf = stackTrace.GetFrame(i);
  50. var mb = sf.GetMethod();
  51. if (IgnoreLine(mb)) continue;
  52. if (IsAsync(mb))
  53. {
  54. sb.Append("async ");
  55. TryResolveStateMachineMethod(ref mb, out var decType);
  56. }
  57. // return type
  58. if (mb is MethodInfo mi)
  59. {
  60. sb.Append(BeautifyType(mi.ReturnType, false));
  61. sb.Append(" ");
  62. }
  63. // method name
  64. sb.Append(BeautifyType(mb.DeclaringType, false));
  65. if (!mb.IsConstructor)
  66. {
  67. sb.Append(".");
  68. }
  69. sb.Append(mb.Name);
  70. if (mb.IsGenericMethod)
  71. {
  72. sb.Append("<");
  73. foreach (var item in mb.GetGenericArguments())
  74. {
  75. sb.Append(BeautifyType(item, true));
  76. }
  77. sb.Append(">");
  78. }
  79. // parameter
  80. sb.Append("(");
  81. sb.Append(string.Join(", ", mb.GetParameters().Select(p => BeautifyType(p.ParameterType, true) + " " + p.Name)));
  82. sb.Append(")");
  83. // file name
  84. if (displayFilenames && (sf.GetILOffset() != -1))
  85. {
  86. String fileName = null;
  87. try
  88. {
  89. fileName = sf.GetFileName();
  90. }
  91. catch (NotSupportedException)
  92. {
  93. displayFilenames = false;
  94. }
  95. catch (SecurityException)
  96. {
  97. displayFilenames = false;
  98. }
  99. if (fileName != null)
  100. {
  101. sb.Append(' ');
  102. sb.AppendFormat(CultureInfo.InvariantCulture, "(at {0})", AppendHyperLink(fileName, sf.GetFileLineNumber().ToString()));
  103. }
  104. }
  105. sb.AppendLine();
  106. }
  107. return sb.ToString();
  108. }
  109. static bool IsAsync(MethodBase methodInfo)
  110. {
  111. var declareType = methodInfo.DeclaringType;
  112. return typeof(IAsyncStateMachine).IsAssignableFrom(declareType);
  113. }
  114. // code from Ben.Demystifier/EnhancedStackTrace.Frame.cs
  115. static bool TryResolveStateMachineMethod(ref MethodBase method, out Type declaringType)
  116. {
  117. declaringType = method.DeclaringType;
  118. var parentType = declaringType.DeclaringType;
  119. if (parentType == null)
  120. {
  121. return false;
  122. }
  123. var methods = parentType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly);
  124. if (methods == null)
  125. {
  126. return false;
  127. }
  128. foreach (var candidateMethod in methods)
  129. {
  130. var attributes = candidateMethod.GetCustomAttributes<StateMachineAttribute>(false);
  131. if (attributes == null)
  132. {
  133. continue;
  134. }
  135. foreach (var asma in attributes)
  136. {
  137. if (asma.StateMachineType == declaringType)
  138. {
  139. method = candidateMethod;
  140. declaringType = candidateMethod.DeclaringType;
  141. // Mark the iterator as changed; so it gets the + annotation of the original method
  142. // async statemachines resolve directly to their builder methods so aren't marked as changed
  143. return asma is IteratorStateMachineAttribute;
  144. }
  145. }
  146. }
  147. return false;
  148. }
  149. static string BeautifyType(Type t, bool shortName)
  150. {
  151. if (builtInTypeNames.TryGetValue(t, out var builtin))
  152. {
  153. return builtin;
  154. }
  155. if (t.IsGenericParameter) return t.Name;
  156. if (t.IsArray) return BeautifyType(t.GetElementType(), shortName) + "[]";
  157. if (t.FullName?.StartsWith("System.ValueTuple") ?? false)
  158. {
  159. return "(" + string.Join(", ", t.GetGenericArguments().Select(x => BeautifyType(x, true))) + ")";
  160. }
  161. if (!t.IsGenericType) return shortName ? t.Name : t.FullName.Replace("Cysharp.Threading.Tasks.Triggers.", "").Replace("Cysharp.Threading.Tasks.Internal.", "").Replace("Cysharp.Threading.Tasks.", "") ?? t.Name;
  162. var innerFormat = string.Join(", ", t.GetGenericArguments().Select(x => BeautifyType(x, true)));
  163. var genericType = t.GetGenericTypeDefinition().FullName;
  164. if (genericType == "System.Threading.Tasks.Task`1")
  165. {
  166. genericType = "Task";
  167. }
  168. return typeBeautifyRegex.Replace(genericType, "").Replace("Cysharp.Threading.Tasks.Triggers.", "").Replace("Cysharp.Threading.Tasks.Internal.", "").Replace("Cysharp.Threading.Tasks.", "") + "<" + innerFormat + ">";
  169. }
  170. static bool IgnoreLine(MethodBase methodInfo)
  171. {
  172. var declareType = methodInfo.DeclaringType.FullName;
  173. if (declareType == "System.Threading.ExecutionContext")
  174. {
  175. return true;
  176. }
  177. else if (declareType.StartsWith("System.Runtime.CompilerServices"))
  178. {
  179. return true;
  180. }
  181. else if (declareType.StartsWith("Cysharp.Threading.Tasks.CompilerServices"))
  182. {
  183. return true;
  184. }
  185. else if (declareType == "System.Threading.Tasks.AwaitTaskContinuation")
  186. {
  187. return true;
  188. }
  189. else if (declareType.StartsWith("System.Threading.Tasks.Task"))
  190. {
  191. return true;
  192. }
  193. else if (declareType.StartsWith("Cysharp.Threading.Tasks.UniTaskCompletionSourceCore"))
  194. {
  195. return true;
  196. }
  197. else if (declareType.StartsWith("Cysharp.Threading.Tasks.AwaiterActions"))
  198. {
  199. return true;
  200. }
  201. return false;
  202. }
  203. static string AppendHyperLink(string path, string line)
  204. {
  205. var fi = new FileInfo(path);
  206. if (fi.Directory == null)
  207. {
  208. return fi.Name;
  209. }
  210. else
  211. {
  212. var fname = fi.FullName.Replace(Path.DirectorySeparatorChar, '/').Replace(PlayerLoopHelper.ApplicationDataPath, "");
  213. var withAssetsPath = "Assets/" + fname;
  214. return "<a href=\"" + withAssetsPath + "\" line=\"" + line + "\">" + withAssetsPath + ":" + line + "</a>";
  215. }
  216. }
  217. }
  218. }