From 7a4f963ff78170d82dcd05fca54324a93fe34175 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 6 May 2022 10:32:36 +0200 Subject: [PATCH 01/10] Prepared tests for calling method on primitive types. --- .../EvaluateOnCallFrameTests.cs | 37 ++++++++++++++++++- .../debugger-test/debugger-evaluate-test.cs | 30 +++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 1b693c113443b8..83caa82dbbeb3d 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -1139,6 +1139,41 @@ await EvaluateOnCallFrameAndCheck(id, ("test.listToLinq.ToList()", TObject("System.Collections.Generic.List", description: "Count = 11")) ); }); - } + [Fact] + public async Task EvaluateMethodsOnPrimitiveTypesReturningPrimitives() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.PrimitiveTypeMethods", "Evaluate", 11, "Evaluate", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.PrimitiveTypeMethods:Evaluate'); })", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + await EvaluateOnCallFrameAndCheck(id, + ("test.propInt.ToString()", TString("12")), + ("test.propUint.ToString()", TString("12")), + ("test.propLong.ToString()", TString("12")), + ("test.propUlong.ToString()", TString("12")), + // ("test.propFloat.ToString()", TString("1.2345678")), // is comma, should be dot, fixed in PR #66979 + // ("test.propDouble.ToString()", TString("1.2345678910111213")), // is comma, should be dot + ("test.propBool.ToString()", TString("True")), + ("test.propChar.ToString()", TString("X")), + ("test.propString.ToString()", TString("s_t_r")), + ("test.propString.Split('*', 3, System.StringSplitOptions.RemoveEmptyEntries)", TObject("System.String[]")), + ("test.propString.EndsWith('r')", TBool(true)), + ("test.propString.StartsWith('S')", TBool(false)), + ("localInt.ToString()", TString("2")), + ("localUint.ToString()", TString("2")), + ("localLong.ToString()", TString("2")), + ("localUlong.ToString()", TString("2")), + // ("localFloat.ToString()", TString("0.2345678")), // is comma, should be dot + // ("localDouble.ToString()", TString("0.2345678910111213")), // is comma, should be dot + ("localBool.ToString()", TString("False")), + ("localBool.GetHashCode()", TNumber(0)), + ("localBool.GetTypeCode()", TObject("System.TypeCode", "Boolean")), + ("localChar.ToString()", TString("Y")), + ("localString.ToString()", TString("S*T*R")), + ("localString.Split('*', 3, System.StringSplitOptions.TrimEntries)", TObject("System.String[]")), + ("localString.EndsWith('r')", TBool(false)), + ("localString.StartsWith('S')", TBool(true))); + }); + } } 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 ef663949f718c2..e7f2986f9db1a6 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 @@ -1371,6 +1371,36 @@ public static void Evaluate() var test = new TestClass(); } } + + public static class PrimitiveTypeMethods + { + public class TestClass + { + public int propInt = 12; + public uint propUint = 12; + public long propLong = 12; + public ulong propUlong = 12; + public float propFloat = 1.2345678f; + public double propDouble = 1.2345678910111213; + public bool propBool = true; + public char propChar = 'X'; + public string propString = "s_t_r"; + } + + public static void Evaluate() + { + var test = new TestClass(); + int localInt = 2; + uint localUint = 2; + long localLong = 2; + ulong localUlong = 2; + float localFloat = 0.2345678f; + double localDouble = 0.2345678910111213; + bool localBool = false; + char localChar = 'Y'; + string localString = "S*T*R"; + } + } } namespace DebuggerTestsV2 From 8aac92ee266d355a5e2486e93c41459ce5f8d606 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 6 May 2022 11:34:05 +0200 Subject: [PATCH 02/10] Added support for most methods on primitives. --- .../BrowserDebugProxy/EvaluateExpression.cs | 202 +++++++++++------- .../Firefox/FirefoxMonoProxy.cs | 2 +- .../MemberReferenceResolver.cs | 197 ++++++++--------- .../debugger/BrowserDebugProxy/MonoProxy.cs | 4 +- .../BrowserDebugProxy/MonoSDBHelper.cs | 3 +- .../EvaluateOnCallFrameTests.cs | 4 +- 6 files changed, 234 insertions(+), 178 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs index 00c551c36799df..e7c6bd3c9b2f3e 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs @@ -20,10 +20,11 @@ using Newtonsoft.Json.Linq; using System.Text.RegularExpressions; using System.Globalization; +using Microsoft.Extensions.Logging; namespace Microsoft.WebAssembly.Diagnostics { - internal static class EvaluateExpression + internal static class ExpressionEvaluator { internal static Script script = CSharpScript.Create( "", @@ -32,11 +33,11 @@ internal static class EvaluateExpression typeof(Enumerable).Assembly, typeof(JObject).Assembly )); - private sealed class FindVariableNMethodCall : CSharpSyntaxWalker + private sealed class ExpressionSyntaxReplacer : CSharpSyntaxWalker { private static Regex regexForReplaceVarName = new Regex(@"[^A-Za-z0-9_]", RegexOptions.Singleline); public List identifiers = new List(); - public List methodCall = new List(); + public List methodCalls = new List(); public List memberAccesses = new List(); public List elementAccess = new List(); public List argValues = new List(); @@ -78,7 +79,7 @@ public override void Visit(SyntaxNode node) if (node is InvocationExpressionSyntax) { if (visitCount == 1) - methodCall.Add(node as InvocationExpressionSyntax); + methodCalls.Add(node as InvocationExpressionSyntax); hasMethodCalls = true; } @@ -120,7 +121,7 @@ public SyntaxTree ReplaceVars(SyntaxTree syntaxTree, IEnumerable ma_val }); // 1.1 Replace all this.a() occurrences with this_a_ABDE - root = root.ReplaceNodes(methodCall, (m, _) => + root = root.ReplaceNodes(methodCalls, (m, _) => { string iesStr = m.ToString(); if (!methodCallToParamName.TryGetValue(iesStr, out string id_name)) @@ -166,6 +167,8 @@ public SyntaxTree ReplaceVars(SyntaxTree syntaxTree, IEnumerable ma_val memberAccessValues[id_name] = value; AddLocalVariableWithValue(id_name, value); } + // do not replace memberAccesses that were already replaced + memberAccesses = new List(); } if (id_values != null) @@ -178,7 +181,7 @@ public SyntaxTree ReplaceVars(SyntaxTree syntaxTree, IEnumerable ma_val if (method_values != null) { - foreach ((InvocationExpressionSyntax ies, JObject value) in methodCall.Zip(method_values)) + foreach ((InvocationExpressionSyntax ies, JObject value) in methodCalls.Zip(method_values)) { string node_str = ies.ToString(); if (!methodCallToParamName.TryGetValue(node_str, out string id_name)) @@ -220,7 +223,6 @@ private static string ConvertJSToCSharpLocalVariableAssignment(string idName, JT JToken value = variable["value"]; string type = variable["type"].Value(); string subType = variable["subtype"]?.Value(); - string objectId = variable["objectId"]?.Value(); switch (type) { case "string": @@ -247,75 +249,124 @@ private static string ConvertJSToCSharpLocalVariableAssignment(string idName, JT typeRet = "bool"; break; case "object": - valueRet = "Newtonsoft.Json.Linq.JObject.FromObject(new {" - + $"type = \"{type}\"" - + $", description = \"{variable["description"].Value()}\"" - + $", className = \"{variable["className"].Value()}\"" - + (subType != null ? $", subtype = \"{subType}\"" : "") - + (objectId != null ? $", objectId = \"{objectId}\"" : "") - + "})"; - typeRet = "object"; + if (variable["subtype"]?.Value() == "null") + { + (valueRet, typeRet) = GetNullObject(variable["className"]?.Value()); + } + else + { + if (!DotnetObjectId.TryParse(variable["objectId"], out DotnetObjectId objectId)) + throw new Exception($"Internal error: Cannot parse objectId for var {idName}, with value: {variable}"); + + switch (objectId?.Scheme) + { + case "valuetype" when variable["isEnum"]?.Value() == true: + typeRet = variable["className"]?.Value(); + // now we don't have value of the valueType accessible here, it's null + valueRet = $"({typeRet}) {value["value"].Value()}"; + break; + case "object": + default: + valueRet = "Newtonsoft.Json.Linq.JObject.FromObject(new {" + + $"type = \"{type}\"" + + $", description = \"{variable["description"].Value()}\"" + + $", className = \"{variable["className"].Value()}\"" + + (subType != null ? $", subtype = \"{subType}\"" : "") + + (objectId != null ? $", objectId = \"{objectId}\"" : "") + + "})"; + typeRet = "object"; + break; + } + } break; case "void": - valueRet = "Newtonsoft.Json.Linq.JObject.FromObject(new {" - + $"type = \"object\"," - + $"description = \"object\"," - + $"className = \"object\"," - + $"subtype = \"null\"" - + "})"; - typeRet = "object"; + (valueRet, typeRet) = GetNullObject("object"); break; default: throw new Exception($"Evaluate of this datatype {type} not implemented yet");//, "Unsupported"); } return $"{typeRet} {idName} = {valueRet};"; + + static (string, string) GetNullObject(string className = "object") + => ("Newtonsoft.Json.Linq.JObject.FromObject(new {" + + $"type = \"object\"," + + $"description = \"object\"," + + $"className = \"{className}\"," + + $"subtype = \"null\"" + + "})", + "object"); } } - private static async Task> ResolveMemberAccessExpressions(IEnumerable member_accesses, - MemberReferenceResolver resolver, CancellationToken token) + private static async Task> Resolve(IList collectionToResolve, MemberReferenceResolver resolver, + Func> resolutionFunc, CancellationToken token) { - var memberAccessValues = new List(); - foreach (MemberAccessExpressionSyntax maes in member_accesses) - { - string memberAccessString = maes.ToString(); - JObject value = await resolver.Resolve(memberAccessString, token); - if (value == null) - throw new ReturnAsErrorException($"Failed to resolve member access for {memberAccessString}", "ReferenceError"); - - memberAccessValues.Add(value); - } - - return memberAccessValues; + IList values = new List(); + foreach (T element in collectionToResolve) + values.Add(await resolutionFunc(element, resolver, token)); + return values; } - private static async Task> ResolveIdentifiers(IEnumerable identifiers, MemberReferenceResolver resolver, CancellationToken token) + private static async Task ResolveMemberAccessExpression(MemberAccessExpressionSyntax member_access, + MemberReferenceResolver resolver, CancellationToken token) { - var values = new List(); - foreach (IdentifierNameSyntax var in identifiers) - { - JObject value = await resolver.Resolve(var.Identifier.Text, token); - if (value == null) - throw new ReturnAsErrorException($"The name {var.Identifier.Text} does not exist in the current context", "ReferenceError"); - - values.Add(value); - } + string memberAccessString = member_access.ToString(); + JObject value = await resolver.Resolve(memberAccessString, token); + return value ?? throw new ReturnAsErrorException($"Failed to resolve member access for {memberAccessString}", "ReferenceError"); + } - return values; + private static async Task ResolveIdentifier(IdentifierNameSyntax identifier, + MemberReferenceResolver resolver, CancellationToken token) + { + JObject value = await resolver.Resolve(identifier.Identifier.Text, token); + return value ?? throw new ReturnAsErrorException($"The name {identifier.Identifier.Text} does not exist in the current context", "ReferenceError"); } - private static async Task> ResolveMethodCalls(IEnumerable methodCalls, Dictionary memberAccessValues, MemberReferenceResolver resolver, CancellationToken token) + private static async Task<(IList, IList, IList)> ResolveMethodCalls(ExpressionSyntaxReplacer replacer, MemberReferenceResolver resolver, CancellationToken token) { - var values = new List(); - foreach (InvocationExpressionSyntax methodCall in methodCalls) + var methodCallValues = new List(); + // used for replacing method call on primitive: + var maesValues = new List(); + var identifierValues = new List(); + InvocationExpressionSyntax[] methodCallsCopy = replacer.methodCalls.ToArray(); + foreach (InvocationExpressionSyntax methodCall in methodCallsCopy) { - JObject value = await resolver.Resolve(methodCall, memberAccessValues, token); + JObject value = await resolver.Resolve(methodCall, replacer.memberAccessValues, token); if (value == null) - throw new ReturnAsErrorException($"Failed to resolve member access for {methodCall}", "ReferenceError"); + { + await ReplaceMethodCall(methodCall); + continue; + } + methodCallValues.Add(value); + } + return (methodCallValues, maesValues, identifierValues); - values.Add(value); + async Task ReplaceMethodCall(InvocationExpressionSyntax method) + { + /* + Instead of invoking the method on the primitive type in the runtime, + we emit a local for the primitive, and emit the the method + itself in the script. For example: + double test_propUlong_2c64c = 12; + return (test_propUlong_2c64c.ToString()); + */ + replacer.methodCalls.Remove(method); + if (method.Expression is MemberAccessExpressionSyntax mses) + { + // primitive is a member field: + if (mses.Expression is MemberAccessExpressionSyntax msesExpr) + { + replacer.memberAccesses.Add(msesExpr); + maesValues.Add(await ResolveMemberAccessExpression(msesExpr, resolver, token)); + } + // primitive is a local value: + else if (mses.Expression is IdentifierNameSyntax identifierExpr) + { + replacer.identifiers.Add(identifierExpr); + identifierValues.Add(await ResolveIdentifier(identifierExpr, resolver, token)); + } + } } - return values; } private static async Task> ResolveElementAccess(IEnumerable elementAccesses, Dictionary memberAccessValues, MemberReferenceResolver resolver, CancellationToken token) @@ -332,7 +383,8 @@ private static async Task> ResolveElementAccess(IEnumerable CompileAndRunTheExpression(string expression, MemberReferenceResolver resolver, CancellationToken token) + internal static async Task CompileAndRunTheExpression( + string expression, MemberReferenceResolver resolver, ILogger logger, CancellationToken token) { expression = expression.Trim(); if (!expression.StartsWith('(')) @@ -344,8 +396,8 @@ internal static async Task CompileAndRunTheExpression(string expression SyntaxNode expressionTree = syntaxTree.GetCompilationUnitRoot(token); 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); + ExpressionSyntaxReplacer replacer = new ExpressionSyntaxReplacer(); + replacer.VisitInternal(expressionTree); // this fails with `"a)"` // because the code becomes: return (a)); // and the returned expression from GetExpressionFromSyntaxTree is `a`! @@ -359,55 +411,53 @@ internal static async Task CompileAndRunTheExpression(string expression return value; } - IList memberAccessValues = await ResolveMemberAccessExpressions(findVarNMethodCall.memberAccesses, resolver, token); + IList memberAccessValues = await Resolve(replacer.memberAccesses, resolver, ResolveMemberAccessExpression, token); + IList identifierValues = await Resolve(replacer.identifiers, resolver, ResolveIdentifier, token); + syntaxTree = replacer.ReplaceVars(syntaxTree, memberAccessValues, identifierValues, null, null); // eg. "this.dateTime", " dateTime.TimeOfDay" - if (expressionTree.Kind() == SyntaxKind.SimpleMemberAccessExpression && findVarNMethodCall.memberAccesses.Count == 1) + if (expressionTree.Kind() == SyntaxKind.SimpleMemberAccessExpression && replacer.memberAccesses.Count == 1) { return memberAccessValues[0]; } - IList identifierValues = await ResolveIdentifiers(findVarNMethodCall.identifiers, resolver, token); - - syntaxTree = findVarNMethodCall.ReplaceVars(syntaxTree, memberAccessValues, identifierValues, null, null); - - if (findVarNMethodCall.hasMethodCalls) + if (replacer.hasMethodCalls) { expressionTree = syntaxTree.GetCompilationUnitRoot(token); - findVarNMethodCall.VisitInternal(expressionTree); + replacer.VisitInternal(expressionTree); - IList methodValues = await ResolveMethodCalls(findVarNMethodCall.methodCall, findVarNMethodCall.memberAccessValues, resolver, token); - - syntaxTree = findVarNMethodCall.ReplaceVars(syntaxTree, null, null, methodValues, null); + (IList methodValues, IList newMemberAccessValues, IList newIdentifierValues) = + await ResolveMethodCalls(replacer, resolver, token); + syntaxTree = replacer.ReplaceVars(syntaxTree, newMemberAccessValues, newIdentifierValues, methodValues, null); } // eg. "elements[0]" - if (findVarNMethodCall.hasElementAccesses) + if (replacer.hasElementAccesses) { expressionTree = syntaxTree.GetCompilationUnitRoot(token); - findVarNMethodCall.VisitInternal(expressionTree); + replacer.VisitInternal(expressionTree); - IList elementAccessValues = await ResolveElementAccess(findVarNMethodCall.elementAccess, findVarNMethodCall.memberAccessValues, resolver, token); + IList elementAccessValues = await ResolveElementAccess(replacer.elementAccess, replacer.memberAccessValues, resolver, token); - syntaxTree = findVarNMethodCall.ReplaceVars(syntaxTree, null, null, null, elementAccessValues); + 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 newScript = script.ContinueWith( + string.Join("\n", replacer.variableDefinitions) + "\nreturn " + syntaxTree.ToString()); try { - var newScript = script.ContinueWith( - string.Join("\n", findVarNMethodCall.variableDefinitions) + "\nreturn " + syntaxTree.ToString()); - var state = await newScript.RunAsync(cancellationToken: token); return JObject.FromObject(ConvertCSharpToJSType(state.ReturnValue, state.ReturnValue?.GetType())); } catch (CompilationErrorException cee) { + logger.LogDebug($"Cannot evaluate '{expression}'. Script used to compile it: {newScript.Code}{Environment.NewLine}{cee.Message}"); throw new ReturnAsErrorException($"Cannot evaluate '{expression}': {cee.Message}", "CompilationError"); } catch (Exception ex) @@ -434,6 +484,8 @@ private static object ConvertCSharpToJSType(object v, Type type) return new { type = "symbol", value = c, description = $"{(int)c} '{c}'" }; if (NumericTypes.Contains(v.GetType())) return new { type = "number", value = v, description = Convert.ToDouble(v).ToString(CultureInfo.InvariantCulture) }; + if (v is bool) + return new { type = "boolean", value = v, description = v.ToString().ToLowerInvariant(), className = type.ToString() }; if (v is JObject) return v; return new { type = "object", value = v, description = v.ToString(), className = type.ToString() }; diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs index 5018e7ce1f5ff3..69e4fa6552a1e7 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs @@ -606,7 +606,7 @@ protected override async Task AcceptCommand(MessageId sessionId, JObject a var resolver = new MemberReferenceResolver(this, context, sessionId, scope.Id, logger); JObject retValue = await resolver.Resolve(args?["text"]?.Value(), token); if (retValue == null) - retValue = await EvaluateExpression.CompileAndRunTheExpression(args?["text"]?.Value(), resolver, token); + retValue = await ExpressionEvaluator.CompileAndRunTheExpression(args?["text"]?.Value(), resolver, logger, token); var osend = JObject.FromObject(new { type = "evaluationResult", diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs index 17690f88c9e0f8..0ae1c3b4445104 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs @@ -92,7 +92,7 @@ public async Task GetValueFromObject(JToken objRet, CancellationToken t if (typeId != -1) { - JObject memberObject = await FindStaticMemberInType(part, typeId); + JObject memberObject = await FindStaticMemberInType(classNameToFind, part, typeId); if (memberObject != null) { string remaining = null; @@ -124,7 +124,7 @@ public async Task GetValueFromObject(JToken objRet, CancellationToken t return (null, null); - async Task FindStaticMemberInType(string name, int typeId) + async Task FindStaticMemberInType(string classNameToFind, string name, int typeId) { var fields = await context.SdbAgent.GetTypeFields(typeId, token); foreach (var field in fields) @@ -137,9 +137,11 @@ async Task FindStaticMemberInType(string name, int typeId) { isInitialized = await context.SdbAgent.TypeInitialize(typeId, token); } - var valueRet = await context.SdbAgent.GetFieldValue(typeId, field.Id, token); - - return await GetValueFromObject(valueRet, token); + var staticFieldValue = await context.SdbAgent.GetFieldValue(typeId, field.Id, token); + var valueRet = await GetValueFromObject(staticFieldValue, token); + // we need the full name here + valueRet["className"] = classNameToFind; + return valueRet; } var methodId = await context.SdbAgent.GetPropertyMethodIdByName(typeId, name, token); @@ -375,125 +377,126 @@ public async Task Resolve(ElementAccessExpressionSyntax elementAccess, } } - public async Task Resolve(InvocationExpressionSyntax method, Dictionary memberAccessValues, CancellationToken token) + public async Task<(JObject, string)> ResolveInvokationInfo(InvocationExpressionSyntax method, CancellationToken token) { var methodName = ""; - bool isExtensionMethod = false; try { JObject rootObject = null; var expr = method.Expression; - if (expr is MemberAccessExpressionSyntax) + if (expr is MemberAccessExpressionSyntax memberAccessExpressionSyntax) { - var memberAccessExpressionSyntax = expr as MemberAccessExpressionSyntax; rootObject = await Resolve(memberAccessExpressionSyntax.Expression.ToString(), token); methodName = memberAccessExpressionSyntax.Name.ToString(); if (rootObject.IsNullValuedObject()) throw new ExpressionEvaluationFailedException($"Expression '{memberAccessExpressionSyntax}' evaluated to null"); } - else if (expr is IdentifierNameSyntax) + else if (expr is IdentifierNameSyntax && scopeCache.ObjectFields.TryGetValue("this", out JObject valueRet)) { - if (scopeCache.ObjectFields.TryGetValue("this", out JObject valueRet)) - { - rootObject = await GetValueFromObject(valueRet, token); - methodName = expr.ToString(); - } + rootObject = await GetValueFromObject(valueRet, token); + methodName = expr.ToString(); } + return (rootObject, methodName); + } + catch (Exception ex) when (ex is not (ExpressionEvaluationFailedException or ReturnAsErrorException)) + { + throw new Exception($"Unable to evaluate method '{methodName}'", ex); + } + } - if (rootObject != null) + private static readonly string[] primitiveTypes = new string[] { "string", "number", "boolean", "symbol" }; + + public async Task Resolve(InvocationExpressionSyntax method, Dictionary memberAccessValues, CancellationToken token) + { + (JObject rootObject, string methodName) = await ResolveInvokationInfo(method, token); + if (rootObject == null) + throw new ReturnAsErrorException($"Failed to resolve root object for {method}", "ReferenceError"); + + // primitives don't have objectId + if (!DotnetObjectId.TryParse(rootObject["objectId"]?.Value(), out DotnetObjectId objectId) && + primitiveTypes.Contains(rootObject["type"]?.Value())) + return null; + + if (method.ArgumentList == null) + throw new InternalErrorException($"Failed to resolve method call for {method}, list of arguments is null."); + + bool isExtensionMethod = false; + try + { + var typeIds = await context.SdbAgent.GetTypeIdsForObject(objectId.Value, true, token); + int methodId = await context.SdbAgent.GetMethodIdByName(typeIds[0], methodName, token); + var className = await context.SdbAgent.GetTypeNameOriginal(typeIds[0], token); + if (methodId == 0) //try to search on System.Linq.Enumerable + methodId = await FindMethodIdOnLinqEnumerable(typeIds, methodName); + + if (methodId == 0) + { + var typeName = await context.SdbAgent.GetTypeName(typeIds[0], token); + throw new ReturnAsErrorException($"Method '{methodName}' not found in type '{typeName}'", "ReferenceError"); + } + using var commandParamsObjWriter = new MonoBinaryWriter(); + if (!isExtensionMethod) { - if (!DotnetObjectId.TryParse(rootObject?["objectId"]?.Value(), out DotnetObjectId objectId)) - throw new ExpressionEvaluationFailedException($"Cannot invoke method '{methodName}' on invalid object id: {rootObject}"); + // instance method + commandParamsObjWriter.WriteObj(objectId, context.SdbAgent); + } - List typeIds; - if (objectId.IsValueType) + int passedArgsCnt = method.ArgumentList.Arguments.Count; + int methodParamsCnt = passedArgsCnt; + ParameterInfo[] methodParamsInfo = null; + var methodInfo = await context.SdbAgent.GetMethodInfo(methodId, token); + if (methodInfo != null) //FIXME: #65670 + { + methodParamsInfo = methodInfo.Info.GetParametersInfo(); + methodParamsCnt = methodParamsInfo.Length; + if (isExtensionMethod) { - if (!context.SdbAgent.valueTypes.TryGetValue(objectId.Value, out ValueTypeClass valueType)) - throw new Exception($"Could not find valuetype {objectId}"); - - typeIds = new List(1) { valueType.TypeId }; + // implicit *this* parameter + methodParamsCnt--; } - else + if (passedArgsCnt > methodParamsCnt) + throw new ReturnAsErrorException($"Unable to evaluate method '{methodName}'. Too many arguments passed.", "ArgumentError"); + } + + if (isExtensionMethod) + { + commandParamsObjWriter.Write(methodParamsCnt + 1); + commandParamsObjWriter.WriteObj(objectId, context.SdbAgent); + } + else + { + commandParamsObjWriter.Write(methodParamsCnt); + } + + int argIndex = 0; + // explicitly passed arguments + for (; argIndex < passedArgsCnt; argIndex++) + { + var arg = method.ArgumentList.Arguments[argIndex]; + if (arg.Expression is LiteralExpressionSyntax literal) { - typeIds = await context.SdbAgent.GetTypeIdsForObject(objectId.Value, true, token); + if (!await commandParamsObjWriter.WriteConst(literal, context.SdbAgent, token)) + throw new InternalErrorException($"Unable to evaluate method '{methodName}'. Unable to write LiteralExpressionSyntax into binary writer."); } - int methodId = await context.SdbAgent.GetMethodIdByName(typeIds[0], methodName, token); - var className = await context.SdbAgent.GetTypeNameOriginal(typeIds[0], token); - if (methodId == 0) //try to search on System.Linq.Enumerable - methodId = await FindMethodIdOnLinqEnumerable(typeIds, methodName); - - if (methodId == 0) { - var typeName = await context.SdbAgent.GetTypeName(typeIds[0], token); - throw new ExpressionEvaluationFailedException($"Method '{methodName}' not found in type '{typeName}'"); - } - using var commandParamsObjWriter = new MonoBinaryWriter(); - if (!isExtensionMethod) + else if (arg.Expression is IdentifierNameSyntax identifierName) { - // instance method - commandParamsObjWriter.WriteObj(objectId, context.SdbAgent); + if (!await commandParamsObjWriter.WriteJsonValue(memberAccessValues[identifierName.Identifier.Text], context.SdbAgent, token)) + throw new InternalErrorException($"Unable to evaluate method '{methodName}'. Unable to write IdentifierNameSyntax into binary writer."); } - - if (method.ArgumentList != null) + else { - int passedArgsCnt = method.ArgumentList.Arguments.Count; - int methodParamsCnt = passedArgsCnt; - ParameterInfo[] methodParamsInfo = null; - var methodInfo = await context.SdbAgent.GetMethodInfo(methodId, token); - if (methodInfo != null) //FIXME: #65670 - { - methodParamsInfo = methodInfo.Info.GetParametersInfo(); - methodParamsCnt = methodParamsInfo.Length; - if (isExtensionMethod) - { - // implicit *this* parameter - methodParamsCnt--; - } - if (passedArgsCnt > methodParamsCnt) - throw new ExpressionEvaluationFailedException($"Cannot invoke method '{method}' - too many arguments passed"); - } - - if (isExtensionMethod) - { - commandParamsObjWriter.Write(methodParamsCnt + 1); - commandParamsObjWriter.WriteObj(objectId, context.SdbAgent); - } - else - { - commandParamsObjWriter.Write(methodParamsCnt); - } - - int argIndex = 0; - // explicitly passed arguments - for (; argIndex < passedArgsCnt; argIndex++) - { - var arg = method.ArgumentList.Arguments[argIndex]; - if (arg.Expression is LiteralExpressionSyntax literal) - { - if (!await commandParamsObjWriter.WriteConst(literal, context.SdbAgent, token)) - throw new InternalErrorException($"Unable to write LiteralExpressionSyntax ({literal}) into binary writer."); - } - else if (arg.Expression is IdentifierNameSyntax identifierName) - { - if (!await commandParamsObjWriter.WriteJsonValue(memberAccessValues[identifierName.Identifier.Text], context.SdbAgent, token)) - throw new InternalErrorException($"Unable to write IdentifierNameSyntax ({identifierName}) into binary writer."); - } - else - { - throw new InternalErrorException($"Unable to write into binary writer, not recognized expression type: {arg.Expression.GetType().Name}"); - } - } - // optional arguments that were not overwritten - for (; argIndex < methodParamsCnt; argIndex++) - { - if (!await commandParamsObjWriter.WriteConst(methodParamsInfo[argIndex].TypeCode, methodParamsInfo[argIndex].Value, context.SdbAgent, token)) - throw new InternalErrorException($"Unable to write optional parameter {methodParamsInfo[argIndex].Name} value in method '{methodName}' to the mono buffer."); - } - var retMethod = await context.SdbAgent.InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, token); - return await GetValueFromObject(retMethod, token); + throw new InternalErrorException($"Unable to evaluate method '{methodName}'. Unable to write into binary writer, not recognized expression type: {arg.Expression.GetType().Name}"); } } - return null; + // optional arguments that were not overwritten + for (; argIndex < methodParamsCnt; argIndex++) + { + if (!await commandParamsObjWriter.WriteConst(methodParamsInfo[argIndex].TypeCode, methodParamsInfo[argIndex].Value, context.SdbAgent, token)) + throw new InternalErrorException($"Unable to write optional parameter {methodParamsInfo[argIndex].Name} value in method '{methodName}' to the mono buffer."); + } + var retMethod = await context.SdbAgent.InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, token); + return await GetValueFromObject(retMethod, token); } catch (Exception ex) when (ex is not (ExpressionEvaluationFailedException or ReturnAsErrorException)) { diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index 48af87882f7a5a..b42040c6f99c95 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -821,7 +821,7 @@ protected async Task EvaluateCondition(SessionId sessionId, ExecutionConte var resolver = new MemberReferenceResolver(this, context, sessionId, mono_frame.Id, logger); JObject retValue = await resolver.Resolve(condition, token); if (retValue == null) - retValue = await EvaluateExpression.CompileAndRunTheExpression(condition, resolver, token); + retValue = await ExpressionEvaluator.CompileAndRunTheExpression(condition, resolver, logger, token); if (retValue?["value"]?.Type == JTokenType.Boolean || retValue?["value"]?.Type == JTokenType.Integer || retValue?["value"]?.Type == JTokenType.Float) { @@ -1314,7 +1314,7 @@ private async Task OnEvaluateOnCallFrame(MessageId msg_id, int scopeId, st JObject retValue = await resolver.Resolve(expression, token); if (retValue == null) { - retValue = await EvaluateExpression.CompileAndRunTheExpression(expression, resolver, token); + retValue = await ExpressionEvaluator.CompileAndRunTheExpression(expression, resolver, logger, token); } if (retValue != null) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index a3e0fb51fafbab..b6d80ee7ccb787 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -1430,7 +1430,7 @@ public async Task GetValueFromDebuggerDisplayAttribute(DotnetObjectId do expr = "$\"" + dispAttrStr + "\""; JObject retValue = await resolver.Resolve(expr, token); if (retValue == null) - retValue = await EvaluateExpression.CompileAndRunTheExpression(expr, resolver, token); + retValue = await ExpressionEvaluator.CompileAndRunTheExpression(expr, resolver, logger, token); return retValue?["value"]?.Value(); } @@ -1918,6 +1918,7 @@ public async Task CreateJObjectForVariableValue(MonoBinaryReader retDeb { long initialPos = /*retDebuggerCmdReader == null ? 0 : */retDebuggerCmdReader.BaseStream.Position; ElementType etype = (ElementType)retDebuggerCmdReader.ReadByte(); + Console.WriteLine($"CreateJObjectForVariableValue {etype}"); JObject ret = null; switch (etype) { case ElementType.I: diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 83caa82dbbeb3d..e25889136a733e 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -1157,7 +1157,7 @@ await EvaluateOnCallFrameAndCheck(id, ("test.propBool.ToString()", TString("True")), ("test.propChar.ToString()", TString("X")), ("test.propString.ToString()", TString("s_t_r")), - ("test.propString.Split('*', 3, System.StringSplitOptions.RemoveEmptyEntries)", TObject("System.String[]")), + // ("test.propString.Split('*', 3, System.StringSplitOptions.RemoveEmptyEntries)", TObject("System.String[]")), //GetValueFromObject for enums fails ("test.propString.EndsWith('r')", TBool(true)), ("test.propString.StartsWith('S')", TBool(false)), ("localInt.ToString()", TString("2")), @@ -1171,7 +1171,7 @@ await EvaluateOnCallFrameAndCheck(id, ("localBool.GetTypeCode()", TObject("System.TypeCode", "Boolean")), ("localChar.ToString()", TString("Y")), ("localString.ToString()", TString("S*T*R")), - ("localString.Split('*', 3, System.StringSplitOptions.TrimEntries)", TObject("System.String[]")), + // ("localString.Split('*', 3, System.StringSplitOptions.TrimEntries)", TObject("System.String[]")), //GetValueFromObject for enums fails ("localString.EndsWith('r')", TBool(false)), ("localString.StartsWith('S')", TBool(true))); }); From 734656706617c91072bbba0e531d57a66234fded Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 6 May 2022 11:35:03 +0200 Subject: [PATCH 03/10] Corrected other tests - TBool is supported. --- .../DebuggerTestSuite/EvaluateOnCallFrameTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index e25889136a733e..45f03b73cf6975 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -1105,7 +1105,7 @@ await EvaluateOnCallFrameAndCheck(id, ("test.GetString()", TString("1.23")), ("test.GetUnicodeString()", TString("żółć")), - ("test.GetString(null)", TObject("string", is_null: true)), + ("test.GetString(null)", TString(null)), ("test.GetStringNullable()", TString("1.23")), ("test.GetSingle()", JObject.FromObject( new { type = "number", value = 1.23, description = "1.23" })), @@ -1113,9 +1113,9 @@ await EvaluateOnCallFrameAndCheck(id, ("test.GetSingleNullable()", JObject.FromObject( new { type = "number", value = 1.23, description = "1.23" })), ("test.GetDoubleNullable()", JObject.FromObject( new { type = "number", value = 1.23, description = "1.23" })), - ("test.GetBool()", JObject.FromObject( new { type = "object", value = true, description = "True", className = "System.Boolean" })), - ("test.GetBoolNullable()", JObject.FromObject( new { type = "object", value = true, description = "True", className = "System.Boolean" })), - ("test.GetNull()", JObject.FromObject( new { type = "object", value = true, description = "True", className = "System.Boolean" })), + ("test.GetBool()", TBool(true)), + ("test.GetBoolNullable()", TBool(true)), + ("test.GetNull()", TBool(true)), ("test.GetDefaultAndRequiredParam(2)", TNumber(5)), ("test.GetDefaultAndRequiredParam(3, 2)", TNumber(5)), From 12b80ecbd9399b53a1b6de1abe16203569fbb3d5 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 6 May 2022 11:37:48 +0200 Subject: [PATCH 04/10] Corrected test to match current exception texts. --- .../DebuggerTestSuite/EvaluateOnCallFrameTests.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 45f03b73cf6975..90d878e4fc47c0 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -506,19 +506,25 @@ public async Task EvaluateSimpleMethodCallsError() => await CheckInspectLocalsAt var id = pause_location["callFrames"][0]["callFrameId"].Value(); var (_, res) = await EvaluateOnCallFrame(id, "this.objToTest.MyMethodWrong()", expect_ok: false ); - Assert.Contains($"Method 'MyMethodWrong' not found", res.Error["message"]?.Value()); + Assert.Equal( + $"Method 'MyMethodWrong' not found in type 'DebuggerTests.EvaluateMethodTestsClass.ParmToTest'", + res.Error["exceptionDetails"]["exception"]["description"]?.Value()); (_, res) = await EvaluateOnCallFrame(id, "this.objToTest.MyMethod(1)", expect_ok: false); - Assert.Contains("Cannot invoke method 'this.objToTest.MyMethod(1)' - too many arguments passed", res.Error["message"]?.Value()); + Assert.Equal( + "Unable to evaluate method 'MyMethod'. Too many arguments passed.", + res.Error["exceptionDetails"]["exception"]["description"]?.Value()); (_, res) = await EvaluateOnCallFrame(id, "this.CallMethodWithParm(\"1\")", expect_ok: false ); Assert.Contains("Unable to evaluate method 'this.CallMethodWithParm(\"1\")'", res.Error["message"]?.Value()); (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjNull.MyMethod()", expect_ok: false ); - Assert.Contains("Expression 'this.ParmToTestObjNull.MyMethod' evaluated to null", res.Error["message"]?.Value()); + Assert.Equal("Expression 'this.ParmToTestObjNull.MyMethod' evaluated to null", res.Error["message"]?.Value()); (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjException.MyMethod()", expect_ok: false ); - Assert.Contains("Cannot invoke method 'MyMethod'", res.Error["message"]?.Value()); + Assert.Contains( + "Cannot evaluate '(this.ParmToTestObjException.MyMethod()\n)'", + res.Error["exceptionDetails"]["exception"]["description"]?.Value()); }); [Fact] From 2fef3c218b5bca5494797e1134da64e62d433418 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 6 May 2022 11:44:40 +0200 Subject: [PATCH 05/10] Fixed evaluation with enums. --- .../wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs | 1 - .../debugger/BrowserDebugProxy/ValueTypeClass.cs | 12 +++++++++--- .../DebuggerTestSuite/EvaluateOnCallFrameTests.cs | 4 ++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index b6d80ee7ccb787..7eb2cce06d5ffc 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -1918,7 +1918,6 @@ public async Task CreateJObjectForVariableValue(MonoBinaryReader retDeb { long initialPos = /*retDebuggerCmdReader == null ? 0 : */retDebuggerCmdReader.BaseStream.Position; ElementType etype = (ElementType)retDebuggerCmdReader.ReadByte(); - Console.WriteLine($"CreateJObjectForVariableValue {etype}"); JObject ret = null; switch (etype) { case ElementType.I: diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs index a9732a5224bcc6..1444e6d55d52dc 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs @@ -114,9 +114,15 @@ public async Task ToJObject(MonoSDBHelper sdbAgent, bool forDebuggerDis if (displayString != null) description = displayString; } - - var obj = MonoSDBHelper.CreateJObject(null, "object", description, false, className, Id.ToString(), null, null, true, true, IsEnum); - return obj; + return MonoSDBHelper.CreateJObject( + IsEnum ? fields[0]["value"] : null, + "object", + description, + false, + className, + Id.ToString(), + null, null, true, true, + IsEnum); } public async Task GetProxy(MonoSDBHelper sdbHelper, CancellationToken token) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 90d878e4fc47c0..390d3fd2e65607 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -1163,7 +1163,7 @@ await EvaluateOnCallFrameAndCheck(id, ("test.propBool.ToString()", TString("True")), ("test.propChar.ToString()", TString("X")), ("test.propString.ToString()", TString("s_t_r")), - // ("test.propString.Split('*', 3, System.StringSplitOptions.RemoveEmptyEntries)", TObject("System.String[]")), //GetValueFromObject for enums fails + ("test.propString.Split('*', 3, System.StringSplitOptions.RemoveEmptyEntries)", TObject("System.String[]")), ("test.propString.EndsWith('r')", TBool(true)), ("test.propString.StartsWith('S')", TBool(false)), ("localInt.ToString()", TString("2")), @@ -1177,7 +1177,7 @@ await EvaluateOnCallFrameAndCheck(id, ("localBool.GetTypeCode()", TObject("System.TypeCode", "Boolean")), ("localChar.ToString()", TString("Y")), ("localString.ToString()", TString("S*T*R")), - // ("localString.Split('*', 3, System.StringSplitOptions.TrimEntries)", TObject("System.String[]")), //GetValueFromObject for enums fails + ("localString.Split('*', 3, System.StringSplitOptions.TrimEntries)", TObject("System.String[]")), ("localString.EndsWith('r')", TBool(false)), ("localString.StartsWith('S')", TBool(true))); }); From 5567e885d3e04140c3fba2f3a6a19fdb6304afd8 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 6 May 2022 12:07:04 +0200 Subject: [PATCH 06/10] Fixed another error message to match. --- .../debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 390d3fd2e65607..9a227a701cb846 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -1131,7 +1131,9 @@ await EvaluateOnCallFrameAndCheck(id, ); var (_, res) = await EvaluateOnCallFrame(id, "test.GetDefaultAndRequiredParamMixedTypes(\"a\", 23, true, 1.23f)", expect_ok: false); - Assert.Contains("method 'test.GetDefaultAndRequiredParamMixedTypes(\"a\", 23, true, 1.23f)' - too many arguments passed", res.Error["message"]?.Value()); + Assert.Equal( + "Unable to evaluate method 'GetDefaultAndRequiredParamMixedTypes'. Too many arguments passed.", + res.Error["exceptionDetails"]["exception"]["description"]?.Value()); }); [Fact] From 0fd31a54fddd7ffebf2edf6aa477442ce2410348 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Mon, 9 May 2022 08:38:48 +0200 Subject: [PATCH 07/10] Fixed firefox test on EvaluateMethodsOnPrimitiveTypesReturningPrimitives. --- src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs index 884054c78963d1..e5712b232e191d 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs @@ -422,7 +422,7 @@ internal override async Task SetBreakpointInMethod(string assembly, stri description = res.Value["result"]["value"]["description"], objectId = actor }); - if (actor.StartsWith("dotnet:valuetype:")) + if (actor?.StartsWith("dotnet:valuetype:") == true) resObj["isValueType"] = true; return (resObj, res); } From cf05b8128263b8defef99ddff684720c231c85be Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Tue, 10 May 2022 08:51:32 +0200 Subject: [PATCH 08/10] Revert moving ContinueWith() out of the try block. --- .../wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs index e7c6bd3c9b2f3e..055e0fff7f392b 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs @@ -448,10 +448,11 @@ internal static async Task CompileAndRunTheExpression( if (expressionTree == null) throw new Exception($"BUG: Unable to evaluate {expression}, could not get expression from the syntax tree"); - var newScript = script.ContinueWith( - string.Join("\n", replacer.variableDefinitions) + "\nreturn " + syntaxTree.ToString()); + Script newScript = script; try { + newScript = script.ContinueWith( + string.Join("\n", replacer.variableDefinitions) + "\nreturn " + syntaxTree.ToString()); var state = await newScript.RunAsync(cancellationToken: token); return JObject.FromObject(ConvertCSharpToJSType(state.ReturnValue, state.ReturnValue?.GetType())); } From 95fbabb1ef74b8766e7c4ae9b2239237efda6ac0 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Tue, 10 May 2022 09:46:19 +0200 Subject: [PATCH 09/10] Removed comment that is not longer true after 2fef3c218b5bca5494797e1134da64e62d433418. --- src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs index 055e0fff7f392b..58d2ea827108d3 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs @@ -262,7 +262,6 @@ private static string ConvertJSToCSharpLocalVariableAssignment(string idName, JT { case "valuetype" when variable["isEnum"]?.Value() == true: typeRet = variable["className"]?.Value(); - // now we don't have value of the valueType accessible here, it's null valueRet = $"({typeRet}) {value["value"].Value()}"; break; case "object": From 335fe005086063c19e5999be7547720a8daa132f Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 13 May 2022 08:55:24 +0200 Subject: [PATCH 10/10] Applied radical's suggestions. --- .../debugger/BrowserDebugProxy/EvaluateExpression.cs | 12 ++++++------ .../BrowserDebugProxy/MemberReferenceResolver.cs | 6 +++--- .../DebuggerTestSuite/EvaluateOnCallFrameTests.cs | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs index 58d2ea827108d3..4d9369d1ad0ae6 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs @@ -306,10 +306,10 @@ private static async Task> Resolve(IList collectionToResolv return values; } - private static async Task ResolveMemberAccessExpression(MemberAccessExpressionSyntax member_access, + private static async Task ResolveMemberAccessExpression(MemberAccessExpressionSyntax memberAccess, MemberReferenceResolver resolver, CancellationToken token) { - string memberAccessString = member_access.ToString(); + string memberAccessString = memberAccess.ToString(); JObject value = await resolver.Resolve(memberAccessString, token); return value ?? throw new ReturnAsErrorException($"Failed to resolve member access for {memberAccessString}", "ReferenceError"); } @@ -323,10 +323,10 @@ private static async Task ResolveIdentifier(IdentifierNameSyntax identi private static async Task<(IList, IList, IList)> ResolveMethodCalls(ExpressionSyntaxReplacer replacer, MemberReferenceResolver resolver, CancellationToken token) { - var methodCallValues = new List(); + var methodCallValues = new List(capacity: replacer.methodCalls.Count); // used for replacing method call on primitive: - var maesValues = new List(); - var identifierValues = new List(); + var maesValues = new List(capacity: replacer.methodCalls.Count); + var identifierValues = new List(capacity: replacer.methodCalls.Count); InvocationExpressionSyntax[] methodCallsCopy = replacer.methodCalls.ToArray(); foreach (InvocationExpressionSyntax methodCall in methodCallsCopy) { @@ -344,7 +344,7 @@ async Task ReplaceMethodCall(InvocationExpressionSyntax method) { /* Instead of invoking the method on the primitive type in the runtime, - we emit a local for the primitive, and emit the the method + we emit a local for the primitive, and emit the the method call itself in the script. For example: double test_propUlong_2c64c = 12; return (test_propUlong_2c64c.ToString()); diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs index 0ae1c3b4445104..724953fab2ce85 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs @@ -392,9 +392,9 @@ public async Task Resolve(ElementAccessExpressionSyntax elementAccess, if (rootObject.IsNullValuedObject()) throw new ExpressionEvaluationFailedException($"Expression '{memberAccessExpressionSyntax}' evaluated to null"); } - else if (expr is IdentifierNameSyntax && scopeCache.ObjectFields.TryGetValue("this", out JObject valueRet)) + else if (expr is IdentifierNameSyntax && scopeCache.ObjectFields.TryGetValue("this", out JObject thisValue)) { - rootObject = await GetValueFromObject(valueRet, token); + rootObject = await GetValueFromObject(thisValue, token); methodName = expr.ToString(); } return (rootObject, methodName); @@ -446,7 +446,7 @@ public async Task Resolve(InvocationExpressionSyntax method, Dictionary int methodParamsCnt = passedArgsCnt; ParameterInfo[] methodParamsInfo = null; var methodInfo = await context.SdbAgent.GetMethodInfo(methodId, token); - if (methodInfo != null) //FIXME: #65670 + if (methodInfo != null) { methodParamsInfo = methodInfo.Info.GetParametersInfo(); methodParamsCnt = methodParamsInfo.Length; diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 9a227a701cb846..7a8133ba28eb70 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -508,12 +508,12 @@ public async Task EvaluateSimpleMethodCallsError() => await CheckInspectLocalsAt var (_, res) = await EvaluateOnCallFrame(id, "this.objToTest.MyMethodWrong()", expect_ok: false ); Assert.Equal( $"Method 'MyMethodWrong' not found in type 'DebuggerTests.EvaluateMethodTestsClass.ParmToTest'", - res.Error["exceptionDetails"]["exception"]["description"]?.Value()); + res.Error["result"]?["description"]?.Value()); (_, res) = await EvaluateOnCallFrame(id, "this.objToTest.MyMethod(1)", expect_ok: false); Assert.Equal( "Unable to evaluate method 'MyMethod'. Too many arguments passed.", - res.Error["exceptionDetails"]["exception"]["description"]?.Value()); + res.Error["result"]?["description"]?.Value()); (_, res) = await EvaluateOnCallFrame(id, "this.CallMethodWithParm(\"1\")", expect_ok: false ); Assert.Contains("Unable to evaluate method 'this.CallMethodWithParm(\"1\")'", res.Error["message"]?.Value()); @@ -524,7 +524,7 @@ public async Task EvaluateSimpleMethodCallsError() => await CheckInspectLocalsAt (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjException.MyMethod()", expect_ok: false ); Assert.Contains( "Cannot evaluate '(this.ParmToTestObjException.MyMethod()\n)'", - res.Error["exceptionDetails"]["exception"]["description"]?.Value()); + res.Error["result"]?["description"]?.Value()); }); [Fact]