From d61f0e7e088a52c5b182bcc71083a0e4e9055ff6 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 9 Jun 2022 15:54:08 +0200 Subject: [PATCH 1/9] Testcases. --- .../EvaluateOnCallFrameTests.cs | 25 +++++++++++++++++-- .../debugger-test/debugger-evaluate-test.cs | 14 +++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index a93d416f909df4..a5a86752219ecb 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -1149,7 +1149,7 @@ await EvaluateOnCallFrameAndCheck(id, [ConditionalFact(nameof(RunningOnChrome))] public async Task EvaluateNullObjectPropertiesPositive() => await CheckInspectLocalsAtBreakpointSite( - $"DebuggerTests.EvaluateNullableProperties", "Evaluate", 6, "Evaluate", + $"DebuggerTests.EvaluateNullableProperties", "Evaluate", 11, "Evaluate", $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.EvaluateNullableProperties:Evaluate'); 1 }})", wait_for_event_fn: async (pause_location) => { @@ -1168,7 +1168,11 @@ await EvaluateOnCallFrameAndCheck(id, ("tc!.MemberList!.Count", TNumber(2)), ("tc?.MemberListNull?.Count", TObject("System.Collections.Generic.List", is_null: true)), ("tc.MemberListNull?.Count", TObject("System.Collections.Generic.List", is_null: true)), - ("tcNull?.MemberListNull?.Count", TObject("DebuggerTests.EvaluateNullableProperties.TestClass", is_null: true))); + ("tcNull?.MemberListNull?.Count", TObject("DebuggerTests.EvaluateNullableProperties.TestClass", is_null: true)), + ("str!.Length", TNumber(9)), + ("str?.Length", TNumber(9)), + ("str_null?.Length", TObject("string", is_null: true)) + ); }); [ConditionalFact(nameof(RunningOnChrome))] @@ -1194,6 +1198,8 @@ public async Task EvaluateNullObjectPropertiesNegative() => await CheckInspectLo await CheckEvaluateFail("listNull?", "Expected expression."); await CheckEvaluateFail("listNull!.Count", GetNullReferenceErrorOn("\"Count\"")); await CheckEvaluateFail("x?.p", "Operation '?' not allowed on primitive type - 'x?'"); + await CheckEvaluateFail("str_null.Length", GetNullReferenceErrorOn("\"Length\"")); + await CheckEvaluateFail("str_null!.Length", GetNullReferenceErrorOn("\"Length\"")); string GetNullReferenceErrorOn(string name) => $"Expression threw NullReferenceException trying to access {name} on a null-valued object."; @@ -1294,5 +1300,20 @@ await EvaluateOnCallFrameAndCheck(id, ("this", TObject("DIMClass")), ("this.dimClassMember", TNumber(123))); }); + + [Fact] + public async Task EvaluateStringProperties() => await CheckInspectLocalsAtBreakpointSite( + $"DebuggerTests.TypeProperties", "Run", 3, "Run", + $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.TypeProperties:Run'); 1 }})", + wait_for_event_fn: async (pause_location) => + { + 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')) + ); + }); } } 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 9733b5a564c9ea..d94bfce19b98eb 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 @@ -1425,6 +1425,20 @@ static void Evaluate() int? x_val = x; } } + + public static class TypeProperties + { + public class InstanceProperties + { + public string str = "aB.c["; + } + + public static void Run() + { + var instance = new InstanceProperties(); + var localString = "aB.c["; + } + } } namespace DebuggerTestsV2 From cef3404bd461279cd0da539c9dac5359bb3e8679 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 9 Jun 2022 15:56:04 +0200 Subject: [PATCH 2/9] Fixed indexing property and length property. --- .../BrowserDebugProxy/EvaluateExpression.cs | 160 +++++++++--------- .../MemberReferenceResolver.cs | 53 +++++- 2 files changed, 132 insertions(+), 81 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs index 159cb3ec5e277f..68549deb2f1665 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs @@ -120,7 +120,7 @@ public SyntaxTree ReplaceVars(SyntaxTree syntaxTree, IEnumerable ma_val return SyntaxFactory.IdentifierName(id_name); }); - // 1.1 Replace all this.a() occurrences with this_a_ABDE + // 1.1 Replace all this.a() occurrences with this_a_ABDE root = root.ReplaceNodes(methodCalls, (m, _) => { string iesStr = m.ToString(); @@ -215,17 +215,18 @@ void AddLocalVariableWithValue(string idName, JObject value) variableDefinitions.Add(ConvertJSToCSharpLocalVariableAssignment(idName, value)); } } + } - private static string ConvertJSToCSharpLocalVariableAssignment(string idName, JToken variable) + public static string ConvertJSToCSharpLocalVariableAssignment(string idName, JToken variable) + { + string typeRet; + object valueRet; + JToken value = variable["value"]; + string type = variable["type"].Value(); + string subType = variable["subtype"]?.Value(); + switch (type) { - string typeRet; - object valueRet; - JToken value = variable["value"]; - string type = variable["type"].Value(); - string subType = variable["subtype"]?.Value(); - switch (type) - { - case "string": + case "string": { var str = value?.Value(); str = str.Replace("\"", "\\\""); @@ -233,68 +234,67 @@ private static string ConvertJSToCSharpLocalVariableAssignment(string idName, JT typeRet = "string"; break; } - case "symbol": - { - valueRet = $"'{value?.Value()}'"; - typeRet = "char"; - break; - } - case "number": - //casting to double and back to string would loose precision; so casting straight to string - valueRet = value?.Value(); - typeRet = "double"; - break; - case "boolean": - valueRet = value?.Value().ToLowerInvariant(); - typeRet = "bool"; + case "symbol": + { + valueRet = $"'{value?.Value()}'"; + typeRet = "char"; break; - case "object": - if (variable["subtype"]?.Value() == "null") - { - (valueRet, typeRet) = GetNullObject(variable["className"]?.Value()); - } - else + } + case "number": + //casting to double and back to string would loose precision; so casting straight to string + valueRet = value?.Value(); + typeRet = "double"; + break; + case "boolean": + valueRet = value?.Value().ToLowerInvariant(); + typeRet = "bool"; + break; + case "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) { - 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(); - 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; - } + case "valuetype" when variable["isEnum"]?.Value() == true: + typeRet = variable["className"]?.Value(); + 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, 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"); + } + break; + case "void": + (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> Resolve(IList collectionToResolve, MemberReferenceResolver resolver, @@ -368,13 +368,14 @@ async Task ReplaceMethodCall(InvocationExpressionSyntax method) } } - private static async Task> ResolveElementAccess(IEnumerable elementAccesses, Dictionary memberAccessValues, MemberReferenceResolver resolver, CancellationToken token) + private static async Task> ResolveElementAccess(ExpressionSyntaxReplacer replacer, MemberReferenceResolver resolver, CancellationToken token) { var values = new List(); JObject index = null; + IEnumerable elementAccesses = replacer.elementAccess; foreach (ElementAccessExpressionSyntax elementAccess in elementAccesses.Reverse()) { - index = await resolver.Resolve(elementAccess, memberAccessValues, index, token); + index = await resolver.Resolve(elementAccess, replacer.memberAccessValues, index, replacer.variableDefinitions, token); if (index == null) throw new ReturnAsErrorException($"Failed to resolve element access for {elementAccess}", "ReferenceError"); } @@ -438,7 +439,7 @@ internal static async Task CompileAndRunTheExpression( replacer.VisitInternal(expressionTree); - IList elementAccessValues = await ResolveElementAccess(replacer.elementAccess, replacer.memberAccessValues, resolver, token); + IList elementAccessValues = await ResolveElementAccess(replacer, resolver, token); syntaxTree = replacer.ReplaceVars(syntaxTree, null, null, null, elementAccessValues); } @@ -447,22 +448,27 @@ internal static async Task CompileAndRunTheExpression( if (expressionTree == null) throw new Exception($"BUG: Unable to evaluate {expression}, could not get expression from the syntax tree"); + return await EvaluateSimpleExpression(syntaxTree.ToString(), expression, replacer.variableDefinitions, logger, token); + } + + internal static async Task EvaluateSimpleExpression( + string compiledExpression, string orginalExpression, List variableDefinitions, ILogger logger, CancellationToken token) + { Script newScript = script; try { - newScript = script.ContinueWith( - string.Join("\n", replacer.variableDefinitions) + "\nreturn " + syntaxTree.ToString()); + newScript = script.ContinueWith(string.Join("\n", variableDefinitions) + "\nreturn " + compiledExpression + ";"); var state = await newScript.RunAsync(cancellationToken: token); - return JObject.FromObject(resolver.ConvertCSharpToJSType(state.ReturnValue, state.ReturnValue.GetType())); + return JObject.FromObject(ConvertCLRToJSType(state.ReturnValue)); } 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"); + logger.LogDebug($"Cannot evaluate '{orginalExpression}'. Script used to compile it: {newScript.Code}{Environment.NewLine}{cee.Message}"); + throw new ReturnAsErrorException($"Cannot evaluate '{orginalExpression}': {cee.Message}", "CompilationError"); } catch (Exception ex) { - throw new Exception($"Internal Error: Unable to run {expression}, error: {ex.Message}.", ex); + throw new Exception($"Internal Error: Unable to run {orginalExpression}, error: {ex.Message}.", ex); } } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs index 89089fc2f1e77d..ee49c50e063792 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs @@ -290,8 +290,12 @@ async Task ResolveAsInstanceMember(ArraySegment parts, JObject if (!DotnetObjectId.TryParse(resolvedObject?["objectId"]?.Value(), out DotnetObjectId objectId)) { - if (resolvedObject["type"].Value() == "string") - throw new ReturnAsErrorException($"String properties evaluation is not supported yet.", "ReferenceError"); // Issue #66823 + if (resolvedObject["type"].Value() == "string" && i == parts.Count - 1) + { + JObject resolvedProperty = await ResolveAsStringProperty(resolvedObject); + if (resolvedProperty != null) + return resolvedProperty; + } if (!throwOnNullReference) throw new ReturnAsErrorException($"Operation '?' not allowed on primitive type - '{parts[i - 1]}'", "ReferenceError"); throw new ReturnAsErrorException($"Cannot find member '{part}' on a primitive type", "ReferenceError"); @@ -318,10 +322,33 @@ async Task ResolveAsInstanceMember(ArraySegment parts, JObject throwOnNullReference = !hasCurrentPartNullCondition; } return resolvedObject; + + // checks for string properties, e.g. obj.str.Length or str.Length + async Task ResolveAsStringProperty(JObject obj) + { + if (parts[0][^1] == '!' || parts[0][^1] == '?') + parts[0] = parts[0].Remove(parts[0].Length - 1); + var replacedExpression = parts[0]; + var resolvedExpression = varName; + if (parts.Count > 2) + { + replacedExpression = string.Join("_", parts[0..^2]); //obj_str + resolvedExpression = replacedExpression + $".{parts[parts.Count - 1]}"; //obj_str.Length + } + try + { + var replacement = ExpressionEvaluator.ConvertJSToCSharpLocalVariableAssignment(replacedExpression, obj); + return await ExpressionEvaluator.EvaluateSimpleExpression(resolvedExpression, varName, new List() { replacement }, logger, token); + } + catch + { + return null; + } + } } } - public async Task Resolve(ElementAccessExpressionSyntax elementAccess, Dictionary memberAccessValues, JObject indexObject, CancellationToken token) + public async Task Resolve(ElementAccessExpressionSyntax elementAccess, Dictionary memberAccessValues, JObject indexObject, List variableDefinitions, CancellationToken token) { try { @@ -337,6 +364,7 @@ public async Task Resolve(ElementAccessExpressionSyntax elementAccess, { string elementIdxStr; int elementIdx = 0; + var elementAccessStr = elementAccess.ToString(); // x[1] or x[a] or x[a.b] if (indexObject == null) { @@ -353,7 +381,7 @@ public async Task Resolve(ElementAccessExpressionSyntax elementAccess, } // e.g. x[a] or x[a.b] - if (arg.Expression is IdentifierNameSyntax) + else if (arg.Expression is IdentifierNameSyntax) { var argParm = arg.Expression as IdentifierNameSyntax; @@ -368,6 +396,8 @@ public async Task Resolve(ElementAccessExpressionSyntax elementAccess, elementIdxStr = indexObject["value"].ToString(); int.TryParse(elementIdxStr, out elementIdx); } + + // FixMe: indexing with expressions, e.g. x[a + 1] } } } @@ -380,6 +410,21 @@ public async Task Resolve(ElementAccessExpressionSyntax elementAccess, if (elementIdx >= 0) { DotnetObjectId.TryParse(rootObject?["objectId"]?.Value(), out DotnetObjectId objectId); + // primitive types do not have objectIds + if (objectId == null) + { + var type = rootObject?["type"]?.Value(); + switch (type) + { + case "string": + var eaExpressionFormatted = elementAccessStrExpression.Replace('.', '_'); // instance_str + variableDefinitions.Add(ExpressionEvaluator.ConvertJSToCSharpLocalVariableAssignment(eaExpressionFormatted, rootObject)); + var eaFormatted = elementAccessStr.Replace('.', '_'); // instance_str[1] + return await ExpressionEvaluator.EvaluateSimpleExpression(eaFormatted, elementAccessStr, variableDefinitions, logger, token); + default: + throw new InvalidOperationException($"Cannot apply indexing with [] to an expression of type '{type}'"); + } + } switch (objectId.Scheme) { case "array": From 8b9a5567536bce74221e89622ea544895e8330db Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 10 Jun 2022 12:31:54 +0200 Subject: [PATCH 3/9] Fixed changes from https://github.com/dotnet/runtime/pull/67095 that I broke sometime when merging. --- .../BrowserDebugProxy/EvaluateExpression.cs | 26 ++---------- .../MemberReferenceResolver.cs | 41 ++++++++----------- 2 files changed, 22 insertions(+), 45 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs index 68549deb2f1665..9dc261b9bddc71 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs @@ -120,7 +120,7 @@ public SyntaxTree ReplaceVars(SyntaxTree syntaxTree, IEnumerable ma_val return SyntaxFactory.IdentifierName(id_name); }); - // 1.1 Replace all this.a() occurrences with this_a_ABDE + // 1.1 Replace all this.a() occurrences with this_a_ABDE root = root.ReplaceNodes(methodCalls, (m, _) => { string iesStr = m.ToString(); @@ -448,18 +448,18 @@ internal static async Task CompileAndRunTheExpression( if (expressionTree == null) throw new Exception($"BUG: Unable to evaluate {expression}, could not get expression from the syntax tree"); - return await EvaluateSimpleExpression(syntaxTree.ToString(), expression, replacer.variableDefinitions, logger, token); + return await EvaluateSimpleExpression(resolver, syntaxTree.ToString(), expression, replacer.variableDefinitions, logger, token); } internal static async Task EvaluateSimpleExpression( - string compiledExpression, string orginalExpression, List variableDefinitions, ILogger logger, CancellationToken token) + MemberReferenceResolver resolver, string compiledExpression, string orginalExpression, List variableDefinitions, ILogger logger, CancellationToken token) { Script newScript = script; try { newScript = script.ContinueWith(string.Join("\n", variableDefinitions) + "\nreturn " + compiledExpression + ";"); var state = await newScript.RunAsync(cancellationToken: token); - return JObject.FromObject(ConvertCLRToJSType(state.ReturnValue)); + return JObject.FromObject(resolver.ConvertCSharpToJSType(state.ReturnValue, state.ReturnValue.GetType())); } catch (CompilationErrorException cee) { @@ -471,24 +471,6 @@ internal static async Task EvaluateSimpleExpression( throw new Exception($"Internal Error: Unable to run {orginalExpression}, error: {ex.Message}.", ex); } } - - private static JObject ConvertCLRToJSType(object v) - { - if (v is JObject jobj) - return jobj; - - if (v is null) - return JObjectValueCreator.CreateNull("")?["value"] as JObject; - - string typeName = v.GetType().ToString(); - jobj = JObjectValueCreator.CreateFromPrimitiveType(v); - return jobj is not null - ? jobj["value"] as JObject - : JObjectValueCreator.Create(value: null, - type: "object", - description: v.ToString(), - className: typeName)?["value"] as JObject; - } } internal sealed class ReturnAsErrorException : Exception diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs index ee49c50e063792..37a1aa424839af 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs @@ -338,7 +338,8 @@ async Task ResolveAsStringProperty(JObject obj) try { var replacement = ExpressionEvaluator.ConvertJSToCSharpLocalVariableAssignment(replacedExpression, obj); - return await ExpressionEvaluator.EvaluateSimpleExpression(resolvedExpression, varName, new List() { replacement }, logger, token); + return await ExpressionEvaluator.EvaluateSimpleExpression( + this, resolvedExpression, varName, new List() { replacement }, logger, token); } catch { @@ -420,7 +421,7 @@ public async Task Resolve(ElementAccessExpressionSyntax elementAccess, var eaExpressionFormatted = elementAccessStrExpression.Replace('.', '_'); // instance_str variableDefinitions.Add(ExpressionEvaluator.ConvertJSToCSharpLocalVariableAssignment(eaExpressionFormatted, rootObject)); var eaFormatted = elementAccessStr.Replace('.', '_'); // instance_str[1] - return await ExpressionEvaluator.EvaluateSimpleExpression(eaFormatted, elementAccessStr, variableDefinitions, logger, token); + return await ExpressionEvaluator.EvaluateSimpleExpression(this, eaFormatted, elementAccessStr, variableDefinitions, logger, token); default: throw new InvalidOperationException($"Cannot apply indexing with [] to an expression of type '{type}'"); } @@ -618,28 +619,14 @@ async Task FindMethodIdOnLinqEnumerable(IList typeIds, string methodNa } } - private static readonly HashSet NumericTypes = new HashSet + public JObject ConvertCSharpToJSType(object v, Type type) { - typeof(decimal), typeof(byte), typeof(sbyte), - typeof(short), typeof(ushort), - typeof(int), typeof(uint), - typeof(float), typeof(double) - }; + if (v is JObject jobj) + return jobj; + + if (v is null) + return JObjectValueCreator.CreateNull("")?["value"] as JObject; - public object ConvertCSharpToJSType(object v, Type type) - { - if (v == null) - return new { type = "object", subtype = "null", className = type?.ToString(), description = type?.ToString() }; - if (v is string s) - return new { type = "string", value = s, description = s }; - if (v is char c) - 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; if (v is Array arr) { return CacheEvaluationResult( @@ -658,7 +645,15 @@ public object ConvertCSharpToJSType(object v, Type type) className = type.ToString() })); } - return new { type = "object", value = v, description = v.ToString(), className = type.ToString() }; + + string typeName = v.GetType().ToString(); + jobj = JObjectValueCreator.CreateFromPrimitiveType(v); + return jobj is not null + ? jobj["value"] as JObject + : JObjectValueCreator.Create(value: null, + type: "object", + description: v.ToString(), + className: typeName)?["value"] as JObject; } private JObject CacheEvaluationResult(JObject value) From 63fcb3649be27e6d51840f1ef73c51b354e0d6ed Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 15 Jun 2022 10:13:25 +0200 Subject: [PATCH 4/9] Granted objectId to string: properties and methods on strings are evaluated the similarly as on objects. --- .../BrowserDebugProxy/JObjectValueCreator.cs | 6 +- .../MemberReferenceResolver.cs | 242 ++++++++++-------- .../BrowserDebugProxy/MonoSDBHelper.cs | 35 ++- .../BrowserDebugProxy/ValueTypeClass.cs | 6 +- .../EvaluateOnCallFrameTests.cs | 61 +++-- .../debugger-test/debugger-evaluate-test.cs | 2 +- 6 files changed, 205 insertions(+), 147 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs b/src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs index 523204d6ea858a..5b2eb76ff662b7 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs @@ -58,10 +58,10 @@ public static JObject Create(T value, return ret; } - public static JObject CreateFromPrimitiveType(object v) + public static JObject CreateFromPrimitiveType(object v, int? stringId = null) => v switch { - string s => Create(s, type: "string", description: s), + string s => Create(s, type: "string", description: s, objectId: $"dotnet:object:{stringId}"), char c => CreateJObjectForChar(Convert.ToInt32(c)), bool b => Create(b, type: "boolean", description: b ? "true" : "false", className: "System.Boolean"), @@ -182,7 +182,7 @@ public async Task ReadAsVariableValue( { var stringId = retDebuggerCmdReader.ReadInt32(); string value = await _sdbAgent.GetStringValue(stringId, token); - ret = CreateFromPrimitiveType(value); + ret = CreateFromPrimitiveType(value, stringId); break; } case ElementType.SzArray: diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs index 37a1aa424839af..5cecb35579f6c8 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs @@ -290,12 +290,6 @@ async Task ResolveAsInstanceMember(ArraySegment parts, JObject if (!DotnetObjectId.TryParse(resolvedObject?["objectId"]?.Value(), out DotnetObjectId objectId)) { - if (resolvedObject["type"].Value() == "string" && i == parts.Count - 1) - { - JObject resolvedProperty = await ResolveAsStringProperty(resolvedObject); - if (resolvedProperty != null) - return resolvedProperty; - } if (!throwOnNullReference) throw new ReturnAsErrorException($"Operation '?' not allowed on primitive type - '{parts[i - 1]}'", "ReferenceError"); throw new ReturnAsErrorException($"Cannot find member '{part}' on a primitive type", "ReferenceError"); @@ -322,30 +316,6 @@ async Task ResolveAsInstanceMember(ArraySegment parts, JObject throwOnNullReference = !hasCurrentPartNullCondition; } return resolvedObject; - - // checks for string properties, e.g. obj.str.Length or str.Length - async Task ResolveAsStringProperty(JObject obj) - { - if (parts[0][^1] == '!' || parts[0][^1] == '?') - parts[0] = parts[0].Remove(parts[0].Length - 1); - var replacedExpression = parts[0]; - var resolvedExpression = varName; - if (parts.Count > 2) - { - replacedExpression = string.Join("_", parts[0..^2]); //obj_str - resolvedExpression = replacedExpression + $".{parts[parts.Count - 1]}"; //obj_str.Length - } - try - { - var replacement = ExpressionEvaluator.ConvertJSToCSharpLocalVariableAssignment(replacedExpression, obj); - return await ExpressionEvaluator.EvaluateSimpleExpression( - this, resolvedExpression, varName, new List() { replacement }, logger, token); - } - catch - { - return null; - } - } } } @@ -410,37 +380,58 @@ public async Task Resolve(ElementAccessExpressionSyntax elementAccess, } if (elementIdx >= 0) { - DotnetObjectId.TryParse(rootObject?["objectId"]?.Value(), out DotnetObjectId objectId); - // primitive types do not have objectIds - if (objectId == null) - { - var type = rootObject?["type"]?.Value(); - switch (type) - { - case "string": - var eaExpressionFormatted = elementAccessStrExpression.Replace('.', '_'); // instance_str - variableDefinitions.Add(ExpressionEvaluator.ConvertJSToCSharpLocalVariableAssignment(eaExpressionFormatted, rootObject)); - var eaFormatted = elementAccessStr.Replace('.', '_'); // instance_str[1] - return await ExpressionEvaluator.EvaluateSimpleExpression(this, eaFormatted, elementAccessStr, variableDefinitions, logger, token); - default: - throw new InvalidOperationException($"Cannot apply indexing with [] to an expression of type '{type}'"); - } - } + var type = rootObject?["type"]?.Value(); + if (!DotnetObjectId.TryParse(rootObject?["objectId"]?.Value(), out DotnetObjectId objectId)) + throw new InvalidOperationException($"Cannot apply indexing with [] to a primitive object of type '{type}'"); + switch (objectId.Scheme) { case "array": rootObject["value"] = await context.SdbAgent.GetArrayValues(objectId.Value, token); return (JObject)rootObject["value"][elementIdx]["value"]; case "object": + if (type == "string") + { + // ToArray() does not exist on string + var eaExpressionFormatted = elementAccessStrExpression.Replace('.', '_'); // instance_str + variableDefinitions.Add(ExpressionEvaluator.ConvertJSToCSharpLocalVariableAssignment(eaExpressionFormatted, rootObject)); + var eaFormatted = elementAccessStr.Replace('.', '_'); // instance_str[1] + return await ExpressionEvaluator.EvaluateSimpleExpression(this, eaFormatted, elementAccessStr, variableDefinitions, logger, token); + } var typeIds = await context.SdbAgent.GetTypeIdsForObject(objectId.Value, true, token); - int methodId = await context.SdbAgent.GetMethodIdByName(typeIds[0], "ToArray", token); - var toArrayRetMethod = await context.SdbAgent.InvokeMethod(objectId.Value, methodId, isValueType: false, token); - rootObject = await GetValueFromObject(toArrayRetMethod, token); - DotnetObjectId.TryParse(rootObject?["objectId"]?.Value(), out DotnetObjectId arrayObjectId); - rootObject["value"] = await context.SdbAgent.GetArrayValues(arrayObjectId.Value, token); - return (JObject)rootObject["value"][elementIdx]["value"]; + int[] methodIds = await context.SdbAgent.GetMethodIdsByName(typeIds[0], "ToArray", token); + // ToArray should not have an overload, but if user defined it, take the default one: without params + if (methodIds == null) + throw new InvalidOperationException($"Type '{rootObject?["className"]?.Value()}' cannot be indexed."); + + int toArrayId = methodIds[0]; + if (methodIds.Length > 1) + { + foreach (var methodId in methodIds) + { + MethodInfoWithDebugInformation methodInfo = await context.SdbAgent.GetMethodInfo(methodId, token); + ParameterInfo[] paramInfo = methodInfo.GetParametersInfo(); + if (paramInfo.Length == 0) + { + toArrayId = methodId; + break; + } + } + } + try + { + var toArrayRetMethod = await context.SdbAgent.InvokeMethod(objectId.Value, toArrayId, isValueType: false, token); + rootObject = await GetValueFromObject(toArrayRetMethod, token); + DotnetObjectId.TryParse(rootObject?["objectId"]?.Value(), out DotnetObjectId arrayObjectId); + rootObject["value"] = await context.SdbAgent.GetArrayValues(arrayObjectId.Value, token); + return (JObject)rootObject["value"][elementIdx]["value"]; + } + catch + { + throw new InvalidOperationException($"Cannot apply indexing with [] to an object of type '{rootObject?["className"]?.Value()}'"); + } default: - throw new InvalidOperationException($"Cannot apply indexing with [] to an expression of type '{objectId.Scheme}'"); + throw new InvalidOperationException($"Cannot apply indexing with [] to an expression of scheme '{objectId.Scheme}'"); } } } @@ -504,85 +495,100 @@ public async Task Resolve(InvocationExpressionSyntax method, Dictionary { if (!context.SdbAgent.ValueCreator.TryGetValueTypeById(objectId.Value, out ValueTypeClass valueType)) throw new Exception($"Could not find valuetype {objectId}"); - typeIds = new List(1) { valueType.TypeId }; } else { 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) - { - // instance method - commandParamsObjWriter.WriteObj(objectId, context.SdbAgent); - } - - int passedArgsCnt = method.ArgumentList.Arguments.Count; - int methodParamsCnt = passedArgsCnt; - ParameterInfo[] methodParamsInfo = null; - var methodInfo = await context.SdbAgent.GetMethodInfo(methodId, token); - if (methodInfo != null) + int[] methodIds = await context.SdbAgent.GetMethodIdsByName(typeIds[0], methodName, token); + if (methodIds == null) { - methodParamsInfo = methodInfo.Info.GetParametersInfo(); - methodParamsCnt = methodParamsInfo.Length; - if (isExtensionMethod) + //try to search on System.Linq.Enumerable + int methodId = await FindMethodIdOnLinqEnumerable(typeIds, methodName); + if (methodId == 0) { - // implicit *this* parameter - methodParamsCnt--; + var typeName = await context.SdbAgent.GetTypeName(typeIds[0], token); + throw new ReturnAsErrorException($"Method '{methodName}' not found in type '{typeName}'", "ReferenceError"); } - if (passedArgsCnt > methodParamsCnt) - throw new ReturnAsErrorException($"Unable to evaluate method '{methodName}'. Too many arguments passed.", "ArgumentError"); + methodIds = new int[] { methodId }; } - + // get information about params in all overloads for *methodName* + List methodInfos = await GetMethodParamInfosForMethods(methodIds); + int passedArgsCnt = method.ArgumentList.Arguments.Count; + int maxMethodParamsCnt = methodInfos.Max(v => v.GetParametersInfo().Length); if (isExtensionMethod) { - commandParamsObjWriter.Write(methodParamsCnt + 1); - commandParamsObjWriter.WriteObj(objectId, context.SdbAgent); - } - else - { - commandParamsObjWriter.Write(methodParamsCnt); + // implicit *this* parameter + maxMethodParamsCnt--; } + if (passedArgsCnt > maxMethodParamsCnt) + throw new ReturnAsErrorException($"Unable to evaluate method '{methodName}'. Too many arguments passed.", "ArgumentError"); - int argIndex = 0; - // explicitly passed arguments - for (; argIndex < passedArgsCnt; argIndex++) + foreach (var methodInfo in methodInfos) { - var arg = method.ArgumentList.Arguments[argIndex]; - if (arg.Expression is LiteralExpressionSyntax literal) + ParameterInfo[] methodParamsInfo = methodInfo.GetParametersInfo(); + int methodParamsCnt = isExtensionMethod ? methodParamsInfo.Length - 1 : methodParamsInfo.Length; + int optionalParams = methodParamsInfo.Count(v => v.Value != null); + if (passedArgsCnt > methodParamsCnt || passedArgsCnt < methodParamsCnt - optionalParams) { - if (!await commandParamsObjWriter.WriteConst(literal, context.SdbAgent, token)) - throw new InternalErrorException($"Unable to evaluate method '{methodName}'. Unable to write LiteralExpressionSyntax into binary writer."); + // this overload does not match the number of params passed, try another one + continue; } - else if (arg.Expression is IdentifierNameSyntax identifierName) + int methodId = methodInfo.DebugId; + using var commandParamsObjWriter = new MonoBinaryWriter(); + + if (isExtensionMethod) { - 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."); + commandParamsObjWriter.Write(methodParamsCnt + 1); + commandParamsObjWriter.WriteObj(objectId, context.SdbAgent); } else { - throw new InternalErrorException($"Unable to evaluate method '{methodName}'. Unable to write into binary writer, not recognized expression type: {arg.Expression.GetType().Name}"); + // instance method + commandParamsObjWriter.WriteObj(objectId, context.SdbAgent); + 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 evaluate method '{methodName}'. Unable to write LiteralExpressionSyntax 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 evaluate method '{methodName}'. Unable to write IdentifierNameSyntax into binary writer."); + } + else + { + throw new InternalErrorException($"Unable to evaluate method '{methodName}'. 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."); + } + try + { + var retMethod = await context.SdbAgent.InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, token); + return await GetValueFromObject(retMethod, token); + } + catch + { + // try further methodIds, we're looking for a method with the same type of params that the user passed + logger.LogDebug($"InvokeMethod failed due to parameter type mismatch for {methodName} with {methodParamsCnt} parameters, including {optionalParams} optional."); + continue; } } - // 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 ReturnAsErrorException($"No implementation of method '{methodName}' matching '{method}' found in type {rootObject["className"]}.", "ArgumentError"); } catch (Exception ex) when (ex is not (ExpressionEvaluationFailedException or ReturnAsErrorException)) { @@ -601,8 +607,8 @@ async Task FindMethodIdOnLinqEnumerable(IList typeIds, string methodNa } } - int newMethodId = await context.SdbAgent.GetMethodIdByName(linqTypeId, methodName, token); - if (newMethodId == 0) + int[] newMethodIds = await context.SdbAgent.GetMethodIdsByName(linqTypeId, methodName, token); + if (newMethodIds == null) return 0; foreach (int typeId in typeIds) @@ -611,12 +617,24 @@ async Task FindMethodIdOnLinqEnumerable(IList typeIds, string methodNa if (genericTypeArgs.Count > 0) { isExtensionMethod = true; - return await context.SdbAgent.MakeGenericMethod(newMethodId, genericTypeArgs, token); + return await context.SdbAgent.MakeGenericMethod(newMethodIds[0], genericTypeArgs, token); } } return 0; } + + async Task> GetMethodParamInfosForMethods(int[] methodIds) + { + List allMethodInfos = new(); + for (int i = 0; i < methodIds.Length; i++) + { + var ithMethodInfo = await context.SdbAgent.GetMethodInfo(methodIds[i], token); + if (ithMethodInfo != null) + allMethodInfos.Add(ithMethodInfo); + } + return allMethodInfos; + } } public JObject ConvertCSharpToJSType(object v, Type type) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index a95715a4ef19a0..4b4b8abbdffad8 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -402,7 +402,27 @@ internal string GetArrayIndexString(int idx) } } - internal sealed record MethodInfoWithDebugInformation(MethodInfo Info, int DebugId, string Name); + internal sealed class MethodInfoWithDebugInformation + { + private ParameterInfo[] _paramsInfo; + public MethodInfo Info { get; } + public int DebugId { get; } + public string Name { get; } + public ParameterInfo[] GetParametersInfo() + { + if (_paramsInfo != null) + return _paramsInfo; + _paramsInfo = Info.GetParametersInfo(); + return _paramsInfo; + } + + public MethodInfoWithDebugInformation(MethodInfo info, int debugId, string name) + { + Info = info; + DebugId = debugId; + Name = name; + } + } internal sealed class TypeInfoWithDebugInformation { @@ -1554,7 +1574,7 @@ public async Task GetTypeIdFromToken(int assemblyId, int typeToken, Cancell return retDebuggerCmdReader.ReadInt32(); } - public async Task GetMethodIdByName(int type_id, string method_name, CancellationToken token) + public async Task GetMethodIdsByName(int type_id, string method_name, CancellationToken token) { if (type_id <= 0) throw new DebuggerAgentException($"Invalid type_id {type_id} (method_name: {method_name}"); @@ -1566,7 +1586,12 @@ public async Task GetMethodIdByName(int type_id, string method_name, Cancel commandParamsWriter.Write((int)1); //case sensitive using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdType.GetMethodsByNameFlags, commandParamsWriter, token); var nMethods = retDebuggerCmdReader.ReadInt32(); - return retDebuggerCmdReader.ReadInt32(); + if (nMethods == 0) + return null; + int[] methodIds = new int[nMethods]; + for (int i = 0; i < nMethods; i++) + methodIds[i] = retDebuggerCmdReader.ReadInt32(); + return methodIds; } public async Task IsDelegate(int objectId, CancellationToken token) @@ -1965,7 +1990,9 @@ private async Task FindDebuggerProxyConstructorIdFor(int typeId, Cancellati break; cAttrTypeId = genericTypeId; } - methodId = await GetMethodIdByName(cAttrTypeId, ".ctor", token); + int[] methodIds = await GetMethodIdsByName(cAttrTypeId, ".ctor", token); + if (methodIds != null) + methodId = methodIds[0]; break; } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs index 6c781acc27c35c..3eadb5b7517d4d 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs @@ -102,8 +102,10 @@ public async Task ToJObject(MonoSDBHelper sdbAgent, bool forDebuggerDis string description = className; if (ShouldAutoInvokeToString(className) || IsEnum) { - int methodId = await sdbAgent.GetMethodIdByName(TypeId, "ToString", token); - var retMethod = await sdbAgent.InvokeMethod(Buffer, methodId, token, "methodRet"); + int[] methodIds = await sdbAgent.GetMethodIdsByName(TypeId, "ToString", token); + if (methodIds == null) + throw new InternalErrorException($"Cannot find method 'ToString' on typeId = {TypeId}"); + var retMethod = await sdbAgent.InvokeMethod(Buffer, methodIds[0], token, "methodRet"); description = retMethod["value"]?["value"].Value(); if (className.Equals("System.Guid")) description = description.ToUpperInvariant(); //to keep the old behavior diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index f607e50dc2d328..9b298315a6f96e 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -516,15 +516,13 @@ public async Task EvaluateSimpleMethodCallsError() => await CheckInspectLocalsAt 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()); + Assert.Equal("No implementation of method 'CallMethodWithParm' matching 'this.CallMethodWithParm(\"1\")' found in type DebuggerTests.EvaluateMethodTestsClass.TestEvaluate.", res.Error["result"]?["description"]?.Value()); (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjNull.MyMethod()", expect_ok: false ); 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 evaluate '(this.ParmToTestObjException.MyMethod()\n)'", - res.Error["result"]?["description"]?.Value()); + Assert.Equal("Method 'MyMethod' not found in type 'string'", res.Error["result"]?["description"]?.Value()); }); [Fact] @@ -587,10 +585,24 @@ await EvaluateOnCallFrameAndCheck(id, ("this.CallMethodWithObj(this.objToTest)", TNumber(10))); }); + + [Fact] + public async Task EvaluateIndexingNegative() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "EvaluateLocals", + $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); 1 }})", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var (_, res) = await EvaluateOnCallFrame(id, "f.idx0[2]", expect_ok: false ); + Assert.Equal("Unable to evaluate element access 'f.idx0[2]': Cannot apply indexing with [] to a primitive object of type 'number'", res.Error["message"]?.Value()); + (_, res) = await EvaluateOnCallFrame(id, "f[1]", expect_ok: false ); + Assert.Equal( "Unable to evaluate element access 'f[1]': Type 'DebuggerTests.EvaluateLocalsWithIndexingTests.TestEvaluate' cannot be indexed.", res.Error["message"]?.Value()); + }); + [Fact] - public async Task EvaluateExpressionsWithElementAccessByConstant() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.EvaluateLocalsWithElementAccessTests", "EvaluateLocals", 5, "EvaluateLocals", - "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithElementAccessTests:EvaluateLocals'); })", + public async Task EvaluateIndexingsByConstant() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "EvaluateLocals", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })", wait_for_event_fn: async (pause_location) => { var id = pause_location["callFrames"][0]["callFrameId"].Value(); @@ -603,9 +615,9 @@ await EvaluateOnCallFrameAndCheck(id, }); [Fact] - public async Task EvaluateExpressionsWithElementAccessByLocalVariable() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.EvaluateLocalsWithElementAccessTests", "EvaluateLocals", 5, "EvaluateLocals", - "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithElementAccessTests:EvaluateLocals'); })", + public async Task EvaluateIndexingByLocalVariable() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "EvaluateLocals", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })", wait_for_event_fn: async (pause_location) => { var id = pause_location["callFrames"][0]["callFrameId"].Value(); @@ -619,9 +631,9 @@ await EvaluateOnCallFrameAndCheck(id, }); [Fact] - public async Task EvaluateExpressionsWithElementAccessByMemberVariables() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.EvaluateLocalsWithElementAccessTests", "EvaluateLocals", 5, "EvaluateLocals", - "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithElementAccessTests:EvaluateLocals'); })", + public async Task EvaluateIndexingByMemberVariables() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "EvaluateLocals", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })", wait_for_event_fn: async (pause_location) => { var id = pause_location["callFrames"][0]["callFrameId"].Value(); @@ -637,9 +649,9 @@ await EvaluateOnCallFrameAndCheck(id, }); [Fact] - public async Task EvaluateExpressionsWithElementAccessNested() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.EvaluateLocalsWithElementAccessTests", "EvaluateLocals", 5, "EvaluateLocals", - "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithElementAccessTests:EvaluateLocals'); })", + public async Task EvaluateIndexingNested() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "EvaluateLocals", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })", wait_for_event_fn: async (pause_location) => { var id = pause_location["callFrames"][0]["callFrameId"].Value(); @@ -654,9 +666,9 @@ await EvaluateOnCallFrameAndCheck(id, }); [ConditionalFact(nameof(RunningOnChrome))] - public async Task EvaluateExpressionsWithElementAccessMultidimentional() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.EvaluateLocalsWithElementAccessTests", "EvaluateLocals", 5, "EvaluateLocals", - "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithElementAccessTests:EvaluateLocals'); })", + public async Task EvaluateIndexingMultidimensional() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "EvaluateLocals", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })", wait_for_event_fn: async (pause_location) => { var id = pause_location["callFrames"][0]["callFrameId"].Value(); @@ -1225,7 +1237,6 @@ 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.EndsWith('r')", TBool(true)), ("test.propString.StartsWith('S')", TBool(false)), ("localInt.ToString()", TString("2")), @@ -1237,9 +1248,9 @@ 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.EndsWith('r')", TBool(false)), - ("localString.StartsWith('S')", TBool(true))); + ("localString.StartsWith('S')", TBool(true)) + ); }); [Fact] @@ -1281,7 +1292,7 @@ public async Task EvaluateMethodsOnPrimitiveTypesReturningObjects() => await Ch (res, _) = await EvaluateOnCallFrame(id, "localString.Split('*', 3, System.StringSplitOptions.RemoveEmptyEntries)"); props = res["value"] ?? await GetProperties(res["objectId"]?.Value()); expected_props = new [] { TString("S"), TString("T"), TString("R") }; - await CheckProps(props, expected_props, "props#2"); + await CheckProps(props, expected_props, "props#3"); }); [Theory] @@ -1325,9 +1336,9 @@ 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.Length", TNumber(5)), ("instance.str[3]", TChar('c')) ); }); 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 d94bfce19b98eb..651ea5e3613a8a 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 @@ -470,7 +470,7 @@ public async void run() } } - public class EvaluateLocalsWithElementAccessTests + public class EvaluateLocalsWithIndexingTests { public class TestEvaluate { From b26504e41b9ac5f7871b4504ec1f8fc4f85711ab Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 15 Jun 2022 10:36:41 +0200 Subject: [PATCH 5/9] Removed the comments. --- .../debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 9b298315a6f96e..ecab50130b9cbc 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -516,7 +516,7 @@ public async Task EvaluateSimpleMethodCallsError() => await CheckInspectLocalsAt res.Error["result"]?["description"]?.Value()); (_, res) = await EvaluateOnCallFrame(id, "this.CallMethodWithParm(\"1\")", expect_ok: false ); - Assert.Equal("No implementation of method 'CallMethodWithParm' matching 'this.CallMethodWithParm(\"1\")' found in type DebuggerTests.EvaluateMethodTestsClass.TestEvaluate.", res.Error["result"]?["description"]?.Value()); + Assert.Contains("No implementation of method 'CallMethodWithParm' matching 'this.CallMethodWithParm(\"1\")' found in type DebuggerTests.EvaluateMethodTestsClass.TestEvaluate.", res.Error["result"]?["description"]?.Value()); (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjNull.MyMethod()", expect_ok: false ); Assert.Equal("Expression 'this.ParmToTestObjNull.MyMethod' evaluated to null", res.Error["message"]?.Value()); @@ -1336,9 +1336,9 @@ 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.Length", TNumber(5)), ("instance.str[3]", TChar('c')) ); }); From f08a3945bee0f7eaa7548ffddbf23f48453d89b0 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 16 Jun 2022 13:11:10 +0200 Subject: [PATCH 6/9] Fixed EvaluateMethodsOnPrimitiveTypesReturningObjects on Firefox. --- .../Firefox/FirefoxMonoProxy.cs | 2 +- .../DebuggerTestSuite/DebuggerTestFirefox.cs | 73 +++++++++++-------- .../EvaluateOnCallFrameTests.cs | 12 +-- 3 files changed, 49 insertions(+), 38 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs index ba8cdf740ef92e..e559919f498a4c 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs @@ -732,7 +732,7 @@ private static JObject ConvertToFirefoxContent(ValueOrError re @class = variable["value"]?["className"]?.Value(), value = variable["value"]?["description"]?.Value(), actor = variable["value"]["objectId"].Value(), - type = "object" + type = variable["value"]?["type"]?.Value() ?? "object" }), enumerable = true, configurable = false, diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs index c43a1217e0e433..092c99462d7600 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs @@ -225,52 +225,63 @@ internal JObject ConvertFromFirefoxToDefaultFormat(KeyValuePair JToken value = variable.Value; JObject variableValue = null; string valueType = "value"; - if (value?["type"] == null || value["type"].Value() == "object") + if (value?["type"] == null || value["type"].Value() == "object" || value["type"].Value() == "string") { var actor = value["value"]?["actor"]?.Value(); - if (value["value"]["type"].Value() == "null") + string type = value["value"]?["type"]?.Value(); + switch (type) { - variableValue = JObject.FromObject(new + case "null": + variableValue = JObject.FromObject(new { type = "object", subtype = "null", className = value["value"]["class"].Value(), description = value["value"]["class"].Value() }); - if (actor != null && actor.StartsWith("dotnet:pointer:")) - variableValue["type"] = "symbol"; - } - else if (value?["value"]?["type"].Value() == "function") - { - variableValue = JObject.FromObject(new - { - type = "function", - objectId = value["value"]["actor"].Value(), - className = "Function", - description = $"get {name} ()" - }); - valueType = "get"; - } - else { - variableValue = JObject.FromObject(new + if (actor != null && actor.StartsWith("dotnet:pointer:")) + variableValue["type"] = "symbol"; + break; + case "function": + variableValue = JObject.FromObject(new { - type = value["value"]["type"], + type = type, + objectId = value["value"]["actor"].Value(), + className = "Function", + description = $"get {name} ()" + }); + valueType = "get"; + break; + case "string": + variableValue = JObject.FromObject(new + { + type = type, + objectId = value["value"]["actor"]?.Value(), + value = value["value"]["value"]?.Value(), + description = value["value"]["value"].Value() + }); + break; + default: + variableValue = JObject.FromObject(new + { + type = type, value = (string)null, description = value["value"]?["value"]?.Value() == null ? value["value"]["class"].Value() : value["value"]?["value"]?.Value(), className = value["value"]["class"].Value(), objectId = actor, }); - if (actor.StartsWith("dotnet:valuetype:")) - variableValue["isValueType"] = true; - if (actor.StartsWith("dotnet:array:")) - variableValue["subtype"] = "array"; - if (actor.StartsWith("dotnet:pointer:")) - variableValue["type"] = "object"; - if (actor.StartsWith("dotnet:pointer:-1")) - { - variableValue["type"] = "symbol"; - variableValue["value"] = value["value"]?["value"]?.Value(); - } + if (actor.StartsWith("dotnet:valuetype:")) + variableValue["isValueType"] = true; + if (actor.StartsWith("dotnet:array:")) + variableValue["subtype"] = "array"; + if (actor.StartsWith("dotnet:pointer:")) + variableValue["type"] = "object"; + if (actor.StartsWith("dotnet:pointer:-1")) + { + variableValue["type"] = "symbol"; + variableValue["value"] = value["value"]?["value"]?.Value(); + } + break; } } else diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index ecab50130b9cbc..9c357f651ca5b6 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -1285,15 +1285,15 @@ public async Task EvaluateMethodsOnPrimitiveTypesReturningObjects() => await Ch var id = pause_location["callFrames"][0]["callFrameId"].Value(); var (res, _) = await EvaluateOnCallFrame(id, "test.propString.Split('_', 3, System.StringSplitOptions.TrimEntries)"); - var props = res["value"] ?? await GetProperties(res["objectId"]?.Value()); // in firefox getProps is necessary - var expected_props = new [] { TString("s"), TString("t"), TString("r") }; + var props = await GetProperties(res["objectId"]?.Value()); + var expected_props = new[] { TString("s"), TString("t"), TString("r") }; await CheckProps(props, expected_props, "props#1"); (res, _) = await EvaluateOnCallFrame(id, "localString.Split('*', 3, System.StringSplitOptions.RemoveEmptyEntries)"); - props = res["value"] ?? await GetProperties(res["objectId"]?.Value()); - expected_props = new [] { TString("S"), TString("T"), TString("R") }; - await CheckProps(props, expected_props, "props#3"); - }); + props = await GetProperties(res["objectId"]?.Value()); + expected_props = new[] { TString("S"), TString("T"), TString("R") }; + await CheckProps(props, expected_props, "props#2"); + }); [Theory] [InlineData("DefaultMethod", "IDefaultInterface", "Evaluate")] From 71c1df6d5cabf8df6dce1cd50a083165059515cd Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 16 Jun 2022 14:11:15 +0200 Subject: [PATCH 7/9] Disabled firefox test https://github.com/dotnet/runtime/issues/70819. --- .../debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 9c357f651ca5b6..a5ea93ef04d643 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -585,8 +585,7 @@ await EvaluateOnCallFrameAndCheck(id, ("this.CallMethodWithObj(this.objToTest)", TNumber(10))); }); - - [Fact] + [ConditionalFact(nameof(RunningOnChrome))] public async Task EvaluateIndexingNegative() => await CheckInspectLocalsAtBreakpointSite( "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "EvaluateLocals", $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); 1 }})", From 16afcf32ca61e65a697beb6f0f78879fcce3fac1 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Tue, 19 Jul 2022 20:17:37 +0200 Subject: [PATCH 8/9] Fixed the test build error. --- .../EvaluateOnCallFrameTests.cs | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 14711e9e5c13e7..20c5aad35180a9 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -1303,24 +1303,26 @@ public async Task EvaluateNullObjectPropertiesNegative() => await CheckInspectLo wait_for_event_fn: async (pause_location) => { var id = pause_location["callFrames"][0]["callFrameId"].Value(); - await CheckEvaluateFail("list.Count.x", "Cannot find member 'x' on a primitive type"); - await CheckEvaluateFail("listNull.Count", GetNullReferenceErrorOn("\"Count\"")); - await CheckEvaluateFail("listNull!.Count", GetNullReferenceErrorOn("\"Count\"")); - await CheckEvaluateFail("tcNull.MemberListNull.Count", GetNullReferenceErrorOn("\"MemberListNull\"")); - await CheckEvaluateFail("tc.MemberListNull.Count", GetNullReferenceErrorOn("\"Count\"")); - await CheckEvaluateFail("tcNull?.MemberListNull.Count", GetNullReferenceErrorOn("\"Count\"")); - await CheckEvaluateFail("listNull?.Count.NonExistingProperty", GetNullReferenceErrorOn("\"NonExistingProperty\"")); - await CheckEvaluateFail("tc?.MemberListNull! .Count", GetNullReferenceErrorOn("\"Count\"")); - await CheckEvaluateFail("tc?. MemberListNull!.Count", GetNullReferenceErrorOn("\"Count\"")); - await CheckEvaluateFail("tc?.MemberListNull.Count", GetNullReferenceErrorOn("\"Count\"")); - await CheckEvaluateFail("tc! .MemberListNull!.Count", GetNullReferenceErrorOn("\"Count\"")); - await CheckEvaluateFail("tc!.MemberListNull. Count", GetNullReferenceErrorOn("\"Count\"")); - await CheckEvaluateFail("tcNull?.Sibling.MemberListNull?.Count", GetNullReferenceErrorOn("\"MemberListNull?\"")); - await CheckEvaluateFail("listNull?", "Expected expression."); - await CheckEvaluateFail("listNull!.Count", GetNullReferenceErrorOn("\"Count\"")); - await CheckEvaluateFail("x?.p", "Operation '?' not allowed on primitive type - 'x?'"); - await CheckEvaluateFail("str_null.Length", GetNullReferenceErrorOn("\"Length\"")); - await CheckEvaluateFail("str_null!.Length", GetNullReferenceErrorOn("\"Length\"")); + await CheckEvaluateFail(id, + ("list.Count.x", "Cannot find member 'x' on a primitive type"), + ("listNull.Count", GetNullReferenceErrorOn("\"Count\"")), + ("listNull!.Count", GetNullReferenceErrorOn("\"Count\"")), + ("tcNull.MemberListNull.Count", GetNullReferenceErrorOn("\"MemberListNull\"")), + ("tc.MemberListNull.Count", GetNullReferenceErrorOn("\"Count\"")), + ("tcNull?.MemberListNull.Count", GetNullReferenceErrorOn("\"Count\"")), + ("listNull?.Count.NonExistingProperty", GetNullReferenceErrorOn("\"NonExistingProperty\"")), + ("tc?.MemberListNull! .Count", GetNullReferenceErrorOn("\"Count\"")), + ("tc?. MemberListNull!.Count", GetNullReferenceErrorOn("\"Count\"")), + ("tc?.MemberListNull.Count", GetNullReferenceErrorOn("\"Count\"")), + ("tc! .MemberListNull!.Count", GetNullReferenceErrorOn("\"Count\"")), + ("tc!.MemberListNull. Count", GetNullReferenceErrorOn("\"Count\"")), + ("tcNull?.Sibling.MemberListNull?.Count", GetNullReferenceErrorOn("\"MemberListNull?\"")), + ("listNull?", "Expected expression."), + ("listNull!.Count", GetNullReferenceErrorOn("\"Count\"")), + ("x?.p", "Operation '?' not allowed on primitive type - 'x?'"), + ("str_null.Length", GetNullReferenceErrorOn("\"Length\"")), + ("str_null!.Length", GetNullReferenceErrorOn("\"Length\"")) + ); string GetNullReferenceErrorOn(string name) => $"Expression threw NullReferenceException trying to access {name} on a null-valued object."; }); From d5e94c4bc90d8499ae225f428c81f48122ee7e50 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 20 Jul 2022 08:55:43 +0200 Subject: [PATCH 9/9] Full names to fix the tests. --- .../EvaluateOnCallFrameTests.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 7d8b8f902bf3ea..60fea60f9af1ff 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -585,7 +585,7 @@ await EvaluateOnCallFrameAndCheck(id, [ConditionalFact(nameof(RunningOnChrome))] public async Task EvaluateIndexingNegative() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "EvaluateLocals", + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); 1 }})", wait_for_event_fn: async (pause_location) => { @@ -598,7 +598,7 @@ public async Task EvaluateIndexingNegative() => await CheckInspectLocalsAtBreakp [Fact] public async Task EvaluateIndexingsByConstant() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "EvaluateLocals", + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })", wait_for_event_fn: async (pause_location) => { @@ -613,7 +613,7 @@ await EvaluateOnCallFrameAndCheck(id, [Fact] public async Task EvaluateIndexingByLocalVariable() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "EvaluateLocals", + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })", wait_for_event_fn: async (pause_location) => { @@ -629,7 +629,7 @@ await EvaluateOnCallFrameAndCheck(id, [Fact] public async Task EvaluateIndexingByMemberVariables() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "EvaluateLocals", + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })", wait_for_event_fn: async (pause_location) => { @@ -647,7 +647,7 @@ await EvaluateOnCallFrameAndCheck(id, [Fact] public async Task EvaluateIndexingNested() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "EvaluateLocals", + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })", wait_for_event_fn: async (pause_location) => { @@ -664,7 +664,7 @@ await EvaluateOnCallFrameAndCheck(id, [ConditionalFact(nameof(RunningOnChrome))] public async Task EvaluateIndexingMultidimensional() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "EvaluateLocals", + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })", wait_for_event_fn: async (pause_location) => { @@ -1192,7 +1192,7 @@ await EvaluateOnCallFrameAndCheck(id, [ConditionalFact(nameof(RunningOnChrome))] public async Task EvaluateNullObjectPropertiesPositive() => await CheckInspectLocalsAtBreakpointSite( - $"DebuggerTests.EvaluateNullableProperties", "Evaluate", 11, "Evaluate", + $"DebuggerTests.EvaluateNullableProperties", "Evaluate", 11, "DebuggerTests.EvaluateNullableProperties.Evaluate", $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.EvaluateNullableProperties:Evaluate'); 1 }})", wait_for_event_fn: async (pause_location) => { @@ -1358,7 +1358,7 @@ await EvaluateOnCallFrameAndCheck(id, [Fact] public async Task EvaluateStringProperties() => await CheckInspectLocalsAtBreakpointSite( - $"DebuggerTests.TypeProperties", "Run", 3, "Run", + $"DebuggerTests.TypeProperties", "Run", 3, "DebuggerTests.TypeProperties.Run", $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.TypeProperties:Run'); 1 }})", wait_for_event_fn: async (pause_location) => {