Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wasm] [debugger] Support method calls #55458

Merged
merged 8 commits into from
Jul 14, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
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
130 changes: 102 additions & 28 deletions src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,38 +27,54 @@ private class FindVariableNMethodCall : CSharpSyntaxWalker
public List<InvocationExpressionSyntax> methodCall = new List<InvocationExpressionSyntax>();
public List<MemberAccessExpressionSyntax> memberAccesses = new List<MemberAccessExpressionSyntax>();
public List<object> argValues = new List<object>();
public Dictionary<string, JObject> memberAccessValues = new Dictionary<string, JObject>();
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved
private int visitCount;

public void VisitInternal(SyntaxNode node)
{
Visit(node);
visitCount++;
}
public override void Visit(SyntaxNode node)
{
// TODO: PointerMemberAccessExpression
if (node is MemberAccessExpressionSyntax maes
&& node.Kind() == SyntaxKind.SimpleMemberAccessExpression
&& !(node.Parent is MemberAccessExpressionSyntax))
if (visitCount == 0)
{
memberAccesses.Add(maes);
}
if (node is MemberAccessExpressionSyntax maes
&& node.Kind() == SyntaxKind.SimpleMemberAccessExpression
&& !(node.Parent is MemberAccessExpressionSyntax)
&& !(node.Parent is InvocationExpressionSyntax))
{
memberAccesses.Add(maes);
}

if (node is IdentifierNameSyntax identifier
&& !(identifier.Parent is MemberAccessExpressionSyntax)
&& !identifiers.Any(x => x.Identifier.Text == identifier.Identifier.Text))
{
identifiers.Add(identifier);
if (node is IdentifierNameSyntax identifier
&& !(identifier.Parent is MemberAccessExpressionSyntax)
&& !identifiers.Any(x => x.Identifier.Text == identifier.Identifier.Text))
{
identifiers.Add(identifier);
}
}

if (node is InvocationExpressionSyntax)
if (visitCount == 1)
{
methodCall.Add(node as InvocationExpressionSyntax);
throw new Exception("Method Call is not implemented yet");
if (node is InvocationExpressionSyntax)
{
methodCall.Add(node as InvocationExpressionSyntax);
}
}

if (node is AssignmentExpressionSyntax)
throw new Exception("Assignment is not implemented yet");
base.Visit(node);
}

public SyntaxTree ReplaceVars(SyntaxTree syntaxTree, IEnumerable<JObject> ma_values, IEnumerable<JObject> id_values)
public SyntaxTree ReplaceVars(SyntaxTree syntaxTree, IEnumerable<JObject> ma_values, IEnumerable<JObject> id_values, IEnumerable<JObject> method_values)
{
CompilationUnitSyntax root = syntaxTree.GetCompilationUnitRoot();
var memberAccessToParamName = new Dictionary<string, string>();
var methodCallToParamName = new Dictionary<string, string>();

CompilationUnitSyntax root = syntaxTree.GetCompilationUnitRoot();

// 1. Replace all this.a occurrences with this_a_ABDE
root = root.ReplaceNodes(memberAccesses, (maes, _) =>
Expand All @@ -77,25 +93,61 @@ public SyntaxTree ReplaceVars(SyntaxTree syntaxTree, IEnumerable<JObject> ma_val
return SyntaxFactory.IdentifierName(id_name);
});

// 1.1 Replace all this.a() occurrences with this_a_ABDE
root = root.ReplaceNodes(methodCall, (m, _) =>
{
string ies_str = m.ToString();
thaystg marked this conversation as resolved.
Show resolved Hide resolved
if (!methodCallToParamName.TryGetValue(ies_str, out string id_name))
{
// Generate a random suffix
string suffix = Guid.NewGuid().ToString().Substring(0, 5);
string prefix = ies_str.Trim().Replace(".", "_").Replace("(", "_").Replace(")", "_");
id_name = $"{prefix}_{suffix}";
methodCallToParamName[ies_str] = id_name;
}

return SyntaxFactory.IdentifierName(id_name);
});

var paramsSet = new HashSet<string>();

// 2. For every unique member ref, add a corresponding method param
foreach ((MemberAccessExpressionSyntax maes, JObject value) in memberAccesses.Zip(ma_values))
if (ma_values != null)
{
string node_str = maes.ToString();
if (!memberAccessToParamName.TryGetValue(node_str, out string id_name))
foreach ((MemberAccessExpressionSyntax maes, JObject value) in memberAccesses.Zip(ma_values))
{
throw new Exception($"BUG: Expected to find an id name for the member access string: {node_str}");
string node_str = maes.ToString();
if (!memberAccessToParamName.TryGetValue(node_str, out string id_name))
{
throw new Exception($"BUG: Expected to find an id name for the member access string: {node_str}");
}
memberAccessValues[id_name] = value;
root = UpdateWithNewMethodParam(root, id_name, value);
}
}

root = UpdateWithNewMethodParam(root, id_name, value);
if (id_values != null)
{
foreach ((IdentifierNameSyntax idns, JObject value) in identifiers.Zip(id_values))
{
root = UpdateWithNewMethodParam(root, idns.Identifier.Text, value);
}
}

foreach ((IdentifierNameSyntax idns, JObject value) in identifiers.Zip(id_values))
if (method_values != null)
{
root = UpdateWithNewMethodParam(root, idns.Identifier.Text, value);
foreach ((InvocationExpressionSyntax ies, JObject value) in methodCall.Zip(method_values))
{
string node_str = ies.ToString();
if (!methodCallToParamName.TryGetValue(node_str, out string id_name))
{
throw new Exception($"BUG: Expected to find an id name for the member access string: {node_str}");
}
root = UpdateWithNewMethodParam(root, id_name, value);
}
}


return syntaxTree.WithRootAndOptions(root, syntaxTree.Options);

CompilationUnitSyntax UpdateWithNewMethodParam(CompilationUnitSyntax root, string id_name, JObject value)
Expand Down Expand Up @@ -139,9 +191,7 @@ private object ConvertJSToCSharpType(JToken variable)
case "boolean":
return value?.Value<bool>();
case "object":
if (subType == "null")
return null;
break;
return null;
}
throw new Exception($"Evaluate of this datatype {type} not implemented yet");//, "Unsupported");
}
Expand All @@ -158,7 +208,8 @@ private string GetTypeFullName(JToken variable)
{
if (subType == "null")
return variable["className"].Value<string>();
break;
else
return "object";
}
default:
return value.GetType().FullName;
Expand Down Expand Up @@ -211,6 +262,20 @@ private static async Task<IList<JObject>> ResolveIdentifiers(IEnumerable<Identif
return values;
}

