UniTask/Assets/UniRx.Async/DiagnosticsExtensions.cs

252 lines
8.7 KiB
C#
Raw Normal View History

2019-05-19 23:14:47 +08:00
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
2020-04-18 04:07:59 +08:00
using UnityEngine;
2019-05-19 23:14:47 +08:00
namespace UniRx.Async
{
2020-04-18 04:07:59 +08:00
// TODO: Internal.
2019-05-19 23:14:47 +08:00
public static class DiagnosticsExtensions
{
static bool displayFilenames = true;
static readonly Regex typeBeautifyRegex = new Regex("`.+$", RegexOptions.Compiled);
static readonly Dictionary<Type, string> builtInTypeNames = new Dictionary<Type, string>
{
{ typeof(void), "void" },
{ typeof(bool), "bool" },
{ typeof(byte), "byte" },
{ typeof(char), "char" },
{ typeof(decimal), "decimal" },
{ typeof(double), "double" },
{ typeof(float), "float" },
{ typeof(int), "int" },
{ typeof(long), "long" },
{ typeof(object), "object" },
{ typeof(sbyte), "sbyte" },
{ typeof(short), "short" },
{ typeof(string), "string" },
{ typeof(uint), "uint" },
{ typeof(ulong), "ulong" },
{ typeof(ushort), "ushort" },
{ typeof(Task), "Task" },
{ typeof(UniTask), "UniTask" },
{ typeof(UniTaskVoid), "UniTaskVoid" }
};
public static string CleanupAsyncStackTrace(this StackTrace stackTrace)
{
if (stackTrace == null) return "";
var sb = new StringBuilder();
for (int i = 0; i < stackTrace.FrameCount; i++)
{
var sf = stackTrace.GetFrame(i);
var mb = sf.GetMethod();
if (IgnoreLine(mb)) continue;
if (IsAsync(mb))
{
sb.Append("async ");
TryResolveStateMachineMethod(ref mb, out var decType);
}
// return type
if (mb is MethodInfo mi)
{
sb.Append(BeautifyType(mi.ReturnType, false));
sb.Append(" ");
}
// method name
sb.Append(BeautifyType(mb.DeclaringType, false));
if (!mb.IsConstructor)
{
sb.Append(".");
}
sb.Append(mb.Name);
if (mb.IsGenericMethod)
{
sb.Append("<");
foreach (var item in mb.GetGenericArguments())
{
sb.Append(BeautifyType(item, true));
}
sb.Append(">");
}
// parameter
sb.Append("(");
sb.Append(string.Join(", ", mb.GetParameters().Select(p => BeautifyType(p.ParameterType, true) + " " + p.Name)));
sb.Append(")");
// file name
if (displayFilenames && (sf.GetILOffset() != -1))
{
String fileName = null;
try
{
fileName = sf.GetFileName();
}
catch (NotSupportedException)
{
displayFilenames = false;
}
catch (SecurityException)
{
displayFilenames = false;
}
if (fileName != null)
{
sb.Append(' ');
2020-04-18 04:07:59 +08:00
sb.AppendFormat(CultureInfo.InvariantCulture, "(at {0})", AppendHyperLink(fileName, sf.GetFileLineNumber().ToString()));
2019-05-19 23:14:47 +08:00
}
}
sb.AppendLine();
}
return sb.ToString();
}
static bool IsAsync(MethodBase methodInfo)
{
var declareType = methodInfo.DeclaringType;
return typeof(IAsyncStateMachine).IsAssignableFrom(declareType);
}
// code from Ben.Demystifier/EnhancedStackTrace.Frame.cs
static bool TryResolveStateMachineMethod(ref MethodBase method, out Type declaringType)
{
declaringType = method.DeclaringType;
var parentType = declaringType.DeclaringType;
if (parentType == null)
{
return false;
}
var methods = parentType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly);
if (methods == null)
{
return false;
}
foreach (var candidateMethod in methods)
{
var attributes = candidateMethod.GetCustomAttributes<StateMachineAttribute>();
if (attributes == null)
{
continue;
}
foreach (var asma in attributes)
{
if (asma.StateMachineType == declaringType)
{
method = candidateMethod;
declaringType = candidateMethod.DeclaringType;
// Mark the iterator as changed; so it gets the + annotation of the original method
// async statemachines resolve directly to their builder methods so aren't marked as changed
return asma is IteratorStateMachineAttribute;
}
}
}
return false;
}
static string BeautifyType(Type t, bool shortName)
{
if (builtInTypeNames.TryGetValue(t, out var builtin))
{
return builtin;
}
if (t.IsGenericParameter) return t.Name;
if (t.IsArray) return BeautifyType(t.GetElementType(), shortName) + "[]";
if (t.FullName?.StartsWith("System.ValueTuple") ?? false)
{
return "(" + string.Join(", ", t.GetGenericArguments().Select(x => BeautifyType(x, true))) + ")";
}
2020-04-18 04:07:59 +08:00
if (!t.IsGenericType) return shortName ? t.Name : t.FullName.Replace("UniRx.Async.Triggers.", "").Replace("UniRx.Async.Internal.", "").Replace("UniRx.Async.", "") ?? t.Name;
2019-05-19 23:14:47 +08:00
var innerFormat = string.Join(", ", t.GetGenericArguments().Select(x => BeautifyType(x, true)));
var genericType = t.GetGenericTypeDefinition().FullName;
if (genericType == "System.Threading.Tasks.Task`1")
{
genericType = "Task";
}
2020-04-18 04:07:59 +08:00
return typeBeautifyRegex.Replace(genericType, "").Replace("UniRx.Async.Triggers.", "").Replace("UniRx.Async.Internal.", "").Replace("UniRx.Async.", "") + "<" + innerFormat + ">";
2019-05-19 23:14:47 +08:00
}
static bool IgnoreLine(MethodBase methodInfo)
{
var declareType = methodInfo.DeclaringType.FullName;
if (declareType == "System.Threading.ExecutionContext")
{
return true;
}
else if (declareType.StartsWith("System.Runtime.CompilerServices"))
{
return true;
}
else if (declareType.StartsWith("UniRx.Async.CompilerServices"))
{
return true;
}
else if (declareType == "System.Threading.Tasks.AwaitTaskContinuation")
{
return true;
}
else if (declareType.StartsWith("System.Threading.Tasks.Task"))
{
return true;
}
2020-04-18 04:07:59 +08:00
else if (declareType.StartsWith("UniRx.Async.UniTaskCompletionSourceCore"))
{
return true;
}
else if (declareType.StartsWith("UniRx.Async.AwaiterActions"))
{
return true;
}
2019-05-19 23:14:47 +08:00
return false;
}
2020-04-18 04:07:59 +08:00
static string AppendHyperLink(string path, string line)
2019-05-19 23:14:47 +08:00
{
var fi = new FileInfo(path);
if (fi.Directory == null)
{
return fi.Name;
}
else
{
2020-04-18 04:07:59 +08:00
var fname = fi.FullName.Replace(Path.DirectorySeparatorChar, '/').Replace(Application.dataPath, "");
var withAssetsPath = "Assets/" + fname;
return "<a href=\"" + withAssetsPath + "\" line=\"" + line + "\">" + withAssetsPath + ":" + line + "</a>";
2019-05-19 23:14:47 +08:00
}
}
}
}
#endif