Skip to content

Commit

Permalink
[debugger][wasm] Support DebuggerProxyAttribute (#56872)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
thaystg and lewing committed Aug 16, 2021
1 parent ac7b120 commit 91673de
Show file tree
Hide file tree
Showing 10 changed files with 413 additions and 146 deletions.
2 changes: 1 addition & 1 deletion src/mono/mono/component/debugger-agent.c
Original file line number Diff line number Diff line change
Expand Up @@ -6459,7 +6459,7 @@ get_types (gpointer key, gpointer value, gpointer user_data)
t = mono_reflection_get_type_checked (alc, ass->image, ass->image, ud->info, ud->ignore_case, TRUE, &type_resolve, probe_type_error);
mono_error_cleanup (probe_type_error);
if (t) {
g_ptr_array_add (ud->res_classes, mono_type_get_class_internal (t));
g_ptr_array_add (ud->res_classes, mono_class_from_mono_type_internal (t));
g_ptr_array_add (ud->res_domains, domain);
}
}
Expand Down
29 changes: 25 additions & 4 deletions src/mono/sample/wasm/browser/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Runtime.CompilerServices;
using System.Diagnostics;
using System.Collections.Generic;

namespace Sample
{
Expand Down Expand Up @@ -37,6 +38,28 @@ string GetDebuggerDisplay ()
}
}

[DebuggerTypeProxy(typeof(TheProxy))]
class WithProxy
{
public string Val1 {
get { return "one"; }
}
}

class TheProxy
{
WithProxy wp;

public TheProxy (WithProxy wp)
{
this.wp = wp;
}

public string Val2 {
get { return wp.Val1; }
}
}

public static void Main(string[] args)
{
Console.WriteLine ("Hello, World!");
Expand All @@ -45,10 +68,8 @@ public static void Main(string[] args)
[MethodImpl(MethodImplOptions.NoInlining)]
public static int TestMeaning()
{
var a = new WithDisplayString();
var c = new DebuggerDisplayMethodTest();
Console.WriteLine(a);
Console.WriteLine(c);
List<int> myList = new List<int>{ 1, 2, 3, 4 };
Console.WriteLine(myList);
return 42;
}
}
Expand Down
16 changes: 5 additions & 11 deletions src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ private object ConvertJSToCSharpType(JToken variable)
case "boolean":
return value?.Value<bool>();
case "object":
return null;
return variable;
case "void":
return null;
}
Expand Down Expand Up @@ -396,19 +396,13 @@ private static object ConvertCSharpToJSType(object v, ITypeSymbol type)
{
if (v == null)
return new { type = "object", subtype = "null", className = type.ToString(), description = type.ToString() };

if (v is string s)
{
return new { type = "string", value = s, description = s };
}
else if (NumericTypes.Contains(v.GetType()))
{
if (NumericTypes.Contains(v.GetType()))
return new { type = "number", value = v, description = v.ToString() };
}
else
{
return new { type = "object", value = v, description = v.ToString(), className = type.ToString() };
}
if (v is JObject)
return v;
return new { type = "object", value = v, description = v.ToString(), className = type.ToString() };
}

}
Expand Down
68 changes: 49 additions & 19 deletions src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ internal class MemberReferenceResolver
private PerScopeCache scopeCache;
private ILogger logger;
private bool localsFetched;
private int linqTypeId;
private MonoSDBHelper sdbHelper;

