summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgnacio Roldán Etcheverry <ignalfonsore@gmail.com>2022-02-27 21:57:39 +0100
committerIgnacio Roldán Etcheverry <ignalfonsore@gmail.com>2022-08-22 03:36:51 +0200
commit778007a358961f30ccadd738300a353edf0a0c51 (patch)
treef0565278dd7456446f546b416231ed72d30d0017
parent67db89988d3c9283d4d58b328b959e1531b16dae (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.
-rw-r--r--modules/mono/csharp_script.cpp82
-rw-r--r--modules/mono/csharp_script.h7
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs12
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs8
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs34
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs93
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Dispatcher.cs12
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/GodotUnhandledExceptionEvent.cs30
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs117
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs20
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs20
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj3
-rw-r--r--modules/mono/glue/runtime_interop.cpp29
-rw-r--r--modules/mono/mono_gd/gd_mono.cpp30
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.cpp23
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.h8
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;