Skip to content

Commit 5d51394

Browse files
authored
backport 78651 (#79542)
1 parent e981540 commit 5d51394

File tree

8 files changed

+297
-11
lines changed

8 files changed

+297
-11
lines changed

src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ internal sealed class MethodInfo
350350
private ParameterInfo[] _parametersInfo;
351351
public int KickOffMethod { get; }
352352
internal bool IsCompilerGenerated { get; }
353+
private readonly AsyncScopeDebugInformation[] _asyncScopes;
353354

354355
public MethodInfo(AssemblyInfo assembly, string methodName, int methodToken, TypeInfo type, MethodAttributes attrs)
355356
{
@@ -361,6 +362,7 @@ public MethodInfo(AssemblyInfo assembly, string methodName, int methodToken, Typ
361362
this.TypeInfo = type;
362363
TypeInfo.Methods.Add(this);
363364
assembly.Methods[methodToken] = this;
365+
_asyncScopes = Array.Empty<AsyncScopeDebugInformation>();
364366
}
365367

366368
public MethodInfo(AssemblyInfo assembly, MethodDefinitionHandle methodDefHandle, int token, SourceFile source, TypeInfo type, MetadataReader asmMetadataReader, MetadataReader pdbMetadataReader)
@@ -447,7 +449,34 @@ public MethodInfo(AssemblyInfo assembly, MethodDefinitionHandle methodDefHandle,
447449
DebuggerAttrInfo.ClearInsignificantAttrFlags();
448450
}
449451
if (pdbMetadataReader != null)
452+
{
450453
localScopes = pdbMetadataReader.GetLocalScopes(methodDefHandle);
454+
byte[] scopeDebugInformation =
455+
(from cdiHandle in pdbMetadataReader.GetCustomDebugInformation(methodDefHandle)
456+
let cdi = pdbMetadataReader.GetCustomDebugInformation(cdiHandle)
457+
where pdbMetadataReader.GetGuid(cdi.Kind) == PortableCustomDebugInfoKinds.StateMachineHoistedLocalScopes
458+
select pdbMetadataReader.GetBlobBytes(cdi.Value)).FirstOrDefault();
459+
460+
if (scopeDebugInformation != null)
461+
{
462+
_asyncScopes = new AsyncScopeDebugInformation[scopeDebugInformation.Length / 8];
463+
for (int i = 0; i < _asyncScopes.Length; i++)
464+
{
465+
int scopeOffset = BitConverter.ToInt32(scopeDebugInformation, i * 8);
466+
int scopeLen = BitConverter.ToInt32(scopeDebugInformation, (i * 8) + 4);
467+
_asyncScopes[i] = new AsyncScopeDebugInformation(scopeOffset, scopeOffset + scopeLen);
468+
}
469+
}
470+
471+
_asyncScopes ??= Array.Empty<AsyncScopeDebugInformation>();
472+
}
473+
}
474+
475+
public bool ContainsAsyncScope(int oneBasedIdx, int offset)
476+
{
477+
int arrIdx = oneBasedIdx - 1;
478+
return arrIdx >= 0 && arrIdx < _asyncScopes.Length &&
479+
offset >= _asyncScopes[arrIdx].StartOffset && offset <= _asyncScopes[arrIdx].EndOffset;
451480
}
452481

453482
public ParameterInfo[] GetParametersInfo()
@@ -617,6 +646,8 @@ public override int GetHashCode(MethodInfo loc)
617646
return loc.Source.Id;
618647
}
619648
}
649+
650+
private record struct AsyncScopeDebugInformation(int StartOffset, int EndOffset);
620651
}
621652

622653
internal sealed class ParameterInfo

