From 94015cd98a5128930dd20f7fea94da2ae30268c6 Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Fri, 12 Aug 2022 23:38:46 -0300 Subject: [PATCH] [wasm][debugger] Breakpoint set in a invalid line, set it to the next available line (#73189) * Adding more tests, including async cases. Some odd ones are broken at this time Co-authored-by: Ankit Jain --- .../debugger/BrowserDebugProxy/DebugStore.cs | 58 +++++++- .../debugger/BrowserDebugProxy/MonoProxy.cs | 15 +- .../DebuggerTestSuite/SteppingTests.cs | 46 ++++-- .../debugger-test/debugger-async-test.cs | 134 ++++++++++++++++++ .../tests/debugger-test/debugger-test.cs | 18 ++- 5 files changed, 251 insertions(+), 20 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs index fa7bd1142ec..5bc2aaf5644 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs @@ -1596,7 +1596,7 @@ private static bool Match(SequencePoint sp, int line, int column) return true; } - public IEnumerable FindBreakpointLocations(BreakpointRequest request) + public IEnumerable FindBreakpointLocations(BreakpointRequest request, bool ifNoneFoundThenFindNext = false) { request.TryResolve(this); @@ -1606,16 +1606,64 @@ public IEnumerable FindBreakpointLocations(BreakpointRequest req if (sourceFile == null) yield break; - foreach (MethodInfo method in sourceFile.Methods) + List methodList = FindMethodsContainingLine(sourceFile, request.Line); + if (methodList.Count == 0) + yield break; + + List locations = new List(); + foreach (var method in methodList) + { + foreach (SequencePoint sequencePoint in method.DebugInformation.GetSequencePoints()) + { + if (!sequencePoint.IsHidden && + Match(sequencePoint, request.Line, request.Column) && + sequencePoint.StartLine - 1 == request.Line && + (request.Column == 0 || sequencePoint.StartColumn - 1 == request.Column)) + { + // Found an exact match + locations.Add(new SourceLocation(method, sequencePoint)); + } + } + } + if (locations.Count == 0 && ifNoneFoundThenFindNext) { - if (!method.DebugInformation.SequencePointsBlob.IsNil) + (MethodInfo method, SequencePoint seqPoint)? closest = null; + foreach (var method in methodList) { foreach (SequencePoint sequencePoint in method.DebugInformation.GetSequencePoints()) { - if (!sequencePoint.IsHidden && Match(sequencePoint, request.Line, request.Column)) - yield return new SourceLocation(method, sequencePoint); + if (!sequencePoint.IsHidden && + sequencePoint.StartLine > request.Line && + (closest is null || closest.Value.seqPoint.StartLine > sequencePoint.StartLine)) + { + // sequence points in a method are ordered, + // and we found the one right after request.Line + closest = (method, sequencePoint); + // .. and now we can look for it in other methods + break; + } } } + + if (closest is not null) + locations.Add(new SourceLocation(closest.Value.method, closest.Value.seqPoint)); + } + + foreach (SourceLocation loc in locations) + yield return loc; + + static List FindMethodsContainingLine(SourceFile sourceFile, int line) + { + List ret = new(); + foreach (MethodInfo method in sourceFile.Methods) + { + if (method.DebugInformation.SequencePointsBlob.IsNil) + continue; + if (!(method.StartLocation.Line <= line && line <= method.EndLocation.Line)) + continue; + ret.Add(method); + } + return ret; } } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index f94d666441d..2c3acb5fa2f 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -1553,18 +1553,22 @@ protected async Task RuntimeReady(SessionId sessionId, CancellationT return store; } - private static IEnumerable> GetBPReqLocations(DebugStore store, BreakpointRequest req) + private static IEnumerable> GetBPReqLocations(DebugStore store, BreakpointRequest req, bool ifNoneFoundThenFindNext = false) { var comparer = new SourceLocation.LocationComparer(); // if column is specified the frontend wants the exact matches // and will clear the bp if it isn't close enoug - IEnumerable> locations = store.FindBreakpointLocations(req) - .Distinct(comparer) - .Where(l => l.Line == req.Line && (req.Column == 0 || l.Column == req.Column)) + var bpLocations = store.FindBreakpointLocations(req, ifNoneFoundThenFindNext); + IEnumerable> locations = bpLocations.Distinct(comparer) .OrderBy(l => l.Column) .GroupBy(l => l.Id); + if (ifNoneFoundThenFindNext && !locations.Any()) + { + locations = bpLocations.GroupBy(l => l.Id); + } return locations; } + private async Task ResetBreakpoint(SessionId msg_id, DebugStore store, MethodInfo method, CancellationToken token) { ExecutionContext context = GetContext(msg_id); @@ -1622,12 +1626,11 @@ protected async Task SetBreakpoint(SessionId sessionId, DebugStore store, Breakp return; } - var locations = GetBPReqLocations(store, req); + var locations = GetBPReqLocations(store, req, true); logger.LogDebug("BP request for '{Req}' runtime ready {Context.RuntimeReady}", req, context.IsRuntimeReady); var breakpoints = new List(); - foreach (IGrouping sourceId in locations) { SourceLocation loc = sourceId.First(); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/SteppingTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/SteppingTests.cs index a8861bf154f..77a967f1f96 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/SteppingTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/SteppingTests.cs @@ -839,7 +839,7 @@ public async Task StepOverHiddenLinesShouldResumeAtNextAvailableLineInTheMethod( } [ConditionalFact(nameof(RunningOnChrome))] - async Task StepOverHiddenLinesInMethodWithNoNextAvailableLineShouldResumeAtCallSite() + public async Task StepOverHiddenLinesInMethodWithNoNextAvailableLineShouldResumeAtCallSite() { string source_loc = "dotnet://debugger-test.dll/debugger-test.cs"; await SetBreakpoint(source_loc, 552, 8); @@ -852,15 +852,45 @@ async Task StepOverHiddenLinesInMethodWithNoNextAvailableLineShouldResumeAtCallS await StepAndCheck(StepKind.Over, source_loc, 544, 4, "HiddenSequencePointTest.StepOverHiddenSP"); } - // [ConditionalFact(nameof(RunningOnChrome))] - // Issue: https://github.com/dotnet/runtime/issues/42704 - async Task BreakpointOnHiddenLineShouldStopAtEarliestNextAvailableLine() + [ConditionalTheory(nameof(RunningOnChrome))] + [InlineData(539, 8, 542, 8, "StepOverHiddenSP", "HiddenSequencePointTest.StepOverHiddenSP")] + [InlineData(1272, 8, 1266, 8, "StepOverHiddenSP3", "HiddenSequencePointTest.StepOverHiddenSP3")] + public async Task BreakpointOnHiddenLineShouldStopAtEarliestNextAvailableLine(int line_bp, int column_bp, int line_pause, int column_pause, string method_to_call, string method_name) { - await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 539, 8); + Console.WriteLine(await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", line_bp, column_bp)); await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method ('[debugger-test] HiddenSequencePointTest:StepOverHiddenSP'); }, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 546, 4, - "StepOverHiddenSP2"); + "window.setTimeout(function() { invoke_static_method ('[debugger-test] HiddenSequencePointTest:" + method_to_call + "'); }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", line_pause, column_pause, + method_name); + } + + // [ConditionalTheory(nameof(RunningOnChrome))] + //[ActiveIssue("https://github.com/dotnet/runtime/issues/73867")] + [InlineData(184, 20, 161, 8, "HiddenLinesContainingStartOfAnAsyncBlock")] + [InlineData(206, 20, 201, 8, "HiddenLinesAtTheEndOfANestedAsyncBlockWithWithLineDefaultOutsideTheMethod")] + [InlineData(224, 20, 220, 8, "HiddenLinesAtTheEndOfANestedAsyncBlockWithWithLineDefaultOutsideTheMethod2")] + public async Task BreakpointOnHiddenLineShouldStopAtEarliestNextAvailableLineAsync_PauseEarlier(int line_bp, int column_bp, int line_pause, int column_pause, string method_name) + { + await SetBreakpoint("dotnet://debugger-test.dll/debugger-async-test.cs", line_bp, column_bp); + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method('[debugger-test] DebuggerTests.AsyncTests.ContinueWithTests:RunAsyncWithLineHidden'); }, 1);", + "dotnet://debugger-test.dll/debugger-async-test.cs", line_pause, column_pause, + $"DebuggerTests.AsyncTests.ContinueWithTests.{method_name}"); + } + + [ConditionalTheory(nameof(RunningOnChrome))] + [InlineData(112, 16, 114, 16, "HiddenLinesInAnAsyncBlock")] + [InlineData(130, 16, 133, 16, "HiddenLinesJustBeforeANestedAsyncBlock")] + [InlineData(153, 20, 155, 16, "HiddenLinesAtTheEndOfANestedAsyncBlockWithNoLinesAtEndOfTheMethod.AnonymousMethod__1")] + [InlineData(154, 20, 155, 16, "HiddenLinesAtTheEndOfANestedAsyncBlockWithNoLinesAtEndOfTheMethod.AnonymousMethod__1")] + [InlineData(170, 20, 172, 16, "HiddenLinesAtTheEndOfANestedAsyncBlockWithBreakableLineAtEndOfTheMethod.AnonymousMethod__1")] + public async Task BreakpointOnHiddenLineShouldStopAtEarliestNextAvailableLineAsync(int line_bp, int column_bp, int line_pause, int column_pause, string method_name) + { + await SetBreakpoint("dotnet://debugger-test.dll/debugger-async-test.cs", line_bp, column_bp); + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method('[debugger-test] DebuggerTests.AsyncTests.ContinueWithTests:RunAsyncWithLineHidden'); }, 1);", + "dotnet://debugger-test.dll/debugger-async-test.cs", line_pause, column_pause, + $"DebuggerTests.AsyncTests.ContinueWithTests.{method_name}"); } [ConditionalFact(nameof(RunningOnChrome))] diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-async-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-async-test.cs index 087e56201c2..da30a313296 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-async-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-async-test.cs @@ -94,6 +94,140 @@ public async Task NestedContinueWithInstanceAsync(string str) Console.WriteLine ($"done with this method"); } + public static async Task RunAsyncWithLineHidden() + { + await HiddenLinesInAnAsyncBlock("foobar"); + await HiddenLinesJustBeforeANestedAsyncBlock("foobar"); + await HiddenLinesAtTheEndOfANestedAsyncBlockWithNoLinesAtEndOfTheMethod("foobar"); + await HiddenLinesAtTheEndOfANestedAsyncBlockWithBreakableLineAtEndOfTheMethod("foobar"); + await HiddenLinesContainingStartOfAnAsyncBlock("foobar"); + await HiddenLinesAtTheEndOfANestedAsyncBlockWithWithLineDefaultOutsideTheMethod("foobar"); + await HiddenLinesAtTheEndOfANestedAsyncBlockWithWithLineDefaultOutsideTheMethod2("foobar"); + System.Diagnostics.Debugger.Break(); + } + public static async Task HiddenLinesInAnAsyncBlock(string str) + { + await Task.Delay(500).ContinueWith(async t => + { +#line hidden + var code = t.Status; +#line default + var ncs_dt0 = new DateTime(3412, 4, 6, 8, 0, 2); + Console.WriteLine ($"First continueWith: {code}, {ncs_dt0}"); // t, code, str, dt0 + await Task.Delay(300).ContinueWith(t2 => + { + var ncs_dt1 = new DateTime(4513, 4, 5, 6, 7, 8); + Console.WriteLine ($"t2: {t2.Status}, str: {str}, {ncs_dt1}, {ncs_dt0}");//t2, dt1, str, dt0 + }); + }); + Console.WriteLine ($"done with this method"); + } + static async Task HiddenLinesJustBeforeANestedAsyncBlock(string str) + { + await Task.Delay(500).ContinueWith(async t => + { + Console.WriteLine($"First continueWith"); + #line hidden + var code = t.Status; // Next line will be in the next async block? hidden line just before async block + Console.WriteLine("another line of code"); + #line default + await Task.Delay(300).ContinueWith(t2 => + { + var ncs_dt1 = new DateTime(4513, 4, 5, 6, 7, 8); + Console.WriteLine($"t2: {t2.Status}, str: {str}, {ncs_dt1}");//t2, dt1, str, dt0 + }); + }); + Console.WriteLine($"done with this method"); + } + + static async Task HiddenLinesAtTheEndOfANestedAsyncBlockWithNoLinesAtEndOfTheMethod(string str) + { + await Task.Delay(500).ContinueWith(async t => + { + var code = t.Status; + Console.WriteLine($"First continueWith"); + await Task.Delay(300).ContinueWith(t2 => + { + var ncs_dt1 = new DateTime(4513, 4, 5, 6, 7, 8); + Console.WriteLine($"t2: {t2.Status}, str: {str}, {ncs_dt1}");//t2, dt1, str, dt0 + #line hidden + Console.WriteLine("something else"); // Next line will be in the next async block? hidden line at end of async block + #line default + }); + }); + } + + static async Task HiddenLinesAtTheEndOfANestedAsyncBlockWithBreakableLineAtEndOfTheMethod(string str) + { + await Task.Delay(500).ContinueWith(async t => + { + var code = t.Status; + Console.WriteLine($"First continueWith"); + await Task.Delay(300).ContinueWith(t2 => + { + var ncs_dt1 = new DateTime(4513, 4, 5, 6, 7, 8); + Console.WriteLine($"t2: {t2.Status}, str: {str}, {ncs_dt1}");//t2, dt1, str, dt0 + #line hidden + Console.WriteLine("something else"); // Next line will be in the next async block? hidden line at end of async block + #line default + }); + }); + Console.WriteLine ($"Last line.."); + } + + static async Task HiddenLinesContainingStartOfAnAsyncBlock(string str) + { + await Task.Delay(500).ContinueWith(async t => + { + var code = t.Status; + Console.WriteLine($"First continueWith"); + #line hidden + await Task.Delay(300).ContinueWith(t2 => + #line default + { + var ncs_dt1 = new DateTime(4513, 4, 5, 6, 7, 8); + Console.WriteLine($"t2: {t2.Status}, str: {str}, {ncs_dt1}");//t2, dt1, str, dt0 + Console.WriteLine("something else"); // Next line will be in the next async block? hidden line at end of async block + }); + }); + Console.WriteLine($"done with this method"); + } + + static async Task HiddenLinesAtTheEndOfANestedAsyncBlockWithWithLineDefaultOutsideTheMethod(string str) + { + await Task.Delay(500).ContinueWith(async t => + { + var code = t.Status; + Console.WriteLine($"First continueWith"); + await Task.Delay(300).ContinueWith(t2 => + { + var ncs_dt1 = new DateTime(4513, 4, 5, 6, 7, 8); + Console.WriteLine($"t2: {t2.Status}, str: {str}, {ncs_dt1}");//t2, dt1, str, dt0 + #line hidden + Console.WriteLine("somethind else"); // Next line will be in the next async block? hidden line at end of async block + }); + }); + #line default + Console.WriteLine($"done with this method"); + } + + static async Task HiddenLinesAtTheEndOfANestedAsyncBlockWithWithLineDefaultOutsideTheMethod2(string str) + { + await Task.Delay(500).ContinueWith(async t => + { + var code = t.Status; + Console.WriteLine($"First continueWith"); + await Task.Delay(300).ContinueWith(t2 => + { + var ncs_dt1 = new DateTime(4513, 4, 5, 6, 7, 8); + Console.WriteLine($"t2: {t2.Status}, str: {str}, {ncs_dt1}");//t2, dt1, str, dt0 + #line hidden + Console.WriteLine("somethind else"); // Next line will be in the next async block? hidden line at end of async block + }); + #line default + }); + Console.WriteLine($"done with this method"); + } } } diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs index fc9902754a7..6424ca3d5cd 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs @@ -532,7 +532,7 @@ public static void LoadLazyAssembly(string asm_base64, string pdb_base64) } } -public class HiddenSequencePointTest { +public partial class HiddenSequencePointTest { public static void StepOverHiddenSP() { Console.WriteLine("first line"); @@ -1259,3 +1259,19 @@ public static void Run() System.Diagnostics.Debugger.Break(); } } + +public partial class HiddenSequencePointTest { + public static void StepOverHiddenSP3() + { + MethodWithHiddenLinesAtTheEnd3(); + System.Diagnostics.Debugger.Break(); + } + public static void MethodWithHiddenLinesAtTheEnd3() + { + Console.WriteLine ($"MethodWithHiddenLinesAtTheEnd"); +#line hidden + Console.WriteLine ($"debugger shouldn't be able to step here"); + } +#line default +} + -- GitLab