[wasm] [debugger] Support Exception Break on Debugger (handled and unhandled) (#40480)
authorThays Grazia <thaystg@gmail.com>
Fri, 7 Aug 2020 23:07:48 +0000 (20:07 -0300)
committerGitHub <noreply@github.com>
Fri, 7 Aug 2020 23:07:48 +0000 (18:07 -0500)
* Support Exception Break on Debugger (handled and unhandled)

Co-authored-by: Ankit Jain <radical@gmail.com>
src/mono/mono/mini/mini-wasm-debugger.c
src/mono/wasm/debugger/BrowserDebugHost/BrowserDebugHost.csproj
src/mono/wasm/debugger/BrowserDebugProxy/AssemblyInfo.cs [deleted file]
src/mono/wasm/debugger/BrowserDebugProxy/BrowserDebugProxy.csproj
src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs
src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
src/mono/wasm/debugger/DebuggerTestSuite/ExceptionTests.cs [new file with mode: 0644]
src/mono/wasm/debugger/DebuggerTestSuite/Support.cs
src/mono/wasm/debugger/tests/debugger-exception-test.cs [new file with mode: 0644]
src/mono/wasm/debugger/tests/other.js
src/mono/wasm/runtime/library_mono.js

index dc1f5d3..90855b5 100644 (file)
@@ -24,6 +24,12 @@ static int log_level = 1;
 
 #define DEBUG_PRINTF(level, ...) do { if (G_UNLIKELY ((level) <= log_level)) { fprintf (stdout, __VA_ARGS__); } } while (0)
 
+enum {
+       EXCEPTION_MODE_NONE,
+       EXCEPTION_MODE_UNCAUGHT,
+       EXCEPTION_MODE_ALL
+};
+
 //functions exported to be used by JS
 G_BEGIN_DECLS
 
@@ -34,6 +40,7 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_enum_frames (void);
 EMSCRIPTEN_KEEPALIVE void mono_wasm_get_var_info (int scope, int* pos, int len);
 EMSCRIPTEN_KEEPALIVE void mono_wasm_clear_all_breakpoints (void);
 EMSCRIPTEN_KEEPALIVE int mono_wasm_setup_single_step (int kind);
+EMSCRIPTEN_KEEPALIVE int mono_wasm_pause_on_exceptions (int state);
 EMSCRIPTEN_KEEPALIVE void mono_wasm_get_object_properties (int object_id, gboolean expand_value_types);
 EMSCRIPTEN_KEEPALIVE void mono_wasm_get_array_values (int object_id);
 EMSCRIPTEN_KEEPALIVE void mono_wasm_get_array_value_expanded (int object_id, int idx);
@@ -43,6 +50,7 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_get_deref_ptr_value (void *value_addr, MonoC
 //JS functions imported that we use
 extern void mono_wasm_add_frame (int il_offset, int method_token, const char *assembly_name, const char *method_name);
 extern void mono_wasm_fire_bp (void);
+extern void mono_wasm_fire_exception (int exception_obj_id, const char* message, const char* class_name, gboolean uncaught);
 extern void mono_wasm_add_obj_var (const char*, const char*, guint64);
 extern void mono_wasm_add_value_type_unexpanded_var (const char*, const char*);
 extern void mono_wasm_begin_value_type_var (const char*, const char*);
@@ -57,6 +65,7 @@ extern void mono_wasm_add_typed_value (const char *type, const char *str_value,
 G_END_DECLS
 
 static void describe_object_properties_for_klass (void *obj, MonoClass *klass, gboolean isAsyncLocalThis, gboolean expandValueType);
+static void handle_exception (MonoException *exc, MonoContext *throw_ctx, MonoContext *catch_ctx, StackFrameInfo *catch_frame);
 
 //FIXME move all of those fields to the profiler object
 static gboolean debugger_enabled;
@@ -65,6 +74,7 @@ static int event_request_id;
 static GHashTable *objrefs;
 static GHashTable *obj_to_objref;
 static int objref_id = 0;
+static int pause_on_exc = EXCEPTION_MODE_NONE;
 
 static const char*
 all_getters_allowed_class_names[] = {
@@ -363,6 +373,8 @@ mono_wasm_debugger_init (void)
 
        obj_to_objref = g_hash_table_new (NULL, NULL);
        objrefs = g_hash_table_new_full (NULL, NULL, NULL, mono_debugger_free_objref);
+
+       mini_get_dbg_callbacks ()->handle_exception = handle_exception;
 }
 
 MONO_API void
@@ -374,6 +386,14 @@ mono_wasm_enable_debugging (int debug_level)
 }
 
 EMSCRIPTEN_KEEPALIVE int
+mono_wasm_pause_on_exceptions (int state)
+{
+       pause_on_exc = state;
+       DEBUG_PRINTF (1, "setting pause on exception: %d\n", pause_on_exc);
+       return 1;
+}
+
+EMSCRIPTEN_KEEPALIVE int
 mono_wasm_setup_single_step (int kind)
 {
        int nmodifiers = 1;
@@ -428,6 +448,50 @@ mono_wasm_setup_single_step (int kind)
        return isBPOnNativeCode;
 }
 
+static int 
+get_object_id(MonoObject *obj) 
+{
+       ObjRef *ref;
+       if (!obj)
+               return 0;
+
+       ref = (ObjRef *)g_hash_table_lookup (obj_to_objref, GINT_TO_POINTER (~((gsize)obj)));
+       if (ref)
+               return ref->id;
+       ref = g_new0 (ObjRef, 1);
+       ref->id = mono_atomic_inc_i32 (&objref_id);
+       ref->handle = mono_gchandle_new_weakref_internal (obj, FALSE);
+       g_hash_table_insert (objrefs, GINT_TO_POINTER (ref->id), ref);
+       g_hash_table_insert (obj_to_objref, GINT_TO_POINTER (~((gsize)obj)), ref);
+       return ref->id;
+}
+
+static void
+handle_exception (MonoException *exc, MonoContext *throw_ctx, MonoContext *catch_ctx, StackFrameInfo *catch_frame)
+{
+       ERROR_DECL (error);
+       DEBUG_PRINTF (1, "handle exception - %d - %p - %p - %p\n", pause_on_exc, exc, throw_ctx, catch_ctx);
+
+       if (pause_on_exc == EXCEPTION_MODE_NONE)
+               return;
+       if (pause_on_exc == EXCEPTION_MODE_UNCAUGHT && catch_ctx != NULL)
+               return;
+
+       int obj_id = get_object_id ((MonoObject *)exc);
+       const char *error_message = mono_string_to_utf8_checked_internal (exc->message, error);
+
+       if (!is_ok (error))
+               error_message = "Failed to get exception message.";
+
+       const char *class_name = mono_class_full_name (mono_object_class (exc));
+       DEBUG_PRINTF (2, "handle exception - calling mono_wasm_fire_exc(): %d - message - %s, class_name: %s\n", obj_id,  error_message, class_name);
+
+       mono_wasm_fire_exception (obj_id, error_message, class_name, !catch_ctx);
+
+       DEBUG_PRINTF (2, "handle exception - done\n");
+}
+
+
 EMSCRIPTEN_KEEPALIVE void
 mono_wasm_clear_all_breakpoints (void)
 {
@@ -567,23 +631,6 @@ mono_wasm_current_bp_id (void)
        return evt->id;
 }
 
-static int get_object_id(MonoObject *obj) 
-{
-       ObjRef *ref;
-       if (!obj)
-               return 0;
-
-       ref = (ObjRef *)g_hash_table_lookup (obj_to_objref, GINT_TO_POINTER (~((gsize)obj)));
-       if (ref)
-               return ref->id;
-       ref = g_new0 (ObjRef, 1);
-       ref->id = mono_atomic_inc_i32 (&objref_id);
-       ref->handle = mono_gchandle_new_weakref_internal (obj, FALSE);
-       g_hash_table_insert (objrefs, GINT_TO_POINTER (ref->id), ref);
-       g_hash_table_insert (obj_to_objref, GINT_TO_POINTER (~((gsize)obj)), ref);
-       return ref->id;
-}
-
 static gboolean
 list_frames (MonoStackFrameInfo *info, MonoContext *ctx, gpointer data)
 {
index aee852c..9650dc8 100644 (file)
@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk.Web">
 
   <PropertyGroup>
-    <TargetFramework>netcoreapp3.0</TargetFramework>
+    <TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
   </PropertyGroup>
 
   <ItemGroup>
diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/AssemblyInfo.cs b/src/mono/wasm/debugger/BrowserDebugProxy/AssemblyInfo.cs
deleted file mode 100644 (file)
index 5d9d173..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Runtime.CompilerServices;
\ No newline at end of file
index 4063416..7da1586 100644 (file)
@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>netstandard2.1</TargetFramework>
+    <TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
     <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
   </PropertyGroup>
 
index 45edb96..d17c9e7 100644 (file)
@@ -175,6 +175,8 @@ namespace Microsoft.WebAssembly.Diagnostics
 
         public static MonoCommands GetCallStack() => new MonoCommands("MONO.mono_wasm_get_call_stack()");
 
+        public static MonoCommands GetExceptionObject () => new MonoCommands ("MONO.mono_wasm_get_exception_object()");
+
         public static MonoCommands IsRuntimeReady() => new MonoCommands("MONO.mono_wasm_runtime_is_ready");
 
         public static MonoCommands StartSingleStepping(StepKind kind) => new MonoCommands($"MONO.mono_wasm_start_single_stepping ({(int)kind})");
@@ -200,6 +202,8 @@ namespace Microsoft.WebAssembly.Diagnostics
         public static MonoCommands CallFunctionOn(JToken args) => new MonoCommands($"MONO.mono_wasm_call_function_on ({args.ToString ()})");
 
         public static MonoCommands Resume() => new MonoCommands($"MONO.mono_wasm_debugger_resume ()");
+
+        public static MonoCommands SetPauseOnExceptions (string state) => new MonoCommands ($"MONO.mono_wasm_set_pause_on_exceptions(\"{state}\")");
     }
 
     internal enum MonoErrorCodes
index 61707ae..1f1079f 100644 (file)
@@ -99,9 +99,9 @@ namespace Microsoft.WebAssembly.Diagnostics
                         //TODO figure out how to stich out more frames and, in particular what happens when real wasm is on the stack
                         var top_func = args?["callFrames"] ? [0] ? ["functionName"]?.Value<string>();
 
-                        if (top_func == "mono_wasm_fire_bp" || top_func == "_mono_wasm_fire_bp")
+                        if (top_func == "mono_wasm_fire_bp" || top_func == "_mono_wasm_fire_bp" || top_func == "_mono_wasm_fire_exception")
                         {
-                            return await OnBreakpointHit(sessionId, args, token);
+                            return await OnPause(sessionId, args, token);
                         }
                         break;
                     }
@@ -329,6 +329,14 @@ namespace Microsoft.WebAssembly.Diagnostics
                         return true;
                     }
 