private static async Task<IList<JObject>> ResolveMethodCalls(IEnumerable<InvocationExpressionSyntax> methodCalls, Dictionary<string, JObject> memberAccessValues, MemberReferenceResolver resolver, CancellationToken token)
{
var values = new List<JObject>();
foreach (InvocationExpressionSyntax methodCall in methodCalls)
{
JObject value = await resolver.Resolve(methodCall, memberAccessValues, token);
if (value == null)
throw new ReturnAsErrorException($"Failed to resolve member access for {methodCall}", "ReferenceError");

values.Add(value);
}
return values;
}

[UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file",
Justification = "Suppressing the warning until gets fixed, see https://github.com/dotnet/runtime/issues/51202")]
internal static async Task<JObject> CompileAndRunTheExpression(string expression, MemberReferenceResolver resolver, CancellationToken token)
Expand All @@ -231,7 +296,7 @@ public static object Evaluate()
throw new Exception($"BUG: Unable to evaluate {expression}, could not get expression from the syntax tree");

FindVariableNMethodCall findVarNMethodCall = new FindVariableNMethodCall();
findVarNMethodCall.Visit(expressionTree);
findVarNMethodCall.VisitInternal(expressionTree);

// this fails with `"a)"`
// because the code becomes: return (a));
Expand All @@ -256,7 +321,16 @@ public static object Evaluate()

IList<JObject> identifierValues = await ResolveIdentifiers(findVarNMethodCall.identifiers, resolver, token);

syntaxTree = findVarNMethodCall.ReplaceVars(syntaxTree, memberAccessValues, identifierValues);
syntaxTree = findVarNMethodCall.ReplaceVars(syntaxTree, memberAccessValues, identifierValues, null);

expressionTree = GetExpressionFromSyntaxTree(syntaxTree);

findVarNMethodCall.VisitInternal(expressionTree);

IList<JObject> methodValues = await ResolveMethodCalls(findVarNMethodCall.methodCall, findVarNMethodCall.memberAccessValues, resolver, token);

syntaxTree = findVarNMethodCall.ReplaceVars(syntaxTree, null, null, methodValues);

expressionTree = GetExpressionFromSyntaxTree(syntaxTree);
if (expressionTree == null)
throw new Exception($"BUG: Unable to evaluate {expression}, could not get expression from the syntax tree");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.IO;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;

namespace Microsoft.WebAssembly.Diagnostics
{
Expand Down Expand Up @@ -126,5 +128,50 @@ public async Task<JObject> Resolve(string var_name, CancellationToken token)
return rootObject;
}

public async Task<JObject> Resolve(InvocationExpressionSyntax method, Dictionary<string, JObject> memberAccessValues, CancellationToken token)
{
JObject rootObject = null;

var expr = method.Expression;
var methodName = "";
if (expr is MemberAccessExpressionSyntax)
{
MemberAccessExpressionSyntax memberAccessExpressionSyntax = expr as MemberAccessExpressionSyntax;
thaystg marked this conversation as resolved.
Show resolved Hide resolved
rootObject = await Resolve(memberAccessExpressionSyntax.Expression.ToString(), token);
methodName = memberAccessExpressionSyntax.Name.ToString();
}
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);
Copy link
Member

Choose a reason for hiding this comment

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

proxy.sdbHelper could be local variable sdbHelper here for readability. Also I wonder if that's private field since it has lowercase name.

int method_id = await proxy.sdbHelper.GetMethodIdByName(sessionId, typeId[0], methodName, token);
var command_params_obj = new MemoryStream();
Copy link
Member

