Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,9 @@ internal class ExecutionContext
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; }
Expand Down
62 changes: 60 additions & 2 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ internal class MonoProxy : DevToolsProxy
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)
{
Expand Down Expand Up @@ -122,8 +124,41 @@ protected override async Task<bool> AcceptEvent(SessionId sessionId, string meth
return true;
}

case "Runtime.exceptionThrown":
{
if (!GetContext(sessionId).IsRuntimeReady)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might need to remove this check if you were trying the on attach version

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Attach is the case when the page is already loaded and then we connect the debugger? I tested it and it's working.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only tested debugging on Chrome, I didn't test with VS or VSCode.

{
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) {
Expand Down Expand Up @@ -398,7 +433,23 @@ protected override async Task<bool> AcceptCommand(MessageId id, string method, J
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;
}
Expand Down Expand Up @@ -1152,6 +1203,11 @@ private async Task<DebugStore> RuntimeReady(SessionId sessionId, CancellationTok
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);

Expand Down Expand Up @@ -1289,10 +1345,12 @@ private async Task AttachToTarget(SessionId sessionId, CancellationToken token)
// 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)
Expand Down
105 changes: 105 additions & 0 deletions src/mono/wasm/debugger/DebuggerTestSuite/ExceptionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Threading.Tasks;
using Microsoft.WebAssembly.Diagnostics;
using Newtonsoft.Json.Linq;
using System.Threading;
using Xunit;

namespace DebuggerTests
Expand Down Expand Up @@ -191,6 +192,110 @@ await CheckValue(pause_location["data"], JObject.FromObject(new
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)
Expand Down