From 513cc78f85d8c45a9492ae25034cd2357714cfdf Mon Sep 17 00:00:00 2001 From: Ignacio Etcheverry Date: Wed, 24 Jul 2019 23:13:24 +0200 Subject: [PATCH 1/2] Mono: Add option to keep running after unhandled exceptions By default, an unhandled exception will cause the application to be terminated; but the project setting `mono/unhandled_exception_policy` was added to change this behaviour. The editor is hard-coded to never terminate because of unhandled exceptions, as that would make writing editor plugins a painful task, and we cannot kill the editor because of a mistake in a thirdparty plugin. --- modules/mono/csharp_script.cpp | 1 - modules/mono/mono_gd/gd_mono.cpp | 14 ++++++++++++++ modules/mono/mono_gd/gd_mono.h | 13 ++++++++++++- modules/mono/mono_gd/gd_mono_internals.cpp | 15 ++++++++++++--- modules/mono/mono_gd/gd_mono_internals.h | 2 +- modules/mono/mono_gd/gd_mono_utils.cpp | 17 ++++++++++++----- modules/mono/mono_gd/gd_mono_utils.h | 2 +- 7 files changed, 52 insertions(+), 12 deletions(-) diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 078a490b22..846c84d222 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -629,7 +629,6 @@ void CSharpLanguage::frame() { if (exc) { GDMonoUtils::debug_unhandled_exception(exc); - GD_UNREACHABLE(); } } } diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 571bf598f3..8b9813f472 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -283,6 +283,18 @@ void GDMono::initialize() { add_mono_shared_libs_dir_to_path(); + { + PropertyInfo exc_policy_prop = PropertyInfo(Variant::INT, "mono/unhandled_exception_policy", PROPERTY_HINT_ENUM, + vformat("Terminate Application:%s,Log Error:%s", (int)POLICY_TERMINATE_APP, (int)POLICY_LOG_ERROR)); + unhandled_exception_policy = (UnhandledExceptionPolicy)(int)GLOBAL_DEF(exc_policy_prop.name, (int)POLICY_TERMINATE_APP); + ProjectSettings::get_singleton()->set_custom_property_info(exc_policy_prop.name, exc_policy_prop); + + if (Engine::get_singleton()->is_editor_hint()) { + // Unhandled exceptions should not terminate the editor + unhandled_exception_policy = POLICY_LOG_ERROR; + } + } + GDMonoAssembly::initialize(); gdmono_profiler_init(); @@ -1063,6 +1075,8 @@ GDMono::GDMono() { #ifdef TOOLS_ENABLED api_editor_hash = 0; #endif + + unhandled_exception_policy = POLICY_TERMINATE_APP; } GDMono::~GDMono() { diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index deebe5fd50..c5bcce4fa1 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -80,6 +80,13 @@ String to_string(Type p_type); class GDMono { +public: + enum UnhandledExceptionPolicy { + POLICY_TERMINATE_APP, + POLICY_LOG_ERROR + }; + +private: bool runtime_initialized; bool finalizing_scripts_domain; @@ -102,6 +109,8 @@ class GDMono { HashMap > assemblies; + UnhandledExceptionPolicy unhandled_exception_policy; + void _domain_assemblies_cleanup(uint32_t p_domain_id); bool _are_api_assemblies_out_of_sync(); @@ -162,7 +171,9 @@ public: static GDMono *get_singleton() { return singleton; } - static void unhandled_exception_hook(MonoObject *p_exc, void *p_user_data); + GD_NORETURN static void unhandled_exception_hook(MonoObject *p_exc, void *p_user_data); + + UnhandledExceptionPolicy get_unhandled_exception_policy() const { return unhandled_exception_policy; } // Do not use these, unless you know what you're doing void add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly); diff --git a/modules/mono/mono_gd/gd_mono_internals.cpp b/modules/mono/mono_gd/gd_mono_internals.cpp index a84332d4cd..e50e3b0794 100644 --- a/modules/mono/mono_gd/gd_mono_internals.cpp +++ b/modules/mono/mono_gd/gd_mono_internals.cpp @@ -108,9 +108,18 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) { void unhandled_exception(MonoException *p_exc) { mono_unhandled_exception((MonoObject *)p_exc); // prints the exception as well - // Too bad 'mono_invoke_unhandled_exception_hook' is not exposed to embedders - GDMono::unhandled_exception_hook((MonoObject *)p_exc, NULL); - GD_UNREACHABLE(); + + if (GDMono::get_singleton()->get_unhandled_exception_policy() == GDMono::POLICY_TERMINATE_APP) { + // Too bad 'mono_invoke_unhandled_exception_hook' is not exposed to embedders + GDMono::unhandled_exception_hook((MonoObject *)p_exc, NULL); + GD_UNREACHABLE(); + } else { +#ifdef DEBUG_ENABLED + GDMonoUtils::debug_send_unhandled_exception_error((MonoException *)p_exc); + if (ScriptDebugger::get_singleton()) + ScriptDebugger::get_singleton()->idle_poll(); +#endif + } } } // namespace GDMonoInternals diff --git a/modules/mono/mono_gd/gd_mono_internals.h b/modules/mono/mono_gd/gd_mono_internals.h index 2d77bde27c..0d82723913 100644 --- a/modules/mono/mono_gd/gd_mono_internals.h +++ b/modules/mono/mono_gd/gd_mono_internals.h @@ -45,7 +45,7 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged); * Do not call this function directly. * Use GDMonoUtils::debug_unhandled_exception(MonoException *) instead. */ -GD_NORETURN void unhandled_exception(MonoException *p_exc); +void unhandled_exception(MonoException *p_exc); } // namespace GDMonoInternals diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp index 5987fa8ebb..7afdfc8ac8 100644 --- a/modules/mono/mono_gd/gd_mono_utils.cpp +++ b/modules/mono/mono_gd/gd_mono_utils.cpp @@ -37,6 +37,10 @@ #include "core/project_settings.h" #include "core/reference.h" +#ifdef TOOLS_ENABLED +#include "editor/script_editor_debugger.h" +#endif + #include "../csharp_script.h" #include "../utils/macros.h" #include "../utils/mutex_utils.h" @@ -596,8 +600,14 @@ void debug_print_unhandled_exception(MonoException *p_exc) { void debug_send_unhandled_exception_error(MonoException *p_exc) { #ifdef DEBUG_ENABLED - if (!ScriptDebugger::get_singleton()) + if (!ScriptDebugger::get_singleton()) { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + ERR_PRINTS(GDMonoUtils::get_exception_name_and_message(p_exc)); + } +#endif return; + } _TLS_RECURSION_GUARD_; @@ -621,7 +631,7 @@ void debug_send_unhandled_exception_error(MonoException *p_exc) { if (unexpected_exc) { GDMonoInternals::unhandled_exception(unexpected_exc); - GD_UNREACHABLE(); + return; } Vector _si; @@ -655,7 +665,6 @@ void debug_send_unhandled_exception_error(MonoException *p_exc) { void debug_unhandled_exception(MonoException *p_exc) { GDMonoInternals::unhandled_exception(p_exc); // prints the exception as well - GD_UNREACHABLE(); } void print_unhandled_exception(MonoException *p_exc) { @@ -665,11 +674,9 @@ void print_unhandled_exception(MonoException *p_exc) { void set_pending_exception(MonoException *p_exc) { #ifdef NO_PENDING_EXCEPTIONS debug_unhandled_exception(p_exc); - GD_UNREACHABLE(); #else if (get_runtime_invoke_count() == 0) { debug_unhandled_exception(p_exc); - GD_UNREACHABLE(); } if (!mono_runtime_set_pending_exception(p_exc, false)) { diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h index f535fbb6d0..d73743bf0b 100644 --- a/modules/mono/mono_gd/gd_mono_utils.h +++ b/modules/mono/mono_gd/gd_mono_utils.h @@ -289,7 +289,7 @@ void set_exception_message(MonoException *p_exc, String message); void debug_print_unhandled_exception(MonoException *p_exc); void debug_send_unhandled_exception_error(MonoException *p_exc); -GD_NORETURN void debug_unhandled_exception(MonoException *p_exc); +void debug_unhandled_exception(MonoException *p_exc); void print_unhandled_exception(MonoException *p_exc); /** From c0cdbb793822961e68727925d0f3f4e00f780306 Mon Sep 17 00:00:00 2001 From: Ignacio Etcheverry Date: Wed, 24 Jul 2019 23:18:55 +0200 Subject: [PATCH 2/2] Mono: Fix regression: external editors not working on Windows --- .../GodotTools/GodotTools/GodotSharpEditor.cs | 11 +++++++++- .../GodotTools/MonoDevelopInstance.cs | 22 +++++++++++-------- .../GodotTools/Utils/CollectionExtensions.cs | 5 +++-- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 90dec43412..9b5afb94a3 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -298,7 +298,16 @@ namespace GodotTools if (line >= 0) scriptPath += $";{line + 1};{col}"; - GetMonoDevelopInstance(GodotSharpDirs.ProjectSlnPath).Execute(scriptPath); + try + { + GetMonoDevelopInstance(GodotSharpDirs.ProjectSlnPath).Execute(scriptPath); + } + catch (FileNotFoundException) + { + string editorName = editor == ExternalEditor.VisualStudioForMac ? "Visual Studio" : "MonoDevelop"; + GD.PushError($"Cannot find code editor: {editorName}"); + return Error.FileNotFound; + } break; } diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoDevelopInstance.cs b/modules/mono/editor/GodotTools/GodotTools/MonoDevelopInstance.cs index 0c8d86e799..61a0a992ce 100644 --- a/modules/mono/editor/GodotTools/GodotTools/MonoDevelopInstance.cs +++ b/modules/mono/editor/GodotTools/GodotTools/MonoDevelopInstance.cs @@ -4,6 +4,7 @@ using System.IO; using System.Collections.Generic; using System.Diagnostics; using GodotTools.Internals; +using GodotTools.Utils; namespace GodotTools { @@ -30,7 +31,7 @@ namespace GodotTools if (Utils.OS.IsOSX()) { - string bundleId = CodeEditorBundleIds[editorId]; + string bundleId = BundleIds[editorId]; if (Internal.IsOsxAppBundleInstalled(bundleId)) { @@ -47,12 +48,12 @@ namespace GodotTools } else { - command = CodeEditorPaths[editorId]; + command = OS.PathWhich(ExecutableNames[editorId]); } } else { - command = CodeEditorPaths[editorId]; + command = OS.PathWhich(ExecutableNames[editorId]); } args.Add("--ipc-tcp"); @@ -70,6 +71,9 @@ namespace GodotTools args.Add("\"" + Path.GetFullPath(filePath.NormalizePath()) + cursor + "\""); } + if (command == null) + throw new FileNotFoundException(); + if (newWindow) { process = Process.Start(new ProcessStartInfo @@ -99,20 +103,20 @@ namespace GodotTools this.editorId = editorId; } - private static readonly IReadOnlyDictionary CodeEditorPaths; - private static readonly IReadOnlyDictionary CodeEditorBundleIds; + private static readonly IReadOnlyDictionary ExecutableNames; + private static readonly IReadOnlyDictionary BundleIds; static MonoDevelopInstance() { if (Utils.OS.IsOSX()) { - CodeEditorPaths = new Dictionary + ExecutableNames = new Dictionary { // Rely on PATH {EditorId.MonoDevelop, "monodevelop"}, {EditorId.VisualStudioForMac, "VisualStudio"} }; - CodeEditorBundleIds = new Dictionary + BundleIds = new Dictionary { // TODO EditorId.MonoDevelop {EditorId.VisualStudioForMac, "com.microsoft.visual-studio"} @@ -120,7 +124,7 @@ namespace GodotTools } else if (Utils.OS.IsWindows()) { - CodeEditorPaths = new Dictionary + ExecutableNames = new Dictionary { // XamarinStudio is no longer a thing, and the latest version is quite old // MonoDevelop is available from source only on Windows. The recommendation @@ -131,7 +135,7 @@ namespace GodotTools } else if (Utils.OS.IsUnix()) { - CodeEditorPaths = new Dictionary + ExecutableNames = new Dictionary { // Rely on PATH {EditorId.MonoDevelop, "monodevelop"} diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs index 3ae6c10bbf..288c65de74 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs @@ -10,8 +10,9 @@ namespace GodotTools.Utils { foreach (T elem in enumerable) { - if (predicate(elem) != null) - return elem; + T result = predicate(elem); + if (result != null) + return result; } return orElse;