未验证 提交 19e84e73 编写于 作者: T Thays Grazia 提交者: GitHub

[wasm][debugger]Evaluate a sum between an object and a string (#90175)

* Support sum an object and a string

* Update src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs
Co-authored-by: NAnkit Jain <radical@gmail.com>

* Apply suggestions from code review
Co-authored-by: NAnkit Jain <radical@gmail.com>

* Addressing @radical comments

* Adding more tests as suggested by @radical.

---------
Co-authored-by: NAnkit Jain <radical@gmail.com>
上级 c6f64abc
......@@ -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(
......@@ -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)
{
......@@ -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);
......@@ -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)));
}
}
}
......@@ -445,12 +458,49 @@ private static async Task<IList<JObject>> ResolveElementAccess(ExpressionSyntaxR
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);
}
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(
......
......@@ -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;
}
......
......@@ -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)
{
......@@ -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
{
......@@ -422,9 +423,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.");
......@@ -511,7 +513,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}'");
......
......@@ -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)
......
......@@ -130,7 +130,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
......@@ -147,7 +147,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;
}
......
......@@ -711,5 +711,25 @@ public async Task EvaluateLocalObjectFromAssemblyNotRelatedButLoaded()
("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'")
);
});
}
}
......@@ -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);
}
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册