Skip to content
Merged
Next Next commit
Fix memory consumption.
  • Loading branch information
thaystg committed Nov 11, 2021
commit 72ad10820e980472ad14fe9b9ea26730a2674e8c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.7.0" />
Copy link
Member

Choose a reason for hiding this comment

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

For future PR, we should consider using the common version (more than one!) property for this, being used in other places in the repo.

<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.7" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.7.0" />
Expand Down
89 changes: 16 additions & 73 deletions src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Scripting;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

Expand All @@ -32,6 +34,7 @@ private class FindVariableNMethodCall : CSharpSyntaxWalker
private int visitCount;
public bool hasMethodCalls;
public bool hasElementAccesses;
internal string variableDefinition;

public void VisitInternal(SyntaxNode node)
{
Expand Down Expand Up @@ -193,26 +196,14 @@ public SyntaxTree ReplaceVars(SyntaxTree syntaxTree, IEnumerable<JObject> ma_val

CompilationUnitSyntax UpdateWithNewMethodParam(CompilationUnitSyntax root, string id_name, JObject value)
{
var classDeclaration = root.Members.ElementAt(0) as ClassDeclarationSyntax;
var method = classDeclaration.Members.ElementAt(0) as MethodDeclarationSyntax;

if (paramsSet.Contains(id_name))
{
// repeated member access expression
// eg. this.a + this.a
return root;
}

argValues.Add(ConvertJSToCSharpType(value));

MethodDeclarationSyntax updatedMethod = method.AddParameterListParameters(
SyntaxFactory.Parameter(
SyntaxFactory.Identifier(id_name))
.WithType(SyntaxFactory.ParseTypeName(GetTypeFullName(value))));

paramsSet.Add(id_name);
root = root.ReplaceNode(method, updatedMethod);

variableDefinition += $"{GetTypeFullName(value)} {id_name} = {ConvertJSToCSharpType(value)};\n";
return root;
}
}
Expand All @@ -226,7 +217,7 @@ private object ConvertJSToCSharpType(JToken variable)
switch (type)
{
case "string":
return value?.Value<string>();
return $"\"{value?.Value<string>()}\"";
case "number":
return value?.Value<double>();
case "boolean":
Expand Down Expand Up @@ -266,13 +257,8 @@ private string GetTypeFullName(JToken variable)
private static SyntaxNode GetExpressionFromSyntaxTree(SyntaxTree syntaxTree)
{
CompilationUnitSyntax root = syntaxTree.GetCompilationUnitRoot();
ClassDeclarationSyntax classDeclaration = root.Members.ElementAt(0) as ClassDeclarationSyntax;
MethodDeclarationSyntax methodDeclaration = classDeclaration.Members.ElementAt(0) as MethodDeclarationSyntax;
BlockSyntax blockValue = methodDeclaration.Body;
ReturnStatementSyntax returnValue = blockValue.Statements.ElementAt(0) as ReturnStatementSyntax;
ParenthesizedExpressionSyntax expressionParenthesized = returnValue.Expression as ParenthesizedExpressionSyntax;

return expressionParenthesized?.Expression;
return root;
}

private static async Task<IList<JObject>> ResolveMemberAccessExpressions(IEnumerable<MemberAccessExpressionSyntax> member_accesses,
Expand Down Expand Up @@ -335,32 +321,20 @@ private static async Task<IList<JObject>> ResolveElementAccess(IEnumerable<Eleme
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)
{
expression = expression.Trim();
if (!expression.StartsWith('('))
{
expression = "(" + expression + ")";
}
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(@"
using System;
public class CompileAndRunTheExpression
{
public static object Evaluate()
{
return " + expression + @";
}
}", cancellationToken: token);
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(expression + @";", cancellationToken: token);

SyntaxNode expressionTree = GetExpressionFromSyntaxTree(syntaxTree);
if (expressionTree == null)
throw new Exception($"BUG: Unable to evaluate {expression}, could not get expression from the syntax tree");

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

// this fails with `"a)"`
// because the code becomes: return (a));
// and the returned expression from GetExpressionFromSyntaxTree is `a`!
Expand Down Expand Up @@ -413,45 +387,14 @@ public static object Evaluate()
if (expressionTree == null)
throw new Exception($"BUG: Unable to evaluate {expression}, could not get expression from the syntax tree");

MetadataReference[] references = new MetadataReference[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location)
};

CSharpCompilation compilation = CSharpCompilation.Create(
"compileAndRunTheExpression",
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

SemanticModel semanticModel = compilation.GetSemanticModel(syntaxTree);
CodeAnalysis.TypeInfo TypeInfo = semanticModel.GetTypeInfo(expressionTree, cancellationToken: token);

using (var ms = new MemoryStream())
{
EmitResult result = compilation.Emit(ms, cancellationToken: token);
if (!result.Success)
{
var sb = new StringBuilder();
foreach (Diagnostic d in result.Diagnostics)
sb.Append(d.ToString());

throw new ReturnAsErrorException(sb.ToString(), "CompilationError");
}

ms.Seek(0, SeekOrigin.Begin);
Assembly assembly = Assembly.Load(ms.ToArray());
Type type = assembly.GetType("CompileAndRunTheExpression");

object ret = type.InvokeMember("Evaluate",
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public,
null,
null,
findVarNMethodCall.argValues.ToArray());

return JObject.FromObject(ConvertCSharpToJSType(ret, TypeInfo.Type));
}
var task = CSharpScript.EvaluateAsync(
findVarNMethodCall.variableDefinition + "return " + syntaxTree.ToString(),
ScriptOptions.Default.WithReferences(
typeof(object).Assembly,
typeof(Enumerable).Assembly
),
cancellationToken: token);
return JObject.FromObject(ConvertCSharpToJSType(task.Result, task.Result.GetType()));
}

private static readonly HashSet<Type> NumericTypes = new HashSet<Type>
Expand All @@ -462,7 +405,7 @@ public static object Evaluate()
typeof(float), typeof(double)
};

private static object ConvertCSharpToJSType(object v, ITypeSymbol type)
private static object ConvertCSharpToJSType(object v, Type type)
{
if (v == null)
return new { type = "object", subtype = "null", className = type.ToString(), description = type.ToString() };
Expand Down