Skip to content

Commit 91673de

Browse files
thaystglewing
andauthored
[debugger][wasm] Support DebuggerProxyAttribute (#56872)
* Implementing debugger proxy * fix compilation * Implement debuggerproxy attribute. * Reusing code for DebuggerProxy method and DebuggerDisplay method. * Fix unit tests. * Fixing unit tests that uses List<T>. * Fix unit tests that uses List. * Addressing @radical comments. * Using flags enum as suggested by @lewing. * Fixing merge. * Addressing @radical comments. * Addressing @radical comments. Co-authored-by: Larry Ewing <lewing@microsoft.com>
1 parent ac7b120 commit 91673de

File tree

10 files changed

+413
-146
lines changed

10 files changed

+413
-146
lines changed

src/mono/mono/component/debugger-agent.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6459,7 +6459,7 @@ get_types (gpointer key, gpointer value, gpointer user_data)
64596459
t = mono_reflection_get_type_checked (alc, ass->image, ass->image, ud->info, ud->ignore_case, TRUE, &type_resolve, probe_type_error);
64606460
mono_error_cleanup (probe_type_error);
64616461
if (t) {
6462-
g_ptr_array_add (ud->res_classes, mono_type_get_class_internal (t));
6462+
g_ptr_array_add (ud->res_classes, mono_class_from_mono_type_internal (t));
64636463
g_ptr_array_add (ud->res_domains, domain);
64646464
}
64656465
}

src/mono/sample/wasm/browser/Program.cs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Runtime.CompilerServices;
66
using System.Diagnostics;
7+
using System.Collections.Generic;
78

89
namespace Sample
910
{
@@ -37,6 +38,28 @@ string GetDebuggerDisplay ()
3738
}
3839
}
3940

41+
[DebuggerTypeProxy(typeof(TheProxy))]
42+
class WithProxy
43+
{
44+
public string Val1 {
45+
get { return "one"; }
46+
}
47+
}
48+
49+
class TheProxy
50+
{
51+
WithProxy wp;
52+
53+
public TheProxy (WithProxy wp)
54+
{
55+
this.wp = wp;
56+
}
57+
58+
public string Val2 {
59+
get { return wp.Val1; }
60+
}
61+
}
62+
4063
public static void Main(string[] args)
4164
{
4265
Console.WriteLine ("Hello, World!");
@@ -45,10 +68,8 @@ public static void Main(string[] args)
4568
[MethodImpl(MethodImplOptions.NoInlining)]
4669
public static int TestMeaning()
4770
{
48-
var a = new WithDisplayString();
49-
var c = new DebuggerDisplayMethodTest();
50-
Console.WriteLine(a);
51-
Console.WriteLine(c);
71+
List<int> myList = new List<int>{ 1, 2, 3, 4 };
72+
Console.WriteLine(myList);
5273
return 42;
5374
}
5475
}

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

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ private object ConvertJSToCSharpType(JToken variable)
192192
case "boolean":
193193
return value?.Value<bool>();
194194
case "object":
195-
return null;
195+
return variable;
196196
case "void":
197197
return null;
198198
}
@@ -396,19 +396,13 @@ private static object ConvertCSharpToJSType(object v, ITypeSymbol type)
396396
{
397397
if (v == null)
398398
return new { type = "object", subtype = "null", className = type.ToString(), description = type.ToString() };
399-
400399
if (v is string s)
401-
{
402400
return new { type = "string", value = s, description = s };
403-
}
404-
else if (NumericTypes.Contains(v.GetType()))
405-
{
401+
if (NumericTypes.Contains(v.GetType()))
406402
return new { type = "number", value = v, description = v.ToString() };
407-
}
408-
else
409-
{
410-
return new { type = "object", value = v, description = v.ToString(), className = type.ToString() };
411-
}
403+
if (v is JObject)
404+
return v;
405+
return new { type = "object", value = v, description = v.ToString(), className = type.ToString() };
412406
}
413407

414408
}

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

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ internal class MemberReferenceResolver
2323
private PerScopeCache scopeCache;
2424
private ILogger logger;
2525
private bool localsFetched;
26+
private int linqTypeId;
27+
private MonoSDBHelper sdbHelper;
2628

