Skip to content

Commit

Permalink
[wasm] [debugger] Support method calls (dotnet#55458)
Browse files Browse the repository at this point in the history
* Implement method calls and method calls with simple parameters.

* removing useless change.

* Support const char.
Support object.

* Adding more tests as suggested by @pavelsavara.

* Adding a test calling a method that returns void and change an attribute value.

* Changing what @pavelsavara suggested.

* Fix merge.
  • Loading branch information
thaystg authored Jul 14, 2021
1 parent 559011c commit 599b7ac
Show file tree
Hide file tree
Showing 8 changed files with 945 additions and 508 deletions.
6 changes: 3 additions & 3 deletions src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -301,13 +301,13 @@ public DebugStore Store
}
}

public PerScopeCache GetCacheForScope(int scope_id)
public PerScopeCache GetCacheForScope(int scopeId)
{
if (perScopeCaches.TryGetValue(scope_id, out PerScopeCache cache))
if (perScopeCaches.TryGetValue(scopeId, out PerScopeCache cache))
return cache;

cache = new PerScopeCache();
perScopeCaches[scope_id] = cache;
perScopeCaches[scopeId] = cache;
return cache;
}

Expand Down
143 changes: 112 additions & 31 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>();
private int visitCount;
public bool hasMethodCalls;

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)
{
methodCall.Add(node as InvocationExpressionSyntax);
throw new Exception("Method Call is not implemented yet");
if (visitCount == 1)
methodCall.Add(node as InvocationExpressionSyntax);
hasMethodCalls = true;
}

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 iesStr = m.ToString();
if (!methodCallToParamName.TryGetValue(iesStr, out string id_name))
{
// Generate a random suffix
string suffix = Guid.NewGuid().ToString().Substring(0, 5);
string prefix = iesStr.Trim().Replace(".", "_").Replace("(", "_").Replace(")", "_");
id_name = $"{prefix}_{suffix}";
methodCallToParamName[iesStr] = 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,9 @@ private object ConvertJSToCSharpType(JToken variable)
case "boolean":
return value?.Value<bool>();
case "object":
if (subType == "null")
return null;
break;
return null;
case "void":
return null;
}
throw new Exception($"Evaluate of this datatype {type} not implemented yet");//, "Unsupported");
}
Expand All @@ -158,8 +210,11 @@ private string GetTypeFullName(JToken variable)
{
if (subType == "null")
return variable["className"].Value<string>();
break;
else
return "object";
}
case "void":
return "object";
default:
return value.GetType().FullName;
}
Expand Down Expand Up @@ -211,6 +266,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,17 +300,17 @@ 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));
// and the returned expression from GetExpressionFromSyntaxTree is `a`!
if (expressionTree.Kind() == SyntaxKind.IdentifierName || expressionTree.Kind() == SyntaxKind.ThisExpression)
{
string var_name = expressionTree.ToString();
JObject value = await resolver.Resolve(var_name, token);
string varName = expressionTree.ToString();
JObject value = await resolver.Resolve(varName, token);
if (value == null)
throw new ReturnAsErrorException($"Cannot find member named '{var_name}'.", "ReferenceError");
throw new ReturnAsErrorException($"Cannot find member named '{varName}'.", "ReferenceError");

return value;
}
Expand All @@ -256,7 +325,19 @@ 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);

if (findVarNMethodCall.hasMethodCalls)
{
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 Expand Up @@ -313,7 +394,7 @@ public static object Evaluate()
private static object ConvertCSharpToJSType(object v, ITypeSymbol type)
{
if (v == null)
return new { type = "object", subtype = "null", className = type.ToString() };
return new { type = "object", subtype = "null", className = type.ToString(), description = type.ToString() };

if (v is string s)
{
Expand Down
Loading

0 comments on commit 599b7ac

Please sign in to comment.