Skip to content
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
58 changes: 54 additions & 4 deletions src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@

namespace Microsoft.WebAssembly.Diagnostics
{
internal sealed record VariableDefinition(
string IdName,
JObject Obj,
string Definition);

internal static partial class ExpressionEvaluator
{
internal static Script<object> script = CSharpScript.Create(
Expand All @@ -47,7 +52,8 @@ private sealed partial class ExpressionSyntaxReplacer : CSharpSyntaxWalker
private int visitCount;
public bool hasMethodCalls;
public bool hasElementAccesses;
internal List<string> variableDefinitions = new List<string>();
public bool hasStringExpressionStatement;
internal List<VariableDefinition> variableDefinitions = new ();

public void VisitInternal(SyntaxNode node)
{
Expand Down Expand Up @@ -92,6 +98,13 @@ public override void Visit(SyntaxNode node)
hasElementAccesses = true;
}

if (node is BinaryExpressionSyntax)
{
var binaryExpression = node as BinaryExpressionSyntax;
if (binaryExpression.Left.Kind() == SyntaxKind.StringLiteralExpression || binaryExpression.Right.Kind() == SyntaxKind.StringLiteralExpression)
hasStringExpressionStatement = true;
}

if (node is AssignmentExpressionSyntax)
throw new Exception("Assignment is not implemented yet");
base.Visit(node);
Expand Down Expand Up @@ -214,7 +227,7 @@ void AddLocalVariableWithValue(string idName, JObject value)
if (localsSet.Contains(idName))
return;
localsSet.Add(idName);
variableDefinitions.Add(ConvertJSToCSharpLocalVariableAssignment(idName, value));
variableDefinitions.Add(new (idName, value, ConvertJSToCSharpLocalVariableAssignment(idName, value)));
}
}
}
Expand Down Expand Up @@ -445,12 +458,49 @@ internal static async Task<JObject> CompileAndRunTheExpression(

syntaxTree = replacer.ReplaceVars(syntaxTree, null, null, null, elementAccessValues);
}

expressionTree = syntaxTree.GetCompilationUnitRoot(token);
if (expressionTree == null)
throw new Exception($"BUG: Unable to evaluate {expression}, could not get expression from the syntax tree");
var variableDef = await GetVariableDefinitions(resolver, replacer.variableDefinitions, invokeToStringInObject: replacer.hasStringExpressionStatement, token);
return await EvaluateSimpleExpression(resolver, syntaxTree.ToString(), expression, variableDef, logger, token);
}
Comment thread
thaystg marked this conversation as resolved.

internal static async Task<List<string>> GetVariableDefinitions(MemberReferenceResolver resolver, List<VariableDefinition> variableDefinitions, bool invokeToStringInObject, CancellationToken token)
{
var variableDefStrings = new List<string>();
foreach (var definition in variableDefinitions)
{
if (!invokeToStringInObject || definition.Obj?["type"]?.Value<string>() != "object")
{
variableDefStrings.Add(definition.Definition);
continue;
}

if (definition.Obj["subtype"]?.Value<string>()?.Equals("null") == true)
{
variableDefStrings.Add($"string {definition.IdName} = \"\";");
continue;
}

return await EvaluateSimpleExpression(resolver, syntaxTree.ToString(), expression, replacer.variableDefinitions, logger, token);
if (DotnetObjectId.TryParse(definition.Obj?["objectId"]?.Value<string>(), out DotnetObjectId objectId))
{
if (objectId.IsValueType)
{
variableDefStrings.Add($"string {definition.IdName} = \"{definition.Obj["description"].Value<string>()}\";");
}
else
{
var typeIds = await resolver.GetContext().SdbAgent.GetTypeIdsForObject(objectId.Value, withParents: true, token);
var toString = await resolver.GetContext().SdbAgent.InvokeToStringAsync(typeIds, isValueType: false, isEnum: false, objectId.Value, BindingFlags.DeclaredOnly, invokeToStringInObject: true, token);
variableDefStrings.Add($"string {definition.IdName} = \"{toString}\";");
}
}
else
{
variableDefStrings.Add(definition.Definition);
}
}
return variableDefStrings;
}