+                case "Debugger.setPauseOnExceptions": 
+                    {
+                        string state = args["state"].Value<string> ();
+                        await SendMonoCommand(id, MonoCommands.SetPauseOnExceptions (state), token);
+                        // Pass this on to JS too
+                        return false;
+                    }
+
                     // Protocol extensions
                 case "DotnetDebugger.getMethodLocation":
                     {
@@ -449,12 +457,14 @@ namespace Microsoft.WebAssembly.Diagnostics
         }
 
         //static int frame_id=0;
-        async Task<bool> OnBreakpointHit(SessionId sessionId, JObject args, CancellationToken token)
+        async Task<bool> OnPause(SessionId sessionId, JObject args, CancellationToken token)
         {
             //FIXME we should send release objects every now and then? Or intercept those we inject and deal in the runtime
             var res = await SendMonoCommand(sessionId, MonoCommands.GetCallStack(), token);
             var orig_callframes = args?["callFrames"]?.Values<JObject>();
             var context = GetContext(sessionId);
+            JObject data = null;
+            var reason = "other";//other means breakpoint
 
             if (res.IsErr)
             {
@@ -486,8 +496,24 @@ namespace Microsoft.WebAssembly.Diagnostics
             {
                 var function_name = frame["functionName"]?.Value<string>();
                 var url = frame["url"]?.Value<string>();
-                if ("mono_wasm_fire_bp" == function_name ||"_mono_wasm_fire_bp" == function_name)
+                if ("mono_wasm_fire_bp" == function_name ||"_mono_wasm_fire_bp" == function_name ||
+                    "_mono_wasm_fire_exception" == function_name)
                 {
+                    if ("_mono_wasm_fire_exception" == function_name) {
+                        var exception_obj_id = await SendMonoCommand(sessionId, MonoCommands.GetExceptionObject (), token);
+                        var res_val = exception_obj_id.Value? ["result"]? ["value"];
+                        var exception_dotnet_obj_id = new DotnetObjectId("object", res_val?["exception_id"]?.Value<string> ());
+                        data = JObject.FromObject (new {
+                            type        = "object",
+                            subtype     = "error",
+                            className   = res_val? ["class_name"]?.Value<string>(),
+                            uncaught    = res_val? ["uncaught"]?.Value<bool>(),
+                            description = res_val? ["message"]?.Value<string>() + "\n",
+                            objectId    = exception_dotnet_obj_id.ToString()
+                        });
+                        reason = "exception";
+                    }
+
                     var frames = new List<Frame>();
                     int frame_id = 0;
                     var the_mono_frames = res.Value?["result"] ? ["value"] ? ["frames"]?.Values<JObject>();
@@ -580,7 +606,8 @@ namespace Microsoft.WebAssembly.Diagnostics
             var o = JObject.FromObject(new
             {
                 callFrames,
-                reason = "other", //other means breakpoint
+                reason,
+                data,
                 hitBreakpoints = bp_list,
             });
 
diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/ExceptionTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/ExceptionTests.cs
new file mode 100644 (file)
index 0000000..301c422
--- /dev/null
@@ -0,0 +1,264 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Newtonsoft.Json.Linq;
+using Microsoft.WebAssembly.Diagnostics;
+using Xunit;
+
+namespace DebuggerTests
+{
+
+    public class ExceptionTests : DebuggerTestBase
+    {
+        [Fact]
+        public async Task ExceptionTestAll()
+        {
+            var insp = new Inspector();
+            //Collect events
+            var scripts = SubscribeToScripts(insp);
+            int line = 15;
+            int col = 20;
+            string entry_method_name = "[debugger-test] DebuggerTests.ExceptionTestsClass:TestExceptions";
+
+            await Ready();
+            await insp.Ready(async(cli, token) =>
+            {
+                ctx = new DebugTestContext(cli, insp, token, scripts);
+                var debugger_test_loc = "dotnet://debugger-test.dll/debugger-exception-test.cs";
+
+                await SetPauseOnException("all");
+
+                var eval_expr = "window.setTimeout(function() { invoke_static_method (" +
+                    $"'{entry_method_name}'" +
+                    "); }, 1);";
+
+                var pause_location = await EvaluateAndCheck(eval_expr,  null, 0, 0, null);
+                //stop in the managed caught exception
+                pause_location = await WaitForManagedException(pause_location);
+
+                AssertEqual("run", pause_location["callFrames"] ? [0] ? ["functionName"]?.Value<string>(), "pause0");
+                
+                await CheckValue(pause_location["data"], JObject.FromObject(new
+                {
+                    type = "object",
+                    subtype = "error",
+                    className = "DebuggerTests.CustomException",
+                    uncaught = false
+                }), "exception0.data");
+
+                var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value<string>());
+                CheckString(exception_members, "message", "not implemented caught");
+
+                pause_location = await WaitForManagedException(null);
+                AssertEqual("run", pause_location["callFrames"] ? [0] ? ["functionName"]?.Value<string>(), "pause1");
+
+                //stop in the uncaught exception
+                CheckLocation(debugger_test_loc, 28, 16, scripts, pause_location["callFrames"][0]["location"]);
+
+                await CheckValue(pause_location["data"], JObject.FromObject(new
+                {
+                    type = "object",
+                    subtype = "error",
+                    className = "DebuggerTests.CustomException",
+                    uncaught = true
+                }), "exception1.data");
+
+                exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value<string>());
+                CheckString(exception_members, "message", "not implemented uncaught");
+            });
+        }
+
+        [Fact]
+        public async Task JSExceptionTestAll()
+        {
+            var insp = new Inspector();
+            //Collect events
+            var scripts = SubscribeToScripts(insp);
+
+            await Ready();
+            await insp.Ready(async(cli, token) =>
+            {
+                ctx = new DebugTestContext(cli, insp, token, scripts);
+
+                await SetPauseOnException("all");
+
+                var eval_expr = "window.setTimeout(function () { exceptions_test (); }, 1)";
+                var pause_location = await EvaluateAndCheck(eval_expr, null, 0, 0, "exception_caught_test", null, null);
+
+                Assert.Equal("exception", pause_location["reason"]);
+                await CheckValue(pause_location["data"], JObject.FromObject(new
+                {
+                    type = "object",
+                    subtype = "error",
+                    className = "TypeError",
+                    uncaught = false
+                }), "exception0.data");
+
+                var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value<string>());
+                CheckString(exception_members, "message", "exception caught");
+
+                pause_location = await SendCommandAndCheck(null, "Debugger.resume", null, 0, 0, "exception_uncaught_test");
+
+                Assert.Equal("exception", pause_location["reason"]);
+                await CheckValue(pause_location["data"], JObject.FromObject(new
+                {
+                    type = "object",
+                    subtype = "error",
+                    className = "RangeError",
+                    uncaught = true
+                }), "exception1.data");
+
+                exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value<string>());
+                CheckString(exception_members, "message", "exception uncaught");
+            });
+        }
+
+        // FIXME? BUG? We seem to get the stack trace for Runtime.exceptionThrown at `call_method`,
+        // but JS shows the original error type, and original trace
+        [Fact]
+        public async Task ExceptionTestNone()
+        {
+            var insp = new Inspector();
+            //Collect events
+            var scripts = SubscribeToScripts(insp);
+            string entry_method_name = "[debugger-test] DebuggerTests.ExceptionTestsClass:TestExceptions";
+
+            await Ready();
+            await insp.Ready(async(cli, token) =>
+            {
+                ctx = new DebugTestContext(cli, insp, token, scripts);
+
+                await SetPauseOnException("none");
+
+                var eval_expr = "window.setTimeout(function() { invoke_static_method (" +
+                    $"'{entry_method_name}'" +
+                    "); }, 1);";
+
+                try
+                {
+                    await EvaluateAndCheck(eval_expr, null, 0, 0, "", null, null);
+                }
+                catch (ArgumentException ae)
+                {
+                    var eo = JObject.Parse(ae.Message);
+
+                    // AssertEqual (line, eo ["exceptionDetails"]?["lineNumber"]?.Value<int> (), "lineNumber");
+                    AssertEqual("Uncaught", eo["exceptionDetails"] ? ["text"]?.Value<string>(), "text");
+
+                    await CheckValue(eo["exceptionDetails"] ? ["exception"], JObject.FromObject(new
+                    {
+                        type = "object",
+                        subtype = "error",
+                        className = "Error" // BUG?: "DebuggerTests.CustomException"
+                    }), "exception");
+
+                    return;
+                }
+
+                Assert.True(false, "Expected to get an ArgumentException from the uncaught user exception");
+            });
+        }
+
+        [Fact]
+        public async Task JSExceptionTestNone()
+        {
+            var insp = new Inspector();
+            //Collect events
+            var scripts = SubscribeToScripts(insp);
+
+            await Ready();
+            await insp.Ready(async(cli, token) =>
+            {
+                ctx = new DebugTestContext(cli, insp, token, scripts);
+
+                await SetPauseOnException("none");
+
+                var eval_expr = "window.setTimeout(function () { exceptions_test (); }, 1)";
+
+                int line = 44;
+                try
+                {
+                    await EvaluateAndCheck(eval_expr, null, 0, 0, "", null, null);
+                }
+                catch (ArgumentException ae)
+                {
+                    Console.WriteLine($"{ae}");
+                    var eo = JObject.Parse(ae.Message);
+
+                    AssertEqual(line, eo["exceptionDetails"] ? ["lineNumber"]?.Value<int>(), "lineNumber");
+                    AssertEqual("Uncaught", eo["exceptionDetails"] ? ["text"]?.Value<string>(), "text");
+
+                    await CheckValue(eo["exceptionDetails"] ? ["exception"], JObject.FromObject(new
+                    {
+                        type = "object",
+                        subtype = "error",
+                        className = "RangeError"
+                    }), "exception");
+
+                    return;
+                }
+
+                Assert.True(false, "Expected to get an ArgumentException from the uncaught user exception");
+            });
+        }
+
+        [Theory]
+        [InlineData("function () { exceptions_test (); }", null, 0, 0, "exception_uncaught_test", "RangeError", "exception uncaught")]
+        [InlineData("function () { invoke_static_method ('[debugger-test] DebuggerTests.ExceptionTestsClass:TestExceptions'); }",
+            "dotnet://debugger-test.dll/debugger-exception-test.cs", 28, 16, "run",
+            "DebuggerTests.CustomException", "not implemented uncaught")]
+        public async Task ExceptionTestUncaught(string eval_fn, string loc, int line, int col, string fn_name,
+            string exception_type, string exception_message)
+        {
+            var insp = new Inspector();
+            //Collect events
+            var scripts = SubscribeToScripts(insp);
+            await Ready();
+            await insp.Ready(async(cli, token) =>
+            {
+                ctx = new DebugTestContext(cli, insp, token, scripts);
+
+                await SetPauseOnException("uncaught");
+
+                var eval_expr = $"window.setTimeout({eval_fn}, 1);";
+                var pause_location = await EvaluateAndCheck(eval_expr, loc, line, col, fn_name);
+
+                Assert.Equal("exception", pause_location["reason"]);
+                await CheckValue(pause_location["data"], JObject.FromObject(new
+                {
+                    type = "object",
+                    subtype = "error",
+                    className = exception_type,
+                    uncaught = true
+                }), "exception.data");
+
+                var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value<string>());
+                CheckString(exception_members, "message", exception_message);
+            });
+        }
+
+        async Task<JObject> WaitForManagedException(JObject pause_location)
+        {
+            while (true)
+            {
+                if (pause_location != null)
+                {
+                    AssertEqual("exception", pause_location ["reason"]?.Value<string> (), $"Expected to only pause because of an exception. {pause_location}");
+
+                    // return in case of a managed exception, and ignore JS ones
+                    if (pause_location["data"]?["objectId"]?.Value<string> ()?.StartsWith("dotnet:object:") == true)
+                    {
+                        break;
+                    }
+                }
+
+                pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume",  null, 0, 0, null);
+            }
+
+            return pause_location;
+        }
+    }
+}
index b038d2c..cac5e73 100644 (file)
@@ -869,6 +869,12 @@ namespace DebuggerTests
             return bp1_res;
         }
 