public MemberReferenceResolver(MonoProxy proxy, ExecutionContext ctx, SessionId sessionId, int scopeId, ILogger logger)
{
Expand All @@ -32,6 +34,8 @@ public MemberReferenceResolver(MonoProxy proxy, ExecutionContext ctx, SessionId
this.ctx = ctx;
this.logger = logger;
scopeCache = ctx.GetCacheForScope(scopeId);
sdbHelper = proxy.SdbHelper;
linqTypeId = -1;
}

public MemberReferenceResolver(MonoProxy proxy, ExecutionContext ctx, SessionId sessionId, JArray objectValues, ILogger logger)
Expand All @@ -43,6 +47,8 @@ public MemberReferenceResolver(MonoProxy proxy, ExecutionContext ctx, SessionId
this.logger = logger;
scopeCache = new PerScopeCache(objectValues);
localsFetched = true;
sdbHelper = proxy.SdbHelper;
linqTypeId = -1;
}

public async Task<JObject> GetValueFromObject(JToken objRet, CancellationToken token)
Expand All @@ -51,7 +57,7 @@ public async Task<JObject> GetValueFromObject(JToken objRet, CancellationToken t
{
if (DotnetObjectId.TryParse(objRet?["value"]?["objectId"]?.Value<string>(), out DotnetObjectId objectId))
{
var exceptionObject = await proxy.SdbHelper.GetObjectValues(sessionId, int.Parse(objectId.Value), true, false, false, true, token);
var exceptionObject = await sdbHelper.GetObjectValues(sessionId, int.Parse(objectId.Value), GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.OwnProperties, token);
var exceptionObjectMessage = exceptionObject.FirstOrDefault(attr => attr["name"].Value<string>().Equals("_message"));
exceptionObjectMessage["value"]["value"] = objRet["value"]?["className"]?.Value<string>() + ": " + exceptionObjectMessage["value"]?["value"]?.Value<string>();
return exceptionObjectMessage["value"]?.Value<JObject>();
Expand All @@ -67,8 +73,8 @@ public async Task<JObject> GetValueFromObject(JToken objRet, CancellationToken t
{
var commandParams = new MemoryStream();
var commandParamsWriter = new MonoBinaryWriter(commandParams);
commandParamsWriter.WriteObj(objectId, proxy.SdbHelper);
var ret = await proxy.SdbHelper.InvokeMethod(sessionId, commandParams.ToArray(), objRet["get"]["methodId"].Value<int>(), objRet["name"].Value<string>(), token);
commandParamsWriter.WriteObj(objectId, sdbHelper);
var ret = await sdbHelper.InvokeMethod(sessionId, commandParams.ToArray(), objRet["get"]["methodId"].Value<int>(), objRet["name"].Value<string>(), token);
return await GetValueFromObject(ret, token);
}

Expand All @@ -88,27 +94,27 @@ public async Task<JObject> TryToRunOnLoadedClasses(string varName, CancellationT
classNameToFind += part.Trim();
if (typeId != -1)
{
var fields = await proxy.SdbHelper.GetTypeFields(sessionId, typeId, token);
var fields = await sdbHelper.GetTypeFields(sessionId, typeId, onlyPublic: false, token);
foreach (var field in fields)
{
if (field.Name == part.Trim())
{
var isInitialized = await proxy.SdbHelper.TypeIsInitialized(sessionId, typeId, token);
var isInitialized = await sdbHelper.TypeIsInitialized(sessionId, typeId, token);
if (isInitialized == 0)
{
isInitialized = await proxy.SdbHelper.TypeInitialize(sessionId, typeId, token);
isInitialized = await sdbHelper.TypeInitialize(sessionId, typeId, token);
}
var valueRet = await proxy.SdbHelper.GetFieldValue(sessionId, typeId, field.Id, token);
var valueRet = await sdbHelper.GetFieldValue(sessionId, typeId, field.Id, token);
return await GetValueFromObject(valueRet, token);
}
}
var methodId = await proxy.SdbHelper.GetPropertyMethodIdByName(sessionId, typeId, part.Trim(), token);
var methodId = await sdbHelper.GetPropertyMethodIdByName(sessionId, typeId, part.Trim(), token);
if (methodId != -1)
{
var commandParamsObj = new MemoryStream();
var commandParamsObjWriter = new MonoBinaryWriter(commandParamsObj);
commandParamsObjWriter.Write(0); //param count
var retMethod = await proxy.SdbHelper.InvokeMethod(sessionId, commandParamsObj.ToArray(), methodId, "methodRet", token);
var retMethod = await sdbHelper.InvokeMethod(sessionId, commandParamsObj.ToArray(), methodId, "methodRet", token);
return await GetValueFromObject(retMethod, token);
}
}
Expand All @@ -118,8 +124,8 @@ public async Task<JObject> TryToRunOnLoadedClasses(string varName, CancellationT
var type = asm.GetTypeByName(classNameToFind);
if (type != null)
{
var assemblyId = await proxy.SdbHelper.GetAssemblyId(sessionId, type.assembly.Name, token);
typeId = await proxy.SdbHelper.GetTypeIdFromToken(sessionId, assemblyId, type.Token, token);
var assemblyId = await sdbHelper.GetAssemblyId(sessionId, type.assembly.Name, token);
typeId = await sdbHelper.GetTypeIdFromToken(sessionId, assemblyId, type.Token, token);
}
}
}
Expand Down Expand Up @@ -204,6 +210,7 @@ public async Task<JObject> Resolve(string varName, CancellationToken token)
public async Task<JObject> Resolve(InvocationExpressionSyntax method, Dictionary<string, JObject> memberAccessValues, CancellationToken token)
{
var methodName = "";
int isTryingLinq = 0;
try
{
JObject rootObject = null;
Expand All @@ -223,33 +230,56 @@ public async Task<JObject> Resolve(InvocationExpressionSyntax method, Dictionary
if (rootObject != null)
{
DotnetObjectId.TryParse(rootObject?["objectId"]?.Value<string>(), out DotnetObjectId objectId);
var typeId = await proxy.SdbHelper.GetTypeIdFromObject(sessionId, int.Parse(objectId.Value), true, token);
int methodId = await proxy.SdbHelper.GetMethodIdByName(sessionId, typeId[0], methodName, token);
var typeIds = await sdbHelper.GetTypeIdFromObject(sessionId, int.Parse(objectId.Value), true, token);
int methodId = await sdbHelper.GetMethodIdByName(sessionId, typeIds[0], methodName, token);
var className = await sdbHelper.GetTypeNameOriginal(sessionId, typeIds[0], token);
if (methodId == 0) //try to search on System.Linq.Enumerable
{
if (linqTypeId == -1)
linqTypeId = await sdbHelper.GetTypeByName(sessionId, "System.Linq.Enumerable", token);
methodId = await sdbHelper.GetMethodIdByName(sessionId, linqTypeId, methodName, token);
if (methodId != 0)
{
foreach (var typeId in typeIds)
{
var genericTypeArgs = await sdbHelper.GetTypeParamsOrArgsForGenericType(sessionId, typeId, token);
if (genericTypeArgs.Count > 0)
{
isTryingLinq = 1;
methodId = await sdbHelper.MakeGenericMethod(sessionId, methodId, genericTypeArgs, token);
break;
}
}
}
}
if (methodId == 0) {
var typeName = await proxy.SdbHelper.GetTypeName(sessionId, typeId[0], token);
var typeName = await sdbHelper.GetTypeName(sessionId, typeIds[0], token);
throw new Exception($"Method '{methodName}' not found in type '{typeName}'");
}
var commandParamsObj = new MemoryStream();
var commandParamsObjWriter = new MonoBinaryWriter(commandParamsObj);
commandParamsObjWriter.WriteObj(objectId, proxy.SdbHelper);
if (isTryingLinq == 0)
commandParamsObjWriter.WriteObj(objectId, sdbHelper);
if (method.ArgumentList != null)
{
commandParamsObjWriter.Write((int)method.ArgumentList.Arguments.Count);
commandParamsObjWriter.Write((int)method.ArgumentList.Arguments.Count + isTryingLinq);
if (isTryingLinq == 1)
commandParamsObjWriter.WriteObj(objectId, sdbHelper);
foreach (var arg in method.ArgumentList.Arguments)
{
if (arg.Expression is LiteralExpressionSyntax)
{
if (!await commandParamsObjWriter.WriteConst(sessionId, arg.Expression as LiteralExpressionSyntax, proxy.SdbHelper, token))
if (!await commandParamsObjWriter.WriteConst(sessionId, arg.Expression as LiteralExpressionSyntax, sdbHelper, token))
return null;
}
if (arg.Expression is IdentifierNameSyntax)
{
var argParm = arg.Expression as IdentifierNameSyntax;
if (!await commandParamsObjWriter.WriteJsonValue(sessionId, memberAccessValues[argParm.Identifier.Text], proxy.SdbHelper, token))
if (!await commandParamsObjWriter.WriteJsonValue(sessionId, memberAccessValues[argParm.Identifier.Text], sdbHelper, token))
return null;
}
}
var retMethod = await proxy.SdbHelper.InvokeMethod(sessionId, commandParamsObj.ToArray(), methodId, "methodRet", token);
var retMethod = await sdbHelper.InvokeMethod(sessionId, commandParamsObj.ToArray(), methodId, "methodRet", token);
return await GetValueFromObject(retMethod, token);
}
}
Expand Down
26 changes: 16 additions & 10 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ protected override async Task<bool> AcceptEvent(SessionId sessionId, string meth
}
catch (Exception) //if the page is refreshed maybe it stops here.
{
return false;
await SendCommand(sessionId, "Debugger.resume", new JObject(), token);
return true;
}
}
}
Expand Down Expand Up @@ -617,13 +618,18 @@ private async Task<bool> OnSetVariableValue(MessageId id, int scopeId, string va
internal async Task<JToken> RuntimeGetPropertiesInternal(SessionId id, DotnetObjectId objectId, JToken args, CancellationToken token)
{
var accessorPropertiesOnly = false;
var ownProperties = false;
GetObjectCommandOptions objectValuesOpt = GetObjectCommandOptions.WithProperties;
if (args != null)
{
if (args["accessorPropertiesOnly"] != null)
accessorPropertiesOnly = args["accessorPropertiesOnly"].Value<bool>();
if (args["ownProperties"] != null)
ownProperties = args["ownProperties"].Value<bool>();
if (args["accessorPropertiesOnly"] != null && args["accessorPropertiesOnly"].Value<bool>())
{
objectValuesOpt |= GetObjectCommandOptions.AccessorPropertiesOnly;
accessorPropertiesOnly = true;
}
if (args["ownProperties"] != null && args["ownProperties"].Value<bool>())
{
objectValuesOpt |= GetObjectCommandOptions.OwnProperties;
}
}
//Console.WriteLine($"RuntimeGetProperties - {args}");
try {
Expand All @@ -639,7 +645,7 @@ internal async Task<JToken> RuntimeGetPropertiesInternal(SessionId id, DotnetObj
case "array":
return await SdbHelper.GetArrayValues(id, int.Parse(objectId.Value), token);
case "object":
return await SdbHelper.GetObjectValues(id, int.Parse(objectId.Value), true, false, accessorPropertiesOnly, ownProperties, token);
return await SdbHelper.GetObjectValues(id, int.Parse(objectId.Value), objectValuesOpt, token);
case "pointer":
return new JArray{await SdbHelper.GetPointerContent(id, int.Parse(objectId.Value), token)};
case "cfo_res":
Expand Down Expand Up @@ -717,7 +723,7 @@ private async Task<bool> SendBreakpointsOfMethodUpdated(SessionId sessionId, Exe
AssemblyInfo asm = store.GetAssemblyByName(assembly_name);
if (asm == null)
{
assembly_name = await SdbHelper.GetAssemblyNameFull(sessionId, assembly_id, token);
assembly_name = await SdbHelper.GetAssemblyFileNameFromId(sessionId, assembly_id, token);
asm = store.GetAssemblyByName(assembly_name);
if (asm == null)
{
Expand Down Expand Up @@ -764,7 +770,7 @@ private async Task<bool> SendCallStack(SessionId sessionId, ExecutionContext con
AssemblyInfo asm = store.GetAssemblyByName(assembly_name);
if (asm == null)
{
assembly_name = await SdbHelper.GetAssemblyNameFull(sessionId, assembly_id, token); //maybe is a lazy loaded assembly
assembly_name = await SdbHelper.GetAssemblyFileNameFromId(sessionId, assembly_id, token); //maybe is a lazy loaded assembly
asm = store.GetAssemblyByName(assembly_name);
if (asm == null)
{
Expand Down Expand Up @@ -911,7 +917,7 @@ private async Task<bool> OnReceiveDebuggerAgentEvent(SessionId sessionId, JObjec
string reason = "exception";
int object_id = retDebuggerCmdReader.ReadInt32();
var caught = retDebuggerCmdReader.ReadByte();
var exceptionObject = await SdbHelper.GetObjectValues(sessionId, object_id, true, false, false, true, token);
var exceptionObject = await SdbHelper.GetObjectValues(sessionId, object_id, GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.OwnProperties, token);
var exceptionObjectMessage = exceptionObject.FirstOrDefault(attr => attr["name"].Value<string>().Equals("message"));
var data = JObject.FromObject(new
{
Expand Down
Loading

0 comments on commit 91673de

Please sign in to comment.