src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1430,7 +1430,7 @@ internal async Task<Result> GetScopeProperties(SessionId msg_id, int scopeId, Ca
14301430

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

1433-
var values = await context.SdbAgent.StackFrameGetValues(scope.Method, context.ThreadId, scopeId, varIds, token);
1433+
var values = await context.SdbAgent.StackFrameGetValues(scope.Method, context.ThreadId, scopeId, varIds, scope.Location.IlLocation.Offset, token);
14341434
if (values != null)
14351435
{
14361436
if (values == null || values.Count == 0)

src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -787,8 +787,10 @@ internal sealed class MonoSDBHelper
787787
private DebugStore store;
788788
private SessionId sessionId;
789789

790-
private readonly ILogger logger;
791-
private static readonly Regex regexForAsyncLocals = new (@"\<([^)]*)\>", RegexOptions.Singleline);
790+
internal readonly ILogger logger;
791+
private static readonly Regex regexForAsyncLocals = new(@"\<([^)]*)\>([^)]*)([_][_])([0-9]*)", RegexOptions.Singleline); //<testCSharpScope>5__1
792+
private static readonly Regex regexForVBAsyncLocals = new(@"\$VB\$ResumableLocal_([^)]*)\$([0-9]*)", RegexOptions.Singleline); //$VB$ResumableLocal_testVbScope$2
793+
private static readonly Regex regexForVBAsyncMethodName = new(@"VB\$StateMachine_([0-9]*)_([^)]*)", RegexOptions.Singleline); //VB$StateMachine_2_RunVBScope
792794
private static readonly Regex regexForAsyncMethodName = new (@"\<([^>]*)\>([d][_][_])([0-9]*)", RegexOptions.Compiled);
793795
private static readonly Regex regexForGenericArgs = new (@"[`][0-9]+", RegexOptions.Compiled);
794796
private static readonly Regex regexForNestedLeftRightAngleBrackets = new ("^(((?'Open'<)[^<>]*)+((?'Close-Open'>)[^<>]*)+)*(?(Open)(?!))[^<>]*", RegexOptions.Compiled);
@@ -1271,6 +1273,14 @@ public async Task<string> GetPrettyMethodName(int methodId, bool isAnonymous, Ca
12711273
if (anonymousMethodId.LastIndexOf('_') >= 0)
12721274
anonymousMethodId = klassName.Substring(klassName.LastIndexOf('_') + 1);
12731275
}
1276+
else if (klassName.StartsWith("VB$"))
1277+
{
1278+
var match = regexForVBAsyncMethodName.Match(klassName);
1279+
if (match.Success)
1280+
ret = ret.Insert(0, match.Groups[2].Value);
1281+
else
1282+
ret = ret.Insert(0, klassName);
1283+
}
12741284
else
12751285
{
12761286
var matchOnClassName = regexForNestedLeftRightAngleBrackets.Match(klassName);
@@ -1920,7 +1930,7 @@ private static bool IsClosureReferenceField (string fieldName)
19201930
fieldName.StartsWith ("<>8__", StringComparison.Ordinal);
19211931
}
19221932

1923-
public async Task<JArray> GetHoistedLocalVariables(int objectId, IEnumerable<JToken> asyncLocals, CancellationToken token)
1933+
public async Task<JArray> GetHoistedLocalVariables(MethodInfoWithDebugInformation method, int objectId, IEnumerable<JToken> asyncLocals, int offset, CancellationToken token)
19241934
{
19251935
JArray asyncLocalsFull = new JArray();
19261936
List<int> objectsAlreadyRead = new();
@@ -1931,7 +1941,6 @@ public async Task<JArray> GetHoistedLocalVariables(int objectId, IEnumerable<JTo
19311941
if (fieldName.EndsWith("__this", StringComparison.Ordinal))
19321942
{
19331943
asyncLocal["name"] = "this";
1934-
asyncLocalsFull.Add(asyncLocal);
19351944
}
19361945
else if (IsClosureReferenceField(fieldName)) //same code that has on debugger-libs
19371946
{
@@ -1941,10 +1950,11 @@ public async Task<JArray> GetHoistedLocalVariables(int objectId, IEnumerable<JTo
19411950
{
19421951
var asyncProxyMembersFromObject = await MemberObjectsExplorer.GetObjectMemberValues(
19431952
this, dotnetObjectId.Value, GetObjectCommandOptions.WithProperties, token);
1944-
var hoistedLocalVariable = await GetHoistedLocalVariables(dotnetObjectId.Value, asyncProxyMembersFromObject.Flatten(), token);
1953+
var hoistedLocalVariable = await GetHoistedLocalVariables(method, dotnetObjectId.Value, asyncProxyMembersFromObject.Flatten(), offset, token);
19451954
asyncLocalsFull = new JArray(asyncLocalsFull.Union(hoistedLocalVariable));
19461955
}
19471956
}
1957+
continue;
19481958
}
19491959
else if (fieldName.StartsWith("<>", StringComparison.Ordinal)) //examples: <>t__builder, <>1__state
19501960
{
@@ -1954,18 +1964,37 @@ public async Task<JArray> GetHoistedLocalVariables(int objectId, IEnumerable<JTo
19541964
{
19551965
var match = regexForAsyncLocals.Match(fieldName);
19561966
if (match.Success)
1967+
{
1968+
if (!method.Info.ContainsAsyncScope(Convert.ToInt32(match.Groups[4].Value), offset))
1969+
continue;
19571970
asyncLocal["name"] = match.Groups[1].Value;
1958-
asyncLocalsFull.Add(asyncLocal);
1971+
}
19591972
}
1960-
else
1973+
//VB language
1974+
else if (fieldName.StartsWith("$VB$Local_", StringComparison.Ordinal))
1975+
{
1976+
asyncLocal["name"] = fieldName.Remove(0, 10);
1977+
}
1978+
else if (fieldName.StartsWith("$VB$ResumableLocal_", StringComparison.Ordinal))
1979+
{
1980+
var match = regexForVBAsyncLocals.Match(fieldName);
1981+
if (match.Success)
1982+
{
1983+
if (!method.Info.ContainsAsyncScope(Convert.ToInt32(match.Groups[2].Value) + 1, offset))
1984+
continue;
1985+
asyncLocal["name"] = match.Groups[1].Value;
1986+
}
1987+
}
1988+
else if (fieldName.StartsWith("$"))
19611989
{
1962-
asyncLocalsFull.Add(asyncLocal);
1990+
continue;
19631991
}
1992+
asyncLocalsFull.Add(asyncLocal);
19641993
}
19651994
return asyncLocalsFull;
19661995
}
19671996

1968-
public async Task<JArray> StackFrameGetValues(MethodInfoWithDebugInformation method, int thread_id, int frame_id, VarInfo[] varIds, CancellationToken token)
1997+
public async Task<JArray> StackFrameGetValues(MethodInfoWithDebugInformation method, int thread_id, int frame_id, VarInfo[] varIds, int offset, CancellationToken token)
19691998
{
19701999
using var commandParamsWriter = new MonoBinaryWriter();
19712000
commandParamsWriter.Write(thread_id);
@@ -1982,7 +2011,7 @@ public async Task<JArray> StackFrameGetValues(MethodInfoWithDebugInformation met
19822011
retDebuggerCmdReader.ReadByte(); //ignore type
19832012
var objectId = retDebuggerCmdReader.ReadInt32();
19842013
GetMembersResult asyncProxyMembers = await MemberObjectsExplorer.GetObjectMemberValues(this, objectId, GetObjectCommandOptions.WithProperties, token, includeStatic: true);
1985-
var asyncLocals = await GetHoistedLocalVariables(objectId, asyncProxyMembers.Flatten(), token);
2014+
var asyncLocals = await GetHoistedLocalVariables(method, objectId, asyncProxyMembers.Flatten(), offset, token);
19862015
return asyncLocals;
19872016
}
19882017

src/mono/wasm/debugger/DebuggerTestSuite/AsyncTests.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,77 @@ public async Task AsyncLocalsInNestedContinueWithStaticBlock() => await CheckIns
8181
ncs_dt0 = TDateTime(new DateTime(3412, 4, 6, 8, 0, 2))
8282
}, "locals");
8383
});
84+
85+
[Theory]
86+
[InlineData("Run", 246, 16, 252, 16, "RunCSharpScope")]
87+
[InlineData("RunContinueWith", 277, 20, 283, 20, "RunContinueWithSameVariableName")]
88+
[InlineData("RunNestedContinueWith", 309, 24, 315, 24, "RunNestedContinueWithSameVariableName.AnonymousMethod__1")]
89+
[InlineData("RunNonAsyncMethod", 334, 16, 340, 16, "RunNonAsyncMethodSameVariableName")]
90+
public async Task InspectLocalsWithSameNameInDifferentScopesInAsyncMethod_CSharp(string method_to_run, int line1, int col1, int line2, int col2, string func_to_pause)
91+
=> await InspectLocalsWithSameNameInDifferentScopesInAsyncMethod(
92+
$"[debugger-test] DebuggerTests.AsyncTests.VariablesWithSameNameDifferentScopes:{method_to_run}",
93+
"dotnet://debugger-test.dll/debugger-async-test.cs",
94+
line1,
95+
col1,
96+
line2,
97+
col2,
98+
$"DebuggerTests.AsyncTests.VariablesWithSameNameDifferentScopes.{func_to_pause}",
99+
"testCSharpScope");
100+
101+
[Theory]
102+
[InlineData("[debugger-test-vb] DebuggerTestVB.TestVbScope:Run", 14, 12, 22, 12, "DebuggerTestVB.TestVbScope.RunVBScope", "testVbScope")]
103+
public async Task InspectLocalsWithSameNameInDifferentScopesInAsyncMethod_VB(string method_to_run, int line1, int col1, int line2, int col2, string func_to_pause, string variable_to_inspect)
104+
=> await InspectLocalsWithSameNameInDifferentScopesInAsyncMethod(
105+
method_to_run,
106+
"dotnet://debugger-test-vb.dll/debugger-test-vb.vb",
107+
line1,
108+
col1,
109+
line2,
110+
col2,
111+
func_to_pause,
112+
variable_to_inspect);
113+
114+
private async Task InspectLocalsWithSameNameInDifferentScopesInAsyncMethod(string method_to_run, string source_to_pause, int line1, int col1, int line2, int col2, string func_to_pause, string variable_to_inspect)
115+
{
116+
var expression = $"{{ invoke_static_method('{method_to_run}'); }}";
117+
118+
await EvaluateAndCheck(
119+
"window.setTimeout(function() {" + expression + "; }, 1);",
120+
source_to_pause, line1, col1,
121+
func_to_pause,
122+
locals_fn: async (locals) =>
123+
{
124+
await CheckString(locals, variable_to_inspect, "hello");
125+
await CheckString(locals, "onlyInFirstScope", "only-in-first-scope");
126+
Assert.False(locals.Any(jt => jt["name"]?.Value<string>() == "onlyInSecondScope"));
127+
}
128+
);
129+
await StepAndCheck(StepKind.Resume, source_to_pause, line2, col2, func_to_pause,
130+
locals_fn: async (locals) =>
131+
{
132+
await CheckString(locals, variable_to_inspect, "hi");
133+
await CheckString(locals, "onlyInSecondScope", "only-in-second-scope");
134+
Assert.False(locals.Any(jt => jt["name"]?.Value<string>() == "onlyInFirstScope"));
135+
}
136+
);
137+
}
138+
139+
[Fact]
140+
public async Task InspectLocalsInAsyncVBMethod()
141+
{
142+
var expression = $"{{ invoke_static_method('[debugger-test-vb] DebuggerTestVB.TestVbScope:Run'); }}";
143+
144+
await EvaluateAndCheck(
145+
"window.setTimeout(function() {" + expression + "; }, 1);",
146+
"dotnet://debugger-test-vb.dll/debugger-test-vb.vb", 14, 12,
147+
"DebuggerTestVB.TestVbScope.RunVBScope",
148+
locals_fn: async (locals) =>
149+
{
150+
await CheckString(locals, "testVbScope", "hello");
151+
CheckNumber(locals, "a", 10);
152+
CheckNumber(locals, "data", 10);
153+
}
154+
);
155+
}
84156
}
85157
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
Public Class TestVbScope
2+
Public Shared Async Function Run() As Task
3+
Await RunVBScope(10)
4+
Await RunVBScope(1000)
5+
End Function
6+
7+
Public Shared Async Function RunVBScope(data As Integer) As Task(Of Integer)
8+
Dim a As Integer
9+
a = 10
10+
If data < 999 Then
11+
Dim testVbScope As String
12+
Dim onlyInFirstScope As String
13+
testVbScope = "hello"
14+
onlyInFirstScope = "only-in-first-scope"
15+
System.Diagnostics.Debugger.Break()
16+
Await Task.Delay(1)
17+
Return data
18+
Else
19+
Dim testVbScope As String
20+
Dim onlyInSecondScope As String
21+
testVbScope = "hi"
22+
onlyInSecondScope = "only-in-second-scope"
23+
System.Diagnostics.Debugger.Break()
24+
Await Task.Delay(1)
25+
Return data
26+
End If
27+
28+
End Function
29+
30+
End Class
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<RootNamespace>DebuggerTestVB</RootNamespace>
5+
<TargetFramework>net7.0</TargetFramework>
6+
</PropertyGroup>
7+
8+
</Project>

0 commit comments

Comments
 (0)