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 all 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
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>();
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved
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