Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Support DebuggerDisplay attribute on type.
  • Loading branch information
thaystg committed Jul 12, 2021
commit 107ccd8a601ce5ea9ed1cf495dbe9ce257dfe4d1
11 changes: 11 additions & 0 deletions src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -322,5 +322,16 @@ internal class PerScopeCache
{
public Dictionary<string, JObject> Locals { get; } = new Dictionary<string, JObject>();
public Dictionary<string, JObject> MemberReferences { get; } = new Dictionary<string, JObject>();
public Dictionary<string, JObject> ObjectFields { get; } = new Dictionary<string, JObject>();
public PerScopeCache(JArray objectValues)
{
foreach (var objectValue in objectValues)
{
ObjectFields[objectValue["name"].Value<string>()] = objectValue.Value<JObject>();
}
}
public PerScopeCache()
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ public MemberReferenceResolver(MonoProxy proxy, ExecutionContext ctx, SessionId
this.logger = logger;
scopeCache = ctx.GetCacheForScope(scope_id);
}

public MemberReferenceResolver(MonoProxy proxy, ExecutionContext ctx, SessionId session_id, JArray object_values, ILogger logger)
{
sessionId = session_id;
scopeId = -1;
this.proxy = proxy;
this.ctx = ctx;
this.logger = logger;
scopeCache = new PerScopeCache(object_values);
locals_fetched = true;
}

public async Task<JObject> GetValueFromObject(JToken objRet, CancellationToken token)
{
if (objRet["value"]?["className"]?.Value<string>() == "System.Exception")
Expand Down Expand Up @@ -70,6 +82,11 @@ public async Task<JObject> Resolve(string var_name, CancellationToken token)
if (scopeCache.MemberReferences.TryGetValue(var_name, out JObject ret)) {
return ret;
}

if (scopeCache.ObjectFields.TryGetValue(var_name, out JObject valueRet)) {
return await GetValueFromObject(valueRet, token);
}

foreach (string part in parts)
{
string partTrimmed = part.Trim();
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 @@ -29,7 +29,7 @@ internal class MonoProxy : DevToolsProxy
public MonoProxy(ILoggerFactory loggerFactory, IList<string> urlSymbolServerList) : base(loggerFactory)
{
this.urlSymbolServerList = urlSymbolServerList ?? new List<string>();
sdbHelper = new MonoSDBHelper(this);
sdbHelper = new MonoSDBHelper(this, logger);
}

internal ExecutionContext GetContext(SessionId sessionId)
Expand Down
81 changes: 80 additions & 1 deletion src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -530,9 +530,12 @@ internal class MonoSDBHelper
private MonoProxy proxy;
private static int MINOR_VERSION = 61;
private static int MAJOR_VERSION = 2;
public MonoSDBHelper(MonoProxy proxy)
private readonly ILogger logger;

public MonoSDBHelper(MonoProxy proxy, ILogger logger)
{
this.proxy = proxy;
this.logger = logger;
}

public void ClearCache()
Expand Down Expand Up @@ -836,6 +839,7 @@ public async Task<List<FieldTypeClass>> GetTypeFields(SessionId sessionId, int t
}
return ret;
}

public string ReplaceCommonClassNames(string className)
{
className = className.Replace("System.String", "string");
Expand All @@ -847,6 +851,76 @@ public string ReplaceCommonClassNames(string className)
className = className.Replace("System.Byte", "byte");
return className;
}

public async Task<string> GetDebuggerDisplayAttribute(SessionId sessionId, int object_id, int type_id, CancellationToken token)
{
string expr = "";
var invoke_params = new MemoryStream();
var invoke_params_writer = new MonoBinaryWriter(invoke_params);
var command_params = new MemoryStream();
var command_params_writer = new MonoBinaryWriter(command_params);
command_params_writer.Write(type_id);
command_params_writer.Write(0);
var ret_debugger_cmd_reader = await SendDebuggerAgentCommand<CmdType>(sessionId, CmdType.GetCattrs, command_params, token);
var count = ret_debugger_cmd_reader.ReadInt32();
if (count == 0)
return null;
for (int i = 0 ; i < count; i++)
{
var methodId = ret_debugger_cmd_reader.ReadInt32();
if (methodId == 0)
continue;
command_params = new MemoryStream();
command_params_writer = new MonoBinaryWriter(command_params);
command_params_writer.Write(methodId);
var ret_debugger_cmd_reader_2 = await SendDebuggerAgentCommand<CmdMethod>(sessionId, CmdMethod.GetDeclaringType, command_params, token);
var customAttributeTypeId = ret_debugger_cmd_reader_2.ReadInt32();
var customAttributeName = await GetTypeName(sessionId, customAttributeTypeId, token);
if (customAttributeName == "System.Diagnostics.DebuggerDisplayAttribute")
{
invoke_params_writer.Write((byte)ValueTypeId.Null);
invoke_params_writer.Write((byte)0); //not used
invoke_params_writer.Write(0); //not used
var parmCount = ret_debugger_cmd_reader.ReadInt32();
invoke_params_writer.Write((int)1);
for (int j = 0; j < parmCount; j++)
{
invoke_params_writer.Write((byte)ret_debugger_cmd_reader.ReadByte());
invoke_params_writer.Write(ret_debugger_cmd_reader.ReadInt32());
}
var retMethod = await InvokeMethod(sessionId, invoke_params.ToArray(), methodId, "methodRet", token);
DotnetObjectId.TryParse(retMethod?["value"]?["objectId"]?.Value<string>(), out DotnetObjectId objectId);
var displayAttrs = await GetObjectValues(sessionId, int.Parse(objectId.Value), true, false, false, false, token);
var displayAttrValue = displayAttrs.FirstOrDefault(attr => attr["name"].Value<string>().Equals("Value"));
try {
ExecutionContext context = proxy.GetContext(sessionId);
var objectValues = await GetObjectValues(sessionId, object_id, true, false, false, false, token);
var resolver = new MemberReferenceResolver(proxy, context, sessionId, objectValues, logger);
expr = "$\"" + displayAttrValue["value"]?["value"]?.Value<string>() + "\"";
JObject retValue = await resolver.Resolve(expr, token);
if (retValue == null)
retValue = await EvaluateExpression.CompileAndRunTheExpression(expr, resolver, token);
return retValue?["value"]?.Value<string>();
}
catch (Exception)
{
logger.LogDebug($"Could not evaluate DebuggerDisplayAttribute - {expr}");
return null;
}
}
else
{
var parmCount = ret_debugger_cmd_reader.ReadInt32();
for (int j = 0; j < parmCount; j++)
{
//to read parameters
await CreateJObjectForVariableValue(sessionId, ret_debugger_cmd_reader, "varName", false, -1, token);
}
}
}
return null;
}

public async Task<string> GetTypeName(SessionId sessionId, int type_id, CancellationToken token)
{
var command_params = new MemoryStream();
Expand Down Expand Up @@ -1189,7 +1263,12 @@ public async Task<JObject> CreateJObjectForObject(SessionId sessionId, MonoBinar
var className = "";
var type_id = await GetTypeIdFromObject(sessionId, objectId, false, token);
className = await GetTypeName(sessionId, type_id[0], token);
var debuggerDisplayAttribute = await GetDebuggerDisplayAttribute(sessionId, objectId, type_id[0], token);
var description = className.ToString();

if (debuggerDisplayAttribute != null)
description = debuggerDisplayAttribute;

if (await IsDelegate(sessionId, objectId, token))
{
if (typeIdFromAttribute != -1)
Expand Down
33 changes: 33 additions & 0 deletions src/mono/wasm/debugger/DebuggerTestSuite/CustomViewTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// 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 System.Threading;
using Xunit;

namespace DebuggerTests
{

public class CustomViewTests : DebuggerTestBase
{
[Fact]
public async Task CustomView()
{
var bp = await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.DebuggerCustomViewTest", "run", 5);
var pause_location = await EvaluateAndCheck(
"window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.DebuggerCustomViewTest:run'); }, 1);",
"dotnet://debugger-test.dll/debugger-custom-view-test.cs",
bp.Value["locations"][0]["lineNumber"].Value<int>(),
bp.Value["locations"][0]["columnNumber"].Value<int>(),
"run");

var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
CheckObject(locals, "a", "DebuggerTests.WithDisplayString", description:"Some one Value 2 End");
//CheckObject(locals, "c", "DebuggerTests.DebuggerDisplayMethodTest");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@

// 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.Diagnostics;

namespace DebuggerTests
{
[DebuggerDisplay ("Some {Val1} Value {Val2} End")]
class WithDisplayString
{
internal string Val1 = "one";

public int Val2 { get { return 2; } }
}

class WithToString
{
public override string ToString ()
{
return "SomeString";
}
}

[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; }
}
}

[DebuggerDisplay ("{GetDebuggerDisplay(), nq}")]
class DebuggerDisplayMethodTest
{
int someInt = 32;
int someInt2 = 43;

string GetDebuggerDisplay ()
{
return "First Int:" + someInt + " Second Int:" + someInt2;
}
}

class DebuggerCustomViewTest
{
public static void run()
{
var a = new WithDisplayString();
var b = new WithProxy();
var c = new DebuggerDisplayMethodTest();
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(c);
}
}
}