+        internal async Task<Result> SetPauseOnException(string state)
+        {
+            var exc_res = await ctx.cli.SendCommand("Debugger.setPauseOnExceptions", JObject.FromObject(new { state = state }), ctx.token);
+            return exc_res;
+        }
+
         internal async Task<Result> SetBreakpointInMethod(string assembly, string type, string method, int lineOffset = 0, int col = 0)
         {
             var req = JObject.FromObject(new { assemblyName = assembly, typeName = type, methodName = method, lineOffset = lineOffset });
diff --git a/src/mono/wasm/debugger/tests/debugger-exception-test.cs b/src/mono/wasm/debugger/tests/debugger-exception-test.cs
new file mode 100644 (file)
index 0000000..3eb2535
--- /dev/null
@@ -0,0 +1,55 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Threading.Tasks;
+namespace DebuggerTests
+{
+    public class ExceptionTestsClass
+    {
+        public class TestCaughtException
+        {
+            public void run() 
+            {
+                try
+                {
+                    throw new CustomException ("not implemented caught");
+                }
+                catch
+                {
+                    Console.WriteLine("caught exception");
+                }
+            }
+        }
+
+        public class TestUncaughtException
+        {
+            public void run()
+            {
+                throw new CustomException ("not implemented uncaught");
+            }
+        }
+
+        public static void TestExceptions()
+        {
+            TestCaughtException f = new TestCaughtException();
+            f.run();
+
+            TestUncaughtException g = new TestUncaughtException();
+            g.run();
+        }
+
+    }
+
+    public class CustomException : Exception
+    {
+        // Using this name to match with what js has.
+        // helps with the tests
+        public string message;
+        public CustomException (string message)
+            : base (message)
+        {
+            this.message = message;
+        }
+    }
+}
\ No newline at end of file
index 7ba8e66..a1b121e 100644 (file)
@@ -31,3 +31,22 @@ function getters_js_test () {
        console.log (`break here`);
        return ptd;
 }
+
+function exception_caught_test () {
+       try {
+               throw new TypeError ('exception caught');
+       } catch (e) {
+               console.log(e);
+       }
+}
+
+function exception_uncaught_test () {
+       console.log('uncaught test');
+       throw new RangeError ('exception uncaught');
+}
+
+function exceptions_test () {
+       exception_caught_test ();
+       exception_uncaught_test ();
+}
+
index 0692c65..5e1a177 100644 (file)
@@ -1588,6 +1588,16 @@ var MonoSupportLib = {
                console.log ("mono_wasm_fire_bp");
                debugger;
        },
+
+       mono_wasm_fire_exception: function (exception_id, message, class_name, uncaught) {
+               MONO.active_exception = {
+                       exception_id: exception_id,
+                       message     : Module.UTF8ToString (message),
+                       class_name  : Module.UTF8ToString (class_name),
+                       uncaught    : uncaught
+               };
+               debugger;
+       },
 };
 
 autoAddDeps(MonoSupportLib, '$MONO')