Skip to content

Commit 881e902

Browse files
authored
[wasm][debugger] Detect initial status of pause on exceptions. (#54040)
* 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.
1 parent 75b6c99 commit 881e902

File tree

3 files changed

+168
-2
lines changed

3 files changed

+168
-2
lines changed

src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,9 @@ internal class ExecutionContext
279279
public int Id { get; set; }
280280
public object AuxData { get; set; }
281281

282+
public bool PauseOnUncaught { get; set; }
283+
public bool PauseOnCaught { get; set; }
284+
282285
public List<Frame> CallStack { get; set; }
283286

284287
public string[] LoadedFiles { get; set; }

src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ internal class MonoProxy : DevToolsProxy
2323
private static HttpClient client = new HttpClient();
2424
private HashSet<SessionId> sessions = new HashSet<SessionId>();
2525
private Dictionary<SessionId, ExecutionContext> contexts = new Dictionary<SessionId, ExecutionContext>();
26+
private const string sPauseOnUncaught = "pause_on_uncaught";
27+
private const string sPauseOnCaught = "pause_on_caught";
2628

2729
public MonoProxy(ILoggerFactory loggerFactory, IList<string> urlSymbolServerList) : base(loggerFactory)
2830
{
@@ -122,8 +124,41 @@ protected override async Task<bool> AcceptEvent(SessionId sessionId, string meth
122124
return true;
123125
}
124126

127+
case "Runtime.exceptionThrown":
128+
{
129+
if (!GetContext(sessionId).IsRuntimeReady)
130+
{
131+
string exceptionError = args?["exceptionDetails"]?["exception"]?["value"]?.Value<string>();
132+
if (exceptionError == sPauseOnUncaught || exceptionError == sPauseOnCaught)
133+
{
134+
return true;
135+
}
136+
}
137+
break;
138+
}
139+
125140
case "Debugger.paused":
126141
{
142+
if (!GetContext(sessionId).IsRuntimeReady)
143+
{
144+
string reason = args?["reason"]?.Value<string>();
145+
if (reason == "exception")
146+
{
147+
string exceptionError = args?["data"]?["value"]?.Value<string>();
148+
if (exceptionError == sPauseOnUncaught)
149+
{
150+
await SendCommand(sessionId, "Debugger.resume", new JObject(), token);
151+
GetContext(sessionId).PauseOnUncaught = true;
152+
return true;
153+
}
154+
if (exceptionError == sPauseOnCaught)
155+
{
156+
await SendCommand(sessionId, "Debugger.resume", new JObject(), token);
157+
GetContext(sessionId).PauseOnCaught = true;
158+
return true;
159+
}
160+
}
161+
}
127162
//TODO figure out how to stich out more frames and, in particular what happens when real wasm is on the stack
128163
string top_func = args?["callFrames"]?[0]?["functionName"]?.Value<string>();
129164
switch (top_func) {
@@ -398,7 +433,23 @@ protected override async Task<bool> AcceptCommand(MessageId id, string method, J
398433
case "Debugger.setPauseOnExceptions":
399434
{
400435
string state = args["state"].Value<string>();
401-
await sdbHelper.EnableExceptions(id, state, token);
436+
if (!context.IsRuntimeReady)
437+
{
438+
context.PauseOnCaught = false;
439+
context.PauseOnUncaught = false;
440+
switch (state)
441+
{
442+
case "all":
443+
context.PauseOnCaught = true;
444+
context.PauseOnUncaught = true;
445+
break;
446+
case "uncaught":
447+
context.PauseOnUncaught = true;
448+
break;
449+
}
450+
}
451+
else
452+
await sdbHelper.EnableExceptions(id, state, token);
402453
// Pass this on to JS too
403454
return false;
404455
}
@@ -1152,6 +1203,11 @@ private async Task<DebugStore> RuntimeReady(SessionId sessionId, CancellationTok
11521203
Log("verbose", $"Failed to clear breakpoints");
11531204
}
11541205

1206+
if (context.PauseOnCaught && context.PauseOnUncaught)
1207+
await sdbHelper.EnableExceptions(sessionId, "all", token);
1208+
else if (context.PauseOnUncaught)
1209+
await sdbHelper.EnableExceptions(sessionId, "uncaught", token);
1210+
11551211
await sdbHelper.SetProtocolVersion(sessionId, token);
11561212
await sdbHelper.EnableReceiveUserBreakRequest(sessionId, token);
11571213

@@ -1289,10 +1345,12 @@ private async Task AttachToTarget(SessionId sessionId, CancellationToken token)
12891345
// see https://github.com/mono/mono/issues/19549 for background
12901346
if (sessions.Add(sessionId))
12911347
{
1348+
string checkUncaughtExceptions = $"throw \"{sPauseOnUncaught}\";";
1349+
string checkCaughtExceptions = $"try {{throw \"{sPauseOnCaught}\";}} catch {{}}";
12921350
await SendMonoCommand(sessionId, new MonoCommands("globalThis.dotnetDebugger = true"), token);
12931351
Result res = await SendCommand(sessionId,
12941352
"Page.addScriptToEvaluateOnNewDocument",
1295-
JObject.FromObject(new { source = "globalThis.dotnetDebugger = true; delete navigator.constructor.prototype.webdriver" }),
1353+
JObject.FromObject(new { source = $"globalThis.dotnetDebugger = true; delete navigator.constructor.prototype.webdriver; {checkCaughtExceptions} {checkUncaughtExceptions}" }),
12961354
token);
12971355

12981356
if (sessionId != SessionId.Null && !res.IsOk)

src/mono/wasm/debugger/DebuggerTestSuite/ExceptionTests.cs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Threading.Tasks;
77
using Microsoft.WebAssembly.Diagnostics;
88
using Newtonsoft.Json.Linq;
9+
using System.Threading;
910
using Xunit;
1011

1112
namespace DebuggerTests
@@ -191,6 +192,110 @@ await CheckValue(pause_location["data"], JObject.FromObject(new
191192
CheckString(exception_members, "message", exception_message);
192193
}
193194

195+
[Fact]
196+
public async Task ExceptionTestUncaughtWithReload()
197+
{
198+
string entry_method_name = "[debugger-test] DebuggerTests.ExceptionTestsClass:TestExceptions";
199+
var debugger_test_loc = "dotnet://debugger-test.dll/debugger-exception-test.cs";
200+
201+
await SetPauseOnException("uncaught");
202+
203+
await SendCommand("Page.enable", null);
204+
await SendCommand("Page.reload", JObject.FromObject(new
205+
{
206+
ignoreCache = true
207+
}));
208+
Thread.Sleep(1000);
209+
210+
var eval_expr = "window.setTimeout(function() { invoke_static_method (" +
211+
$"'{entry_method_name}'" +
212+
"); }, 1);";
213+
214+
var pause_location = await EvaluateAndCheck(eval_expr, null, 0, 0, null);
215+
//stop in the managed caught exception
216+
pause_location = await WaitForManagedException(pause_location);
217+
218+
AssertEqual("run", pause_location["callFrames"]?[0]?["functionName"]?.Value<string>(), "pause1");
219+
220+
//stop in the uncaught exception
221+
CheckLocation(debugger_test_loc, 28, 16, scripts, pause_location["callFrames"][0]["location"]);
222+
223+
await CheckValue(pause_location["data"], JObject.FromObject(new
224+
{
225+
type = "object",
226+
subtype = "error",
227+
className = "DebuggerTests.CustomException",
228+
uncaught = true
229+
}), "exception1.data");
230+
231+
var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value<string>());
232+
CheckString(exception_members, "message", "not implemented uncaught");
233+
}
234+
235+
[Fact]
236+
public async Task ExceptionTestAllWithReload()
237+
{
238+
string entry_method_name = "[debugger-test] DebuggerTests.ExceptionTestsClass:TestExceptions";
239+
var debugger_test_loc = "dotnet://debugger-test.dll/debugger-exception-test.cs";
240+
241+
await SetPauseOnException("all");
242+
243+
await SendCommand("Page.enable", null);
244+
var pause_location = await SendCommandAndCheck(JObject.FromObject(new
245+
{
246+
ignoreCache = true
247+
}), "Page.reload",null, 0, 0, null);
248+
Thread.Sleep(1000);
249+
250+
//send a lot of resumes to "skip" all the pauses on caught exception and completely reload the page
251+
int i = 0;
252+
while (i < 100)
253+
{
254+
Result res = await cli.SendCommand("Debugger.resume", null, token);
255+
i++;
256+
}
257+
258+
259+
var eval_expr = "window.setTimeout(function() { invoke_static_method (" +
260+
$"'{entry_method_name}'" +
261+
"); }, 1);";
262+
263+
pause_location = await EvaluateAndCheck(eval_expr, null, 0, 0, null);
264+
//stop in the managed caught exception
265+
pause_location = await WaitForManagedException(pause_location);
266+
267+
AssertEqual("run", pause_location["callFrames"]?[0]?["functionName"]?.Value<string>(), "pause0");
268+
269+
await CheckValue(pause_location["data"], JObject.FromObject(new
270+
{
271+
type = "object",
272+
subtype = "error",
273+
className = "DebuggerTests.CustomException",
274+
uncaught = false
275+
}), "exception0.data");
276+
277+
var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value<string>());
278+
CheckString(exception_members, "message", "not implemented caught");
279+
280+
pause_location = await WaitForManagedException(null);
281+
AssertEqual("run", pause_location["callFrames"]?[0]?["functionName"]?.Value<string>(), "pause1");
282+
283+
//stop in the uncaught exception
284+
CheckLocation(debugger_test_loc, 28, 16, scripts, pause_location["callFrames"][0]["location"]);
285+
286+
await CheckValue(pause_location["data"], JObject.FromObject(new
287+
{
288+
type = "object",
289+
subtype = "error",
290+
className = "DebuggerTests.CustomException",
291+
uncaught = true
292+
}), "exception1.data");
293+
294+
exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value<string>());
295+
CheckString(exception_members, "message", "not implemented uncaught");
296+
}
297+
298+
194299
async Task<JObject> WaitForManagedException(JObject pause_location)
195300
{
196301
while (true)

0 commit comments

Comments
 (0)