Skip to content
Merged
20 changes: 20 additions & 0 deletions src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,8 @@ public override bool Equals(object obj)
public static bool operator !=(SourceId a, SourceId b) => !a.Equals(b);
}

internal sealed record Scope (int Id, int StartOffset, int EndOffset);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
internal sealed record Scope (int Id, int StartOffset, int EndOffset);
internal sealed record Scope(int Id, int StartOffset, int EndOffset);

Copy link
Member

Choose a reason for hiding this comment

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

Rename to AsyncScope, or AsyncScopeDebugInformation. And it can be a private nested type of MethodInfo.


internal sealed class MethodInfo
{
private MethodDefinition methodDef;
Expand Down Expand Up @@ -350,6 +352,7 @@ internal sealed class MethodInfo
private ParameterInfo[] _parametersInfo;
public int KickOffMethod { get; }
internal bool IsCompilerGenerated { get; }
internal List<Scope> AsyncScopes { get; }

public MethodInfo(AssemblyInfo assembly, string methodName, int methodToken, TypeInfo type, MethodAttributes attrs)
{
Expand Down Expand Up @@ -447,7 +450,24 @@ public MethodInfo(AssemblyInfo assembly, MethodDefinitionHandle methodDefHandle,
DebuggerAttrInfo.ClearInsignificantAttrFlags();
}
if (pdbMetadataReader != null)
{
localScopes = pdbMetadataReader.GetLocalScopes(methodDefHandle);
var scopeDebugInformation =
(from cdiHandle in pdbMetadataReader.GetCustomDebugInformation(methodDefHandle)
let cdi = pdbMetadataReader.GetCustomDebugInformation(cdiHandle)
where pdbMetadataReader.GetGuid(cdi.Kind) == PortableCustomDebugInfoKinds.StateMachineHoistedLocalScopes
select pdbMetadataReader.GetBlobBytes(cdi.Value)).SingleOrDefault();

if (scopeDebugInformation != null)
{
AsyncScopes = new List<Scope>();
for (int i = 0; i < scopeDebugInformation.Length; i += 8) {
var offset = BitConverter.ToInt32 (scopeDebugInformation, i);
var len = BitConverter.ToInt32 (scopeDebugInformation, i + 4);
AsyncScopes.Add(new Scope((i / 8) + 1, offset, offset + len));
}
}
}
}

public ParameterInfo[] GetParametersInfo()
Expand Down
2 changes: 1 addition & 1 deletion src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1406,7 +1406,7 @@ internal async Task<GetMembersResult> GetScopeProperties(SessionId msg_id, int s

VarInfo[] varIds = scope.Method.Info.GetLiveVarsAt(scope.Location.IlLocation.Offset);

var values = await context.SdbAgent.StackFrameGetValues(scope.Method, context.ThreadId, scopeId, varIds, token);
var values = await context.SdbAgent.StackFrameGetValues(scope.Method, context.ThreadId, scopeId, varIds, scope.Location.IlLocation.Offset, token);
if (values != null)
{
if (values == null || values.Count == 0)
Expand Down
14 changes: 9 additions & 5 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,7 @@ internal sealed class MonoSDBHelper
private SessionId sessionId;

internal readonly ILogger logger;
private static readonly Regex regexForAsyncLocals = new (@"\<([^)]*)\>", RegexOptions.Singleline);
private static readonly Regex regexForAsyncLocals = new (@"\<([^)]*)\>([^)]*)([_][_])([0-9]*)", RegexOptions.Singleline);
Copy link
Member

Choose a reason for hiding this comment

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

Can you add some example strings that we are trying to match?

private static readonly Regex regexForAsyncMethodName = new (@"\<([^>]*)\>([d][_][_])([0-9]*)", RegexOptions.Compiled);
private static readonly Regex regexForGenericArgs = new (@"[`][0-9]+", RegexOptions.Compiled);
private static readonly Regex regexForNestedLeftRightAngleBrackets = new ("^(((?'Open'<)[^<>]*)+((?'Close-Open'>)[^<>]*)+)*(?(Open)(?!))[^<>]*", RegexOptions.Compiled);
Expand Down Expand Up @@ -1961,7 +1961,7 @@ private static bool IsClosureReferenceField (string fieldName)
fieldName.StartsWith ("<>8__", StringComparison.Ordinal);
}

public async Task<JArray> GetHoistedLocalVariables(int objectId, IEnumerable<JToken> asyncLocals, CancellationToken token)
public async Task<JArray> GetHoistedLocalVariables(MethodInfoWithDebugInformation method, int objectId, IEnumerable<JToken> asyncLocals, int offset, CancellationToken token)
{
JArray asyncLocalsFull = new JArray();
List<int> objectsAlreadyRead = new();
Expand All @@ -1982,7 +1982,7 @@ public async Task<JArray> GetHoistedLocalVariables(int objectId, IEnumerable<JTo
{
var asyncProxyMembersFromObject = await MemberObjectsExplorer.GetObjectMemberValues(
this, dotnetObjectId.Value, GetObjectCommandOptions.WithProperties, token);
var hoistedLocalVariable = await GetHoistedLocalVariables(dotnetObjectId.Value, asyncProxyMembersFromObject.Flatten(), token);
var hoistedLocalVariable = await GetHoistedLocalVariables(method, dotnetObjectId.Value, asyncProxyMembersFromObject.Flatten(), offset, token);
asyncLocalsFull = new JArray(asyncLocalsFull.Union(hoistedLocalVariable));
}
}
Expand All @@ -1995,7 +1995,11 @@ public async Task<JArray> GetHoistedLocalVariables(int objectId, IEnumerable<JTo
{
var match = regexForAsyncLocals.Match(fieldName);
if (match.Success)
{
if (!method.Info.AsyncScopes.Where(s => s.Id == Convert.ToInt32(match.Groups[4].Value) && offset >= s.StartOffset && offset <= s.EndOffset).Any())
continue;
asyncLocal["name"] = match.Groups[1].Value;
}
asyncLocalsFull.Add(asyncLocal);
}
else
Expand All @@ -2006,7 +2010,7 @@ public async Task<JArray> GetHoistedLocalVariables(int objectId, IEnumerable<JTo
return asyncLocalsFull;
}

public async Task<JArray> StackFrameGetValues(MethodInfoWithDebugInformation method, int thread_id, int frame_id, VarInfo[] varIds, CancellationToken token)
public async Task<JArray> StackFrameGetValues(MethodInfoWithDebugInformation method, int thread_id, int frame_id, VarInfo[] varIds, int offset, CancellationToken token)
{
using var commandParamsWriter = new MonoBinaryWriter();
commandParamsWriter.Write(thread_id);
Expand All @@ -2023,7 +2027,7 @@ public async Task<JArray> StackFrameGetValues(MethodInfoWithDebugInformation met
retDebuggerCmdReader.ReadByte(); //ignore type
var objectId = retDebuggerCmdReader.ReadInt32();
GetMembersResult asyncProxyMembers = await MemberObjectsExplorer.GetObjectMemberValues(this, objectId, GetObjectCommandOptions.WithProperties, token, includeStatic: true);
var asyncLocals = await GetHoistedLocalVariables(objectId, asyncProxyMembers.Flatten(), token);
var asyncLocals = await GetHoistedLocalVariables(method, objectId, asyncProxyMembers.Flatten(), offset, token);
return asyncLocals;
}

Expand Down
25 changes: 25 additions & 0 deletions src/mono/wasm/debugger/DebuggerTestSuite/AsyncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,30 @@ public async Task AsyncLocalsInNestedContinueWithStaticBlock() => await CheckIns
ncs_dt0 = TDateTime(new DateTime(3412, 4, 6, 8, 0, 2))
}, "locals");
});

[Fact]
public async Task InspectVariablesWithSameNameInDifferentScopes()
{
var expression = $"{{ invoke_static_method('[debugger-test] DebuggerTests.AsyncTests.VariablesWithSameNameDifferentScopes:Run'); }}";

await EvaluateAndCheck(
"window.setTimeout(function() {" + expression + "; }, 1);",
"dotnet://debugger-test.dll/debugger-async-test.cs", 249, 16,
"DebuggerTests.AsyncTests.VariablesWithSameNameDifferentScopes.RunFirstScope",
wait_for_event_fn: async (pause_location) =>
{
var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
await EvaluateOnCallFrameAndCheck(id,
("testFirstScope", TObject("System.Collections.Generic.List<string>", description: "Count = 2"))
);
}
);
await StepAndCheck(StepKind.Resume, "dotnet://debugger-test.dll/debugger-async-test.cs", 273, 16, "DebuggerTests.AsyncTests.VariablesWithSameNameDifferentScopes.RunSecondScope",
locals_fn: async (locals) =>
{
await CheckObject(locals, "testSecondScope", "System.Collections.Generic.List<string>", description: "Count = 2");
}
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -230,4 +230,51 @@ await Task.Delay(300).ContinueWith(t2 =>
}
}

public class VariablesWithSameNameDifferentScopes
{
public static async System.Threading.Tasks.Task Run()
{
await RunFirstScope();
await RunSecondScope();
}

public static async System.Threading.Tasks.Task<System.Collections.Generic.List<string>> RunFirstScope()
{
await System.Threading.Tasks.Task.Delay(1);
int number = 10;
if (number < 999)
{
System.Collections.Generic.List<string> testFirstScope = new System.Collections.Generic.List<string>();
testFirstScope.Add("hello");
testFirstScope.Add("hi");
System.Diagnostics.Debugger.Break();
return testFirstScope;
}
else
{
System.Collections.Generic.List<string> testFirstScope = new System.Collections.Generic.List<string>();
return testFirstScope;
}
}

public static async System.Threading.Tasks.Task<System.Collections.Generic.List<string>> RunSecondScope()
{
await System.Threading.Tasks.Task.Delay(1);
int number = 10;
if (number < 5)
{
System.Collections.Generic.List<string> testSecondScope = new System.Collections.Generic.List<string>();
return testSecondScope;
}
else
{
System.Collections.Generic.List<string> testSecondScope = new System.Collections.Generic.List<string>();
testSecondScope.Add("hello");
testSecondScope.Add("hi");
System.Diagnostics.Debugger.Break();
return testSecondScope;
}
}
}

}