Choose a reason for hiding this comment

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

using pattern

var command_params_obj_writer = new MonoBinaryWriter(command_params_obj);
command_params_obj_writer.WriteObj(objectId, proxy.sdbHelper);
if (method.ArgumentList != null)
{
command_params_obj_writer.Write((int)method.ArgumentList.Arguments.Count);
foreach (var arg in method.ArgumentList.Arguments)
{
if (arg.Expression is LiteralExpressionSyntax)
{
if (!await command_params_obj_writer.WriteConst(sessionId, arg.Expression as LiteralExpressionSyntax, proxy.sdbHelper, token))
return null;
}
if (arg.Expression is IdentifierNameSyntax)
{
var argParm = arg.Expression as IdentifierNameSyntax;
if (!await command_params_obj_writer.WriteJsonValue(sessionId, memberAccessValues[argParm.Identifier.Text], proxy.sdbHelper, token))
return null;
}
}
}

var retMethod = await proxy.sdbHelper.InvokeMethod(sessionId, command_params_obj.ToArray(), method_id, "methodRet", token);
return await GetValueFromObject(retMethod, token);
}
return null;
}

}
}
96 changes: 96 additions & 0 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
using Newtonsoft.Json.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp;

namespace Microsoft.WebAssembly.Diagnostics
{
Expand Down Expand Up @@ -472,6 +474,88 @@ public void WriteObj(DotnetObjectId objectId, MonoSDBHelper sdbHelper)
Write(sdbHelper.valueTypes[int.Parse(objectId.Value)].valueTypeBuffer);
}
}
public async Task<bool> WriteConst(SessionId sessionId, LiteralExpressionSyntax constValue, MonoSDBHelper sdbHelper, CancellationToken token)
{
switch (constValue.Kind())
{
case SyntaxKind.NumericLiteralExpression:
{
Write((byte)ElementType.I4);
Write((int)constValue.Token.Value);
return true;
}
case SyntaxKind.StringLiteralExpression:
{
int stringId = await sdbHelper.CreateString(sessionId, (string)constValue.Token.Value, token);
Write((byte)ElementType.String);
Write((int)stringId);
return true;
}
case SyntaxKind.TrueLiteralExpression:
{
Write((byte)ElementType.Boolean);
Write((int)1);
return true;
}
case SyntaxKind.FalseLiteralExpression:
{
Write((byte)ElementType.Boolean);
Write((int)0);
return true;
}
case SyntaxKind.NullLiteralExpression:
{
Write((byte)ValueTypeId.Null);
Write((byte)0); //not used
Write((int)0); //not used
return true;
}
case SyntaxKind.CharacterLiteralExpression:
{
Write((byte)ElementType.Char);
Write((int)(char)constValue.Token.Value);
return true;
}
}
return false;
}

public async Task<bool> WriteJsonValue(SessionId sessionId, JObject objValue, MonoSDBHelper sdbHelper, CancellationToken token)
{
switch (objValue["type"].Value<string>())
{
case "number":
{
Write((byte)ElementType.I4);
Write(objValue["value"].Value<int>());
return true;
}
case "string":
{
int stringId = await sdbHelper.CreateString(sessionId, objValue["value"].Value<string>(), token);
Write((byte)ElementType.String);
Write((int)stringId);
return true;
}
case "boolean":
{
Write((byte)ElementType.Boolean);
if (objValue["value"].Value<bool>())
Write((int)1);
else
Write((int)0);
return true;
}
case "object":
{
Console.WriteLine(objValue);
thaystg marked this conversation as resolved.
Show resolved Hide resolved
DotnetObjectId.TryParse(objValue["objectId"]?.Value<string>(), out DotnetObjectId objectId);
WriteObj(objectId, sdbHelper);
return true;
}
}
return false;
}
}
internal class FieldTypeClass
{
Expand Down Expand Up @@ -612,6 +696,18 @@ internal async Task<MonoBinaryReader> SendDebuggerAgentCommandWithParmsInternal(
return ret_debugger_cmd_reader;
}

public async Task<int> CreateString(SessionId sessionId, string value, CancellationToken token)
{
var command_params = new MemoryStream();
Copy link
Member

Choose a reason for hiding this comment

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

using keyword and camelCase in this C# method.

var command_params_writer = new MonoBinaryWriter(command_params);
var ret_debugger_cmd_reader = await SendDebuggerAgentCommand<CmdAppDomain>(sessionId, CmdAppDomain.GetRootDomain, command_params, token);
var root_domain = ret_debugger_cmd_reader.ReadInt32();
command_params_writer.Write(root_domain);
command_params_writer.WriteString(value);
ret_debugger_cmd_reader = await SendDebuggerAgentCommand<CmdAppDomain>(sessionId, CmdAppDomain.CreateString, command_params, token);
return ret_debugger_cmd_reader.ReadInt32();
}

public async Task<int> GetMethodToken(SessionId sessionId, int method_id, CancellationToken token)
{
var command_params = new MemoryStream();
Expand Down
Loading