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)
{
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);
+ }
+ 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();
retDebuggerCmdReader.ReadByte(); //ignore type
var objectId = retDebuggerCmdReader.ReadInt32();
var asyncLocals = await GetObjectValues(sessionId, objectId, true, false, false, false, token);
- 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;
}
--- /dev/null
+// 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");
+ });
+ }
+}
(_, res) = await EvaluateOnCallFrame(id, "DebuggerTests.InvalidEvaluateStaticClass.StaticProperty2", expect_ok: false);
AssertEqual("Failed to resolve member access for DebuggerTests.InvalidEvaluateStaticClass.StaticProperty2", res.Error["result"]?["description"]?.Value<string>(), "wrong error message");
});
+
+ [Fact]
+ public async Task AsyncLocalsInContinueWithBlock() => await CheckInspectLocalsAtBreakpointSite(
+ "DebuggerTests.AsyncTests.ContinueWithTests", "ContinueWithStaticAsync", 4, "<ContinueWithStaticAsync>b__3_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>());
+ var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
+
+ await EvaluateOnCallFrameAndCheck(id,
+ ($"t.Status", TEnum("System.Threading.Tasks.TaskStatus", "RanToCompletion")),
+ ($" t.Status", TEnum("System.Threading.Tasks.TaskStatus", "RanToCompletion"))
+ );
+
+ await EvaluateOnCallFrameFail(id,
+ ("str", "ReferenceError"),
+ (" str", "ReferenceError")
+ );
+ });
+
}
}
{
var pause_location = await EvaluateAndCheck(
"window.setTimeout(function() { invoke_static_method('[debugger-test] TestChild:TestWatchWithInheritance'); }, 1);",
- "dotnet://debugger-test.dll/debugger-test2.cs", 125, 8,
+ "dotnet://debugger-test.dll/debugger-test2.cs", 127, 8,
"TestWatchWithInheritance");
var frame_id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
var frame_locals = await GetProperties(frame_id);
public async Task InspectTaskAtLocals() => await CheckInspectLocalsAtBreakpointSite(
"InspectTask",
"RunInspectTask",
- 7,
+ 10,
"<RunInspectTask>b__0" ,
$"window.setTimeout(function() {{ invoke_static_method_async('[debugger-test] InspectTask:RunInspectTask'); }}, 1);",
wait_for_event_fn: async (pause_location) =>
{
var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ CheckNumber(locals, "a", 10);
var t_props = await GetObjectOnLocals(locals, "t");
await CheckProps(t_props, new
--- /dev/null
+// 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");
+ }
+
+ }
+
+}
{
await getJsonTask.ContinueWith(t =>
{
+ int a = 10;
+ Console.WriteLine(a);
if (t.IsCompletedSuccessfully)
forecasts = t.Result;