2729
public MemberReferenceResolver(MonoProxy proxy, ExecutionContext ctx, SessionId sessionId, int scopeId, ILogger logger)
2830
{
@@ -32,6 +34,8 @@ public MemberReferenceResolver(MonoProxy proxy, ExecutionContext ctx, SessionId
3234
this.ctx = ctx;
3335
this.logger = logger;
3436
scopeCache = ctx.GetCacheForScope(scopeId);
37+
sdbHelper = proxy.SdbHelper;
38+
linqTypeId = -1;
3539
}
3640

3741
public MemberReferenceResolver(MonoProxy proxy, ExecutionContext ctx, SessionId sessionId, JArray objectValues, ILogger logger)
@@ -43,6 +47,8 @@ public MemberReferenceResolver(MonoProxy proxy, ExecutionContext ctx, SessionId
4347
this.logger = logger;
4448
scopeCache = new PerScopeCache(objectValues);
4549
localsFetched = true;
50+
sdbHelper = proxy.SdbHelper;
51+
linqTypeId = -1;
4652
}
4753

4854
public async Task<JObject> GetValueFromObject(JToken objRet, CancellationToken token)
@@ -51,7 +57,7 @@ public async Task<JObject> GetValueFromObject(JToken objRet, CancellationToken t
5157
{
5258
if (DotnetObjectId.TryParse(objRet?["value"]?["objectId"]?.Value<string>(), out DotnetObjectId objectId))
5359
{
54-
var exceptionObject = await proxy.SdbHelper.GetObjectValues(sessionId, int.Parse(objectId.Value), true, false, false, true, token);
60+
var exceptionObject = await sdbHelper.GetObjectValues(sessionId, int.Parse(objectId.Value), GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.OwnProperties, token);
5561
var exceptionObjectMessage = exceptionObject.FirstOrDefault(attr => attr["name"].Value<string>().Equals("_message"));
5662
exceptionObjectMessage["value"]["value"] = objRet["value"]?["className"]?.Value<string>() + ": " + exceptionObjectMessage["value"]?["value"]?.Value<string>();
5763
return exceptionObjectMessage["value"]?.Value<JObject>();
@@ -67,8 +73,8 @@ public async Task<JObject> GetValueFromObject(JToken objRet, CancellationToken t
6773
{
6874
var commandParams = new MemoryStream();
6975
var commandParamsWriter = new MonoBinaryWriter(commandParams);
70-
commandParamsWriter.WriteObj(objectId, proxy.SdbHelper);
71-
var ret = await proxy.SdbHelper.InvokeMethod(sessionId, commandParams.ToArray(), objRet["get"]["methodId"].Value<int>(), objRet["name"].Value<string>(), token);
76+
commandParamsWriter.WriteObj(objectId, sdbHelper);
77+
var ret = await sdbHelper.InvokeMethod(sessionId, commandParams.ToArray(), objRet["get"]["methodId"].Value<int>(), objRet["name"].Value<string>(), token);
7278
return await GetValueFromObject(ret, token);
7379
}
7480

@@ -88,27 +94,27 @@ public async Task<JObject> TryToRunOnLoadedClasses(string varName, CancellationT
8894
classNameToFind += part.Trim();
8995
if (typeId != -1)
9096
{
91-
var fields = await proxy.SdbHelper.GetTypeFields(sessionId, typeId, token);
97+
var fields = await sdbHelper.GetTypeFields(sessionId, typeId, onlyPublic: false, token);
9298
foreach (var field in fields)
9399
{
94100
if (field.Name == part.Trim())
95101
{
96-
var isInitialized = await proxy.SdbHelper.TypeIsInitialized(sessionId, typeId, token);
102+
var isInitialized = await sdbHelper.TypeIsInitialized(sessionId, typeId, token);
97103
if (isInitialized == 0)
98104
{
99-
isInitialized = await proxy.SdbHelper.TypeInitialize(sessionId, typeId, token);
105+
isInitialized = await sdbHelper.TypeInitialize(sessionId, typeId, token);
100106
}
101-
var valueRet = await proxy.SdbHelper.GetFieldValue(sessionId, typeId, field.Id, token);
107+
var valueRet = await sdbHelper.GetFieldValue(sessionId, typeId, field.Id, token);
102108
return await GetValueFromObject(valueRet, token);
103109
}
104110
}
105-
var methodId = await proxy.SdbHelper.GetPropertyMethodIdByName(sessionId, typeId, part.Trim(), token);
111+
var methodId = await sdbHelper.GetPropertyMethodIdByName(sessionId, typeId, part.Trim(), token);
106112
if (methodId != -1)
107113
{
108114
var commandParamsObj = new MemoryStream();
109115
var commandParamsObjWriter = new MonoBinaryWriter(commandParamsObj);
110116
commandParamsObjWriter.Write(0); //param count
111-
var retMethod = await proxy.SdbHelper.InvokeMethod(sessionId, commandParamsObj.ToArray(), methodId, "methodRet", token);
117+
var retMethod = await sdbHelper.InvokeMethod(sessionId, commandParamsObj.ToArray(), methodId, "methodRet", token);
112118
return await GetValueFromObject(retMethod, token);
113119
}
114120
}
@@ -118,8 +124,8 @@ public async Task<JObject> TryToRunOnLoadedClasses(string varName, CancellationT
118124
var type = asm.GetTypeByName(classNameToFind);
119125
if (type != null)
120126
{
121-
var assemblyId = await proxy.SdbHelper.GetAssemblyId(sessionId, type.assembly.Name, token);
122-
typeId = await proxy.SdbHelper.GetTypeIdFromToken(sessionId, assemblyId, type.Token, token);
127+
var assemblyId = await sdbHelper.GetAssemblyId(sessionId, type.assembly.Name, token);
128+
typeId = await sdbHelper.GetTypeIdFromToken(sessionId, assemblyId, type.Token, token);
123129
}
124130
}
125131
}
@@ -204,6 +210,7 @@ public async Task<JObject> Resolve(string varName, CancellationToken token)
204210
public async Task<JObject> Resolve(InvocationExpressionSyntax method, Dictionary<string, JObject> memberAccessValues, CancellationToken token)
205211
{
206212
var methodName = "";
213+
int isTryingLinq = 0;
207214
try
208215
{
209216
JObject rootObject = null;
@@ -223,33 +230,56 @@ public async Task<JObject> Resolve(InvocationExpressionSyntax method, Dictionary
223230
if (rootObject != null)
224231
{
225232
DotnetObjectId.TryParse(rootObject?["objectId"]?.Value<string>(), out DotnetObjectId objectId);
226-
var typeId = await proxy.SdbHelper.GetTypeIdFromObject(sessionId, int.Parse(objectId.Value), true, token);
227-
int methodId = await proxy.SdbHelper.GetMethodIdByName(sessionId, typeId[0], methodName, token);
233+
var typeIds = await sdbHelper.GetTypeIdFromObject(sessionId, int.Parse(objectId.Value), true, token);
234+
int methodId = await sdbHelper.GetMethodIdByName(sessionId, typeIds[0], methodName, token);
235+
var className = await sdbHelper.GetTypeNameOriginal(sessionId, typeIds[0], token);
236+
if (methodId == 0) //try to search on System.Linq.Enumerable
237+
{
238+
if (linqTypeId == -1)
239+
linqTypeId = await sdbHelper.GetTypeByName(sessionId, "System.Linq.Enumerable", token);
240+
methodId = await sdbHelper.GetMethodIdByName(sessionId, linqTypeId, methodName, token);
241+
if (methodId != 0)
242+
{
243+
foreach (var typeId in typeIds)
244+
{
245+
var genericTypeArgs = await sdbHelper.GetTypeParamsOrArgsForGenericType(sessionId, typeId, token);
246+
if (genericTypeArgs.Count > 0)
247+
{
248+
isTryingLinq = 1;
249+
methodId = await sdbHelper.MakeGenericMethod(sessionId, methodId, genericTypeArgs, token);
250+
break;
251+
}
252+
}
253+
}
254+
}
228255
if (methodId == 0) {
229-
var typeName = await proxy.SdbHelper.GetTypeName(sessionId, typeId[0], token);
256+
var typeName = await sdbHelper.GetTypeName(sessionId, typeIds[0], token);
230257
throw new Exception($"Method '{methodName}' not found in type '{typeName}'");
231258
}
232259
var commandParamsObj = new MemoryStream();
233260
var commandParamsObjWriter = new MonoBinaryWriter(commandParamsObj);
234-
commandParamsObjWriter.WriteObj(objectId, proxy.SdbHelper);
261+
if (isTryingLinq == 0)
262+
commandParamsObjWriter.WriteObj(objectId, sdbHelper);
235263
if (method.ArgumentList != null)
236264
{
237-
commandParamsObjWriter.Write((int)method.ArgumentList.Arguments.Count);
265+
commandParamsObjWriter.Write((int)method.ArgumentList.Arguments.Count + isTryingLinq);
266+
if (isTryingLinq == 1)
267+
commandParamsObjWriter.WriteObj(objectId, sdbHelper);
238268
foreach (var arg in method.ArgumentList.Arguments)
239269
{
240270
if (arg.Expression is LiteralExpressionSyntax)
241271
{
242-
if (!await commandParamsObjWriter.WriteConst(sessionId, arg.Expression as LiteralExpressionSyntax, proxy.SdbHelper, token))
272+
if (!await commandParamsObjWriter.WriteConst(sessionId, arg.Expression as LiteralExpressionSyntax, sdbHelper, token))
243273
return null;
244274
}
245275
if (arg.Expression is IdentifierNameSyntax)
246276
{
247277
var argParm = arg.Expression as IdentifierNameSyntax;
248-
if (!await commandParamsObjWriter.WriteJsonValue(sessionId, memberAccessValues[argParm.Identifier.Text], proxy.SdbHelper, token))
278+
if (!await commandParamsObjWriter.WriteJsonValue(sessionId, memberAccessValues[argParm.Identifier.Text], sdbHelper, token))
249279
return null;
250280
}
251281
}
252-
var retMethod = await proxy.SdbHelper.InvokeMethod(sessionId, commandParamsObj.ToArray(), methodId, "methodRet", token);
282+
var retMethod = await sdbHelper.InvokeMethod(sessionId, commandParamsObj.ToArray(), methodId, "methodRet", token);
253283
return await GetValueFromObject(retMethod, token);
254284
}
255285
}

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

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,8 @@ protected override async Task<bool> AcceptEvent(SessionId sessionId, string meth
176176
}
177177
catch (Exception) //if the page is refreshed maybe it stops here.
178178
{
179-
return false;
179+
await SendCommand(sessionId, "Debugger.resume", new JObject(), token);
180+
return true;
180181
}
181182
}
182183
}
@@ -617,13 +618,18 @@ private async Task<bool> OnSetVariableValue(MessageId id, int scopeId, string va
617618
internal async Task<JToken> RuntimeGetPropertiesInternal(SessionId id, DotnetObjectId objectId, JToken args, CancellationToken token)
618619
{
619620
var accessorPropertiesOnly = false;
620-
var ownProperties = false;
621+
GetObjectCommandOptions objectValuesOpt = GetObjectCommandOptions.WithProperties;
621622
if (args != null)
622623
{
623-
if (args["accessorPropertiesOnly"] != null)
624-
accessorPropertiesOnly = args["accessorPropertiesOnly"].Value<bool>();
625-
if (args["ownProperties"] != null)
626-
ownProperties = args["ownProperties"].Value<bool>();
624+
if (args["accessorPropertiesOnly"] != null && args["accessorPropertiesOnly"].Value<bool>())
625+
{
626+
objectValuesOpt |= GetObjectCommandOptions.AccessorPropertiesOnly;
627+
accessorPropertiesOnly = true;
628+
}
629+
if (args["ownProperties"] != null && args["ownProperties"].Value<bool>())
630+
{
631+
objectValuesOpt |= GetObjectCommandOptions.OwnProperties;
632+
}
627633
}
628634
//Console.WriteLine($"RuntimeGetProperties - {args}");
629635
try {
@@ -639,7 +645,7 @@ internal async Task<JToken> RuntimeGetPropertiesInternal(SessionId id, DotnetObj
639645
case "array":
640646
return await SdbHelper.GetArrayValues(id, int.Parse(objectId.Value), token);
641647
case "object":
642-
return await SdbHelper.GetObjectValues(id, int.Parse(objectId.Value), true, false, accessorPropertiesOnly, ownProperties, token);
648+
return await SdbHelper.GetObjectValues(id, int.Parse(objectId.Value), objectValuesOpt, token);
643649
case "pointer":
644650
return new JArray{await SdbHelper.GetPointerContent(id, int.Parse(objectId.Value), token)};
645651
case "cfo_res":
@@ -717,7 +723,7 @@ private async Task<bool> SendBreakpointsOfMethodUpdated(SessionId sessionId, Exe
717723
AssemblyInfo asm = store.GetAssemblyByName(assembly_name);
718724
if (asm == null)
719725
{
720-
assembly_name = await SdbHelper.GetAssemblyNameFull(sessionId, assembly_id, token);
726+
assembly_name = await SdbHelper.GetAssemblyFileNameFromId(sessionId, assembly_id, token);
721727
asm = store.GetAssemblyByName(assembly_name);
722728
if (asm == null)
723729
{
@@ -764,7 +770,7 @@ private async Task<bool> SendCallStack(SessionId sessionId, ExecutionContext con
764770
AssemblyInfo asm = store.GetAssemblyByName(assembly_name);
765771
if (asm == null)
766772
{
767-
assembly_name = await SdbHelper.GetAssemblyNameFull(sessionId, assembly_id, token); //maybe is a lazy loaded assembly
773+
assembly_name = await SdbHelper.GetAssemblyFileNameFromId(sessionId, assembly_id, token); //maybe is a lazy loaded assembly
768774
asm = store.GetAssemblyByName(assembly_name);
769775
if (asm == null)
770776
{
@@ -911,7 +917,7 @@ private async Task<bool> OnReceiveDebuggerAgentEvent(SessionId sessionId, JObjec
911917
string reason = "exception";
912918
int object_id = retDebuggerCmdReader.ReadInt32();
913919
var caught = retDebuggerCmdReader.ReadByte();
914-
var exceptionObject = await SdbHelper.GetObjectValues(sessionId, object_id, true, false, false, true, token);
920+
var exceptionObject = await SdbHelper.GetObjectValues(sessionId, object_id, GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.OwnProperties, token);
915921
var exceptionObjectMessage = exceptionObject.FirstOrDefault(attr => attr["name"].Value<string>().Equals("message"));
916922
var data = JObject.FromObject(new
917923
{

0 commit comments

Comments
 (0)