├── LICENSE.meta ├── README.md.meta ├── package.json.meta ├── src.meta ├── Apkd.UnityDemystifier.csproj.meta ├── src ├── Enumerable.meta ├── Internal.meta ├── Detour.cs.meta ├── CacheDictionary.cs.meta ├── Internal │ ├── ILReader.cs.meta │ ├── StringBuilder.cs.meta │ ├── ReflectionHelper.cs.meta │ ├── ReflectionHelper.cs │ ├── ILReader.cs │ └── StringBuilder.cs ├── ResolvedMethod.cs.meta ├── ResolvedParameter.cs.meta ├── TypeNameHelper.cs.meta ├── EnhancedStackFrame.cs.meta ├── EnhancedStackTrace.cs.meta ├── ExceptionExtensions.cs.meta ├── UnityEditorOverrides.cs.meta ├── EnhancedStackTrace.Frames.cs.meta ├── Enumerable │ ├── EnumerableIList.cs.meta │ ├── EnumeratorIList.cs.meta │ ├── IEnumerableIList.cs.meta │ ├── IEnumerableIList.cs │ ├── EnumeratorIList.cs │ └── EnumerableIList.cs ├── StringBuilderExtensions.cs.meta ├── ValueTupleResolvedParameter.cs.meta ├── CacheDictionary.cs ├── ResolvedParameter.cs ├── StringBuilderExtensions.cs ├── ValueTupleResolvedParameter.cs ├── ExceptionExtensions.cs ├── Detour.cs ├── EnhancedStackTrace.cs ├── EnhancedStackFrame.cs ├── TypeNameHelper.cs ├── UnityEditorOverrides.cs ├── ResolvedMethod.cs └── EnhancedStackTrace.Frames.cs ├── Apkd.UnityDemystifier.asmdef.meta ├── package.json ├── Apkd.UnityDemystifier.asmdef ├── .gitignore ├── Apkd.UnityDemystifier.csproj ├── README.md └── LICENSE /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6a248a3a8231a704faaf6205bdd7ca48 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 750b9a2fa9a990c4fac921dadefd4d50 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 504396a991eb5db43bc1f9c30348dfa7 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /src.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fe3e9f1dd7d092b4e81e70c4cc21cf34 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Apkd.UnityDemystifier.csproj.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 31e7b9af3cf3ee94ebfcc20bf47d2e5f 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /src/Enumerable.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9336b3acf20a31b4ab8e03cb046416dd 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /src/Internal.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b19ba4dc1064c324eaff24604e46e223 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Apkd.UnityDemystifier.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 358743d4cfa06e24ca04147766f1cba3 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /src/Detour.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c5b8678188faaa44ebb3a85430c2703f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /src/CacheDictionary.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 06e34539e0bce554ba901fcb1aea75e5 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /src/Internal/ILReader.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a27f482ebccad8246a2fd1919baed9b3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /src/ResolvedMethod.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f3b55ed993988f24586c3804b3fedae6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /src/ResolvedParameter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f6656aad11193854388451100a3a82b5 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /src/TypeNameHelper.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b81577d521369304fb172e74163074c3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /src/EnhancedStackFrame.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0bd34ae27a07330489908eaafebf8359 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /src/EnhancedStackTrace.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c665c79f230a57d4dbc51fd5ddbf33d2 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /src/ExceptionExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 111407c00883faa4cb3b99f67de89bb5 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /src/Internal/StringBuilder.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5aea04165f60d06459b935b3286b46d1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /src/UnityEditorOverrides.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e51e666221d323a478ab7d069f8bd073 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /src/EnhancedStackTrace.Frames.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7fe6562f95238264b98dcf92ee266e60 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /src/Enumerable/EnumerableIList.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cb71adb18cf7e6e40ade60f5eb77c38c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /src/Enumerable/EnumeratorIList.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a01bab105cfa47947876238f9894d974 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /src/Enumerable/IEnumerableIList.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: acf04e040b63f3f48855f6a5c135369c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /src/Internal/ReflectionHelper.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 69bdfea322628654cb7239d4ac20c169 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /src/StringBuilderExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 73792604ec24d0a4cb43686c8276de30 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /src/ValueTupleResolvedParameter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 37a28f1a48658f94ebb972dd00a3896a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pl.apkd.demystifier", 3 | "displayName": "Apkd.UnityDemystifier", 4 | "version": "0.0.1", 5 | "unity": "2018.3", 6 | "description": "Improved stack trace display for Unity, based on Ben.Demystifier.", 7 | "dependencies": {}, 8 | "keywords": [], 9 | "category": "Unity" 10 | } -------------------------------------------------------------------------------- /Apkd.UnityDemystifier.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Apkd.UnityDemystifier", 3 | "references": [], 4 | "optionalUnityReferences": [], 5 | "includePlatforms": [ 6 | "Editor" 7 | ], 8 | "excludePlatforms": [], 9 | "allowUnsafeCode": true, 10 | "overrideReferences": false, 11 | "precompiledReferences": [], 12 | "autoReferenced": true, 13 | "defineConstraints": [] 14 | } -------------------------------------------------------------------------------- /src/Enumerable/IEnumerableIList.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Ben A Adams. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace Apkd.Internal 7 | { 8 | interface IEnumerableIList : IEnumerable 9 | { 10 | new EnumeratorIList GetEnumerator(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | [Bb]in/ 21 | [Oo]bj/ 22 | [Ll]og/ 23 | 24 | # Visual Studio 2015 cache/options directory 25 | .vs/ 26 | 27 | /DOCUMENTATION.pdf 28 | /DOCUMENTATION.pdf.meta 29 | bin.meta 30 | obj.meta -------------------------------------------------------------------------------- /Apkd.UnityDemystifier.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net46 5 | latest 6 | True 7 | 8 | 9 | 10 | 11 | C:\Program Files\Unity\Hub\Editor\2017.1.5f1\Editor\Data\Managed\UnityEngine.dll 12 | 13 | 14 | C:\Program Files\Unity\Hub\Editor\2017.1.5f1\Editor\Data\Managed\UnityEditor.dll 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Enumerable/EnumeratorIList.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Ben A Adams. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | namespace Apkd.Internal 8 | { 9 | internal struct EnumeratorIList : IEnumerator 10 | { 11 | private readonly IList _list; 12 | private int _index; 13 | 14 | internal EnumeratorIList(IList list) 15 | { 16 | _index = -1; 17 | _list = list; 18 | } 19 | 20 | public T Current => _list[_index]; 21 | 22 | public bool MoveNext() 23 | { 24 | _index++; 25 | 26 | return _index < (_list?.Count ?? 0); 27 | } 28 | 29 | public void Dispose() { } 30 | object IEnumerator.Current => Current; 31 | public void Reset() => _index = -1; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/CacheDictionary.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Apkd.Internal 4 | { 5 | internal sealed class CacheDictionary where TKey : class where TValue : class 6 | { 7 | readonly int cacheSize; 8 | readonly Queue objectReferenceQueue; 9 | readonly System.Runtime.CompilerServices.ConditionalWeakTable weakTable 10 | = new System.Runtime.CompilerServices.ConditionalWeakTable(); 11 | 12 | const int defaultCacheSize = 256; 13 | 14 | public CacheDictionary(int cacheSize = 256) 15 | #if APKD_STACKTRACE_NOCACHE 16 | { } 17 | #else 18 | => (this.cacheSize, objectReferenceQueue) = (cacheSize, new Queue(capacity: cacheSize)); 19 | #endif 20 | 21 | public TValue GetOrInitializeValue(TKey key, System.Func initializer) 22 | { 23 | #if APKD_STACKTRACE_NOCACHE 24 | return initializer(key); 25 | #else 26 | if (!weakTable.TryGetValue(key, out var value)) 27 | weakTable.Add(key, value = initializer(key)); 28 | 29 | if (objectReferenceQueue.Count > cacheSize) 30 | objectReferenceQueue.Dequeue(); 31 | objectReferenceQueue.Enqueue(value); 32 | 33 | return value; 34 | #endif 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/ResolvedParameter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Ben A Adams. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace Apkd.Internal 5 | { 6 | internal class ResolvedParameter 7 | { 8 | internal string Name { get; set; } 9 | 10 | internal System.Type ResolvedType { get; set; } 11 | 12 | internal string Prefix { get; set; } 13 | internal string Prefix2 { get; set; } 14 | 15 | public override string ToString() => Append(new StringBuilder()).ToString(); 16 | 17 | internal StringBuilder Append(StringBuilder sb) 18 | { 19 | sb.AppendFormattingChar('‹'); 20 | 21 | if (!string.IsNullOrEmpty(Prefix2)) 22 | sb.Append(Prefix2).Append(' '); 23 | 24 | if (!string.IsNullOrEmpty(Prefix)) 25 | sb.Append(Prefix).Append(' '); 26 | 27 | if (ResolvedType != null) 28 | AppendTypeName(sb); 29 | else 30 | sb.Append('?'); 31 | 32 | sb.AppendFormattingChar('›'); 33 | 34 | if (!string.IsNullOrEmpty(Name)) 35 | sb.Append(' ').Append(Name); 36 | 37 | return sb; 38 | } 39 | 40 | internal virtual void AppendTypeName(StringBuilder sb) 41 | { 42 | sb.AppendTypeDisplayName(ResolvedType, fullName: false, includeGenericParameterNames: true); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/StringBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Apkd.Internal 4 | { 5 | static class StringBuilderExtensions 6 | { 7 | internal static StringBuilder AppendDemystified(this StringBuilder builder, Exception exception) 8 | { 9 | try 10 | { 11 | var stackTrace = new EnhancedStackTrace(exception); 12 | 13 | builder.Append(exception.GetType()); 14 | if (!string.IsNullOrEmpty(exception.Message)) 15 | builder.Append(": ").Append(exception.Message); 16 | 17 | builder.Append(Environment.NewLine); 18 | 19 | if (stackTrace.FrameCount > 0) 20 | stackTrace.Append(builder); 21 | 22 | if (exception is AggregateException aggEx) 23 | foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions)) 24 | builder.AppendInnerException(ex); 25 | 26 | if (exception.InnerException != null) 27 | builder.AppendInnerException(exception.InnerException); 28 | } 29 | catch 30 | { 31 | // Processing exceptions shouldn't throw exceptions; if it fails 32 | } 33 | 34 | return builder; 35 | } 36 | 37 | internal static StringBuilder AppendFormattingChar(this StringBuilder builder, char c) 38 | #if !APKD_STACKTRACE_NOFORMAT 39 | => builder.Append(c); 40 | #else 41 | => builder; 42 | #endif 43 | 44 | static void AppendInnerException(this StringBuilder builder, Exception exception) 45 | => builder.Append(" ---> ") 46 | .AppendDemystified(exception) 47 | .Append('\n') 48 | .Append(" --- End of inner exception stack trace ---"); 49 | } 50 | } -------------------------------------------------------------------------------- /src/ValueTupleResolvedParameter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Ben A Adams. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | 7 | namespace Apkd.Internal 8 | { 9 | internal sealed class ValueTupleResolvedParameter : ResolvedParameter 10 | { 11 | internal IList TupleNames { get; set; } 12 | 13 | internal override void AppendTypeName(StringBuilder sb) 14 | { 15 | if (ResolvedType.IsValueTuple()) 16 | { 17 | AppendValueTupleParameterName(sb, ResolvedType); 18 | } 19 | else 20 | { 21 | // Need to unwrap the first generic argument first. 22 | sb.Append(TypeNameHelper.GetTypeNameForGenericType(ResolvedType)); 23 | sb.Append('<'); 24 | AppendValueTupleParameterName(sb, ResolvedType.GetGenericArguments()[0]); 25 | sb.Append('>'); 26 | } 27 | } 28 | 29 | 30 | void AppendValueTupleParameterName(StringBuilder sb, System.Type parameterType) 31 | { 32 | sb.Append('('); 33 | var args = parameterType.GetGenericArguments(); 34 | for (var i = 0; i < args.Length; i++) 35 | { 36 | if (i > 0) 37 | sb.Append(',').Append(' '); 38 | 39 | sb.AppendTypeDisplayName(args[i], fullName: false, includeGenericParameterNames: true); 40 | 41 | #if APKD_STACKTRACE_FULLPARAMS 42 | if (i >= TupleNames.Count) 43 | continue; 44 | 45 | var argName = TupleNames[i]; 46 | if (argName == null) 47 | continue; 48 | 49 | sb.Append(' '); 50 | sb.Append(argName); 51 | #endif 52 | } 53 | 54 | sb.Append(')'); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Enumerable/EnumerableIList.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Ben A Adams. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | namespace Apkd.Internal 8 | { 9 | internal static class EnumerableIList 10 | { 11 | internal static EnumerableIList Create(IList list) => new EnumerableIList(list); 12 | } 13 | 14 | internal struct EnumerableIList : IEnumerableIList, IList 15 | { 16 | private readonly IList _list; 17 | 18 | public EnumerableIList(IList list) => _list = list; 19 | 20 | public EnumeratorIList GetEnumerator() => new EnumeratorIList(_list); 21 | 22 | public static implicit operator EnumerableIList(List list) => new EnumerableIList(list); 23 | 24 | public static implicit operator EnumerableIList(T[] array) => new EnumerableIList(array); 25 | 26 | public static EnumerableIList Empty = default; 27 | 28 | 29 | // IList pass through 30 | 31 | /// 32 | public T this[int index] { get => _list[index]; set => _list[index] = value; } 33 | 34 | /// 35 | public int Count => _list?.Count ?? 0; 36 | 37 | /// 38 | public bool IsReadOnly => _list.IsReadOnly; 39 | 40 | /// 41 | public void Add(T item) => _list.Add(item); 42 | 43 | /// 44 | public void Clear() => _list.Clear(); 45 | 46 | /// 47 | public bool Contains(T item) => _list.Contains(item); 48 | 49 | /// 50 | public void CopyTo(T[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex); 51 | 52 | /// 53 | public int IndexOf(T item) => _list.IndexOf(item); 54 | 55 | /// 56 | public void Insert(int index, T item) => _list.Insert(index, item); 57 | 58 | /// 59 | public bool Remove(T item) => _list.Remove(item); 60 | 61 | /// 62 | public void RemoveAt(int index) => _list.RemoveAt(index); 63 | 64 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 65 | 66 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/ExceptionExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Ben A Adams. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.Reflection; 8 | 9 | namespace Apkd.Internal 10 | { 11 | /// 12 | internal static class ExceptionExtensions 13 | { 14 | private static readonly FieldInfo stackTraceString = typeof(Exception).GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic); 15 | 16 | private static void SetStackTracesString(this Exception exception, string value) 17 | => stackTraceString.SetValue(exception, value); 18 | 19 | /// 20 | /// Demystifies the given and tracks the original stack traces for the whole exception tree. 21 | /// 22 | internal static T Demystify(this T exception) where T : Exception 23 | { 24 | try 25 | { 26 | var stackTrace = new EnhancedStackTrace(exception); 27 | 28 | if (stackTrace.FrameCount > 0) 29 | { 30 | exception.SetStackTracesString(stackTrace.ToString()); 31 | } 32 | 33 | if (exception is AggregateException aggEx) 34 | { 35 | foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions)) 36 | { 37 | ex.Demystify(); 38 | } 39 | } 40 | 41 | exception.InnerException?.Demystify(); 42 | } 43 | catch 44 | { 45 | // Processing exceptions shouldn't throw exceptions; if it fails 46 | } 47 | 48 | return exception; 49 | } 50 | 51 | /// 52 | /// Gets demystified string representation of the . 53 | /// 54 | /// 55 | /// method mutates the exception instance that can cause 56 | /// issues if a system relies on the stack trace be in the specific form. 57 | /// Unlike this method is pure. It calls first, 58 | /// computes a demystified string representation and then restores the original state of the exception back. 59 | /// 60 | [System.Diagnostics.Contracts.Pure] 61 | internal static string ToStringDemystified(this Exception exception) 62 | => new StringBuilder().AppendDemystified(exception).ToString(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apkd.UnityDemystifier 2 | 3 | Improved stack trace display for Unity, based on [Ben.Demystifier](https://github.com/benaadams/Ben.Demystifier). Work in progress. 4 | 5 | #### Before 6 | ![before](https://cdn.discordapp.com/attachments/368334636256067597/550672599319969822/before.png) 7 | 8 | #### After 9 | ![after](https://cdn.discordapp.com/attachments/368334636256067597/550672619532320778/after.png) 10 | 11 | ## Requirements 12 | 13 | - The [.NET 4.X Scripting Runtime](https://docs.unity3d.com/Manual/ScriptingRuntimeUpgrade.html) needs to be enabled in the project. 14 | - Only tested on the Windows/x86_64 version of the editor (please [let me know](https://github.com/apkd/Apkd.UnityDemystifier/issues/1) how this performs on your platform). 15 | 16 | ## Installation 17 | 18 | ### Unity Asset Store 19 | 20 | Install the package in your project using the [Asset Store page](http://u3d.as/1te5). 21 | 22 | ### Unity Package Manager *(Unity 2018.3+)* 23 | 24 | Add a reference to the repository in the [`Packages\manifest.json`](https://docs.unity3d.com/Packages/com.unity.package-manager-ui@1.8/manual/index.html#project-manifests) file in your project directory: 25 | 26 | ```json 27 | { 28 | "dependencies": { 29 | "com.unity.postprocessing": "2.1.3", 30 | "pl.apkd.demystifier": "https://github.com/apkd/Apkd.UnityDemystifier.git" 31 | } 32 | } 33 | ``` 34 | 35 | ### Manual 36 | 37 | #### From source *(Unity 2018.3+)* 38 | 39 | Clone/download this repository into the `Assets` directory of your project. 40 | 41 | #### Using compiled DLL *(Unity 2017.1+)* 42 | 43 | Download and extract the latest [release zip](https://github.com/apkd/Apkd.UnityDemystifier/releases) into the `Assets` directory of your project. 44 | 45 | 46 | ## Configuration 47 | 48 | The package will work immediately after installation without any additional steps. 49 | 50 | *(Unity 2018.3+)* You can customize the stack trace by defining the following compilation symbols in the project settings: 51 | - `APKD_STACKTRACE_HIDEPARAMS` - Hide the method parameter list completely. The most compact option. 52 | - `APKD_STACKTRACE_SHORTPARAMS` - Display the parameter list compactly (first letter of each parameter name only). 53 | - `APKD_STACKTRACE_FULLPARAMS` - Shows an expanded parameter list in the method signature. You can enable this if you have an unnaturally wide monitor. 54 | - `APKD_STACKTRACE_NOFORMAT` - Disables stack trace font formatting. Useful if you prefer your copy-pasted stack traces and log files to be clean. 55 | - `APKD_STACKTRACE_DISABLE` - Restores default Unity stack traces. 56 | - `APKD_STACKTRACE_FILEPATH_FONTSIZE_XX` - Change the font size of the source filename/line number string to `XX` (available values: 7-11, default: 8). 57 | - `APKD_STACKTRACE_NOCACHE` - Disable caching. Less memory usage, higher performance impact. 58 | - `APKD_STACKTRACE_LAMBDAORDINALS` - Shows lambda expression ordinals. 59 | 60 | ## Notes 61 | 62 | - This package overrides Unity's built-in stack trace parsing in an unsupported way and can make your editor unstable. All feedback is appreciated. 63 | - This package is editor-only and won't be included in builds. 64 | -------------------------------------------------------------------------------- /src/Internal/ReflectionHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Ben A Adams. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Reflection; 7 | using System.Threading; 8 | 9 | namespace Apkd.Internal 10 | { 11 | /// 12 | /// A helper class that contains utilities methods for dealing with reflection. 13 | /// 14 | internal static class ReflectionHelper 15 | { 16 | static PropertyInfo tranformerNamesLazyPropertyInfo; 17 | static readonly Type isReadOnlyAttribute = Type.GetType("System.Runtime.CompilerServices.IsReadOnlyAttribute", false); 18 | static readonly Type tupleElementNamesAttribute = Type.GetType("System.Runtime.CompilerServices.TupleElementNamesAttribute", false); 19 | 20 | /// 21 | /// Returns true if is System.Runtime.CompilerServices.IsReadOnlyAttribute. 22 | /// 23 | internal static bool IsIsReadOnlyAttribute(this Type type) 24 | => type == isReadOnlyAttribute; 25 | 26 | /// 27 | /// Returns true if the is a value tuple type. 28 | /// 29 | internal static bool IsValueTuple(this Type type) 30 | => type.FullName.StartsWith("System.ValueTuple`", StringComparison.Ordinal); 31 | 32 | /// 33 | /// Returns true if the given is of type TupleElementNameAttribute. 34 | /// 35 | /// 36 | /// To avoid compile-time depencency hell with System.ValueTuple, this method uses reflection and not checks statically that 37 | /// the given is TupleElementNameAttribute. 38 | /// 39 | internal static bool IsTupleElementNameAttribute(this Attribute attribute) 40 | => attribute.GetType() == tupleElementNamesAttribute; 41 | 42 | /// 43 | /// Returns 'TransformNames' property value from a given . 44 | /// 45 | /// 46 | /// To avoid compile-time depencency hell with System.ValueTuple, this method uses reflection 47 | /// instead of casting the attribute to a specific type. 48 | /// 49 | internal static IList GetTransformNames(this Attribute attribute) 50 | { 51 | System.Diagnostics.Debug.Assert(attribute.IsTupleElementNameAttribute()); 52 | var propertyInfo = GetTransformNamesPropertyInfo(attribute.GetType()); 53 | return (IList)propertyInfo.GetValue(attribute); 54 | } 55 | 56 | static PropertyInfo GetTransformNamesPropertyInfo(Type attributeType) 57 | => LazyInitializer.EnsureInitialized(ref tranformerNamesLazyPropertyInfo, 58 | () => attributeType.GetProperty("TransformNames", BindingFlags.Instance | BindingFlags.Public)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Detour.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Diagnostics; 4 | using static System.Reflection.BindingFlags; 5 | using static Apkd.Internal.UnityEditorOverrides; 6 | 7 | namespace Apkd.Internal 8 | { 9 | [UnityEditor.InitializeOnLoad] 10 | static class Detour 11 | { 12 | // This is based on an interesting technique from the RimWorld ComunityCoreLibrary project, originally credited to RawCode: 13 | // https://github.com/RimWorldCCLTeam/CommunityCoreLibrary/blob/master/DLL_Project/Classes/Static/Detours.cs 14 | internal static unsafe void TryDetourFromTo(MethodInfo src, MethodInfo dst) 15 | { 16 | try 17 | { 18 | if (IntPtr.Size == sizeof(Int64)) 19 | { 20 | // 64-bit systems use 64-bit absolute address and jumps 21 | // 12 byte destructive 22 | 23 | // Get function pointers 24 | long Source_Base = src.MethodHandle.GetFunctionPointer().ToInt64(); 25 | long Destination_Base = dst.MethodHandle.GetFunctionPointer().ToInt64(); 26 | 27 | // Native source address 28 | byte* Pointer_Raw_Source = (byte*)Source_Base; 29 | 30 | // Pointer to insert jump address into native code 31 | long* Pointer_Raw_Address = (long*)(Pointer_Raw_Source + 0x02); 32 | 33 | // Insert 64-bit absolute jump into native code (address in rax) 34 | // mov rax, immediate64 35 | // jmp [rax] 36 | *(Pointer_Raw_Source + 0x00) = 0x48; 37 | *(Pointer_Raw_Source + 0x01) = 0xB8; 38 | *Pointer_Raw_Address = Destination_Base; // ( Pointer_Raw_Source + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 ) 39 | *(Pointer_Raw_Source + 0x0A) = 0xFF; 40 | *(Pointer_Raw_Source + 0x0B) = 0xE0; 41 | 42 | } 43 | else 44 | { 45 | // 32-bit systems use 32-bit relative offset and jump 46 | // 5 byte destructive 47 | 48 | // Get function pointers 49 | int Source_Base = src.MethodHandle.GetFunctionPointer().ToInt32(); 50 | int Destination_Base = dst.MethodHandle.GetFunctionPointer().ToInt32(); 51 | 52 | // Native source address 53 | byte* Pointer_Raw_Source = (byte*)Source_Base; 54 | 55 | // Pointer to insert jump address into native code 56 | int* Pointer_Raw_Address = (int*)(Pointer_Raw_Source + 1); 57 | 58 | // Jump offset (less instruction size) 59 | int offset = (Destination_Base - Source_Base) - 5; 60 | 61 | // Insert 32-bit relative jump into native code 62 | *Pointer_Raw_Source = 0xE9; 63 | *Pointer_Raw_Address = offset; 64 | } 65 | } 66 | catch (Exception exception) 67 | { 68 | UnityEngine.Debug.LogError($"Unable to detour: {src.Name}"); 69 | throw; 70 | } 71 | } 72 | 73 | static Detour() 74 | { 75 | #if !APKD_STACKTRACE_DISABLE 76 | TryDetourFromTo( 77 | src: typeof(UnityEngine.StackTraceUtility).GetMethod(nameof(ExtractStringFromExceptionInternal), NonPublic | Static), 78 | dst: typeof(UnityEditorOverrides).GetMethod(nameof(ExtractStringFromExceptionInternal), NonPublic | Static) 79 | ); 80 | 81 | TryDetourFromTo( 82 | src: typeof(UnityEngine.StackTraceUtility).GetMethod(nameof(ExtractFormattedStackTrace), NonPublic | Static), 83 | dst: typeof(UnityEditorOverrides).GetMethod(nameof(ExtractFormattedStackTrace), NonPublic | Static) 84 | ); 85 | #endif 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Internal/ILReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Reflection.Emit; 4 | 5 | namespace Apkd.Internal 6 | { 7 | internal sealed class ILReader 8 | { 9 | static OpCode[] singleByteOpCode; 10 | static OpCode[] doubleByteOpCode; 11 | 12 | readonly byte[] _cil; 13 | int ptr; 14 | 15 | internal ILReader(byte[] cil) => _cil = cil; 16 | 17 | internal OpCode OpCode { get; private set; } 18 | internal int MetadataToken { get; private set; } 19 | internal MemberInfo Operand { get; private set; } 20 | 21 | internal bool Read(MethodBase methodInfo) 22 | { 23 | if (ptr < _cil.Length) 24 | { 25 | OpCode = ReadOpCode(); 26 | Operand = ReadOperand(OpCode, methodInfo); 27 | return true; 28 | } 29 | return false; 30 | } 31 | 32 | OpCode ReadOpCode() 33 | { 34 | var instruction = ReadByte(); 35 | if (instruction < 254) 36 | return singleByteOpCode[instruction]; 37 | else 38 | return doubleByteOpCode[ReadByte()]; 39 | } 40 | 41 | MemberInfo ReadOperand(OpCode code, MethodBase methodInfo) 42 | { 43 | MetadataToken = 0; 44 | int inlineLength; 45 | switch (code.OperandType) 46 | { 47 | case OperandType.InlineMethod: 48 | MetadataToken = ReadInt(); 49 | 50 | Type[] methodArgs = null; 51 | if (methodInfo.GetType() != typeof(ConstructorInfo) && !methodInfo.GetType().IsSubclassOf(typeof(ConstructorInfo))) 52 | methodArgs = methodInfo.GetGenericArguments(); 53 | 54 | Type[] typeArgs = null; 55 | if (methodInfo.DeclaringType != null) 56 | typeArgs = methodInfo.DeclaringType.GetGenericArguments(); 57 | 58 | try 59 | { 60 | return methodInfo.Module.ResolveMember(MetadataToken, typeArgs, methodArgs); 61 | } 62 | catch 63 | { 64 | // Can return System.ArgumentException : Token xxx is not a valid MemberInfo token in the scope of module xxx.dll 65 | return null; 66 | } 67 | 68 | case OperandType.InlineNone: 69 | inlineLength = 0; 70 | break; 71 | 72 | case OperandType.ShortInlineBrTarget: 73 | case OperandType.ShortInlineVar: 74 | case OperandType.ShortInlineI: 75 | inlineLength = 1; 76 | break; 77 | 78 | case OperandType.InlineVar: 79 | inlineLength = 2; 80 | break; 81 | 82 | case OperandType.InlineBrTarget: 83 | case OperandType.InlineField: 84 | case OperandType.InlineI: 85 | case OperandType.InlineString: 86 | case OperandType.InlineSig: 87 | case OperandType.InlineSwitch: 88 | case OperandType.InlineTok: 89 | case OperandType.InlineType: 90 | case OperandType.ShortInlineR: 91 | inlineLength = 4; 92 | break; 93 | 94 | case OperandType.InlineI8: 95 | case OperandType.InlineR: 96 | inlineLength = 8; 97 | break; 98 | 99 | default: 100 | return null; 101 | } 102 | 103 | for (var i = 0; i < inlineLength; i++) 104 | ReadByte(); 105 | 106 | return null; 107 | } 108 | 109 | byte ReadByte() => _cil[ptr++]; 110 | 111 | int ReadInt() 112 | { 113 | var b1 = ReadByte(); 114 | var b2 = ReadByte(); 115 | var b3 = ReadByte(); 116 | var b4 = ReadByte(); 117 | return b1 | b2 << 8 | b3 << 16 | b4 << 24; 118 | } 119 | 120 | static ILReader() 121 | { 122 | singleByteOpCode = new OpCode[225]; 123 | doubleByteOpCode = new OpCode[31]; 124 | 125 | var fields = GetOpCodeFields(); 126 | 127 | for (var i = 0; i < fields.Length; i++) 128 | { 129 | var code = (OpCode)fields[i].GetValue(null); 130 | if (code.OpCodeType == OpCodeType.Nternal) 131 | continue; 132 | 133 | if (code.Size == 1) 134 | singleByteOpCode[code.Value] = code; 135 | else 136 | doubleByteOpCode[code.Value & 0xff] = code; 137 | } 138 | } 139 | 140 | static FieldInfo[] GetOpCodeFields() => typeof(OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/EnhancedStackTrace.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Ben A Adams. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using System.Diagnostics; 8 | using System.IO; 9 | 10 | namespace Apkd.Internal 11 | { 12 | internal sealed partial class EnhancedStackTrace : StackTrace, IEnumerable 13 | { 14 | internal static EnhancedStackTrace Current() => new EnhancedStackTrace(new StackTrace(1 /* skip this one frame */, true)); 15 | 16 | readonly List _frames; 17 | 18 | // Summary: 19 | // Initializes a new instance of the System.Diagnostics.StackTrace class using the 20 | // provided exception object. 21 | // 22 | // Parameters: 23 | // e: 24 | // The exception object from which to construct the stack trace. 25 | // 26 | // Exceptions: 27 | // T:System.ArgumentNullException: 28 | // The parameter e is null. 29 | internal EnhancedStackTrace(Exception e) 30 | { 31 | if (e == null) 32 | throw new ArgumentNullException(nameof(e)); 33 | 34 | _frames = GetFrames(e); 35 | } 36 | 37 | internal EnhancedStackTrace(StackTrace stackTrace) 38 | { 39 | if (stackTrace == null) 40 | throw new ArgumentNullException(nameof(stackTrace)); 41 | 42 | _frames = GetFrames(stackTrace); 43 | } 44 | 45 | /// 46 | /// Gets the number of frames in the stack trace. 47 | /// 48 | /// The number of frames in the stack trace. 49 | public override int FrameCount => _frames.Count; 50 | 51 | /// 52 | /// Gets the specified stack frame. 53 | /// 54 | /// The index of the stack frame requested. 55 | /// The specified stack frame. 56 | public override StackFrame GetFrame(int index) => _frames[index]; 57 | 58 | /// 59 | /// Returns a copy of all stack frames in the current stack trace. 60 | /// 61 | /// 62 | /// An array of type System.Diagnostics.StackFrame representing the function calls 63 | /// in the stack trace. 64 | /// 65 | public override StackFrame[] GetFrames() => _frames.ToArray(); 66 | 67 | /// 68 | /// Builds a readable representation of the stack trace. 69 | /// 70 | /// A readable representation of the stack trace. 71 | public override string ToString() => ToString(new StringBuilder()); 72 | 73 | public string ToString(StringBuilder sb) 74 | { 75 | if (_frames == null || _frames.Count == 0) 76 | return ""; 77 | 78 | Append(sb); 79 | 80 | return sb.ToString(); 81 | } 82 | 83 | 84 | internal void Append(StringBuilder sb) 85 | { 86 | bool loggedFullFilepath = false; 87 | 88 | for (int i = 0, n = _frames.Count; i < n; i++) 89 | { 90 | sb.Append('\n'); 91 | var frame = _frames[i]; 92 | 93 | if (frame.IsEmpty) 94 | { 95 | sb.Append(frame.StackFrame); 96 | } 97 | else 98 | { 99 | frame.MethodInfo.Append(sb); 100 | 101 | var filePath = frame.GetFileName(); 102 | if (!string.IsNullOrEmpty(filePath) && !frame.MethodInfo.Name.StartsWith("Log")) 103 | { 104 | if (!loggedFullFilepath) 105 | { 106 | sb.Append(" (at "); 107 | frame.AppendFullFilename(sb); 108 | loggedFullFilepath = true; 109 | } 110 | else 111 | { 112 | #if !APKD_STACKTRACE_NOFORMAT 113 | sb.Append(" →(at "); 114 | #else 115 | sb.Append(" (at "); 116 | #endif 117 | sb.Append(filePath); 118 | } 119 | 120 | var lineNo = frame.GetFileLineNumber(); 121 | if (lineNo != 0) 122 | { 123 | sb.Append(':'); 124 | sb.Append(lineNo); 125 | sb.Append(')'); 126 | } 127 | } 128 | } 129 | } 130 | } 131 | 132 | EnumerableIList GetEnumerator() => EnumerableIList.Create(_frames); 133 | IEnumerator IEnumerable.GetEnumerator() => _frames.GetEnumerator(); 134 | IEnumerator IEnumerable.GetEnumerator() => _frames.GetEnumerator(); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/EnhancedStackFrame.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Ben A Adams. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Diagnostics; 5 | using System.Reflection; 6 | 7 | namespace Apkd.Internal 8 | { 9 | internal sealed class EnhancedStackFrame : StackFrame 10 | { 11 | string _fileName; 12 | int _lineNumber; 13 | int _colNumber; 14 | 15 | internal StackFrame StackFrame { get; } 16 | 17 | internal ResolvedMethod MethodInfo { get; } 18 | 19 | internal EnhancedStackFrame(StackFrame stackFrame, ResolvedMethod methodInfo, string fileName, int lineNumber, int colNumber) 20 | : base(fileName, lineNumber, colNumber) 21 | { 22 | StackFrame = stackFrame; 23 | MethodInfo = methodInfo; 24 | 25 | _fileName = fileName; 26 | _lineNumber = lineNumber; 27 | _colNumber = colNumber; 28 | } 29 | 30 | /// 31 | /// Gets the column number in the file that contains the code that is executing. 32 | /// This information is typically extracted from the debugging symbols for the executable. 33 | /// 34 | /// The file column number, or 0 (zero) if the file column number cannot be determined. 35 | public override int GetFileColumnNumber() => _colNumber; 36 | 37 | /// 38 | /// Gets the line number in the file that contains the code that is executing. 39 | /// This information is typically extracted from the debugging symbols for the executable. 40 | /// 41 | /// The file line number, or 0 (zero) if the file line number cannot be determined. 42 | public override int GetFileLineNumber() => _lineNumber; 43 | 44 | /// 45 | /// Gets the file name that contains the code that is executing. 46 | /// This information is typically extracted from the debugging symbols for the executable. 47 | /// 48 | /// The file name, or null if the file name cannot be determined. 49 | public override string GetFileName() 50 | { 51 | if (string.IsNullOrWhiteSpace(_fileName)) 52 | return null; 53 | 54 | if (_fileName.StartsWith(@"C:\buildslave\unity", System.StringComparison.Ordinal)) 55 | return null; 56 | 57 | return System.IO.Path.GetFileName(_fileName); 58 | // return _fileName; 59 | } 60 | 61 | internal string GetFullFilename() 62 | { 63 | if (string.IsNullOrWhiteSpace(_fileName)) 64 | return null; 65 | int index = _fileName.IndexOf("\\Assets\\"); 66 | if (index >= 0) 67 | return _fileName.Substring(index + 1); 68 | return _fileName; 69 | } 70 | 71 | internal StringBuilder AppendFullFilename(StringBuilder sb) 72 | { 73 | if (string.IsNullOrWhiteSpace(_fileName)) 74 | return sb; 75 | int index = _fileName.IndexOf("\\Assets\\"); 76 | if (index >= 0) 77 | return sb.Append(_fileName, index + 1); 78 | return sb.Append(_fileName); 79 | } 80 | 81 | internal bool IsEmpty => MethodInfo == null; 82 | /// 83 | /// Gets the offset from the start of the Microsoft intermediate language (MSIL) 84 | /// code for the method that is executing. This offset might be an approximation 85 | /// depending on whether or not the just-in-time (JIT) compiler is generating debugging 86 | /// code. The generation of this debugging information is controlled by the System.Diagnostics.DebuggableAttribute. 87 | /// 88 | /// The offset from the start of the MSIL code for the method that is executing. 89 | public override int GetILOffset() => StackFrame.GetILOffset(); 90 | 91 | /// 92 | /// Gets the method in which the frame is executing. 93 | /// 94 | /// The method in which the frame is executing. 95 | public override MethodBase GetMethod() => StackFrame.GetMethod(); 96 | 97 | /// 98 | /// Gets the offset from the start of the native just-in-time (JIT)-compiled code 99 | /// for the method that is being executed. The generation of this debugging information 100 | /// is controlled by the System.Diagnostics.DebuggableAttribute class. 101 | /// 102 | /// The offset from the start of the JIT-compiled code for the method that is being executed. 103 | public override int GetNativeOffset() => StackFrame.GetNativeOffset(); 104 | 105 | /// 106 | /// Builds a readable representation of the stack trace. 107 | /// 108 | /// A readable representation of the stack trace. 109 | public override string ToString() => MethodInfo.ToString(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/TypeNameHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace Apkd.Internal 8 | { 9 | // Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.TypeNameHelper.Sources/TypeNameHelper.cs 10 | internal static class TypeNameHelper 11 | { 12 | internal static readonly Dictionary BuiltInTypeNames = new Dictionary 13 | { 14 | { typeof(void), "void" }, 15 | { typeof(bool), "bool" }, 16 | { typeof(byte), "byte" }, 17 | { typeof(char), "char" }, 18 | { typeof(decimal), "decimal" }, 19 | { typeof(double), "double" }, 20 | { typeof(float), "float" }, 21 | { typeof(int), "int" }, 22 | { typeof(long), "long" }, 23 | { typeof(object), "object" }, 24 | { typeof(sbyte), "sbyte" }, 25 | { typeof(short), "short" }, 26 | { typeof(string), "string" }, 27 | { typeof(uint), "uint" }, 28 | { typeof(ulong), "ulong" }, 29 | { typeof(ushort), "ushort" } 30 | }; 31 | 32 | /// 33 | /// Pretty print a type name. 34 | /// 35 | /// The . 36 | /// true to print a fully qualified name. 37 | /// true to include generic parameter names. 38 | /// The pretty printed type name. 39 | internal static string GetTypeDisplayName(Type type, bool fullName = true, bool includeGenericParameterNames = false) 40 | { 41 | var builder = new StringBuilder(); 42 | ProcessType(builder, type, (fullName, includeGenericParameterNames)); 43 | return builder.ToString(); 44 | } 45 | 46 | internal static StringBuilder AppendTypeDisplayName(this StringBuilder builder, Type type, bool fullName = true, bool includeGenericParameterNames = false) 47 | { 48 | ProcessType(builder, type, (fullName, includeGenericParameterNames)); 49 | return builder; 50 | } 51 | 52 | /// 53 | /// Returns a name of given generic type without '`'. 54 | /// 55 | internal static string GetTypeNameForGenericType(Type type) 56 | { 57 | if (!type.IsGenericType) 58 | throw new ArgumentException("The given type should be generic", nameof(type)); 59 | 60 | var genericPartIndex = type.Name.IndexOf('`'); 61 | System.Diagnostics.Debug.Assert(genericPartIndex >= 0); 62 | 63 | return type.Name.Substring(0, genericPartIndex); 64 | } 65 | 66 | static void ProcessType(StringBuilder builder, Type type, (bool FullName, bool IncludeGenericParameterNames) options) 67 | { 68 | if (type.IsGenericType) 69 | { 70 | var genericArguments = type.GetGenericArguments(); 71 | ProcessGenericType(builder, type, genericArguments, genericArguments.Length, options); 72 | } 73 | else if (type.IsArray) 74 | { 75 | ProcessArrayType(builder, type, options); 76 | } 77 | else if (BuiltInTypeNames.TryGetValue(type, out var builtInName)) 78 | { 79 | builder.Append(builtInName); 80 | } 81 | else if (type.Namespace == nameof(System)) 82 | { 83 | builder.Append(type.Name); 84 | } 85 | else if (type.IsGenericParameter) 86 | { 87 | if (options.IncludeGenericParameterNames) 88 | builder.Append(type.Name); 89 | } 90 | else 91 | { 92 | builder.Append(options.FullName ? type.FullName ?? type.Name : type.Name); 93 | } 94 | } 95 | 96 | static void ProcessArrayType(StringBuilder builder, Type type, (bool FullName, bool IncludeGenericParameterNames) options) 97 | { 98 | var innerType = type; 99 | while (innerType.IsArray) 100 | innerType = innerType.GetElementType(); 101 | 102 | ProcessType(builder, innerType, options); 103 | 104 | while (type.IsArray) 105 | { 106 | builder.Append('['); 107 | builder.Append(',', type.GetArrayRank() - 1); 108 | builder.Append(']'); 109 | type = type.GetElementType(); 110 | } 111 | } 112 | 113 | static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length, (bool FullName, bool IncludeGenericParameterNames) options) 114 | { 115 | var offset = 0; 116 | if (type.IsNested) 117 | offset = type.DeclaringType.GetGenericArguments().Length; 118 | 119 | if (options.FullName) 120 | { 121 | if (type.IsNested) 122 | { 123 | ProcessGenericType(builder, type.DeclaringType, genericArguments, offset, options); 124 | builder.Append('+'); 125 | options.FullName = false; 126 | } 127 | else if (!string.IsNullOrEmpty(type.Namespace)) 128 | { 129 | builder.Append(type.Namespace); 130 | builder.Append('.'); 131 | } 132 | } 133 | 134 | var genericPartIndex = type.Name.IndexOf('`'); 135 | if (genericPartIndex <= 0) 136 | { 137 | builder.Append(type.Name); 138 | return; 139 | } 140 | 141 | builder.Append(type.Name, 0, genericPartIndex); 142 | 143 | builder.Append('<'); 144 | for (var i = offset; i < length; i++) 145 | { 146 | ProcessType(builder, genericArguments[i], options); 147 | if (i + 1 == length) 148 | continue; 149 | 150 | builder.Append(','); 151 | if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter) 152 | builder.Append(' '); 153 | } 154 | builder.Append('>'); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/UnityEditorOverrides.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace Apkd.Internal 5 | { 6 | static class UnityEditorOverrides 7 | { 8 | static readonly System.Threading.ThreadLocal builderLarge 9 | = new System.Threading.ThreadLocal(() => new StringBuilder(capacity: 4096)); 10 | 11 | static readonly System.Threading.ThreadLocal builderSmall 12 | = new System.Threading.ThreadLocal(() => new StringBuilder(capacity: 1024)); 13 | 14 | static StringBuilder CachedBuilderLarge => builderLarge.Value; 15 | static StringBuilder CachedBuilderSmall => builderSmall.Value; 16 | 17 | // Method used to build the stack trace string (eg. when using UnityEngine.Debug.Log). 18 | internal static string ExtractFormattedStackTrace(StackTrace stackTrace) 19 | { 20 | try 21 | { 22 | var temp = CachedBuilderLarge.Clear(); 23 | return PostprocessStacktrace(new EnhancedStackTrace(stackTrace).ToString(temp), true); 24 | } 25 | catch (Exception e) 26 | { 27 | return $"Failed to extract stacktrace:\n{stackTrace}\n=== Extractor error: ===\n{e}"; 28 | } 29 | } 30 | 31 | // Unity uses this method to post-process the stack trace before displaying it in the editor. 32 | // This doesn't affect the output in the log file. 33 | internal static string PostprocessStacktrace(string oldStackTrace, bool stripEngineInternalInformation) 34 | { 35 | #if APKD_STACKTRACE_NOPOSTPROCESS 36 | return oldStackTrace; 37 | #else 38 | try 39 | { 40 | if (oldStackTrace == null) 41 | return String.Empty; 42 | 43 | var output = CachedBuilderLarge.Clear(); 44 | output.Append('\n'); 45 | 46 | var temp = CachedBuilderSmall.Clear(); 47 | for (int i = 0; i < oldStackTrace.Length; ++i) 48 | { 49 | char c = oldStackTrace[i]; 50 | temp.Append(c); 51 | 52 | // check if we've collected a line into temp 53 | if (c == '\n' || i == oldStackTrace.Length - 1) 54 | { 55 | (bool skip, bool exit) = PostProcessLine(temp, stripEngineInternalInformation); 56 | 57 | if (exit) 58 | break; 59 | 60 | if (!skip) 61 | output.Append(temp); // copy line to output 62 | 63 | temp.Clear(); 64 | } 65 | } 66 | 67 | return output.ToString(); 68 | 69 | (bool skip, bool exit) PostProcessLine(StringBuilder line, bool ignoreInternal) 70 | { 71 | // ignore empty lines 72 | if (line.Length == 0 || line.Length == 1 && line[0] == '\n') 73 | return (skip: true, exit: false); 74 | 75 | // mke GameView GUI stack traces skip editor GUI part 76 | if (ignoreInternal && line.StartsWith("UnityEditor.EditorGUIUtility:RenderGameViewCameras")) 77 | return (skip: false, exit: true); 78 | 79 | line.Insert(0, "│ "); 80 | 81 | // unify path names to unix style 82 | line.Replace('\\', '/'); 83 | 84 | #if !APKD_STACKTRACE_NOFORMAT 85 | // emphasized method return type 86 | { 87 | line.Replace("‹", ""); 88 | line.Replace("›", ""); 89 | } 90 | // emphasized method name 91 | int boldEnd = line.IndexOf('‼'); 92 | if (boldEnd >= 0) 93 | { 94 | int boldStart = 0; 95 | for (int i = boldEnd; i >= 0; --i) 96 | { 97 | char c = line[i]; 98 | if (c == '.' || c == '+') 99 | { 100 | boldStart = i; 101 | break; 102 | } 103 | } 104 | line.Replace("‼", ""); 105 | line.Insert(boldStart + 1, ""); 106 | } 107 | // smaller filename and line number 108 | if (line.IndexOf('→') >= 0) 109 | { 110 | #if APKD_STACKTRACE_FILEPATH_FONTSIZE_7 111 | const string fontsize = "7"; 112 | #elif APKD_STACKTRACE_FILEPATH_FONTSIZE_9 113 | const string fontsize = "9"; 114 | #elif APKD_STACKTRACE_FILEPATH_FONTSIZE_10 115 | const string fontsize = "10"; 116 | #elif APKD_STACKTRACE_FILEPATH_FONTSIZE_11 117 | const string fontsize = "11"; 118 | #else 119 | const string fontsize = "8"; 120 | #endif 121 | line.Replace("→", ""); 122 | bool isLastLine = line[line.Length - 1] != '\n'; 123 | int endIndex = isLastLine ? line.Length : line.Length - 1; 124 | line.Insert(endIndex, ""); 125 | } 126 | #endif 127 | return (skip: false, exit: false); 128 | } 129 | } 130 | catch (Exception e) 131 | { 132 | return $"Failed to post-process stacktrace:\n{oldStackTrace}\n=== Post-Processing error: ===\n{e}"; 133 | } 134 | #endif 135 | } 136 | 137 | // Method used to extract the stack trace from an exception. 138 | internal static void ExtractStringFromExceptionInternal(object topLevel, out string message, out string stackTrace) 139 | { 140 | try 141 | { 142 | StringBuilder temp = CachedBuilderLarge; 143 | Exception current = topLevel as Exception; 144 | 145 | temp.Clear(); 146 | if (current.Message != null) 147 | message = temp.Append(current.GetType().ToString()).Append(": ").Append(current.Message).ToString(); 148 | else 149 | message = current.GetType().ToString(); 150 | 151 | temp.Clear(); 152 | while (current != null) 153 | { 154 | if (current == topLevel) 155 | { 156 | new EnhancedStackTrace(current).Append(temp); 157 | } 158 | else 159 | { 160 | temp.Append($" ---> "); 161 | temp.Append('\n'); 162 | 163 | if (!string.IsNullOrEmpty(current.Message)) 164 | temp.Append(current.GetType()).Append(": ").Append(current.Message).Append('\n'); 165 | else 166 | temp.Append(current.GetType()).Append('\n'); 167 | 168 | new EnhancedStackTrace(current).Append(temp); 169 | 170 | temp.Append('\n'); 171 | temp.Append("Rethrown as:"); 172 | } 173 | 174 | current = current.InnerException; 175 | } 176 | new EnhancedStackTrace(new StackTrace(skipFrames: 1, fNeedFileInfo: true)).Append(temp); 177 | 178 | stackTrace = PostprocessStacktrace(temp.ToString(), true); 179 | } 180 | catch (Exception ex) 181 | { 182 | message = $"{topLevel}\n\nDemistifier: Unable to extract stack trace from exception: {(topLevel as Exception).GetType().Name}."; 183 | stackTrace = ex.ToString(); 184 | } 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/ResolvedMethod.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | using System.Reflection; 7 | using System.Linq; 8 | 9 | namespace Apkd.Internal 10 | { 11 | internal sealed class ResolvedMethod 12 | { 13 | internal MethodBase MethodBase { get; set; } 14 | 15 | internal Type DeclaringType { get; set; } 16 | 17 | internal bool IsAsync { get; set; } 18 | 19 | internal bool IsLambda { get; set; } 20 | 21 | internal ResolvedParameter ReturnParameter { get; set; } 22 | 23 | internal string Name { get; set; } 24 | 25 | internal int? Ordinal { get; set; } 26 | 27 | internal string GenericArguments { get; set; } 28 | 29 | internal Type[] ResolvedGenericArguments { get; set; } 30 | 31 | internal MethodBase SubMethodBase { get; set; } 32 | 33 | internal string SubMethod { get; set; } 34 | 35 | internal EnumerableIList Parameters { get; set; } 36 | 37 | internal EnumerableIList SubMethodParameters { get; set; } 38 | 39 | public override string ToString() => Append(new StringBuilder()).ToString(); 40 | 41 | internal StringBuilder Append(StringBuilder builder) 42 | { 43 | if (ReturnParameter != null) 44 | { 45 | if (IsAsync) 46 | ReturnParameter.Prefix2 = "async"; 47 | 48 | ReturnParameter.Append(builder); 49 | builder.Append(' '); 50 | } 51 | 52 | bool hasSubMethodOrLambda = !string.IsNullOrEmpty(SubMethod) || IsLambda; 53 | 54 | if (DeclaringType != null) 55 | { 56 | 57 | if (Name == ".ctor") 58 | { 59 | if (!hasSubMethodOrLambda) 60 | { 61 | builder 62 | .Append(".new "); 63 | 64 | AppendDeclaringTypeName(builder) 65 | .Append(Name); 66 | } 67 | } 68 | else if (Name == ".cctor") 69 | { 70 | builder.Append("static "); 71 | 72 | AppendDeclaringTypeName(builder); 73 | } 74 | else 75 | { 76 | AppendDeclaringTypeName(builder) 77 | .Append('.') 78 | .Append(Name); 79 | } 80 | } 81 | else 82 | { 83 | builder 84 | .Append('.') 85 | .Append(Name); 86 | 87 | } 88 | builder.Append(GenericArguments); 89 | 90 | if (!hasSubMethodOrLambda) 91 | builder.AppendFormattingChar('‼'); 92 | 93 | #if !APKD_STACKTRACE_HIDEPARAMS 94 | #if APKD_STACKTRACE_FULLPARAMS 95 | builder.Append('('); 96 | if (MethodBase != null) 97 | { 98 | var isFirst = true; 99 | foreach (var param in Parameters) 100 | { 101 | if (isFirst) 102 | isFirst = false; 103 | else 104 | builder.Append(',').Append(' '); 105 | 106 | param.Append(builder); 107 | } 108 | } 109 | else 110 | { 111 | builder.Append('?'); 112 | } 113 | builder.Append(')'); 114 | #elif APKD_STACKTRACE_SHORTPARAMS 115 | char GetParamAlphabeticalName(int index) => (char)((int)'a' + index); 116 | char? GetParamNameFirstLetter(ResolvedParameter param) => string.IsNullOrEmpty(param?.Name) ? null as char? : param.Name[0]; 117 | 118 | builder.Append('('); 119 | if (MethodBase != null) 120 | { 121 | var isFirst = true; 122 | builder.AppendFormattingChar('‹'); 123 | for (int i = 0, n = Parameters.Count; i < n; ++i) 124 | { 125 | if (isFirst) 126 | isFirst = false; 127 | else 128 | builder.Append(',').Append(' '); 129 | 130 | builder.Append(GetParamNameFirstLetter(Parameters[i]) ?? GetParamAlphabeticalName(i)); 131 | } 132 | builder.AppendFormattingChar('›'); 133 | } 134 | else 135 | { 136 | builder.Append('?'); 137 | } 138 | builder.Append(')'); 139 | #else 140 | builder.Append('('); 141 | if (MethodBase != null) 142 | { 143 | var isFirst = true; 144 | builder.AppendFormattingChar('‹'); 145 | foreach (var param in Parameters) 146 | { 147 | if (isFirst) 148 | isFirst = false; 149 | else 150 | builder.Append(',').Append(' '); 151 | 152 | param.AppendTypeName(builder); 153 | } 154 | builder.AppendFormattingChar('›'); 155 | } 156 | else 157 | { 158 | builder.Append('?'); 159 | } 160 | builder.Append(')'); 161 | #endif 162 | #endif 163 | 164 | if (hasSubMethodOrLambda) 165 | { 166 | builder.Append('+'); 167 | builder.Append(SubMethod); 168 | if (IsLambda) 169 | { 170 | builder.Append('('); 171 | if (SubMethodBase != null) 172 | { 173 | var isFirst = true; 174 | builder.AppendFormattingChar('‹'); 175 | foreach (var param in SubMethodParameters) 176 | { 177 | if (isFirst) 178 | isFirst = false; 179 | else 180 | builder.Append(',').Append(' '); 181 | 182 | param.AppendTypeName(builder); 183 | } 184 | builder.AppendFormattingChar('›'); 185 | } 186 | else 187 | { 188 | builder.Append('?'); 189 | } 190 | builder.Append(")➞ "); 191 | 192 | var returnType = (SubMethodBase as MethodInfo)?.ReturnType; 193 | if (returnType != null) 194 | TypeNameHelper.AppendTypeDisplayName(builder, returnType, fullName: false, includeGenericParameterNames: false); 195 | else 196 | builder.Append("{…}"); 197 | 198 | if (Ordinal.HasValue) 199 | { 200 | builder.Append(' '); 201 | builder.Append('['); 202 | builder.Append(Ordinal.Value); 203 | builder.Append(']'); 204 | } 205 | builder.AppendFormattingChar('‼'); 206 | } 207 | else 208 | { 209 | builder.AppendFormattingChar('‼'); 210 | builder.Append('('); 211 | var isFirst = true; 212 | builder.AppendFormattingChar('‹'); 213 | foreach (var param in SubMethodParameters) 214 | { 215 | if (isFirst) 216 | isFirst = false; 217 | else 218 | builder.Append(',').Append(' '); 219 | 220 | param.AppendTypeName(builder); 221 | } 222 | builder.AppendFormattingChar('›'); 223 | builder.Append(')'); 224 | } 225 | } 226 | 227 | return builder; 228 | } 229 | 230 | StringBuilder AppendDeclaringTypeName(StringBuilder builder) 231 | => DeclaringType != null ? builder.AppendTypeDisplayName(DeclaringType, fullName: true, includeGenericParameterNames: true) : builder; 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/Internal/StringBuilder.cs: -------------------------------------------------------------------------------- 1 | // Original author: Nicolas Gadenne (contact@gaddygames.com) 2 | // https://github.com/snozbot/StringBuilder 3 | 4 | // MIT License 5 | // 6 | // Copyright (c) 2017 snozbot 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in all 16 | // copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | // SOFTWARE. 25 | 26 | using System.Collections; 27 | using System.Collections.Generic; 28 | 29 | namespace Apkd 30 | { 31 | public sealed class StringBuilder : IList, IReadOnlyList 32 | { 33 | string _cachedString = ""; 34 | char[] _buffer = null; 35 | int _bufferPos = 0; 36 | List _temp; 37 | 38 | public StringBuilder(int capacity = 64) 39 | => _buffer = new char[capacity]; 40 | 41 | public bool IsEmpty() => _bufferPos == 0; 42 | 43 | public int Length => _bufferPos; 44 | public int Capacity => _buffer.Length; 45 | 46 | [System.Runtime.CompilerServices.IndexerName("Chars")] 47 | public char this[int index] 48 | { 49 | get => _buffer[index]; 50 | set 51 | { 52 | _buffer[index] = value; 53 | _cachedString = null; 54 | } 55 | } 56 | 57 | /// Return the string result 58 | public override string ToString() 59 | { 60 | if (_cachedString == null) 61 | _cachedString = new string(_buffer, 0, _bufferPos); 62 | 63 | return _cachedString; 64 | } 65 | 66 | /// Clears the StringBuilder instance (preserving allocated capacity) 67 | public StringBuilder Clear() 68 | { 69 | _bufferPos = 0; 70 | _cachedString = null; 71 | return this; 72 | } 73 | 74 | /// Insert string at given index 75 | public StringBuilder Insert(int index, string text) 76 | { 77 | AddLength(text.Length); 78 | _temp = _temp ?? new List(Capacity); 79 | _temp.Clear(); 80 | _temp.AddRange(_buffer); 81 | _temp.RemoveRange(_bufferPos, _temp.Count - _bufferPos); 82 | for (int i = 0; i < text.Length; ++i) 83 | _temp.Insert(index + i, text[i]); 84 | _temp.CopyTo(_buffer); 85 | _bufferPos += text.Length; 86 | _cachedString = null; 87 | 88 | return this; 89 | } 90 | 91 | /// Insert character at given index 92 | public StringBuilder Insert(int index, char character) 93 | { 94 | AddLength(1); 95 | _temp = _temp ?? new List(Capacity); 96 | _temp.Clear(); 97 | _temp.AddRange(_buffer); 98 | _temp.RemoveRange(_bufferPos, _temp.Count - _bufferPos); 99 | _temp.Insert(index, character); 100 | _temp.CopyTo(_buffer); 101 | _bufferPos += 1; 102 | _cachedString = null; 103 | 104 | return this; 105 | } 106 | 107 | /// Remove characters starting from specified index 108 | public StringBuilder Remove(int index, int count) 109 | { 110 | _temp = _temp ?? new List(Capacity); 111 | _temp.Clear(); 112 | _temp.AddRange(_buffer); 113 | _temp.RemoveRange(_bufferPos, _temp.Count - _bufferPos); 114 | _temp.RemoveRange(index, count); 115 | _temp.CopyTo(_buffer); 116 | _bufferPos -= count; 117 | _cachedString = null; 118 | 119 | return this; 120 | } 121 | 122 | /// Append the content of another StringBuilder instance 123 | public StringBuilder Append(StringBuilder other) 124 | { 125 | if (other._bufferPos == 0) 126 | return this; 127 | 128 | AddLength(other._buffer.Length); 129 | other._buffer.CopyTo(_buffer, _bufferPos); 130 | _cachedString = null; 131 | _bufferPos += other._bufferPos; 132 | 133 | return this; 134 | } 135 | 136 | /// Append a string 137 | public StringBuilder Append(string value) 138 | { 139 | if (string.IsNullOrEmpty(value)) 140 | return this; 141 | 142 | int n = value.Length; 143 | AddLength(n); 144 | for (int i = 0; i < n; i++) 145 | _buffer[_bufferPos + i] = value[i]; 146 | _bufferPos += n; 147 | _cachedString = null; 148 | 149 | return this; 150 | } 151 | 152 | /// Append a substring of a string 153 | public StringBuilder Append(string value, int valueStartIndex = 0, int? valueLength = default) 154 | { 155 | if (string.IsNullOrEmpty(value)) 156 | return this; 157 | 158 | if (valueLength == 0) 159 | return this; 160 | 161 | int n = System.Math.Min(value.Length, valueLength ?? value.Length - valueStartIndex); 162 | 163 | if (valueStartIndex < 0) 164 | throw new System.ArgumentOutOfRangeException(nameof(valueStartIndex)); 165 | 166 | if (valueLength < 0) 167 | throw new System.ArgumentOutOfRangeException(nameof(valueLength)); 168 | 169 | AddLength(n); 170 | for (int i = 0; i < n; ++i) 171 | _buffer[_bufferPos + i] = value[valueStartIndex + i]; 172 | _bufferPos += n; 173 | _cachedString = null; 174 | 175 | return this; 176 | } 177 | 178 | /// Append a character 179 | public StringBuilder Append(char c) 180 | { 181 | AddLength(1); 182 | _buffer[_bufferPos] = c; 183 | _bufferPos += 1; 184 | _cachedString = null; 185 | return this; 186 | } 187 | 188 | /// Append a character 189 | public StringBuilder Append(char c, int repeat) 190 | { 191 | AddLength(repeat); 192 | for (int i = 0; i < repeat; i++) 193 | _buffer[_bufferPos + i] = c; 194 | _bufferPos += repeat; 195 | _cachedString = null; 196 | return this; 197 | } 198 | 199 | /// Append an object (calls .ToString()) 200 | public StringBuilder Append(object value) 201 | { 202 | Append(value.ToString()); 203 | return this; 204 | } 205 | 206 | /// Append an int without memory allocation 207 | public StringBuilder Append(int value) 208 | { 209 | // Allocate enough memory to handle any int number 210 | AddLength(16); 211 | 212 | // Handle the negative case 213 | if (value < 0) 214 | { 215 | value = -value; 216 | _buffer[_bufferPos++] = '-'; 217 | } 218 | 219 | // Copy the digits in reverse order 220 | int nbChars = 0; 221 | do 222 | { 223 | _buffer[_bufferPos++] = (char)('0' + value % 10); 224 | value /= 10; 225 | nbChars++; 226 | } while (value != 0); 227 | 228 | // Reverse the result 229 | for (int i = nbChars / 2 - 1; i >= 0; i--) 230 | { 231 | char c = _buffer[_bufferPos - i - 1]; 232 | _buffer[_bufferPos - i - 1] = _buffer[_bufferPos - nbChars + i]; 233 | _buffer[_bufferPos - nbChars + i] = c; 234 | } 235 | _cachedString = null; 236 | return this; 237 | } 238 | 239 | /// Append a float without memory allocation. 240 | public StringBuilder Append(float valueF) 241 | { 242 | double value = valueF; 243 | _cachedString = null; 244 | AddLength(32); // Check we have enough buffer allocated to handle any float number 245 | 246 | // Handle the 0 case 247 | if (value == 0) 248 | { 249 | _buffer[_bufferPos++] = '0'; 250 | return this; 251 | } 252 | 253 | // Handle the negative case 254 | if (value < 0) 255 | { 256 | value = -value; 257 | _buffer[_bufferPos++] = '-'; 258 | } 259 | 260 | // Get the 7 meaningful digits as a long 261 | int nbDecimals = 0; 262 | while (value < 1000000) 263 | { 264 | value *= 10; 265 | nbDecimals++; 266 | } 267 | long valueLong = (long)System.Math.Round(value); 268 | 269 | // Parse the number in reverse order 270 | int nbChars = 0; 271 | bool isLeadingZero = true; 272 | while (valueLong != 0 || nbDecimals >= 0) 273 | { 274 | // We stop removing leading 0 when non-0 or decimal digit 275 | if (valueLong % 10 != 0 || nbDecimals <= 0) 276 | isLeadingZero = false; 277 | 278 | // Write the last digit (unless a leading zero) 279 | if (!isLeadingZero) 280 | _buffer[_bufferPos + (nbChars++)] = (char)('0' + valueLong % 10); 281 | 282 | // Add the decimal point 283 | if (--nbDecimals == 0 && !isLeadingZero) 284 | _buffer[_bufferPos + (nbChars++)] = '.'; 285 | 286 | valueLong /= 10; 287 | } 288 | _bufferPos += nbChars; 289 | 290 | // Reverse the result 291 | for (int i = nbChars / 2 - 1; i >= 0; i--) 292 | { 293 | char c = _buffer[_bufferPos - i - 1]; 294 | _buffer[_bufferPos - i - 1] = _buffer[_bufferPos - nbChars + i]; 295 | _buffer[_bufferPos - nbChars + i] = c; 296 | } 297 | 298 | return this; 299 | } 300 | 301 | /// Replace all occurences of a character 302 | public StringBuilder Replace(char a, char b) 303 | { 304 | for (int i = 0; i < _bufferPos; ++i) 305 | if (_buffer[i] == a) 306 | _buffer[i] = b; 307 | 308 | _cachedString = null; 309 | return this; 310 | } 311 | 312 | /// Replace all occurences of a string by another one 313 | public StringBuilder Replace(string oldStr, string newStr) 314 | { 315 | if (newStr == null) 316 | throw new System.ArgumentNullException(nameof(oldStr)); 317 | 318 | if (newStr == null) 319 | throw new System.ArgumentNullException(nameof(newStr)); 320 | 321 | if (_bufferPos == 0) 322 | return this; 323 | 324 | _temp = _temp ?? new List(Capacity); 325 | _temp.Clear(); 326 | 327 | // Create the new string into _temp 328 | for (int i = 0; i < _bufferPos; i++) 329 | { 330 | bool isToReplace = false; 331 | if (_buffer[i] == oldStr[0]) // If first character found, check for the rest of the string to replace 332 | { 333 | int k = 1; 334 | while (k < oldStr.Length && _buffer[i + k] == oldStr[k]) 335 | k++; 336 | isToReplace = (k >= oldStr.Length); 337 | } 338 | if (isToReplace) // Do the replacement 339 | { 340 | i += oldStr.Length - 1; 341 | for (int k = 0; k < newStr.Length; k++) 342 | _temp.Add(newStr[k]); 343 | } 344 | else // No replacement, copy the old character 345 | { 346 | _temp.Add(_buffer[i]); 347 | } 348 | } 349 | 350 | // Copy back the new string into m_chars 351 | AddLength(_temp.Count - _bufferPos); 352 | _temp.CopyTo(_buffer); 353 | _bufferPos = _temp.Count; 354 | _cachedString = null; 355 | return this; 356 | } 357 | 358 | public bool StartsWith(string value) 359 | => StartsWith(value, 0, false); 360 | 361 | public bool StartsWith(string value, bool ignoreCase) 362 | => StartsWith(value, 0, ignoreCase); 363 | 364 | public bool StartsWith(string value, int startIndex = 0, bool ignoreCase = false) 365 | { 366 | int length = value.Length; 367 | int n = startIndex + length; 368 | if (ignoreCase == false) 369 | { 370 | for (int i = startIndex; i < n; i++) 371 | if (_buffer[i] != value[i - startIndex]) 372 | return false; 373 | } 374 | else 375 | { 376 | for (int j = startIndex; j < n; j++) 377 | if (char.ToLower(_buffer[j]) != char.ToLower(value[j - startIndex])) 378 | return false; 379 | } 380 | return true; 381 | } 382 | 383 | /// Increase the buffer capacity if necessary 384 | void AddLength(int charsToAdd) 385 | { 386 | if (_bufferPos + charsToAdd <= Capacity) 387 | return; 388 | 389 | int newCapacity = System.Math.Max(_bufferPos + charsToAdd, Capacity * 2); 390 | char[] newBuffer = new char[newCapacity]; 391 | _buffer.CopyTo(newBuffer, 0); 392 | _buffer = newBuffer; 393 | } 394 | 395 | public int IndexOf(char value) 396 | => IndexOf(value, 0); 397 | 398 | public int IndexOf(char value, int startIndex) 399 | { 400 | for (int i = startIndex, n = _bufferPos; i < n; i++) 401 | if (_buffer[i] == value) 402 | return i; 403 | return -1; 404 | } 405 | 406 | public int IndexOf(string value) 407 | => IndexOf(value, 0, false); 408 | 409 | public int IndexOf(string value, int startIndex) 410 | => IndexOf(value, startIndex, false); 411 | 412 | public int IndexOf(string value, bool ignoreCase) 413 | => IndexOf(value, 0, ignoreCase); 414 | 415 | public int IndexOf(string value, int startIndex, bool ignoreCase) 416 | { 417 | int length = value.Length; 418 | int lengthDelta = (_bufferPos - length) + 1; 419 | if (ignoreCase == false) 420 | { 421 | for (int i = startIndex; i < lengthDelta; i++) 422 | { 423 | if (_buffer[i] == value[0]) 424 | { 425 | int n = 1; 426 | while ((n < length) && (_buffer[i + n] == value[n])) 427 | n++; 428 | 429 | if (n == length) 430 | return i; 431 | } 432 | } 433 | } 434 | else 435 | { 436 | for (int j = startIndex; j < lengthDelta; j++) 437 | { 438 | if (char.ToLower(_buffer[j]) == char.ToLower(value[0])) 439 | { 440 | int n = 1; 441 | while ((n < length) && (char.ToLower(_buffer[j + n]) == char.ToLower(value[n]))) 442 | n++; 443 | 444 | if (n == length) 445 | return j; 446 | } 447 | } 448 | } 449 | return -1; 450 | } 451 | 452 | int ICollection.Count => _bufferPos; 453 | 454 | int IReadOnlyCollection.Count => _bufferPos; 455 | 456 | bool ICollection.IsReadOnly => false; 457 | 458 | void IList.RemoveAt(int index) => Remove(index, 1); 459 | 460 | void IList.Insert(int index, char item) => Insert(index, item); 461 | 462 | void ICollection.Clear() => Clear(); 463 | 464 | void ICollection.Add(char character) => Append(character); 465 | 466 | bool ICollection.Contains(char item) => IndexOf(item) != -1; 467 | 468 | public void CopyTo(char[] array, int arrayIndex) 469 | => System.Array.Copy( 470 | sourceArray: _buffer, 471 | sourceIndex: 0, 472 | destinationArray: array, 473 | destinationIndex: arrayIndex, 474 | length: _bufferPos); 475 | 476 | bool ICollection.Remove(char item) 477 | { 478 | int index = IndexOf(item); 479 | if (index == -1) 480 | return false; 481 | Remove(index, 1); 482 | return true; 483 | } 484 | 485 | public List.Enumerator GetEnumerator() 486 | { 487 | _temp = _temp ?? new List(Capacity); 488 | _temp.Clear(); 489 | _temp.AddRange(_buffer); 490 | _temp.RemoveRange(_bufferPos, _temp.Count - _bufferPos); 491 | return _temp.GetEnumerator(); 492 | } 493 | 494 | IEnumerator IEnumerable.GetEnumerator() 495 | => (this as IEnumerable).GetEnumerator(); 496 | 497 | IEnumerator IEnumerable.GetEnumerator() 498 | => GetEnumerator(); 499 | 500 | public static implicit operator string(StringBuilder builder) 501 | => builder.ToString(); 502 | } 503 | } 504 | -------------------------------------------------------------------------------- /src/EnhancedStackTrace.Frames.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Ben A Adams. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | // Copyright (c) .NET Foundation. All rights reserved. 4 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 5 | 6 | using System; 7 | using System.Collections; 8 | using System.Collections.Generic; 9 | using System.Diagnostics; 10 | using System.Linq; 11 | using System.Reflection; 12 | using System.Runtime.CompilerServices; 13 | using System.Runtime.ExceptionServices; 14 | using System.Threading; 15 | using System.Threading.Tasks; 16 | using static System.Reflection.BindingFlags; 17 | 18 | namespace Apkd.Internal 19 | { 20 | internal sealed partial class EnhancedStackTrace 21 | { 22 | static readonly Type StackTraceHiddenAttibuteType = Type.GetType("System.Diagnostics.StackTraceHiddenAttribute", false); 23 | static readonly MethodInfo UnityEditorInspectorWindowOnGuiMethod = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.InspectorWindow", false)?.GetMethod("OnGUI", NonPublic | Instance); 24 | static readonly ThreadLocal TempStringBuilder = new ThreadLocal(() => new StringBuilder(capacity: 256)); 25 | 26 | static readonly CacheDictionary customAttributeCache 27 | = new CacheDictionary(cacheSize: 256); 28 | 29 | static readonly CacheDictionary resolvedMethodCache 30 | = new CacheDictionary(cacheSize: 256); 31 | 32 | static List GetFrames(Exception exception) 33 | { 34 | if (exception == null) 35 | return new List(); 36 | 37 | var needFileInfo = true; 38 | var stackTrace = new StackTrace(exception, needFileInfo); 39 | 40 | return GetFrames(stackTrace); 41 | } 42 | 43 | static readonly FieldInfo capturedTracesFieldInfo = typeof(StackTrace).GetField("captured_traces", NonPublic | Instance); 44 | 45 | static StackTrace[] GetInnerStackTraces(StackTrace st) 46 | => capturedTracesFieldInfo?.GetValue(st) as StackTrace[] ?? Array.Empty(); 47 | 48 | static List GetFrames(StackTrace stackTrace) 49 | { 50 | IEnumerable EnumerateEnhancedFrames(StackTrace st) 51 | { 52 | IEnumerable EnumerateFrames(StackTrace st2) 53 | { 54 | foreach (var inner in GetInnerStackTraces(st2)) 55 | foreach (var frame in EnumerateFrames(inner)) 56 | yield return frame; 57 | 58 | for (var i = 0; i < st2.FrameCount; i++) 59 | yield return st2.GetFrame(i); 60 | } 61 | 62 | EnhancedStackFrame MakeEnhancedFrame(StackFrame frame, MethodBase method) 63 | => new EnhancedStackFrame( 64 | frame, 65 | methodInfo: GetResolvedMethod(method), 66 | fileName: frame.GetFileName(), 67 | lineNumber: frame.GetFileLineNumber(), 68 | colNumber: frame.GetFileColumnNumber()); 69 | 70 | bool collapseNext = false; 71 | StackFrame current = null; 72 | 73 | foreach (var next in EnumerateFrames(stackTrace)) 74 | { 75 | try 76 | { 77 | if (current != null) 78 | { 79 | var method = current.GetMethod(); 80 | 81 | if (method == null) // TODO: remove this 82 | continue; 83 | 84 | bool shouldExcludeFromCollapse = ShouldExcludeFromCollapse(method); 85 | if (shouldExcludeFromCollapse) 86 | collapseNext = false; 87 | 88 | if ((collapseNext || ShouldCollapseStackFrames(method)) && !shouldExcludeFromCollapse) 89 | { 90 | if (ShouldCollapseStackFrames(next.GetMethod())) 91 | { 92 | collapseNext = true; 93 | continue; 94 | } 95 | else 96 | { 97 | collapseNext = false; 98 | } 99 | } 100 | 101 | if (!ShouldShowInStackTrace(method)) 102 | continue; 103 | 104 | yield return MakeEnhancedFrame(current, method); 105 | } 106 | } 107 | finally 108 | { 109 | current = next; 110 | } 111 | } 112 | 113 | if (current != null) 114 | yield return MakeEnhancedFrame(current, current.GetMethod()); 115 | } 116 | 117 | var resultList = new List(capacity: stackTrace.FrameCount); 118 | foreach (var item in EnumerateEnhancedFrames(stackTrace)) 119 | resultList.Add(item); 120 | return resultList; 121 | } 122 | 123 | static bool IsDefined(MemberInfo member) 124 | { 125 | foreach (var attr in GetCustomAttributes(member)) 126 | if (attr is T) 127 | return true; 128 | return false; 129 | } 130 | 131 | static object[] GetCustomAttributes(ICustomAttributeProvider obj) 132 | => customAttributeCache.GetOrInitializeValue(obj, x => x.GetCustomAttributes(inherit: false)); 133 | 134 | internal static ResolvedMethod GetResolvedMethod(MethodBase methodBase) 135 | => resolvedMethodCache.GetOrInitializeValue(methodBase, x => GetResolvedMethodInternal(methodBase)); 136 | 137 | internal static ResolvedMethod GetResolvedMethodInternal(MethodBase methodBase) 138 | { 139 | // Special case: no method available 140 | if (methodBase == null) 141 | return null; 142 | 143 | var method = methodBase; 144 | 145 | var methodDisplayInfo = new ResolvedMethod { SubMethodBase = method }; 146 | 147 | // Type name 148 | var type = method.DeclaringType; 149 | 150 | var subMethodName = method.Name; 151 | var methodName = method.Name; 152 | 153 | if (type != null && IsDefined(type) && (typeof(IAsyncStateMachine).IsAssignableFrom(type) || typeof(IEnumerator).IsAssignableFrom(type))) 154 | { 155 | methodDisplayInfo.IsAsync = typeof(IAsyncStateMachine).IsAssignableFrom(type); 156 | 157 | // Convert StateMachine methods to correct overload +MoveNext() 158 | if (!TryResolveStateMachineMethod(ref method, out type)) 159 | { 160 | methodDisplayInfo.SubMethodBase = null; 161 | subMethodName = null; 162 | } 163 | 164 | methodName = method.Name; 165 | } 166 | 167 | // Method name 168 | methodDisplayInfo.MethodBase = method; 169 | methodDisplayInfo.Name = methodName; 170 | if (method.Name.IndexOf('<') >= 0) 171 | { 172 | if (TryResolveGeneratedName(ref method, out type, out methodName, out subMethodName, out var kind, out var ordinal)) 173 | { 174 | methodName = method.Name; 175 | methodDisplayInfo.MethodBase = method; 176 | methodDisplayInfo.Name = methodName; 177 | methodDisplayInfo.Ordinal = ordinal; 178 | } 179 | else 180 | { 181 | methodDisplayInfo.MethodBase = null; 182 | } 183 | 184 | methodDisplayInfo.IsLambda = (kind == GeneratedNameKind.LambdaMethod); 185 | 186 | if (methodDisplayInfo.IsLambda && type != null) 187 | { 188 | if (methodName == ".cctor") 189 | { 190 | if (type.IsGenericTypeDefinition && !type.IsConstructedGenericType) 191 | { 192 | // TODO: diagnose type's generic type arguments from frame's "this" or something 193 | } 194 | else 195 | { 196 | var fields = type.GetFields(Static | Public | NonPublic); 197 | foreach (var field in fields) 198 | { 199 | var value = field.GetValue(field); 200 | if (value is Delegate d) 201 | { 202 | if (ReferenceEquals(d.Method, methodBase) && 203 | d.Target.ToString() == methodBase.DeclaringType.ToString()) 204 | { 205 | methodDisplayInfo.Name = field.Name; 206 | methodDisplayInfo.IsLambda = false; 207 | method = methodBase; 208 | break; 209 | } 210 | } 211 | } 212 | } 213 | } 214 | } 215 | } 216 | 217 | if (subMethodName != methodName) 218 | methodDisplayInfo.SubMethod = subMethodName; 219 | 220 | // ResolveStateMachineMethod may have set declaringType to null 221 | if (type != null) 222 | methodDisplayInfo.DeclaringType = type; 223 | 224 | if (method is MethodInfo mi) 225 | { 226 | var returnParameter = mi.ReturnParameter; 227 | if (returnParameter != null) 228 | { 229 | methodDisplayInfo.ReturnParameter = GetParameter(mi.ReturnParameter); 230 | } 231 | else if (mi.ReturnType != null) 232 | { 233 | methodDisplayInfo.ReturnParameter = new ResolvedParameter 234 | { 235 | Prefix = "", 236 | Name = "", 237 | ResolvedType = mi.ReturnType, 238 | }; 239 | } 240 | } 241 | 242 | if (method.IsGenericMethod) 243 | { 244 | var genericArguments = method.GetGenericArguments(); 245 | var builder = TempStringBuilder.Value.Clear(); 246 | 247 | builder.Append('<'); 248 | 249 | for (int i = 0; i < genericArguments.Length; ++i) 250 | { 251 | if (i > 0) 252 | builder.Append(',').Append(' '); 253 | 254 | TypeNameHelper.AppendTypeDisplayName(builder, genericArguments[i], fullName: false, includeGenericParameterNames: true); 255 | } 256 | 257 | builder.Append('>'); 258 | 259 | methodDisplayInfo.GenericArguments = builder.ToString(); 260 | methodDisplayInfo.ResolvedGenericArguments = genericArguments; 261 | } 262 | 263 | // Method parameters 264 | var parameters = method.GetParameters(); 265 | if (parameters.Length > 0) 266 | { 267 | var parameterList = new List(parameters.Length); 268 | foreach (var parameter in parameters) 269 | parameterList.Add(GetParameter(parameter)); 270 | 271 | methodDisplayInfo.Parameters = parameterList; 272 | } 273 | 274 | if (methodDisplayInfo.SubMethodBase == methodDisplayInfo.MethodBase) 275 | { 276 | methodDisplayInfo.SubMethodBase = null; 277 | } 278 | else if (methodDisplayInfo.SubMethodBase != null) 279 | { 280 | parameters = methodDisplayInfo.SubMethodBase.GetParameters(); 281 | if (parameters.Length > 0) 282 | { 283 | var parameterList = new List(parameters.Length); 284 | foreach (var parameter in parameters) 285 | { 286 | var param = GetParameter(parameter); 287 | if (param.Name?.StartsWith("<") ?? true) continue; 288 | 289 | parameterList.Add(param); 290 | } 291 | 292 | methodDisplayInfo.SubMethodParameters = parameterList; 293 | } 294 | } 295 | 296 | return methodDisplayInfo; 297 | } 298 | 299 | static bool TryResolveGeneratedName(ref MethodBase method, out Type type, out string methodName, out string subMethodName, out GeneratedNameKind kind, out int? ordinal) 300 | { 301 | kind = GeneratedNameKind.None; 302 | type = method.DeclaringType; 303 | subMethodName = null; 304 | ordinal = null; 305 | methodName = method.Name; 306 | 307 | var generatedName = methodName; 308 | 309 | if (!TryParseGeneratedName(generatedName, out kind, out var openBracketOffset, out var closeBracketOffset)) 310 | return false; 311 | 312 | methodName = generatedName.Substring(openBracketOffset + 1, closeBracketOffset - openBracketOffset - 1); 313 | 314 | switch (kind) 315 | { 316 | case GeneratedNameKind.LocalFunction: 317 | var localNameStart = generatedName.IndexOf((char)kind, closeBracketOffset + 1); 318 | if (localNameStart < 0) 319 | break; 320 | localNameStart += 3; 321 | 322 | if (localNameStart < generatedName.Length) 323 | { 324 | var localNameEnd = generatedName.IndexOf("|", localNameStart); 325 | if (localNameEnd > 0) 326 | subMethodName = generatedName.Substring(localNameStart, localNameEnd - localNameStart); 327 | } 328 | break; 329 | case GeneratedNameKind.LambdaMethod: 330 | subMethodName = ""; 331 | break; 332 | } 333 | 334 | var dt = method.DeclaringType; 335 | if (dt == null) 336 | return false; 337 | 338 | var matchHint = GetMatchHint(kind, method); 339 | 340 | var matchName = methodName; 341 | 342 | var candidateMethods = dt.GetMethods(Public | NonPublic | Static | Instance | DeclaredOnly).Where(m => m.Name == matchName); 343 | if (TryResolveSourceMethod(candidateMethods, kind, matchHint, ref method, ref type, out ordinal)) 344 | return true; 345 | 346 | var candidateConstructors = dt.GetConstructors(Public | NonPublic | Static | Instance | DeclaredOnly).Where(m => m.Name == matchName); 347 | if (TryResolveSourceMethod(candidateConstructors, kind, matchHint, ref method, ref type, out ordinal)) 348 | return true; 349 | 350 | const int MaxResolveDepth = 10; 351 | for (var i = 0; i < MaxResolveDepth; i++) 352 | { 353 | dt = dt.DeclaringType; 354 | if (dt == null) 355 | return false; 356 | 357 | candidateMethods = dt.GetMethods(Public | NonPublic | Static | Instance | DeclaredOnly).Where(m => m.Name == matchName); 358 | if (TryResolveSourceMethod(candidateMethods, kind, matchHint, ref method, ref type, out ordinal)) 359 | return true; 360 | 361 | candidateConstructors = dt.GetConstructors(Public | NonPublic | Static | Instance | DeclaredOnly).Where(m => m.Name == matchName); 362 | if (TryResolveSourceMethod(candidateConstructors, kind, matchHint, ref method, ref type, out ordinal)) 363 | return true; 364 | 365 | if (methodName == ".cctor") 366 | { 367 | candidateConstructors = dt.GetConstructors(Public | NonPublic | Static | DeclaredOnly).Where(m => m.Name == matchName); 368 | foreach (var cctor in candidateConstructors) 369 | { 370 | method = cctor; 371 | type = dt; 372 | return true; 373 | } 374 | } 375 | } 376 | 377 | return false; 378 | } 379 | 380 | static bool TryResolveSourceMethod(IEnumerable candidateMethods, GeneratedNameKind kind, string matchHint, ref MethodBase method, ref Type type, out int? ordinal) 381 | { 382 | ordinal = null; 383 | foreach (var candidateMethod in candidateMethods) 384 | { 385 | var methodBody = candidateMethod.GetMethodBody(); 386 | if (kind == GeneratedNameKind.LambdaMethod) 387 | { 388 | foreach (var v in EnumerableIList.Create(methodBody?.LocalVariables)) 389 | { 390 | if (v.LocalType == type) 391 | GetOrdinal(method, ref ordinal); 392 | 393 | method = candidateMethod; 394 | type = method.DeclaringType; 395 | return true; 396 | } 397 | } 398 | 399 | try 400 | { 401 | var rawIL = methodBody?.GetILAsByteArray(); 402 | if (rawIL == null) 403 | continue; 404 | var reader = new ILReader(rawIL); 405 | while (reader.Read(candidateMethod)) 406 | { 407 | if (reader.Operand is MethodBase mb) 408 | { 409 | if (method == mb || (matchHint != null && method.Name.Contains(matchHint))) 410 | { 411 | if (kind == GeneratedNameKind.LambdaMethod) 412 | GetOrdinal(method, ref ordinal); 413 | 414 | method = candidateMethod; 415 | type = method.DeclaringType; 416 | return true; 417 | } 418 | } 419 | } 420 | } 421 | catch 422 | { 423 | // https://github.com/benaadams/Ben.Demystifier/issues/32 424 | // Skip methods where il can't be interpreted 425 | } 426 | } 427 | 428 | return false; 429 | } 430 | 431 | static void GetOrdinal(MethodBase method, ref int? ordinal) 432 | { 433 | #if APKD_STACKTRACE_LAMBDAORDINALS 434 | var lamdaStart = method.Name.IndexOf((char)GeneratedNameKind.LambdaMethod + "__") + 3; 435 | if (lamdaStart > 3) 436 | { 437 | var secondStart = method.Name.IndexOf('_', lamdaStart) + 1; 438 | if (secondStart > 0) 439 | { 440 | lamdaStart = secondStart; 441 | } 442 | 443 | if (!int.TryParse(method.Name.Substring(lamdaStart), out var foundOrdinal)) 444 | { 445 | ordinal = null; 446 | return; 447 | } 448 | 449 | ordinal = foundOrdinal; 450 | 451 | var methods = method.DeclaringType.GetMethods(Public | NonPublic | Static | Instance | DeclaredOnly); 452 | var startName = method.Name.Substring(0, lamdaStart); 453 | var count = 0; 454 | foreach (var m in methods) 455 | { 456 | if (m.Name.Length > lamdaStart && m.Name.StartsWith(startName)) 457 | { 458 | count++; 459 | 460 | if (count > 1) 461 | break; 462 | } 463 | } 464 | 465 | if (count <= 1) 466 | ordinal = null; 467 | } 468 | #endif 469 | } 470 | 471 | static string GetMatchHint(GeneratedNameKind kind, MethodBase method) 472 | { 473 | var methodName = method.Name; 474 | 475 | switch (kind) 476 | { 477 | case GeneratedNameKind.LocalFunction: 478 | var start = methodName.IndexOf("|"); 479 | if (start < 1) 480 | return null; 481 | var end = methodName.IndexOf("_", start) + 1; 482 | if (end <= start) 483 | return null; 484 | 485 | return methodName.Substring(start, end - start); 486 | } 487 | return null; 488 | } 489 | 490 | // Parse the generated name. Returns true for names of the form 491 | // [CS$]<[middle]>c[__[suffix]] where [CS$] is included for certain 492 | // generated names, where [middle] and [__[suffix]] are optional, 493 | // and where c is a single character in [1-9a-z] 494 | // (csharp\LanguageAnalysis\LIB\SpecialName.cpp). 495 | internal static bool TryParseGeneratedName( 496 | string name, 497 | out GeneratedNameKind kind, 498 | out int openBracketOffset, 499 | out int closeBracketOffset) 500 | { 501 | openBracketOffset = -1; 502 | 503 | if (name.StartsWith("CS$<", StringComparison.Ordinal)) 504 | openBracketOffset = 3; 505 | else if (name.StartsWith("<", StringComparison.Ordinal)) 506 | openBracketOffset = 0; 507 | 508 | if (openBracketOffset >= 0) 509 | { 510 | closeBracketOffset = IndexOfBalancedParenthesis(name, openBracketOffset, '>'); 511 | if (closeBracketOffset >= 0 && closeBracketOffset + 1 < name.Length) 512 | { 513 | int c = name[closeBracketOffset + 1]; 514 | if ((c >= '1' && c <= '9') || (c >= 'a' && c <= 'z')) // Note '0' is not special. 515 | { 516 | kind = (GeneratedNameKind)c; 517 | return true; 518 | } 519 | } 520 | } 521 | 522 | kind = GeneratedNameKind.None; 523 | openBracketOffset = -1; 524 | closeBracketOffset = -1; 525 | return false; 526 | } 527 | 528 | 529 | static int IndexOfBalancedParenthesis(string str, int openingOffset, char closing) 530 | { 531 | var opening = str[openingOffset]; 532 | 533 | var depth = 1; 534 | for (var i = openingOffset + 1; i < str.Length; i++) 535 | { 536 | var c = str[i]; 537 | if (c == opening) 538 | { 539 | depth++; 540 | } 541 | else if (c == closing) 542 | { 543 | depth--; 544 | 545 | if (depth == 0) 546 | return i; 547 | } 548 | } 549 | 550 | return -1; 551 | } 552 | 553 | static string GetPrefix(ParameterInfo parameter, Type parameterType) 554 | { 555 | if (parameter.IsOut) 556 | return "out"; 557 | 558 | if (parameterType != null && parameterType.IsByRef) 559 | { 560 | var attribs = GetCustomAttributes(parameter); 561 | if (attribs?.Length > 0) 562 | foreach (var attrib in attribs) 563 | if (attrib is Attribute att && att.GetType().IsIsReadOnlyAttribute()) 564 | return "in"; 565 | 566 | return "ref"; 567 | } 568 | 569 | return string.Empty; 570 | } 571 | 572 | static ResolvedParameter GetParameter(ParameterInfo parameter) 573 | { 574 | var parameterType = parameter.ParameterType; 575 | var prefix = GetPrefix(parameter, parameterType); 576 | 577 | if (parameterType == null) 578 | { 579 | return new ResolvedParameter 580 | { 581 | Prefix = prefix, 582 | Name = parameter.Name, 583 | ResolvedType = parameterType, 584 | }; 585 | } 586 | 587 | if (parameterType.IsGenericType) 588 | { 589 | var customAttribs = GetCustomAttributes(parameter); 590 | 591 | Attribute tupleNameAttribute = null; 592 | foreach (var attr in customAttribs) 593 | if (attr is Attribute tena && tena.IsTupleElementNameAttribute()) 594 | tupleNameAttribute = tena; 595 | 596 | #if APKD_STACKTRACE_FULLPARAMS 597 | var tupleNames = tupleNameAttribute?.GetTransformNames(); 598 | #else 599 | var tupleNames = null as IList; 600 | #endif 601 | 602 | if (tupleNameAttribute != null) 603 | return GetValueTupleParameter(tupleNames, prefix, parameter.Name, parameterType); 604 | } 605 | 606 | if (parameterType.IsByRef) 607 | parameterType = parameterType.GetElementType(); 608 | 609 | return new ResolvedParameter 610 | { 611 | Prefix = prefix, 612 | Name = parameter.Name, 613 | ResolvedType = parameterType, 614 | }; 615 | } 616 | 617 | static ResolvedParameter GetValueTupleParameter(IList tupleNames, string prefix, string name, Type parameterType) 618 | { 619 | return new ValueTupleResolvedParameter 620 | { 621 | TupleNames = tupleNames, 622 | Prefix = prefix, 623 | Name = name, 624 | ResolvedType = parameterType 625 | }; 626 | } 627 | 628 | static string GetValueTupleParameterName(IList tupleNames, Type parameterType) 629 | { 630 | var sb = new StringBuilder(); 631 | sb.Append('('); 632 | var args = parameterType.GetGenericArguments(); 633 | for (var i = 0; i < args.Length; i++) 634 | { 635 | if (i > 0) 636 | sb.Append(',').Append(' '); 637 | 638 | 639 | TypeNameHelper.AppendTypeDisplayName(sb, args[i], fullName: false, includeGenericParameterNames: true); 640 | 641 | if (i >= tupleNames.Count) 642 | continue; 643 | 644 | var argName = tupleNames[i]; 645 | if (argName == null) 646 | continue; 647 | 648 | sb.Append(' '); 649 | sb.Append(argName); 650 | } 651 | 652 | sb.Append(')'); 653 | return sb.ToString(); 654 | } 655 | 656 | static bool ShouldCollapseStackFrames(MethodBase method) 657 | { 658 | var comparison = StringComparison.Ordinal; 659 | string typeName = method?.DeclaringType?.FullName; 660 | if (string.IsNullOrWhiteSpace(typeName)) 661 | return false; 662 | return typeName.StartsWith("UnityEditor.", comparison) || 663 | typeName.StartsWith("UnityEngine.", comparison) || 664 | typeName.StartsWith("System.", comparison) || 665 | typeName.StartsWith("UnityScript.Lang.", comparison) || 666 | typeName.StartsWith("Odin.Editor.", comparison) || 667 | typeName.StartsWith("Boo.Lang.", comparison); 668 | } 669 | 670 | static bool ShouldExcludeFromCollapse(MethodBase method) 671 | { 672 | if (method == UnityEditorInspectorWindowOnGuiMethod) 673 | return true; 674 | 675 | return false; 676 | } 677 | 678 | static bool ShouldShowInStackTrace(MethodBase method) 679 | { 680 | Debug.Assert(method != null); 681 | var type = method.DeclaringType; 682 | 683 | if (type == typeof(Task<>) && method.Name == "InnerInvoke") 684 | return false; 685 | 686 | if (type == typeof(Task)) 687 | { 688 | switch (method.Name) 689 | { 690 | case "ExecuteWithThreadLocal": 691 | case "Execute": 692 | case "ExecutionContextCallback": 693 | case "ExecuteEntry": 694 | case "InnerInvoke": 695 | return false; 696 | } 697 | } 698 | 699 | if (type == typeof(ExecutionContext)) 700 | { 701 | switch (method.Name) 702 | { 703 | case "RunInternal": 704 | case "Run": 705 | return false; 706 | } 707 | } 708 | 709 | if (StackTraceHiddenAttibuteType != null) 710 | { 711 | // Don't show any methods marked with the StackTraceHiddenAttribute 712 | // https://github.com/dotnet/coreclr/pull/14652 713 | if (IsStackTraceHidden(method)) 714 | return false; 715 | } 716 | 717 | if (type == null) 718 | return true; 719 | 720 | string typeFullName = type.FullName; 721 | 722 | if (StackTraceHiddenAttibuteType != null) 723 | { 724 | // Don't show any types marked with the StackTraceHiddenAttribute 725 | // https://github.com/dotnet/coreclr/pull/14652 726 | if (IsStackTraceHidden(type)) 727 | return false; 728 | } 729 | else 730 | { 731 | // Fallbacks for runtime pre-StackTraceHiddenAttribute 732 | if (type == typeof(ExceptionDispatchInfo) && method.Name == "Throw") 733 | { 734 | return false; 735 | } 736 | else if (type == typeof(TaskAwaiter) || 737 | type == typeof(TaskAwaiter<>) || 738 | type == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) || 739 | type == typeof(ConfiguredTaskAwaitable<>.ConfiguredTaskAwaiter)) 740 | { 741 | switch (method.Name) 742 | { 743 | case "HandleNonSuccessAndDebuggerNotification": 744 | case "ThrowForNonSuccess": 745 | case "ValidateEnd": 746 | case "GetResult": 747 | return false; 748 | } 749 | } 750 | else if (typeFullName == "System.ThrowHelper") 751 | { 752 | return false; 753 | } 754 | } 755 | 756 | // collapse internal async frames 757 | if (typeFullName.StartsWith("System.Runtime.CompilerServices.Async", StringComparison.Ordinal)) 758 | return false; 759 | 760 | #if ODIN_INSPECTOR 761 | // support for the Sirenix.OdinInspector package 762 | if (typeFullName.StartsWith("Sirenix.OdinInspector", StringComparison.Ordinal)) 763 | return false; 764 | #endif 765 | 766 | // support for the Apkd.AsyncManager package 767 | if (typeFullName.StartsWith("Apkd.Internal.AsyncManager", StringComparison.Ordinal)) 768 | return false; 769 | 770 | if (typeFullName.StartsWith("Apkd.Internal.Continuation`1", StringComparison.Ordinal)) 771 | return false; 772 | 773 | // collapse internal unity logging methods 774 | if (typeFullName == "UnityEngine.DebugLogHandler") 775 | return false; 776 | 777 | if (typeFullName == "UnityEngine.Logger") 778 | return false; 779 | 780 | if (typeFullName == "UnityEngine.Debug") 781 | return false; 782 | 783 | return true; 784 | } 785 | 786 | static bool IsStackTraceHidden(MemberInfo memberInfo) 787 | { 788 | if (!memberInfo.Module.Assembly.ReflectionOnly) 789 | { 790 | foreach (var attr in GetCustomAttributes(memberInfo)) 791 | { 792 | if (attr.GetType() == StackTraceHiddenAttibuteType) 793 | return true; 794 | return false; 795 | } 796 | } 797 | 798 | EnumerableIList attributes; 799 | try 800 | { 801 | attributes = EnumerableIList.Create(memberInfo.GetCustomAttributesData()); 802 | } 803 | catch (NotImplementedException) 804 | { 805 | return false; 806 | } 807 | 808 | // reflection-only attribute, match on name 809 | foreach (var attribute in attributes) 810 | if (attribute.AttributeType.FullName == StackTraceHiddenAttibuteType.FullName) 811 | return true; 812 | 813 | return false; 814 | } 815 | 816 | static bool TryResolveStateMachineMethod(ref MethodBase method, out Type declaringType) 817 | { 818 | Debug.Assert(method != null); 819 | Debug.Assert(method.DeclaringType != null); 820 | 821 | declaringType = method.DeclaringType; 822 | 823 | var parentType = declaringType.DeclaringType; 824 | if (parentType == null) 825 | return false; 826 | 827 | var methods = parentType.GetMethods(Public | NonPublic | Static | Instance | DeclaredOnly); 828 | if (methods == null) 829 | return false; 830 | 831 | foreach (var candidateMethod in methods) 832 | { 833 | var attributes = GetCustomAttributes(candidateMethod); 834 | if (attributes == null) 835 | continue; 836 | 837 | foreach (var attr in attributes) 838 | { 839 | if (attr is StateMachineAttribute sma && sma.StateMachineType == declaringType) 840 | { 841 | method = candidateMethod; 842 | declaringType = candidateMethod.DeclaringType; 843 | // Mark the iterator as changed; so it gets the + annotation of the original method 844 | // async statemachines resolve directly to their builder methods so aren't marked as changed 845 | return sma is IteratorStateMachineAttribute; 846 | } 847 | } 848 | } 849 | 850 | return false; 851 | } 852 | 853 | internal enum GeneratedNameKind 854 | { 855 | None = 0, 856 | 857 | // Used by EE: 858 | ThisProxyField = '4', 859 | HoistedLocalField = '5', 860 | DisplayClassLocalOrField = '8', 861 | LambdaMethod = 'b', 862 | LambdaDisplayClass = 'c', 863 | StateMachineType = 'd', 864 | LocalFunction = 'g', // note collision with Deprecated_InitializerLocal, however this one is only used for method names 865 | 866 | // Used by EnC: 867 | AwaiterField = 'u', 868 | HoistedSynthesizedLocalField = 's', 869 | 870 | // Currently not parsed: 871 | StateMachineStateField = '1', 872 | IteratorCurrentBackingField = '2', 873 | StateMachineParameterProxyField = '3', 874 | ReusableHoistedLocalField = '7', 875 | LambdaCacheField = '9', 876 | FixedBufferField = 'e', 877 | AnonymousType = 'f', 878 | TransparentIdentifier = 'h', 879 | AnonymousTypeField = 'i', 880 | AutoPropertyBackingField = 'k', 881 | IteratorCurrentThreadIdField = 'l', 882 | IteratorFinallyMethod = 'm', 883 | BaseMethodWrapper = 'n', 884 | AsyncBuilderField = 't', 885 | DynamicCallSiteContainerType = 'o', 886 | DynamicCallSiteField = 'p' 887 | } 888 | } 889 | } 890 | --------------------------------------------------------------------------------