diff options
Diffstat (limited to 'modules/mono')
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; |