mirror of
https://github.com/godotengine/godot.git
synced 2024-11-25 21:52:51 +00:00
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.
This commit is contained in:
parent
67db89988d
commit
778007a358
@ -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,21 +604,15 @@ 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) {
|
||||
if (!gdmono->is_runtime_initialized()) {
|
||||
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);
|
||||
|
||||
if (GDMonoCache::godot_api_cache_updated) {
|
||||
GDMonoCache::managed_callbacks.DebuggingUtils_GetCurrentStackInfo(&si);
|
||||
}
|
||||
|
||||
return si;
|
||||
#else
|
||||
@ -628,70 +620,6 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info()
|
||||
#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) {
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
MutexLock lock(unsafe_object_references_lock);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ namespace Godot.Bridge
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExceptionUtils.DebugPrintUnhandledException(e);
|
||||
ExceptionUtils.LogException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
{
|
||||
try
|
||||
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
|
||||
{
|
||||
Trace.Listeners.Clear();
|
||||
Trace.Listeners.Add(new GodotTraceListener());
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _ptr;
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
public void Resize(int size)
|
||||
{
|
||||
ExceptionUtils.DebugPrintUnhandledException(e);
|
||||
ExceptionUtils.PushError("Failed to install 'System.Diagnostics.Trace' listener.");
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetStackFrameInfo(StackFrame frame, out string fileName, out int fileLineNumber,
|
||||
out string methodDecl)
|
||||
[UnmanagedCallersOnly]
|
||||
internal static unsafe void GetCurrentStackInfo(void* destVector)
|
||||
{
|
||||
fileName = frame.GetFileName();
|
||||
fileLineNumber = frame.GetFileLineNumber();
|
||||
try
|
||||
{
|
||||
var vector = (godot_stack_info_vector*)destVector;
|
||||
var stackTrace = new StackTrace(skipFrames: 1, fNeedFileInfo: true);
|
||||
int frameCount = stackTrace.FrameCount;
|
||||
|
||||
MethodBase methodBase = frame.GetMethod();
|
||||
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.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void GetStackFrameMethodDecl(StackFrame frame, out string methodDecl)
|
||||
{
|
||||
MethodBase? methodBase = frame.GetMethod();
|
||||
|
||||
if (methodBase == null)
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ namespace Godot
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExceptionUtils.DebugUnhandledException(e);
|
||||
ExceptionUtils.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Godot.NativeInterop
|
||||
{
|
||||
@ -11,20 +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());
|
||||
try
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
// Well, too bad...
|
||||
}
|
||||
}
|
||||
|
||||
public static void DebugPrintUnhandledException(Exception e)
|
||||
private record struct StackInfoTuple(string? File, string Func, int Line);
|
||||
|
||||
private static void CollectExceptionInfo(Exception exception, List<StackInfoTuple> globalFrames,
|
||||
StringBuilder excMsg)
|
||||
{
|
||||
if (excMsg.Length > 0)
|
||||
excMsg.Append(" ---> ");
|
||||
excMsg.Append(exception.GetType().FullName);
|
||||
excMsg.Append(": ");
|
||||
excMsg.Append(exception.Message);
|
||||
|
||||
var innerExc = exception.InnerException;
|
||||
|
||||
if (innerExc != null)
|
||||
{
|
||||
CollectExceptionInfo(innerExc, globalFrames, excMsg);
|
||||
globalFrames.Add(new("", "--- End of inner exception stack trace ---", 0));
|
||||
}
|
||||
|
||||
var stackTrace = new StackTrace(exception, fNeedFileInfo: true);
|
||||
|
||||
foreach (StackFrame frame in stackTrace.GetFrames())
|
||||
{
|
||||
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 LogException(Exception e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO Not implemented (debug_print_unhandled_exception)
|
||||
GD.PushError(e.ToString());
|
||||
if (NativeFuncs.godotsharp_internal_script_debugger_is_active())
|
||||
{
|
||||
SendToScriptDebugger(e);
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PushError(e.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception unexpected)
|
||||
{
|
||||
@ -32,38 +118,17 @@ namespace Godot.NativeInterop
|
||||
}
|
||||
}
|
||||
|
||||
public static void DebugSendUnhandledExceptionError(Exception e)
|
||||
public static void LogUnhandledException(Exception e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO Not implemented (debug_send_unhandled_exception_error)
|
||||
GD.PushError(e.ToString());
|
||||
}
|
||||
catch (Exception unexpected)
|
||||
{
|
||||
OnExceptionLoggerException(unexpected, e);
|
||||
}
|
||||
}
|
||||
if (NativeFuncs.godotsharp_internal_script_debugger_is_active())
|
||||
{
|
||||
SendToScriptDebugger(e);
|
||||
}
|
||||
|
||||
public static void DebugUnhandledException(Exception e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO Not implemented (debug_unhandled_exception)
|
||||
GD.PushError(e.ToString());
|
||||
}
|
||||
catch (Exception unexpected)
|
||||
{
|
||||
OnExceptionLoggerException(unexpected, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void PrintUnhandledException(Exception e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO Not implemented (print_unhandled_exception)
|
||||
GD.PushError(e.ToString());
|
||||
// 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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -58,7 +58,7 @@ namespace Godot
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExceptionUtils.DebugPrintUnhandledException(e);
|
||||
ExceptionUtils.LogException(e);
|
||||
*outAwaiterIsNull = false.ToGodotBool();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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" />
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user