internal static async Task<JObject> EvaluateSimpleExpression(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ private async Task<JObject> ReadAsObjectValue(MonoBinaryReader retDebuggerCmdRea
}
else
{
var toString = await _sdbAgent.InvokeToStringAsync(typeIds, isValueType: false, isEnum: false, objectId, BindingFlags.DeclaredOnly, token);
var toString = await _sdbAgent.InvokeToStringAsync(typeIds, isValueType: false, isEnum: false, objectId, BindingFlags.DeclaredOnly, invokeToStringInObject: false, token);
if (toString != null)
description = toString;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ internal sealed class MemberReferenceResolver
private readonly ILogger logger;
private bool localsFetched;
private int linqTypeId;
public ExecutionContext GetContext() => context;

public MemberReferenceResolver(MonoProxy proxy, ExecutionContext ctx, SessionId sessionId, int scopeId, ILogger logger)
{
Expand Down Expand Up @@ -365,7 +366,7 @@ async Task<JObject> ResolveAsInstanceMember(ArraySegment<string> parts, JObject
}
}

public async Task<JObject> Resolve(ElementAccessExpressionSyntax elementAccess, Dictionary<string, JObject> memberAccessValues, JObject indexObject, List<string> variableDefinitions, CancellationToken token)
public async Task<JObject> Resolve(ElementAccessExpressionSyntax elementAccess, Dictionary<string, JObject> memberAccessValues, JObject indexObject, List<VariableDefinition> variableDefinitions, CancellationToken token)
{
try
{
Expand Down Expand Up @@ -413,9 +414,10 @@ public async Task<JObject> Resolve(ElementAccessExpressionSyntax elementAccess,
if (type == "string")
{
var eaExpressionFormatted = elementAccessStrExpression.Replace('.', '_'); // instance_str
variableDefinitions.Add(ExpressionEvaluator.ConvertJSToCSharpLocalVariableAssignment(eaExpressionFormatted, rootObject));
variableDefinitions.Add(new (eaExpressionFormatted, rootObject, ExpressionEvaluator.ConvertJSToCSharpLocalVariableAssignment(eaExpressionFormatted, rootObject)));
var eaFormatted = elementAccessStr.Replace('.', '_'); // instance_str[1]
return await ExpressionEvaluator.EvaluateSimpleExpression(this, eaFormatted, elementAccessStr, variableDefinitions, logger, token);
var variableDef = await ExpressionEvaluator.GetVariableDefinitions(this, variableDefinitions, invokeToStringInObject: false, token);
return await ExpressionEvaluator.EvaluateSimpleExpression(this, eaFormatted, elementAccessStr, variableDef, logger, token);
}
if (indexObject is null && elementIdxInfo.IndexingExpression is null)
throw new InternalErrorException($"Unable to write index parameter to invoke the method in the runtime.");
Expand Down Expand Up @@ -502,7 +504,8 @@ async Task<ElementIndexInfo> GetElementIndexInfo()
else
{
string expression = arg.ToString();
indexObject = await ExpressionEvaluator.EvaluateSimpleExpression(this, expression, expression, variableDefinitions, logger, token);
var variableDef = await ExpressionEvaluator.GetVariableDefinitions(this, variableDefinitions, invokeToStringInObject: false, token);
indexObject = await ExpressionEvaluator.EvaluateSimpleExpression(this, expression, expression, variableDef, logger, token);
string idxType = indexObject["type"].Value<string>();
if (idxType != "number")
throw new InvalidOperationException($"Cannot index with an object of type '{idxType}'");
Expand Down
4 changes: 2 additions & 2 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1933,14 +1933,14 @@ public Task<JObject> InvokeMethod(DotnetObjectId dotnetObjectId, CancellationTok
: throw new ArgumentException($"Cannot invoke method with id {methodId} on {dotnetObjectId}", nameof(dotnetObjectId));
}

public async Task<string> InvokeToStringAsync(IEnumerable<int> typeIds, bool isValueType, bool isEnum, int objectId, BindingFlags extraFlags, CancellationToken token)
public async Task<string> InvokeToStringAsync(IEnumerable<int> typeIds, bool isValueType, bool isEnum, int objectId, BindingFlags extraFlags, bool invokeToStringInObject, CancellationToken token)
{
try
{
foreach (var typeId in typeIds)
{
var typeInfo = await GetTypeInfo(typeId, token);
if (typeInfo == null || typeInfo.Name == "object")
if (typeInfo == null || (typeInfo.Name == "object" && !invokeToStringInObject))
continue;
Microsoft.WebAssembly.Diagnostics.MethodInfo methodInfo = typeInfo.Info.Methods.FirstOrDefault(m => m.Name == "ToString");
if (isEnum != true && methodInfo == null)
Expand Down
4 changes: 2 additions & 2 deletions src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public async Task<JObject> ToJObject(MonoSDBHelper sdbAgent, bool forDebuggerDis
string description = className;
if (ShouldAutoInvokeToString(className) || IsEnum)
{
var toString = await sdbAgent.InvokeToStringAsync(new int[]{ TypeId }, isValueType: true, IsEnum, Id.Value, IsEnum ? BindingFlags.Default : BindingFlags.DeclaredOnly, token);
var toString = await sdbAgent.InvokeToStringAsync(new int[]{ TypeId }, isValueType: true, IsEnum, Id.Value, IsEnum ? BindingFlags.Default : BindingFlags.DeclaredOnly, invokeToStringInObject: false, token);
if (toString == null)
sdbAgent.logger.LogDebug($"Error while evaluating ToString method on typeId = {TypeId}");
else
Expand All @@ -133,7 +133,7 @@ public async Task<JObject> ToJObject(MonoSDBHelper sdbAgent, bool forDebuggerDis
}
else
{
var toString = await sdbAgent.InvokeToStringAsync(new int[]{ TypeId }, isValueType: true, IsEnum, Id.Value, IsEnum ? BindingFlags.Default : BindingFlags.DeclaredOnly, token);
var toString = await sdbAgent.InvokeToStringAsync(new int[]{ TypeId }, isValueType: true, IsEnum, Id.Value, IsEnum ? BindingFlags.Default : BindingFlags.DeclaredOnly, invokeToStringInObject: false, token);
if (toString != null)
description = toString;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -711,5 +711,25 @@ await EvaluateOnCallFrameAndCheck(id,
("EvaluateStaticGetterInValueType.A", TNumber(5))
);
});

[Fact]
public async Task EvaluateSumBetweenObjectAndString() => await CheckInspectLocalsAtBreakpointSite(
$"DebuggerTests.SumObjectAndString", "run", 7, "DebuggerTests.SumObjectAndString.run",
$"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.SumObjectAndString:run'); 1 }})",
wait_for_event_fn: async (pause_location) =>
{
var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
await EvaluateOnCallFrameAndCheck(id,
("myList+\"asd\"", TString("System.Collections.Generic.List`1[System.Int32]asd")),
("dt+\"asd\"", TString("1/1/0001 12:00:00 AMasd")),
("myClass+\"asd\"", TString("OverridenToStringasd")),
("listNull+\"asd\"", TString("asd"))
);
await CheckEvaluateFail(id,
("myClass+dt", "Cannot evaluate '(myClass+dt\n)': (3,9): error CS0019: Operator '+' cannot be applied to operands of type 'object' and 'object'"),
("myClass+1", "Cannot evaluate '(myClass+1\n)': (2,9): error CS0019: Operator '+' cannot be applied to operands of type 'object' and 'int'"),
("dt+1", "Cannot evaluate '(dt+1\n)': (2,9): error CS0019: Operator '+' cannot be applied to operands of type 'object' and 'int'")
);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2156,3 +2156,28 @@ public struct EvaluateStaticGetterInValueType
{
public static int A => 5;
}

namespace DebuggerTests
{
public class SumObjectAndString
{
public class MyClass
{
public override string ToString()
{
return "OverridenToString";
}
}
public static void run()
{
DateTime dt = new DateTime();
List<int> myList = new();
List<int> listNull = null;
object o = new();
MyClass myClass = new();
myList.Add(1);
Console.WriteLine(myList);
Console.WriteLine(dt);
}
}
}