-
Notifications
You must be signed in to change notification settings - Fork 5.2k
[wasm][debugger] Fixing async locals in nested ContinueWith blocks #56911
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c74da92
553b282
b23b8f1
34bfac4
3040835
24f0d57
57d9f61
c90737b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -623,6 +623,7 @@ internal class MonoSDBHelper | |
| private static int MINOR_VERSION = 61; | ||
| private static int MAJOR_VERSION = 2; | ||
| private readonly ILogger logger; | ||
| private Regex regexForAsyncLocals = new Regex(@"\<([^)]*)\>", RegexOptions.Singleline); | ||
|
|
||
| public MonoSDBHelper(MonoProxy proxy, ILogger logger) | ||
| { | ||
|
|
@@ -1710,6 +1711,62 @@ public async Task<bool> IsAsyncMethod(SessionId sessionId, int methodId, Cancell | |
| return retDebuggerCmdReader.ReadByte() == 1 ; //token | ||
| } | ||
|
|
||
| private bool IsClosureReferenceField (string fieldName) | ||
| { | ||
| // mcs is "$locvar" | ||
| // old mcs is "<>f__ref" | ||
| // csc is "CS$<>" | ||
| // roslyn is "<>8__" | ||
| return fieldName.StartsWith ("CS$<>", StringComparison.Ordinal) || | ||
| fieldName.StartsWith ("<>f__ref", StringComparison.Ordinal) || | ||
| fieldName.StartsWith ("$locvar", StringComparison.Ordinal) || | ||
| fieldName.StartsWith ("<>8__", StringComparison.Ordinal); | ||
| } | ||
|
|
||
| public async Task<JArray> GetHoistedLocalVariables(SessionId sessionId, int objectId, JArray asyncLocals, CancellationToken token) | ||
| { | ||
| JArray asyncLocalsFull = new JArray(); | ||
| List<int> objectsAlreadyRead = new(); | ||
| objectsAlreadyRead.Add(objectId); | ||
| foreach (var asyncLocal in asyncLocals) | ||
| { | ||
| var fieldName = asyncLocal["name"].Value<string>(); | ||
| if (fieldName.EndsWith("__this", StringComparison.Ordinal)) | ||
| { | ||
| asyncLocal["name"] = "this"; | ||
| asyncLocalsFull.Add(asyncLocal); | ||
| } | ||
| else if (IsClosureReferenceField(fieldName)) //same code that has on debugger-libs | ||
| { | ||
| if (DotnetObjectId.TryParse(asyncLocal?["value"]?["objectId"]?.Value<string>(), out DotnetObjectId dotnetObjectId)) | ||
| { | ||
| if (int.TryParse(dotnetObjectId.Value, out int objectIdToGetInfo) && !objectsAlreadyRead.Contains(objectIdToGetInfo)) | ||
| { | ||
| var asyncLocalsFromObject = await GetObjectValues(sessionId, objectIdToGetInfo, true, false, false, false, token); | ||
| var hoistedLocalVariable = await GetHoistedLocalVariables(sessionId, objectIdToGetInfo, asyncLocalsFromObject, token); | ||
| asyncLocalsFull = new JArray(asyncLocalsFull.Union(hoistedLocalVariable)); | ||
| } | ||
| } | ||
| } | ||
| else if (fieldName.StartsWith("<>", StringComparison.Ordinal)) //examples: <>t__builder, <>1__state | ||
| { | ||
| continue; | ||
| } | ||
| else if (fieldName.StartsWith('<')) //examples: <code>5__2 | ||
| { | ||
| var match = regexForAsyncLocals.Match(fieldName); | ||
| if (match.Success) | ||
| asyncLocal["name"] = match.Groups[1].Value; | ||
| asyncLocalsFull.Add(asyncLocal); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would this still be valid without a name? If not, then we shouldn't add it to |
||
| } | ||
| else | ||
| { | ||
| asyncLocalsFull.Add(asyncLocal); | ||
| } | ||
| } | ||
| return asyncLocalsFull; | ||
| } | ||
|
|
||
| public async Task<JArray> StackFrameGetValues(SessionId sessionId, MethodInfo method, int thread_id, int frame_id, VarInfo[] varIds, CancellationToken token) | ||
| { | ||
| var commandParams = new MemoryStream(); | ||
|
|
@@ -1729,14 +1786,7 @@ public async Task<JArray> StackFrameGetValues(SessionId sessionId, MethodInfo me | |
| retDebuggerCmdReader.ReadByte(); //ignore type | ||
| var objectId = retDebuggerCmdReader.ReadInt32(); | ||
| var asyncLocals = await GetObjectValues(sessionId, objectId, true, false, false, false, token); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just curious, what does this return? reference to the generated class? |
||
| asyncLocals = new JArray(asyncLocals.Where( asyncLocal => !asyncLocal["name"].Value<string>().Contains("<>") || asyncLocal["name"].Value<string>().EndsWith("__this"))); | ||
| foreach (var asyncLocal in asyncLocals) | ||
| { | ||
| if (asyncLocal["name"].Value<string>().EndsWith("__this")) | ||
| asyncLocal["name"] = "this"; | ||
| else if (asyncLocal["name"].Value<string>().Contains('<')) | ||
| asyncLocal["name"] = Regex.Match(asyncLocal["name"].Value<string>(), @"\<([^)]*)\>").Groups[1].Value; | ||
| } | ||
| asyncLocals = await GetHoistedLocalVariables(sessionId, objectId, asyncLocals, token); | ||
| return asyncLocals; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| // 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 Microsoft.WebAssembly.Diagnostics; | ||
| using Newtonsoft.Json.Linq; | ||
| using Xunit; | ||
|
|
||
| namespace DebuggerTests | ||
| { | ||
| public class AsyncTests : DebuggerTestBase | ||
| { | ||
|
|
||
| // FIXME: method with multiple async blocks - so that we have two separate classes for that method! | ||
| // FIXME: nested blocks | ||
| // FIXME: Confirm the actual bp location | ||
| // FIXME: check object properties.. | ||
|
|
||
| //FIXME: function name | ||
| [Theory] | ||
| [InlineData("ContinueWithStaticAsync", "<ContinueWithStaticAsync>b__3_0")] | ||
| [InlineData("ContinueWithInstanceAsync", "<ContinueWithInstanceAsync>b__5_0")] | ||
| public async Task AsyncLocalsInContinueWith(string method_name, string expected_method_name) => await CheckInspectLocalsAtBreakpointSite( | ||
| "DebuggerTests.AsyncTests.ContinueWithTests", method_name, 5, expected_method_name, | ||
| "window.setTimeout(function() { invoke_static_method('[debugger-test] DebuggerTests.AsyncTests.ContinueWithTests:RunAsync'); })", | ||
| wait_for_event_fn: async (pause_location) => | ||
| { | ||
| var frame_locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>()); | ||
| await CheckProps(frame_locals, new | ||
| { | ||
| t = TObject("System.Threading.Tasks.Task.DelayPromise"), | ||
| code = TEnum("System.Threading.Tasks.TaskStatus", "RanToCompletion"), | ||
| @this = TObject("DebuggerTests.AsyncTests.ContinueWithTests.<>c"), | ||
| dt = TDateTime(new DateTime(4513, 4, 5, 6, 7, 8)) | ||
| }, "locals"); | ||
|
|
||
| var res = await InvokeGetter(GetAndAssertObjectWithName(frame_locals, "t"), "Status"); | ||
| await CheckValue(res.Value["result"], TEnum("System.Threading.Tasks.TaskStatus", "RanToCompletion"), "t.Status"); | ||
| }); | ||
|
|
||
| [Fact] | ||
| public async Task AsyncLocalsInContinueWithInstanceUsingThisBlock() => await CheckInspectLocalsAtBreakpointSite( | ||
| "DebuggerTests.AsyncTests.ContinueWithTests", "ContinueWithInstanceUsingThisAsync", 5, "<ContinueWithInstanceUsingThisAsync>b__6_0", | ||
| "window.setTimeout(function() { invoke_static_method('[debugger-test] DebuggerTests.AsyncTests.ContinueWithTests:RunAsync'); })", | ||
| wait_for_event_fn: async (pause_location) => | ||
| { | ||
| var frame_locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>()); | ||
| await CheckProps(frame_locals, new | ||
| { | ||
| t = TObject("System.Threading.Tasks.Task.DelayPromise"), | ||
| code = TEnum("System.Threading.Tasks.TaskStatus", "RanToCompletion"), | ||
| dt = TDateTime(new DateTime(4513, 4, 5, 6, 7, 8)), | ||
| @this = TObject("DebuggerTests.AsyncTests.ContinueWithTests") | ||
| }, "locals"); | ||
|
|
||
| var res = await InvokeGetter(GetAndAssertObjectWithName(frame_locals, "t"), "Status"); | ||
| await CheckValue(res.Value["result"], TEnum("System.Threading.Tasks.TaskStatus", "RanToCompletion"), "t.Status"); | ||
|
|
||
| res = await InvokeGetter(GetAndAssertObjectWithName(frame_locals, "this"), "Date"); | ||
| await CheckValue(res.Value["result"], TDateTime(new DateTime(2510, 1, 2, 3, 4, 5)), "this.Date"); | ||
| }); | ||
|
|
||
| [Fact] // NestedContinueWith | ||
| public async Task AsyncLocalsInNestedContinueWithStaticBlock() => await CheckInspectLocalsAtBreakpointSite( | ||
| "DebuggerTests.AsyncTests.ContinueWithTests", "NestedContinueWithStaticAsync", 5, "MoveNext", | ||
| "window.setTimeout(function() { invoke_static_method('[debugger-test] DebuggerTests.AsyncTests.ContinueWithTests:RunAsync'); })", | ||
| wait_for_event_fn: async (pause_location) => | ||
| { | ||
| var frame_locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>()); | ||
| await CheckProps(frame_locals, new | ||
| { | ||
| t = TObject("System.Threading.Tasks.Task.DelayPromise"), | ||
| code = TEnum("System.Threading.Tasks.TaskStatus", "RanToCompletion"), | ||
| str = TString("foobar"), | ||
| @this = TObject("DebuggerTests.AsyncTests.ContinueWithTests.<>c__DisplayClass4_0"), | ||
| ncs_dt0 = TDateTime(new DateTime(3412, 4, 6, 8, 0, 2)) | ||
| }, "locals"); | ||
| }); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| // 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; | ||
| using System.Runtime.CompilerServices; | ||
|
|
||
| namespace DebuggerTests.AsyncTests | ||
| { | ||
| public class ContinueWithTests | ||
| { | ||
| public DateTime Date => new DateTime(2510, 1, 2, 3, 4, 5); | ||
|
|
||
| public static async Task RunAsync() | ||
| { | ||
| await ContinueWithStaticAsync("foobar"); | ||
| await new ContinueWithTests().ContinueWithInstanceAsync("foobar"); | ||
|
|
||
| await NestedContinueWithStaticAsync("foobar"); | ||
| await new ContinueWithTests().NestedContinueWithInstanceAsync("foobar"); | ||
| await new ContinueWithTests().ContinueWithInstanceUsingThisAsync("foobar"); | ||
|
|
||
| } | ||
|
|
||
| public static async Task ContinueWithStaticAsync(string str) | ||
| { | ||
| await Task.Delay(1000).ContinueWith(t => | ||
| { | ||
| var code = t.Status; | ||
| var dt = new DateTime(4513, 4, 5, 6, 7, 8); | ||
| Console.WriteLine ($"First continueWith: {code}, {dt}"); //t, code, dt | ||
| }); | ||
| Console.WriteLine ($"done with this method"); | ||
| } | ||
|
|
||
| public static async Task NestedContinueWithStaticAsync(string str) | ||
| { | ||
| await Task.Delay(500).ContinueWith(async t => | ||
| { | ||
| var code = t.Status; | ||
| 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"); | ||
| } | ||
|
|
||
| public async Task ContinueWithInstanceAsync(string str) | ||
| { | ||
| await Task.Delay(1000).ContinueWith(t => | ||
| { | ||
| var code = t.Status; | ||
| var dt = new DateTime(4513, 4, 5, 6, 7, 8); | ||
| Console.WriteLine ($"First continueWith: {code}, {dt}");// t, code, dt | ||
| }); | ||
| Console.WriteLine ($"done with this method"); | ||
| } | ||
|
|
||
| public async Task ContinueWithInstanceUsingThisAsync(string str) | ||
| { | ||
| await Task.Delay(1000).ContinueWith(t => | ||
| { | ||
| var code = t.Status; | ||
| var dt = new DateTime(4513, 4, 5, 6, 7, 8); | ||
| Console.WriteLine ($"First continueWith: {code}, {dt}, {this.Date}"); | ||
| }); | ||
| Console.WriteLine ($"done with this method"); | ||
| } | ||
|
|
||
| [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] | ||
| public async Task NestedContinueWithInstanceAsync(string str) | ||
| { | ||
| await Task.Delay(500).ContinueWith(async t => | ||
| { | ||
| var code = t.Status; | ||
| var dt0 = new DateTime(3412, 4, 6, 8, 0, 2); | ||
| if (str == "oi") | ||
| { | ||
| dt0 = new DateTime(3415, 4, 6, 8, 0, 2); | ||
| } | ||
| Console.WriteLine ($"First continueWith: {code}, {dt0}, {Date}");//this, t, code, str, dt0 | ||
| await Task.Delay(300).ContinueWith(t2 => | ||
| { | ||
| var dt1 = new DateTime(4513, 4, 5, 6, 7, 8); | ||
| Console.WriteLine ($"t2: {t2.Status}, str: {str}, {dt1}, {dt0}");//this, t2, dt1, str, dt0 | ||
| }); | ||
| }); | ||
| Console.WriteLine ($"done with this method"); | ||
| } | ||
|
|
||
| } | ||
|
|
||
| } |
Uh oh!
There was an error while loading. Please reload this page.