From 2bef4557b8a8c5193f6aaeb46c8b2d87a28018a5 Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Tue, 8 Aug 2023 14:48:35 -0300 Subject: [PATCH 1/5] Support sum an object and a string --- .../BrowserDebugProxy/EvaluateExpression.cs | 34 ++++++++++++++++--- .../BrowserDebugProxy/JObjectValueCreator.cs | 2 +- .../MemberReferenceResolver.cs | 11 +++--- .../BrowserDebugProxy/MonoSDBHelper.cs | 4 +-- .../BrowserDebugProxy/ValueTypeClass.cs | 4 +-- .../EvaluateOnCallFrame2Tests.cs | 20 ++++++++--- .../debugger-test/debugger-evaluate-test.cs | 13 +++++++ 7 files changed, 70 insertions(+), 18 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs index 455517f8734331..2482290cfb7fdb 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs @@ -47,7 +47,8 @@ private sealed partial class ExpressionSyntaxReplacer : CSharpSyntaxWalker private int visitCount; public bool hasMethodCalls; public bool hasElementAccesses; - internal List variableDefinitions = new List(); + public bool hasExpressionStatement; + internal List<(string idName, JObject obj, string definition)> variableDefinitions = new (); public void VisitInternal(SyntaxNode node) { @@ -92,6 +93,9 @@ public override void Visit(SyntaxNode node) hasElementAccesses = true; } + if (node is BinaryExpressionSyntax) + hasExpressionStatement = true; + if (node is AssignmentExpressionSyntax) throw new Exception("Assignment is not implemented yet"); base.Visit(node); @@ -214,7 +218,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 +449,32 @@ internal static async Task 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"); - - return await EvaluateSimpleExpression(resolver, syntaxTree.ToString(), expression, replacer.variableDefinitions, logger, token); + var variableDef = await GetVariableDefinitions(resolver, replacer.variableDefinitions, replacer.hasExpressionStatement, token); + return await EvaluateSimpleExpression(resolver, syntaxTree.ToString(), expression, variableDef, logger, token); + } + internal static async Task> GetVariableDefinitions(MemberReferenceResolver resolver, List<(string idName, JObject obj, string definition)> variableDefinitions, bool callToString, CancellationToken token) + { + var variableDef = new List(); + foreach ( var definition in variableDefinitions ) + { + if (!callToString || definition.obj?["type"]?.Value() != "object") + { + variableDef.Add(definition.definition); + continue; + } + if (DotnetObjectId.TryParse(definition.obj?["objectId"]?.Value(), out DotnetObjectId objectId)) + { + 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, invokeObjectToString: true, token); + variableDef.Add($"string {definition.idName} = \"{toString}\";"); + } + else + variableDef.Add(definition.definition); + } + return variableDef; } internal static async Task EvaluateSimpleExpression( diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs b/src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs index 3df1c9ba22b140..1f1157393c069c 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs @@ -297,7 +297,7 @@ private async Task 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, invokeObjectToString: false, token); if (toString != null) description = toString; } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs index 73bc4640bcd92d..0bf634089676ae 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs @@ -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 ResolveAsInstanceMember(ArraySegment parts, JObject } } - public async Task Resolve(ElementAccessExpressionSyntax elementAccess, Dictionary memberAccessValues, JObject indexObject, List variableDefinitions, CancellationToken token) + public async Task Resolve(ElementAccessExpressionSyntax elementAccess, Dictionary memberAccessValues, JObject indexObject, List<(string, JObject, string)> variableDefinitions, CancellationToken token) { try { @@ -413,9 +414,10 @@ public async Task 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, 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."); @@ -502,7 +504,8 @@ async Task GetElementIndexInfo() else { string expression = arg.ToString(); - indexObject = await ExpressionEvaluator.EvaluateSimpleExpression(this, expression, expression, variableDefinitions, logger, token); + var variableDef = await ExpressionEvaluator.GetVariableDefinitions(this, variableDefinitions, false, token); + indexObject = await ExpressionEvaluator.EvaluateSimpleExpression(this, expression, expression, variableDef, logger, token); string idxType = indexObject["type"].Value(); if (idxType != "number") throw new InvalidOperationException($"Cannot index with an object of type '{idxType}'"); diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index f8023955317fda..9b0c52baf3314f 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -1933,14 +1933,14 @@ public Task InvokeMethod(DotnetObjectId dotnetObjectId, CancellationTok : throw new ArgumentException($"Cannot invoke method with id {methodId} on {dotnetObjectId}", nameof(dotnetObjectId)); } - public async Task InvokeToStringAsync(IEnumerable typeIds, bool isValueType, bool isEnum, int objectId, BindingFlags extraFlags, CancellationToken token) + public async Task InvokeToStringAsync(IEnumerable typeIds, bool isValueType, bool isEnum, int objectId, BindingFlags extraFlags, bool invokeObjectToString, 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" && !invokeObjectToString)) continue; Microsoft.WebAssembly.Diagnostics.MethodInfo methodInfo = typeInfo.Info.Methods.FirstOrDefault(m => m.Name == "ToString"); if (isEnum != true && methodInfo == null) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs index 0faca91f6713d9..66256ffd877eac 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs @@ -116,7 +116,7 @@ public async Task 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, invokeObjectToString: false, token); if (toString == null) sdbAgent.logger.LogDebug($"Error while evaluating ToString method on typeId = {TypeId}"); else @@ -133,7 +133,7 @@ public async Task 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, invokeObjectToString: false, token); if (toString != null) description = toString; } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrame2Tests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrame2Tests.cs index 25d77ed2285ef2..2a9e745fb41e26 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrame2Tests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrame2Tests.cs @@ -693,10 +693,10 @@ public async Task EvaluateStringProperties() => await CheckInspectLocalsAtBreakp { var id = pause_location["callFrames"][0]["callFrameId"].Value(); await EvaluateOnCallFrameAndCheck(id, - ("localString.Length", TNumber(5)), - ("localString[1]", TChar('B')), - ("instance.str.Length", TNumber(5)), - ("instance.str[3]", TChar('c')) + //("localString.Length", TNumber(5)), + ("localString[1]", TChar('B')) + //("instance.str.Length", TNumber(5)), + //("instance.str[3]", TChar('c')) ); }); @@ -711,5 +711,17 @@ await EvaluateOnCallFrameAndCheck(id, ("EvaluateStaticGetterInValueType.A", TNumber(5)) ); }); + + [Fact] + public async Task EvaluateSumBetweenObjectAndString() => await CheckInspectLocalsAtBreakpointSite( + $"DebuggerTests.SumObjectAndString", "run", 3, "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(); + await EvaluateOnCallFrameAndCheck(id, + ("myList+\"asd\"", TString("System.Collections.Generic.List`1[System.Int32]asd")) + ); + }); } } diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs index ac0785c437bc1a..f62eef79576398 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs @@ -2156,3 +2156,16 @@ public struct EvaluateStaticGetterInValueType { public static int A => 5; } + +namespace DebuggerTests +{ + public class SumObjectAndString + { + public static void run() + { + List myList = new(); + myList.Add(1); + Console.WriteLine(myList); + } + } +} \ No newline at end of file From 40cc3b4eb5ee7700a53aebde96500d3b9ce290bf Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Tue, 8 Aug 2023 14:54:07 -0300 Subject: [PATCH 2/5] Update src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs Co-authored-by: Ankit Jain --- src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs index 2482290cfb7fdb..1590f37242e615 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs @@ -455,6 +455,7 @@ internal static async Task CompileAndRunTheExpression( var variableDef = await GetVariableDefinitions(resolver, replacer.variableDefinitions, replacer.hasExpressionStatement, token); return await EvaluateSimpleExpression(resolver, syntaxTree.ToString(), expression, variableDef, logger, token); } + internal static async Task> GetVariableDefinitions(MemberReferenceResolver resolver, List<(string idName, JObject obj, string definition)> variableDefinitions, bool callToString, CancellationToken token) { var variableDef = new List(); From 8147aee43fb2706636dd5200dd1362e20f29aab7 Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Fri, 11 Aug 2023 11:00:57 -0300 Subject: [PATCH 3/5] Apply suggestions from code review Co-authored-by: Ankit Jain --- .../wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs index 1590f37242e615..80adbf7f710ddf 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs @@ -459,7 +459,7 @@ internal static async Task CompileAndRunTheExpression( internal static async Task> GetVariableDefinitions(MemberReferenceResolver resolver, List<(string idName, JObject obj, string definition)> variableDefinitions, bool callToString, CancellationToken token) { var variableDef = new List(); - foreach ( var definition in variableDefinitions ) + foreach (var definition in variableDefinitions) { if (!callToString || definition.obj?["type"]?.Value() != "object") { @@ -473,7 +473,9 @@ internal static async Task> GetVariableDefinitions(MemberReferenceR variableDef.Add($"string {definition.idName} = \"{toString}\";"); } else + { variableDef.Add(definition.definition); + } } return variableDef; } From c0561b26e331ef14c1623cbd62dc88bc630f1fab Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Fri, 11 Aug 2023 11:26:05 -0300 Subject: [PATCH 4/5] Addressing @radical comments --- .../BrowserDebugProxy/EvaluateExpression.cs | 27 +++++++++++-------- .../BrowserDebugProxy/JObjectValueCreator.cs | 2 +- .../MemberReferenceResolver.cs | 6 ++--- .../BrowserDebugProxy/MonoSDBHelper.cs | 4 +-- .../BrowserDebugProxy/ValueTypeClass.cs | 4 +-- .../EvaluateOnCallFrame2Tests.cs | 6 ++--- 6 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs index 80adbf7f710ddf..d110bab7879aa7 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs @@ -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 script = CSharpScript.Create( @@ -48,7 +53,7 @@ private sealed partial class ExpressionSyntaxReplacer : CSharpSyntaxWalker public bool hasMethodCalls; public bool hasElementAccesses; public bool hasExpressionStatement; - internal List<(string idName, JObject obj, string definition)> variableDefinitions = new (); + internal List variableDefinitions = new (); public void VisitInternal(SyntaxNode node) { @@ -452,32 +457,32 @@ internal static async Task CompileAndRunTheExpression( 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, replacer.hasExpressionStatement, token); + var variableDef = await GetVariableDefinitions(resolver, replacer.variableDefinitions, invokeToStringInObject: replacer.hasExpressionStatement, token); return await EvaluateSimpleExpression(resolver, syntaxTree.ToString(), expression, variableDef, logger, token); } - internal static async Task> GetVariableDefinitions(MemberReferenceResolver resolver, List<(string idName, JObject obj, string definition)> variableDefinitions, bool callToString, CancellationToken token) + internal static async Task> GetVariableDefinitions(MemberReferenceResolver resolver, List variableDefinitions, bool invokeToStringInObject, CancellationToken token) { - var variableDef = new List(); + var variableDefStrings = new List(); foreach (var definition in variableDefinitions) { - if (!callToString || definition.obj?["type"]?.Value() != "object") + if (!invokeToStringInObject || definition.Obj?["type"]?.Value() != "object") { - variableDef.Add(definition.definition); + variableDefStrings.Add(definition.Definition); continue; } - if (DotnetObjectId.TryParse(definition.obj?["objectId"]?.Value(), out DotnetObjectId objectId)) + if (DotnetObjectId.TryParse(definition.Obj?["objectId"]?.Value(), out DotnetObjectId objectId)) { 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, invokeObjectToString: true, token); - variableDef.Add($"string {definition.idName} = \"{toString}\";"); + 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 { - variableDef.Add(definition.definition); + variableDefStrings.Add(definition.Definition); } } - return variableDef; + return variableDefStrings; } internal static async Task EvaluateSimpleExpression( diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs b/src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs index 1f1157393c069c..6fe370a0da62af 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs @@ -297,7 +297,7 @@ private async Task ReadAsObjectValue(MonoBinaryReader retDebuggerCmdRea } else { - var toString = await _sdbAgent.InvokeToStringAsync(typeIds, isValueType: false, isEnum: false, objectId, BindingFlags.DeclaredOnly, invokeObjectToString: false, token); + var toString = await _sdbAgent.InvokeToStringAsync(typeIds, isValueType: false, isEnum: false, objectId, BindingFlags.DeclaredOnly, invokeToStringInObject: false, token); if (toString != null) description = toString; } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs index 0bf634089676ae..480f5dbc54212c 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs @@ -366,7 +366,7 @@ async Task ResolveAsInstanceMember(ArraySegment parts, JObject } } - public async Task Resolve(ElementAccessExpressionSyntax elementAccess, Dictionary memberAccessValues, JObject indexObject, List<(string, JObject, string)> variableDefinitions, CancellationToken token) + public async Task Resolve(ElementAccessExpressionSyntax elementAccess, Dictionary memberAccessValues, JObject indexObject, List variableDefinitions, CancellationToken token) { try { @@ -416,7 +416,7 @@ public async Task Resolve(ElementAccessExpressionSyntax elementAccess, var eaExpressionFormatted = elementAccessStrExpression.Replace('.', '_'); // instance_str variableDefinitions.Add(new (eaExpressionFormatted, rootObject, ExpressionEvaluator.ConvertJSToCSharpLocalVariableAssignment(eaExpressionFormatted, rootObject))); var eaFormatted = elementAccessStr.Replace('.', '_'); // instance_str[1] - var variableDef = await ExpressionEvaluator.GetVariableDefinitions(this, variableDefinitions, false, 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) @@ -504,7 +504,7 @@ async Task GetElementIndexInfo() else { string expression = arg.ToString(); - var variableDef = await ExpressionEvaluator.GetVariableDefinitions(this, variableDefinitions, false, 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(); if (idxType != "number") diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index 9b0c52baf3314f..0e261eac2b2da6 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -1933,14 +1933,14 @@ public Task InvokeMethod(DotnetObjectId dotnetObjectId, CancellationTok : throw new ArgumentException($"Cannot invoke method with id {methodId} on {dotnetObjectId}", nameof(dotnetObjectId)); } - public async Task InvokeToStringAsync(IEnumerable typeIds, bool isValueType, bool isEnum, int objectId, BindingFlags extraFlags, bool invokeObjectToString, CancellationToken token) + public async Task InvokeToStringAsync(IEnumerable 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" && !invokeObjectToString)) + 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) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs index 66256ffd877eac..9ca80c5c5b16ec 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs @@ -116,7 +116,7 @@ public async Task 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, invokeObjectToString: false, 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 @@ -133,7 +133,7 @@ public async Task ToJObject(MonoSDBHelper sdbAgent, bool forDebuggerDis } else { - var toString = await sdbAgent.InvokeToStringAsync(new int[]{ TypeId }, isValueType: true, IsEnum, Id.Value, IsEnum ? BindingFlags.Default : BindingFlags.DeclaredOnly, invokeObjectToString: false, 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; } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrame2Tests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrame2Tests.cs index 2a9e745fb41e26..5dd19e02d44903 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrame2Tests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrame2Tests.cs @@ -693,10 +693,10 @@ public async Task EvaluateStringProperties() => await CheckInspectLocalsAtBreakp { var id = pause_location["callFrames"][0]["callFrameId"].Value(); await EvaluateOnCallFrameAndCheck(id, - //("localString.Length", TNumber(5)), + ("localString.Length", TNumber(5)), ("localString[1]", TChar('B')) - //("instance.str.Length", TNumber(5)), - //("instance.str[3]", TChar('c')) + ("instance.str.Length", TNumber(5)), + ("instance.str[3]", TChar('c')) ); }); From e92e01d61723faeb8dc4d60cbf61328cd63942ea Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Fri, 11 Aug 2023 12:58:51 -0300 Subject: [PATCH 5/5] Adding more tests as suggested by @radical. --- .../BrowserDebugProxy/EvaluateExpression.cs | 30 +++++++++++++++---- .../EvaluateOnCallFrame2Tests.cs | 14 +++++++-- .../debugger-test/debugger-evaluate-test.cs | 12 ++++++++ 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs index d110bab7879aa7..c384f74e9c7223 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs @@ -52,7 +52,7 @@ private sealed partial class ExpressionSyntaxReplacer : CSharpSyntaxWalker private int visitCount; public bool hasMethodCalls; public bool hasElementAccesses; - public bool hasExpressionStatement; + public bool hasStringExpressionStatement; internal List variableDefinitions = new (); public void VisitInternal(SyntaxNode node) @@ -99,7 +99,11 @@ public override void Visit(SyntaxNode node) } if (node is BinaryExpressionSyntax) - hasExpressionStatement = true; + { + 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"); @@ -457,7 +461,7 @@ internal static async Task CompileAndRunTheExpression( 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.hasExpressionStatement, token); + var variableDef = await GetVariableDefinitions(resolver, replacer.variableDefinitions, invokeToStringInObject: replacer.hasStringExpressionStatement, token); return await EvaluateSimpleExpression(resolver, syntaxTree.ToString(), expression, variableDef, logger, token); } @@ -471,11 +475,25 @@ internal static async Task> GetVariableDefinitions(MemberReferenceR variableDefStrings.Add(definition.Definition); continue; } + + if (definition.Obj["subtype"]?.Value()?.Equals("null") == true) + { + variableDefStrings.Add($"string {definition.IdName} = \"\";"); + continue; + } + if (DotnetObjectId.TryParse(definition.Obj?["objectId"]?.Value(), out DotnetObjectId objectId)) { - 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}\";"); + if (objectId.IsValueType) + { + variableDefStrings.Add($"string {definition.IdName} = \"{definition.Obj["description"].Value()}\";"); + } + 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 { diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrame2Tests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrame2Tests.cs index 5dd19e02d44903..051da33469ce2d 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrame2Tests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrame2Tests.cs @@ -694,7 +694,7 @@ public async Task EvaluateStringProperties() => await CheckInspectLocalsAtBreakp var id = pause_location["callFrames"][0]["callFrameId"].Value(); await EvaluateOnCallFrameAndCheck(id, ("localString.Length", TNumber(5)), - ("localString[1]", TChar('B')) + ("localString[1]", TChar('B')), ("instance.str.Length", TNumber(5)), ("instance.str[3]", TChar('c')) ); @@ -714,13 +714,21 @@ await EvaluateOnCallFrameAndCheck(id, [Fact] public async Task EvaluateSumBetweenObjectAndString() => await CheckInspectLocalsAtBreakpointSite( - $"DebuggerTests.SumObjectAndString", "run", 3, "DebuggerTests.SumObjectAndString.run", + $"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(); await EvaluateOnCallFrameAndCheck(id, - ("myList+\"asd\"", TString("System.Collections.Generic.List`1[System.Int32]asd")) + ("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'") ); }); } diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs index f62eef79576398..480dc30115c430 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs @@ -2161,11 +2161,23 @@ namespace DebuggerTests { public class SumObjectAndString { + public class MyClass + { + public override string ToString() + { + return "OverridenToString"; + } + } public static void run() { + DateTime dt = new DateTime(); List myList = new(); + List listNull = null; + object o = new(); + MyClass myClass = new(); myList.Add(1); Console.WriteLine(myList); + Console.WriteLine(dt); } } } \ No newline at end of file