diff options
author | Ignacio Roldán Etcheverry <ignalfonsore@gmail.com> | 2022-02-27 21:57:39 +0100 |
---|---|---|
committer | Ignacio Roldán Etcheverry <ignalfonsore@gmail.com> | 2022-08-22 03:36:51 +0200 |
commit | 778007a358961f30ccadd738300a353edf0a0c51 (patch) | |
tree | f0565278dd7456446f546b416231ed72d30d0017 | |
parent | 67db89988d3c9283d4d58b328b959e1531b16dae (diff) |
C#: Re-introduce exception logging and error stack traces in editor
These two had been disabled while moving to .NET 5, as the previous
implementation relied on Mono embedding APIs.
20 files changed, 310 insertions, 228 deletions
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index ae464700ff..134dd92078 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -104,7 +104,7 @@ Error CSharpLanguage::execute_file(const String &p_path) { return OK; } -extern void *godotsharp_pinvoke_funcs[181]; +extern void *godotsharp_pinvoke_funcs[185]; [[maybe_unused]] volatile void **do_not_strip_godotsharp_pinvoke_funcs; #ifdef TOOLS_ENABLED extern void *godotsharp_editor_pinvoke_funcs[30]; @@ -592,8 +592,6 @@ String CSharpLanguage::debug_get_stack_level_source(int p_level) const { return String(); } -#warning TODO -#if 0 Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() { #ifdef DEBUG_ENABLED // Printing an error here will result in endless recursion, so we must be careful @@ -606,91 +604,21 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() _recursion_flag_ = false; }; - GD_MONO_SCOPE_THREAD_ATTACH; - - if (!gdmono->is_runtime_initialized() || !GDMono::get_singleton()->get_core_api_assembly() || !GDMonoCache::cached_data.corlib_cache_updated) { - return Vector<StackInfo>(); - } - - MonoObject *stack_trace = mono_object_new(mono_domain_get(), CACHED_CLASS(System_Diagnostics_StackTrace)->get_mono_ptr()); - - MonoBoolean need_file_info = true; - void *ctor_args[1] = { &need_file_info }; - - CACHED_METHOD(System_Diagnostics_StackTrace, ctor_bool)->invoke_raw(stack_trace, ctor_args); - - Vector<StackInfo> si; - si = stack_trace_get_info(stack_trace); - - return si; -#else - return Vector<StackInfo>(); -#endif -} - -#ifdef DEBUG_ENABLED -Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObject *p_stack_trace) { - // Printing an error here will result in endless recursion, so we must be careful - static thread_local bool _recursion_flag_ = false; - if (_recursion_flag_) { - return Vector<StackInfo>(); - } - _recursion_flag_ = true; - SCOPE_EXIT { - _recursion_flag_ = false; - }; - - GD_MONO_SCOPE_THREAD_ATTACH; - - MonoException *exc = nullptr; - - MonoArray *frames = CACHED_METHOD_THUNK(System_Diagnostics_StackTrace, GetFrames).invoke(p_stack_trace, &exc); - - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - return Vector<StackInfo>(); - } - - int frame_count = mono_array_length(frames); - - if (frame_count <= 0) { + if (!gdmono->is_runtime_initialized()) { return Vector<StackInfo>(); } Vector<StackInfo> si; - si.resize(frame_count); - - for (int i = 0; i < frame_count; i++) { - StackInfo &sif = si.write[i]; - MonoObject *frame = mono_array_get(frames, MonoObject *, i); - MonoString *file_name; - int file_line_num; - MonoString *method_decl; - CACHED_METHOD_THUNK(DebuggingUtils, GetStackFrameInfo).invoke(frame, &file_name, &file_line_num, &method_decl, &exc); - - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - return Vector<StackInfo>(); - } - - // TODO - // what if the StackFrame method is null (method_decl is empty). should we skip this frame? - // can reproduce with a MissingMethodException on internal calls - - sif.file = GDMonoMarshal::mono_string_to_godot(file_name); - sif.line = file_line_num; - sif.func = GDMonoMarshal::mono_string_to_godot(method_decl); + if (GDMonoCache::godot_api_cache_updated) { + GDMonoCache::managed_callbacks.DebuggingUtils_GetCurrentStackInfo(&si); } return si; -} -#endif #else -Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() { return Vector<StackInfo>(); -} #endif +} void CSharpLanguage::post_unsafe_reference(Object *p_obj) { #ifdef DEBUG_ENABLED diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index eb72df9f42..1ca4e20047 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -464,13 +464,6 @@ public: static void tie_user_managed_to_unmanaged(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged, Ref<CSharpScript> *p_script, bool p_ref_counted); static void tie_managed_to_unmanaged_with_pre_setup(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged); -#warning TODO -#if 0 -#ifdef DEBUG_ENABLED - Vector<StackInfo> stack_trace_get_info(MonoObject *p_stack_trace); -#endif -#endif - void post_unsafe_reference(Object *p_obj); void pre_unsafe_unreference(Object *p_obj); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs index 266df07d63..db53a508ef 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs @@ -39,7 +39,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugPrintUnhandledException(e); + ExceptionUtils.LogException(e); *ret = default; return false.ToGodotBool(); } @@ -69,7 +69,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugPrintUnhandledException(e); + ExceptionUtils.LogException(e); return false.ToGodotBool(); } } @@ -107,7 +107,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugPrintUnhandledException(e); + ExceptionUtils.LogException(e); *outRet = default; return false.ToGodotBool(); } @@ -127,7 +127,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugPrintUnhandledException(e); + ExceptionUtils.LogException(e); } } @@ -159,7 +159,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugPrintUnhandledException(e); + ExceptionUtils.LogException(e); *outRes = default; *outValid = false.ToGodotBool(); } @@ -179,7 +179,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugPrintUnhandledException(e); + ExceptionUtils.LogException(e); return false.ToGodotBool(); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs index c6f2e8f77d..bd11811c7d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs @@ -15,7 +15,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugPrintUnhandledException(e); + ExceptionUtils.LogException(e); } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs index a348776874..9f9d740659 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs @@ -34,9 +34,9 @@ namespace Godot.Bridge public delegate* unmanaged<IntPtr, godot_string*, godot_bool*, void> CSharpInstanceBridge_CallToString; public delegate* unmanaged<IntPtr, godot_string_name*, godot_bool> CSharpInstanceBridge_HasMethodUnknownParams; public delegate* unmanaged<IntPtr, void> GCHandleBridge_FreeGCHandle; - public delegate* unmanaged<void> DebuggingUtils_InstallTraceListener; - public delegate* unmanaged<void> Dispatcher_InitializeDefaultGodotTaskScheduler; + public delegate* unmanaged<void*, void> DebuggingUtils_GetCurrentStackInfo; public delegate* unmanaged<void> DisposablesTracker_OnGodotShuttingDown; + public delegate* unmanaged<godot_bool, void> GD_OnCoreApiAssemblyLoaded; // @formatter:on public static ManagedCallbacks Create() @@ -70,9 +70,9 @@ namespace Godot.Bridge CSharpInstanceBridge_CallToString = &CSharpInstanceBridge.CallToString, CSharpInstanceBridge_HasMethodUnknownParams = &CSharpInstanceBridge.HasMethodUnknownParams, GCHandleBridge_FreeGCHandle = &GCHandleBridge.FreeGCHandle, - DebuggingUtils_InstallTraceListener = &DebuggingUtils.InstallTraceListener, - Dispatcher_InitializeDefaultGodotTaskScheduler = &Dispatcher.InitializeDefaultGodotTaskScheduler, + DebuggingUtils_GetCurrentStackInfo = &DebuggingUtils.GetCurrentStackInfo, DisposablesTracker_OnGodotShuttingDown = &DisposablesTracker.OnGodotShuttingDown, + GD_OnCoreApiAssemblyLoaded = &GD.OnCoreApiAssemblyLoaded, // @formatter:on }; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs index d812626fab..20d0c57179 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs @@ -27,7 +27,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugUnhandledException(e); + ExceptionUtils.LogException(e); } } @@ -54,7 +54,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugPrintUnhandledException(e); + ExceptionUtils.LogException(e); return IntPtr.Zero; } } @@ -111,7 +111,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugPrintUnhandledException(e); + ExceptionUtils.LogException(e); return false.ToGodotBool(); } } @@ -151,7 +151,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugUnhandledException(e); + ExceptionUtils.LogException(e); *outRes = default; } } @@ -167,7 +167,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugUnhandledException(e); + ExceptionUtils.LogException(e); } } @@ -280,7 +280,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugPrintUnhandledException(e); + ExceptionUtils.LogException(e); *outOwnerIsNull = false.ToGodotBool(); } } @@ -374,7 +374,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugUnhandledException(e); + ExceptionUtils.LogException(e); *outRetSignals = NativeFuncs.godotsharp_dictionary_new(); } } @@ -425,7 +425,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugUnhandledException(e); + ExceptionUtils.LogException(e); return false.ToGodotBool(); } } @@ -445,7 +445,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugUnhandledException(e); + ExceptionUtils.LogException(e); return false.ToGodotBool(); } } @@ -473,7 +473,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugUnhandledException(e); + ExceptionUtils.LogException(e); return false.ToGodotBool(); } } @@ -524,7 +524,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugUnhandledException(e); + ExceptionUtils.LogException(e); } } @@ -596,7 +596,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugUnhandledException(e); + ExceptionUtils.LogException(e); *outTool = false.ToGodotBool(); *outRpcFunctionsDest = NativeFuncs.godotsharp_dictionary_new(); } @@ -629,7 +629,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugUnhandledException(e); + ExceptionUtils.LogException(e); *outNewGCHandlePtr = IntPtr.Zero; return false.ToGodotBool(); } @@ -665,7 +665,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugUnhandledException(e); + ExceptionUtils.LogException(e); } } @@ -757,7 +757,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugUnhandledException(e); + ExceptionUtils.LogException(e); } } @@ -794,7 +794,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugUnhandledException(e); + ExceptionUtils.LogException(e); } } @@ -882,7 +882,7 @@ namespace Godot.Bridge } catch (Exception e) { - ExceptionUtils.DebugUnhandledException(e); + ExceptionUtils.LogException(e); } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs index 39904b6154..6fbc04702a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs @@ -1,15 +1,18 @@ using System; using System.Diagnostics; using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using Godot.NativeInterop; +#nullable enable + namespace Godot { internal static class DebuggingUtils { - internal static void AppendTypeName(this StringBuilder sb, Type type) + private static void AppendTypeName(this StringBuilder sb, Type type) { if (type.IsPrimitive) sb.Append(type.Name); @@ -21,28 +24,94 @@ namespace Godot sb.Append(' '); } - [UnmanagedCallersOnly] internal static void InstallTraceListener() { + Trace.Listeners.Clear(); + Trace.Listeners.Add(new GodotTraceListener()); + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + internal ref struct godot_stack_info + { + public godot_string File; + public godot_string Func; + public int Line; + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + internal ref struct godot_stack_info_vector + { + private IntPtr _writeProxy; + private unsafe godot_stack_info* _ptr; + + public readonly unsafe godot_stack_info* Elements + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + public void Resize(int size) + { + if (size < 0) + throw new ArgumentOutOfRangeException(nameof(size)); + var err = NativeFuncs.godotsharp_stack_info_vector_resize(ref this, size); + if (err != Error.Ok) + throw new InvalidOperationException("Failed to resize vector. Error code is: " + err.ToString()); + } + + public unsafe void Dispose() + { + if (_ptr == null) + return; + NativeFuncs.godotsharp_stack_info_vector_destroy(ref this); + _ptr = null; + } + } + + [UnmanagedCallersOnly] + internal static unsafe void GetCurrentStackInfo(void* destVector) + { try { - Trace.Listeners.Clear(); - Trace.Listeners.Add(new GodotTraceListener()); + var vector = (godot_stack_info_vector*)destVector; + var stackTrace = new StackTrace(skipFrames: 1, fNeedFileInfo: true); + int frameCount = stackTrace.FrameCount; + + if (frameCount == 0) + return; + + vector->Resize(frameCount); + + int i = 0; + foreach (StackFrame frame in stackTrace.GetFrames()) + { + string? fileName = frame.GetFileName(); + int fileLineNumber = frame.GetFileLineNumber(); + + GetStackFrameMethodDecl(frame, out string methodDecl); + + godot_stack_info* stackInfo = &vector->Elements[i]; + + // Assign directly to element in Vector. This way we don't need to worry + // about disposal if an exception is thrown. The Vector takes care of it. + stackInfo->File = Marshaling.ConvertStringToNative(fileName); + stackInfo->Func = Marshaling.ConvertStringToNative(methodDecl); + stackInfo->Line = fileLineNumber; + + i++; + } } catch (Exception e) { - ExceptionUtils.DebugPrintUnhandledException(e); - ExceptionUtils.PushError("Failed to install 'System.Diagnostics.Trace' listener."); + ExceptionUtils.LogException(e); } } - public static void GetStackFrameInfo(StackFrame frame, out string fileName, out int fileLineNumber, - out string methodDecl) + internal static void GetStackFrameMethodDecl(StackFrame frame, out string methodDecl) { - fileName = frame.GetFileName(); - fileLineNumber = frame.GetFileLineNumber(); - - MethodBase methodBase = frame.GetMethod(); + MethodBase? methodBase = frame.GetMethod(); if (methodBase == null) { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs index 02ae392f47..fb5e3c6dda 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs @@ -22,7 +22,7 @@ namespace Godot } catch (Exception e) { - ExceptionUtils.DebugUnhandledException(e); + ExceptionUtils.LogException(e); return false.ToGodotBool(); } } @@ -58,7 +58,7 @@ namespace Godot } catch (Exception e) { - ExceptionUtils.DebugPrintUnhandledException(e); + ExceptionUtils.LogException(e); *outRet = default; } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dispatcher.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dispatcher.cs index e8cfb8e1b1..e6cb4171a7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dispatcher.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dispatcher.cs @@ -8,18 +8,8 @@ namespace Godot { internal static GodotTaskScheduler DefaultGodotTaskScheduler; - [UnmanagedCallersOnly] internal static void InitializeDefaultGodotTaskScheduler() - { - try - { - DefaultGodotTaskScheduler = new GodotTaskScheduler(); - } - catch (Exception e) - { - ExceptionUtils.DebugUnhandledException(e); - } - } + => DefaultGodotTaskScheduler = new GodotTaskScheduler(); public static GodotSynchronizationContext SynchronizationContext => DefaultGodotTaskScheduler.Context; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs index bf3b3b083a..4e15b37e44 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs @@ -24,7 +24,7 @@ namespace Godot } catch (Exception e) { - ExceptionUtils.DebugUnhandledException(e); + ExceptionUtils.LogException(e); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotUnhandledExceptionEvent.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotUnhandledExceptionEvent.cs index 729529d093..a17741ddeb 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotUnhandledExceptionEvent.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotUnhandledExceptionEvent.cs @@ -1,17 +1,33 @@ using System; +using System.Runtime.InteropServices; +using Godot.NativeInterop; namespace Godot { public static partial class GD { - /// <summary> - /// Fires when an unhandled exception occurs, regardless of project settings. - /// </summary> - public static event EventHandler<UnhandledExceptionArgs> UnhandledException; - - private static void OnUnhandledException(Exception e) + [UnmanagedCallersOnly] + internal static void OnCoreApiAssemblyLoaded(godot_bool isDebug) { - UnhandledException?.Invoke(null, new UnhandledExceptionArgs(e)); + try + { + Dispatcher.InitializeDefaultGodotTaskScheduler(); + + if (isDebug.ToBool()) + { + DebuggingUtils.InstallTraceListener(); + + AppDomain.CurrentDomain.UnhandledException += (_, e) => + { + // Exception.ToString() includes the inner exception + ExceptionUtils.LogUnhandledException((Exception)e.ExceptionObject); + }; + } + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs index 2830d9c527..7a2f205632 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs @@ -1,4 +1,9 @@ using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +#nullable enable namespace Godot.NativeInterop { @@ -11,46 +16,101 @@ namespace Godot.NativeInterop private static void OnExceptionLoggerException(Exception loggerException, Exception exceptionToLog) { - // This better not throw - PushError("Exception thrown when trying to log another exception..."); - PushError("Exception:"); - PushError(exceptionToLog.ToString()); - PushError("Logger exception:"); - PushError(loggerException.ToString()); - } - - public static void DebugPrintUnhandledException(Exception e) - { try { - // TODO Not implemented (debug_print_unhandled_exception) - GD.PushError(e.ToString()); + // This better not throw + PushError(string.Concat("Exception thrown while trying to log another exception...", + "\n### Exception ###\n", exceptionToLog.ToString(), + "\n### Logger exception ###\n", loggerException.ToString())); } - catch (Exception unexpected) + catch (Exception) { - OnExceptionLoggerException(unexpected, e); + // Well, too bad... } } - public static void DebugSendUnhandledExceptionError(Exception e) + private record struct StackInfoTuple(string? File, string Func, int Line); + + private static void CollectExceptionInfo(Exception exception, List<StackInfoTuple> globalFrames, + StringBuilder excMsg) { - try + if (excMsg.Length > 0) + excMsg.Append(" ---> "); + excMsg.Append(exception.GetType().FullName); + excMsg.Append(": "); + excMsg.Append(exception.Message); + + var innerExc = exception.InnerException; + + if (innerExc != null) { - // TODO Not implemented (debug_send_unhandled_exception_error) - GD.PushError(e.ToString()); + CollectExceptionInfo(innerExc, globalFrames, excMsg); + globalFrames.Add(new("", "--- End of inner exception stack trace ---", 0)); } - catch (Exception unexpected) + + var stackTrace = new StackTrace(exception, fNeedFileInfo: true); + + foreach (StackFrame frame in stackTrace.GetFrames()) { - OnExceptionLoggerException(unexpected, e); + DebuggingUtils.GetStackFrameMethodDecl(frame, out string methodDecl); + globalFrames.Add(new(frame.GetFileName(), methodDecl, frame.GetFileLineNumber())); + } + } + + private static void SendToScriptDebugger(Exception e) + { + var globalFrames = new List<StackInfoTuple>(); + + var excMsg = new StringBuilder(); + + CollectExceptionInfo(e, globalFrames, excMsg); + + string file = globalFrames.Count > 0 ? globalFrames[0].File ?? "" : ""; + string func = globalFrames.Count > 0 ? globalFrames[0].Func : ""; + int line = globalFrames.Count > 0 ? globalFrames[0].Line : 0; + string errorMsg = "Exception"; + + using godot_string nFile = Marshaling.ConvertStringToNative(file); + using godot_string nFunc = Marshaling.ConvertStringToNative(func); + using godot_string nErrorMsg = Marshaling.ConvertStringToNative(errorMsg); + using godot_string nExcMsg = Marshaling.ConvertStringToNative(excMsg.ToString()); + + using DebuggingUtils.godot_stack_info_vector stackInfoVector = default; + + stackInfoVector.Resize(globalFrames.Count); + + unsafe + { + for (int i = 0; i < globalFrames.Count; i++) + { + DebuggingUtils.godot_stack_info* stackInfo = &stackInfoVector.Elements[i]; + + var globalFrame = globalFrames[i]; + + // Assign directly to element in Vector. This way we don't need to worry + // about disposal if an exception is thrown. The Vector takes care of it. + stackInfo->File = Marshaling.ConvertStringToNative(globalFrame.File); + stackInfo->Func = Marshaling.ConvertStringToNative(globalFrame.Func); + stackInfo->Line = globalFrame.Line; + } + + NativeFuncs.godotsharp_internal_script_debugger_send_error(nFunc, nFile, line, + nErrorMsg, nExcMsg, p_warning: false.ToGodotBool(), stackInfoVector); } } - public static void DebugUnhandledException(Exception e) + public static void LogException(Exception e) { try { - // TODO Not implemented (debug_unhandled_exception) - GD.PushError(e.ToString()); + if (NativeFuncs.godotsharp_internal_script_debugger_is_active()) + { + SendToScriptDebugger(e); + } + else + { + GD.PushError(e.ToString()); + } } catch (Exception unexpected) { @@ -58,12 +118,17 @@ namespace Godot.NativeInterop } } - public static void PrintUnhandledException(Exception e) + public static void LogUnhandledException(Exception e) { try { - // TODO Not implemented (print_unhandled_exception) - GD.PushError(e.ToString()); + if (NativeFuncs.godotsharp_internal_script_debugger_is_active()) + { + SendToScriptDebugger(e); + } + + // In this case, print it as well in addition to sending it to the script debugger + GD.PushError("Unhandled exception\n" + e); } catch (Exception unexpected) { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index 2c81ae7816..d7c57fa260 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -35,6 +35,23 @@ namespace Godot.NativeInterop [DllImport(GodotDllName)] public static extern IntPtr godotsharp_engine_get_singleton(in godot_string p_name); + + [DllImport(GodotDllName)] + internal static extern Error godotsharp_stack_info_vector_resize( + ref DebuggingUtils.godot_stack_info_vector p_stack_info_vector, int p_size); + + [DllImport(GodotDllName)] + internal static extern void godotsharp_stack_info_vector_destroy( + ref DebuggingUtils.godot_stack_info_vector p_stack_info_vector); + + [DllImport(GodotDllName)] + internal static extern void godotsharp_internal_script_debugger_send_error(in godot_string p_func, + in godot_string p_file, int p_line, in godot_string p_err, in godot_string p_descr, + godot_bool p_warning, in DebuggingUtils.godot_stack_info_vector p_stack_info_vector); + + [DllImport(GodotDllName)] + internal static extern bool godotsharp_internal_script_debugger_is_active(); + [DllImport(GodotDllName)] internal static extern IntPtr godotsharp_internal_object_get_associated_gchandle(IntPtr ptr); @@ -92,7 +109,8 @@ namespace Godot.NativeInterop out godot_array r_output); [DllImport(GodotDllName)] - public static extern void godotsharp_ref_new_from_ref_counted_ptr(out godot_ref r_dest, IntPtr p_ref_counted_ptr); + public static extern void godotsharp_ref_new_from_ref_counted_ptr(out godot_ref r_dest, + IntPtr p_ref_counted_ptr); [DllImport(GodotDllName)] public static extern void godotsharp_ref_destroy(ref godot_ref p_instance); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs index c8bf686afa..fb72d706c7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs @@ -58,7 +58,7 @@ namespace Godot } catch (Exception e) { - ExceptionUtils.DebugPrintUnhandledException(e); + ExceptionUtils.LogException(e); *outAwaiterIsNull = false.ToGodotBool(); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs deleted file mode 100644 index eae8927ceb..0000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace Godot -{ - /// <summary> - /// Event arguments for when unhandled exceptions occur. - /// </summary> - public class UnhandledExceptionArgs - { - /// <summary> - /// Exception object. - /// </summary> - public Exception Exception { get; private set; } - - internal UnhandledExceptionArgs(Exception exception) - { - Exception = exception; - } - } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index 8b0c421829..cc38ad31b6 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -8,7 +8,7 @@ <DocumentationFile>$(OutputPath)/$(AssemblyName).xml</DocumentationFile> <EnableDefaultItems>false</EnableDefaultItems> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> - <LangVersion>9</LangVersion> + <LangVersion>10</LangVersion> <AnalysisMode>Recommended</AnalysisMode> @@ -91,7 +91,6 @@ <Compile Include="Core\StringName.cs" /> <Compile Include="Core\Transform2D.cs" /> <Compile Include="Core\Transform3D.cs" /> - <Compile Include="Core\UnhandledExceptionArgs.cs" /> <Compile Include="Core\Vector2.cs" /> <Compile Include="Core\Vector2i.cs" /> <Compile Include="Core\Vector3.cs" /> diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index 84db9473c5..df72413fcc 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -29,6 +29,8 @@ /*************************************************************************/ #include "core/config/engine.h" +#include "core/debugger/engine_debugger.h" +#include "core/debugger/script_debugger.h" #include "core/io/marshalls.h" #include "core/object/class_db.h" #include "core/object/method_bind.h" @@ -81,6 +83,27 @@ GD_PINVOKE_EXPORT Object *godotsharp_engine_get_singleton(const String *p_name) return Engine::get_singleton()->get_singleton_object(*p_name); } +GD_PINVOKE_EXPORT int32_t godotsharp_stack_info_vector_resize( + Vector<ScriptLanguage::StackInfo> *p_stack_info_vector, int p_size) { + return (int32_t)p_stack_info_vector->resize(p_size); +} + +GD_PINVOKE_EXPORT void godotsharp_stack_info_vector_destroy( + Vector<ScriptLanguage::StackInfo> *p_stack_info_vector) { + p_stack_info_vector->~Vector(); +} + +GD_PINVOKE_EXPORT void godotsharp_internal_script_debugger_send_error(const String *p_func, + const String *p_file, int32_t p_line, const String *p_err, const String *p_descr, + bool p_warning, const Vector<ScriptLanguage::StackInfo> *p_stack_info_vector) { + EngineDebugger::get_script_debugger()->send_error(*p_func, *p_file, p_line, *p_err, *p_descr, + true, p_warning ? ERR_HANDLER_WARNING : ERR_HANDLER_ERROR, *p_stack_info_vector); +} + +GD_PINVOKE_EXPORT bool godotsharp_internal_script_debugger_is_active() { + return EngineDebugger::is_active(); +} + GD_PINVOKE_EXPORT GCHandleIntPtr godotsharp_internal_object_get_associated_gchandle(Object *p_ptr) { #ifdef DEBUG_ENABLED CRASH_COND(p_ptr == nullptr); @@ -1288,10 +1311,14 @@ GD_PINVOKE_EXPORT void godotsharp_object_to_string(Object *p_ptr, godot_string * #endif // We need this to prevent the functions from being stripped. -void *godotsharp_pinvoke_funcs[181] = { +void *godotsharp_pinvoke_funcs[185] = { (void *)godotsharp_method_bind_get_method, (void *)godotsharp_get_class_constructor, (void *)godotsharp_engine_get_singleton, + (void *)godotsharp_stack_info_vector_resize, + (void *)godotsharp_stack_info_vector_destroy, + (void *)godotsharp_internal_script_debugger_send_error, + (void *)godotsharp_internal_script_debugger_is_active, (void *)godotsharp_internal_object_get_associated_gchandle, (void *)godotsharp_internal_object_disposed, (void *)godotsharp_internal_refcounted_disposed, diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 7adb6f60eb..8499d30ba4 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -360,13 +360,15 @@ static bool _on_core_api_assembly_loaded() { return false; } - GDMonoCache::managed_callbacks.Dispatcher_InitializeDefaultGodotTaskScheduler(); - + bool debug; #ifdef DEBUG_ENABLED - // Install the trace listener now before the project assembly is loaded - GDMonoCache::managed_callbacks.DebuggingUtils_InstallTraceListener(); + debug = true; +#else + debug = false; #endif + GDMonoCache::managed_callbacks.GD_OnCoreApiAssemblyLoaded(debug); + return true; } @@ -534,26 +536,6 @@ Error GDMono::reload_scripts_domain() { #endif #endif -#warning TODO Reimplement in C# -#if 0 -void GDMono::unhandled_exception_hook(MonoObject *p_exc, void *) { - // This method will be called by the runtime when a thrown exception is not handled. - // It won't be called when we manually treat a thrown exception as unhandled. - // We assume the exception was already printed before calling this hook. - -#ifdef DEBUG_ENABLED - GDMonoUtils::debug_send_unhandled_exception_error((MonoException *)p_exc); - if (EngineDebugger::is_active()) { - EngineDebugger::get_singleton()->poll_events(false); - } -#endif - - exit(mono_environment_exitcode_get()); - - GD_UNREACHABLE(); -} -#endif - GDMono::GDMono() { singleton = this; diff --git a/modules/mono/mono_gd/gd_mono_cache.cpp b/modules/mono/mono_gd/gd_mono_cache.cpp index 7d33f0a896..fc47a0e09b 100644 --- a/modules/mono/mono_gd/gd_mono_cache.cpp +++ b/modules/mono/mono_gd/gd_mono_cache.cpp @@ -38,8 +38,14 @@ ManagedCallbacks managed_callbacks; bool godot_api_cache_updated = false; void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) { -#define CHECK_CALLBACK_NOT_NULL_IMPL(m_var, m_class, m_method) ERR_FAIL_COND_MSG(m_var == nullptr, \ - "Mono Cache: Managed callback for '" #m_class "_" #m_method "' is null.") + int checked_count = 0; + +#define CHECK_CALLBACK_NOT_NULL_IMPL(m_var, m_class, m_method) \ + { \ + ERR_FAIL_COND_MSG(m_var == nullptr, \ + "Mono Cache: Managed callback for '" #m_class "_" #m_method "' is null."); \ + checked_count += 1; \ + } #define CHECK_CALLBACK_NOT_NULL(m_class, m_method) CHECK_CALLBACK_NOT_NULL_IMPL(p_managed_callbacks.m_class##_##m_method, m_class, m_method) @@ -56,9 +62,12 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) { CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, HasScriptSignal); CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, ScriptIsOrInherits); CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, AddScriptBridge); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetOrCreateScriptBridgeForPath); CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, RemoveScriptBridge); CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, UpdateScriptClassInfo); CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, SwapGCHandleForType); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetPropertyInfoList); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetPropertyDefaultValues); CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, Call); CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, Set); CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, Get); @@ -66,12 +75,18 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) { CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, CallToString); CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, HasMethodUnknownParams); CHECK_CALLBACK_NOT_NULL(GCHandleBridge, FreeGCHandle); - CHECK_CALLBACK_NOT_NULL(DebuggingUtils, InstallTraceListener); - CHECK_CALLBACK_NOT_NULL(Dispatcher, InitializeDefaultGodotTaskScheduler); + CHECK_CALLBACK_NOT_NULL(DebuggingUtils, GetCurrentStackInfo); CHECK_CALLBACK_NOT_NULL(DisposablesTracker, OnGodotShuttingDown); + CHECK_CALLBACK_NOT_NULL(GD, OnCoreApiAssemblyLoaded); managed_callbacks = p_managed_callbacks; + // It's easy to forget to add new callbacks here, so this should help + if (checked_count * sizeof(void *) != sizeof(ManagedCallbacks)) { + int missing_count = (sizeof(ManagedCallbacks) / sizeof(void *)) - checked_count; + WARN_PRINT("The presence of " + itos(missing_count) + " callback(s) was not validated"); + } + godot_api_cache_updated = true; } } // namespace GDMonoCache diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h index 7daa4c92d3..763a7f3e5e 100644 --- a/modules/mono/mono_gd/gd_mono_cache.h +++ b/modules/mono/mono_gd/gd_mono_cache.h @@ -97,9 +97,9 @@ struct ManagedCallbacks { using FuncCSharpInstanceBridge_CallToString = void(GD_CLR_STDCALL *)(GCHandleIntPtr, String *, bool *); using FuncCSharpInstanceBridge_HasMethodUnknownParams = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *); using FuncGCHandleBridge_FreeGCHandle = void(GD_CLR_STDCALL *)(GCHandleIntPtr); - using FuncDebuggingUtils_InstallTraceListener = void(GD_CLR_STDCALL *)(); - using FuncDispatcher_InitializeDefaultGodotTaskScheduler = void(GD_CLR_STDCALL *)(); + using FuncDebuggingUtils_GetCurrentStackInfo = void(GD_CLR_STDCALL *)(Vector<ScriptLanguage::StackInfo> *); using FuncDisposablesTracker_OnGodotShuttingDown = void(GD_CLR_STDCALL *)(); + using FuncGD_OnCoreApiAssemblyLoaded = void(GD_CLR_STDCALL *)(bool); FuncSignalAwaiter_SignalCallback SignalAwaiter_SignalCallback; FuncDelegateUtils_InvokeWithVariantArgs DelegateUtils_InvokeWithVariantArgs; @@ -127,9 +127,9 @@ struct ManagedCallbacks { FuncCSharpInstanceBridge_CallToString CSharpInstanceBridge_CallToString; FuncCSharpInstanceBridge_HasMethodUnknownParams CSharpInstanceBridge_HasMethodUnknownParams; FuncGCHandleBridge_FreeGCHandle GCHandleBridge_FreeGCHandle; - FuncDebuggingUtils_InstallTraceListener DebuggingUtils_InstallTraceListener; - FuncDispatcher_InitializeDefaultGodotTaskScheduler Dispatcher_InitializeDefaultGodotTaskScheduler; + FuncDebuggingUtils_GetCurrentStackInfo DebuggingUtils_GetCurrentStackInfo; FuncDisposablesTracker_OnGodotShuttingDown DisposablesTracker_OnGodotShuttingDown; + FuncGD_OnCoreApiAssemblyLoaded GD_OnCoreApiAssemblyLoaded; }; extern ManagedCallbacks managed_callbacks; |