DebuggingUtils.cs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. using System;
  2. using System.Diagnostics;
  3. using System.Reflection;
  4. using System.Runtime.CompilerServices;
  5. using System.Runtime.InteropServices;
  6. using System.Text;
  7. using Godot.NativeInterop;
  8. #nullable enable
  9. namespace Godot
  10. {
  11. internal static class DebuggingUtils
  12. {
  13. private static void AppendTypeName(this StringBuilder sb, Type type)
  14. {
  15. // Use the C# type keyword for built-in types.
  16. // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/built-in-types
  17. if (type == typeof(void))
  18. sb.Append("void");
  19. else if (type == typeof(bool))
  20. sb.Append("bool");
  21. else if (type == typeof(byte))
  22. sb.Append("byte");
  23. else if (type == typeof(sbyte))
  24. sb.Append("sbyte");
  25. else if (type == typeof(char))
  26. sb.Append("char");
  27. else if (type == typeof(decimal))
  28. sb.Append("decimal");
  29. else if (type == typeof(double))
  30. sb.Append("double");
  31. else if (type == typeof(float))
  32. sb.Append("float");
  33. else if (type == typeof(int))
  34. sb.Append("int");
  35. else if (type == typeof(uint))
  36. sb.Append("uint");
  37. else if (type == typeof(nint))
  38. sb.Append("nint");
  39. else if (type == typeof(nuint))
  40. sb.Append("nuint");
  41. else if (type == typeof(long))
  42. sb.Append("long");
  43. else if (type == typeof(ulong))
  44. sb.Append("ulong");
  45. else if (type == typeof(short))
  46. sb.Append("short");
  47. else if (type == typeof(ushort))
  48. sb.Append("ushort");
  49. else if (type == typeof(object))
  50. sb.Append("object");
  51. else if (type == typeof(string))
  52. sb.Append("string");
  53. else
  54. sb.Append(type);
  55. }
  56. internal static void InstallTraceListener()
  57. {
  58. Trace.Listeners.Clear();
  59. Trace.Listeners.Add(new GodotTraceListener());
  60. }
  61. [StructLayout(LayoutKind.Sequential)]
  62. // ReSharper disable once InconsistentNaming
  63. internal ref struct godot_stack_info
  64. {
  65. public godot_string File;
  66. public godot_string Func;
  67. public int Line;
  68. }
  69. [StructLayout(LayoutKind.Sequential)]
  70. // ReSharper disable once InconsistentNaming
  71. internal ref struct godot_stack_info_vector
  72. {
  73. private IntPtr _writeProxy;
  74. private unsafe godot_stack_info* _ptr;
  75. public readonly unsafe godot_stack_info* Elements
  76. {
  77. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  78. get => _ptr;
  79. }
  80. public void Resize(int size)
  81. {
  82. if (size < 0)
  83. throw new ArgumentOutOfRangeException(nameof(size));
  84. var err = NativeFuncs.godotsharp_stack_info_vector_resize(ref this, size);
  85. if (err != Error.Ok)
  86. throw new InvalidOperationException("Failed to resize vector. Error code is: " + err.ToString());
  87. }
  88. public unsafe void Dispose()
  89. {
  90. if (_ptr == null)
  91. return;
  92. NativeFuncs.godotsharp_stack_info_vector_destroy(ref this);
  93. _ptr = null;
  94. }
  95. }
  96. internal static unsafe StackFrame? GetCurrentStackFrame(int skipFrames = 0)
  97. {
  98. // We skip 2 frames:
  99. // The first skipped frame is the current method.
  100. // The second skipped frame is a method in NativeInterop.NativeFuncs.
  101. var stackTrace = new StackTrace(skipFrames: 2 + skipFrames, fNeedFileInfo: true);
  102. return stackTrace.GetFrame(0);
  103. }
  104. [UnmanagedCallersOnly]
  105. internal static unsafe void GetCurrentStackInfo(void* destVector)
  106. {
  107. try
  108. {
  109. var vector = (godot_stack_info_vector*)destVector;
  110. // We skip 2 frames:
  111. // The first skipped frame is the current method.
  112. // The second skipped frame is a method in NativeInterop.NativeFuncs.
  113. var stackTrace = new StackTrace(skipFrames: 2, fNeedFileInfo: true);
  114. int frameCount = stackTrace.FrameCount;
  115. if (frameCount == 0)
  116. return;
  117. vector->Resize(frameCount);
  118. int i = 0;
  119. foreach (StackFrame frame in stackTrace.GetFrames())
  120. {
  121. var method = frame.GetMethod();
  122. if (method is MethodInfo methodInfo && methodInfo.IsDefined(typeof(StackTraceHiddenAttribute)))
  123. {
  124. // Skip methods marked hidden from the stack trace.
  125. continue;
  126. }
  127. string? fileName = frame.GetFileName();
  128. int fileLineNumber = frame.GetFileLineNumber();
  129. GetStackFrameMethodDecl(frame, out string methodDecl);
  130. godot_stack_info* stackInfo = &vector->Elements[i];
  131. // Assign directly to element in Vector. This way we don't need to worry
  132. // about disposal if an exception is thrown. The Vector takes care of it.
  133. stackInfo->File = Marshaling.ConvertStringToNative(fileName);
  134. stackInfo->Func = Marshaling.ConvertStringToNative(methodDecl);
  135. stackInfo->Line = fileLineNumber;
  136. i++;
  137. }
  138. // Resize the vector again in case we skipped some frames.
  139. vector->Resize(i);
  140. }
  141. catch (Exception e)
  142. {
  143. ExceptionUtils.LogException(e);
  144. }
  145. }
  146. internal static void GetStackFrameMethodDecl(StackFrame frame, out string methodDecl)
  147. {
  148. MethodBase? methodBase = frame.GetMethod();
  149. if (methodBase == null)
  150. {
  151. methodDecl = string.Empty;
  152. return;
  153. }
  154. var sb = new StringBuilder();
  155. if (methodBase is MethodInfo methodInfo)
  156. {
  157. sb.AppendTypeName(methodInfo.ReturnType);
  158. sb.Append(' ');
  159. }
  160. sb.Append(methodBase.DeclaringType?.FullName ?? "<unknown>");
  161. sb.Append('.');
  162. sb.Append(methodBase.Name);
  163. if (methodBase.IsGenericMethod)
  164. {
  165. Type[] genericParams = methodBase.GetGenericArguments();
  166. sb.Append('<');
  167. for (int j = 0; j < genericParams.Length; j++)
  168. {
  169. if (j > 0)
  170. sb.Append(", ");
  171. sb.AppendTypeName(genericParams[j]);
  172. }
  173. sb.Append('>');
  174. }
  175. sb.Append('(');
  176. bool varArgs = (methodBase.CallingConvention & CallingConventions.VarArgs) != 0;
  177. ParameterInfo[] parameter = methodBase.GetParameters();
  178. for (int i = 0; i < parameter.Length; i++)
  179. {
  180. if (i > 0)
  181. sb.Append(", ");
  182. if (i == parameter.Length - 1 && varArgs)
  183. sb.Append("params ");
  184. sb.AppendTypeName(parameter[i].ParameterType);
  185. }
  186. sb.Append(')');
  187. methodDecl = sb.ToString();
  188. }
  189. }
  190. }