[wasm][debugger] Detect initial status of pause on exceptions. (#54040)
authorThays Grazia <thaystg@gmail.com>
Fri, 25 Jun 2021 01:21:57 +0000 (22:21 -0300)
committerGitHub <noreply@github.com>
Fri, 25 Jun 2021 01:21:57 +0000 (22:21 -0300)
* Detect initial status of pause on exceptions.

* Changing what @radical suggested.

* Changing more things.

* Test case created.
I could not test the pause on "all" exceptions because if I enable the pause on caught exceptions and reload the page it will stop in a lot of exceptions other then the one that I inserted in AttachToTarget.

* Adding a test for Reload page with ALL set.

* Fixing merge conflicts.

* setting icordebug = false.

* Removing unrelated change.

src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs
src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
src/mono/wasm/debugger/DebuggerTestSuite/ExceptionTests.cs

index f39f273..6f1ec09 100644 (file)
@@ -279,6 +279,9 @@ namespace Microsoft.WebAssembly.Diagnostics
         public int Id { get; set; }
         public object AuxData { get; set; }
 
+        public bool PauseOnUncaught { get; set; }
+        public bool PauseOnCaught { get; set; }
+
         public List<Frame> CallStack { get; set; }
 
         public string[] LoadedFiles { get; set; }
index 6899047..f45d31c 100644 (file)
@@ -23,6 +23,8 @@ namespace Microsoft.WebAssembly.Diagnostics
         private static HttpClient client = new HttpClient();
         private HashSet<SessionId> sessions = new HashSet<SessionId>();
         private Dictionary<SessionId, ExecutionContext> contexts = new Dictionary<SessionId, ExecutionContext>();
+        private const string sPauseOnUncaught = "pause_on_uncaught";
+        private const string sPauseOnCaught = "pause_on_caught";
 
         public MonoProxy(ILoggerFactory loggerFactory, IList<string> urlSymbolServerList) : base(loggerFactory)
         {
@@ -122,8 +124,41 @@ namespace Microsoft.WebAssembly.Diagnostics
                         return true;
                     }
 
+                case "Runtime.exceptionThrown":
+                    {
+                        if (!GetContext(sessionId).IsRuntimeReady)
+                        {
+                            string exceptionError = args?["exceptionDetails"]?["exception"]?["value"]?.Value<string>();
+                            if (exceptionError == sPauseOnUncaught || exceptionError == sPauseOnCaught)
+                            {
+                                return true;
+                            }
+                        }
+                        break;
+                    }
+
                 case "Debugger.paused":
                     {
+                        if (!GetContext(sessionId).IsRuntimeReady)
+                        {
+                            string reason = args?["reason"]?.Value<string>();
+                            if (reason == "exception")
+                            {
+                                string exceptionError = args?["data"]?["value"]?.Value<string>();
+                                if (exceptionError == sPauseOnUncaught)
+                                {
+                                    await SendCommand(sessionId, "Debugger.resume", new JObject(), token);
+                                    GetContext(sessionId).PauseOnUncaught = true;
+                                    return true;
+                                }
+                                if (exceptionError == sPauseOnCaught)
+                                {
+                                    await SendCommand(sessionId, "Debugger.resume", new JObject(), token);
+                                    GetContext(sessionId).PauseOnCaught = true;
+                                    return true;
+                                }
+                            }
+                        }
                         //TODO figure out how to stich out more frames and, in particular what happens when real wasm is on the stack
                         string top_func = args?["callFrames"]?[0]?["functionName"]?.Value<string>();
                         switch (top_func) {
@@ -398,7 +433,23 @@ namespace Microsoft.WebAssembly.Diagnostics
                 case "Debugger.setPauseOnExceptions":
                     {
                         string state = args["state"].Value<string>();
-                        await sdbHelper.EnableExceptions(id, state, token);
+                        if (!context.IsRuntimeReady)
+                        {
+                            context.PauseOnCaught = false;
+                            context.PauseOnUncaught = false;
+                            switch (state)
+                            {
+                                case "all":
+                                    context.PauseOnCaught = true;
+                                    context.PauseOnUncaught = true;
+                                    break;
+                                case "uncaught":
+                                    context.PauseOnUncaught = true;
+                                    break;
+                            }
+                        }
+                        else
+                            await sdbHelper.EnableExceptions(id, state, token);
                         // Pass this on to JS too
                         return false;
                     }
@@ -1152,6 +1203,11 @@ namespace Microsoft.WebAssembly.Diagnostics
                 Log("verbose", $"Failed to clear breakpoints");
             }
 
+            if (context.PauseOnCaught && context.PauseOnUncaught)
+                await sdbHelper.EnableExceptions(sessionId, "all", token);
+            else if (context.PauseOnUncaught)
+                await sdbHelper.EnableExceptions(sessionId, "uncaught", token);
+
             await sdbHelper.SetProtocolVersion(sessionId, token);
             await sdbHelper.EnableReceiveUserBreakRequest(sessionId, token);
 
@@ -1289,10 +1345,12 @@ namespace Microsoft.WebAssembly.Diagnostics
             // see https://github.com/mono/mono/issues/19549 for background
             if (sessions.Add(sessionId))
             {
+                string checkUncaughtExceptions = $"throw \"{sPauseOnUncaught}\";";
+                string checkCaughtExceptions = $"try {{throw \"{sPauseOnCaught}\";}} catch {{}}";
                 await SendMonoCommand(sessionId, new MonoCommands("globalThis.dotnetDebugger = true"), token);
                 Result res = await SendCommand(sessionId,
                     "Page.addScriptToEvaluateOnNewDocument",
-                    JObject.FromObject(new { source = "globalThis.dotnetDebugger = true; delete navigator.constructor.prototype.webdriver" }),
+                    JObject.FromObject(new { source = $"globalThis.dotnetDebugger = true; delete navigator.constructor.prototype.webdriver; {checkCaughtExceptions} {checkUncaughtExceptions}" }),
                     token);
 
                 if (sessionId != SessionId.Null && !res.IsOk)
index 1be0b18..eb0ebe0 100644 (file)
@@ -6,6 +6,7 @@ using System.Linq;
 using System.Threading.Tasks;
 using Microsoft.WebAssembly.Diagnostics;
 using Newtonsoft.Json.Linq;
+using System.Threading;
 using Xunit;
 
 namespace DebuggerTests
@@ -191,6 +192,110 @@ namespace DebuggerTests
             CheckString(exception_members, "message", exception_message);
         }
 
+        [Fact]
+        public async Task ExceptionTestUncaughtWithReload()
+        {
+            string entry_method_name = "[debugger-test] DebuggerTests.ExceptionTestsClass:TestExceptions";
+            var debugger_test_loc = "dotnet://debugger-test.dll/debugger-exception-test.cs";
+
+            await SetPauseOnException("uncaught");
+
+            await SendCommand("Page.enable", null);
+            await SendCommand("Page.reload", JObject.FromObject(new
+                                    {
+                                        ignoreCache = true
+                                    }));
+            Thread.Sleep(1000);
+
+            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>(), "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");
+
+            var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value<string>());
+            CheckString(exception_members, "message", "not implemented uncaught");
+        }
+
+        [Fact]
+        public async Task ExceptionTestAllWithReload()
+        {
+            string entry_method_name = "[debugger-test] DebuggerTests.ExceptionTestsClass:TestExceptions";
+            var debugger_test_loc = "dotnet://debugger-test.dll/debugger-exception-test.cs";
+
+            await SetPauseOnException("all");
+
+            await SendCommand("Page.enable", null);
+            var pause_location = await SendCommandAndCheck(JObject.FromObject(new
+                                    {
+                                        ignoreCache = true
+                                    }), "Page.reload",null, 0, 0, null);
+            Thread.Sleep(1000);
+
+            //send a lot of resumes to "skip" all the pauses on caught exception and completely reload the page
+            int i = 0;
+            while (i < 100)
+            {
+                Result res = await cli.SendCommand("Debugger.resume", null, token);
+                i++;
+            }
+
+            
+            var eval_expr = "window.setTimeout(function() { invoke_static_method (" +
+                $"'{entry_method_name}'" +
+                "); }, 1);";
+
+            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");
+        }
+
+
         async Task<JObject> WaitForManagedException(JObject pause_location)
         {
             while (true)