From d93eb4bd8d916af546a74451592484e7ffbee63a Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 24 Mar 2022 13:18:45 +0100 Subject: [PATCH 1/5] Test expanding the properties of a result returned by a method evaluated on a primitive type. --- .../EvaluateOnCallFrameTests.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 3e3071a43988b8..9d9829bcfc6955 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -1110,6 +1110,25 @@ await EvaluateOnCallFrameAndCheck(id, ("test.listToLinq.ToList()", TObject("System.Collections.Generic.List", description: "Count = 11")) ); }); + + [Fact] + public async Task EvaluateMethodsOnPrimitiveTypesReturningObjects() => 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(); + + var (res, _) = await EvaluateOnCallFrame(id, "test.propString.Split('_', 3, System.StringSplitOptions.None)"); + 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.None)"); + props = await GetProperties(res["objectId"]?.Value()); + expected_props = new [] { TString("S"), TString("T"), TString("R") }; + await CheckProps(props, expected_props, "props#1"); + }); } } From 74dc04d9846ad458dbd5f5aa4092ed7d3c6e4393 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 24 Mar 2022 13:20:20 +0100 Subject: [PATCH 2/5] Enable caching evaluationResult in scopeCache. --- .../BrowserDebugProxy/DevToolsHelper.cs | 1 + .../MemberReferenceResolver.cs | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs index a348a42534c312..a8b03afd5e7757 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs @@ -369,6 +369,7 @@ internal class PerScopeCache public Dictionary Locals { get; } = new Dictionary(); public Dictionary MemberReferences { get; } = new Dictionary(); public Dictionary ObjectFields { get; } = new Dictionary(); + public Dictionary EvaluationResults { get; } = new(); public PerScopeCache(JArray objectValues) { foreach (var objectValue in objectValues) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs index ef744beddfdca5..2118d646d5dfc5 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs @@ -16,6 +16,7 @@ namespace Microsoft.WebAssembly.Diagnostics { internal class MemberReferenceResolver { + private static int evaluationResultObjectId; private SessionId sessionId; private int scopeId; private MonoProxy proxy; @@ -490,5 +491,37 @@ public async Task Resolve(InvocationExpressionSyntax method, Dictionary throw new Exception($"Unable to evaluate method '{methodName}'", ex); } } + + public JObject CacheEvaluationResult(JObject value) + { + if (IsDuplicated(value, out JObject duplicate)) + return value; + + var evalResultId = Interlocked.Increment(ref evaluationResultObjectId); + string id = $"dotnet:evaluationResult:{evalResultId}"; + if (!value.TryAdd("objectId", id)) + { + logger.LogWarning($"EvaluationResult cache request passed with ID: {value["objectId"].Value()}. Overwritting it with a automatically assigned ID: {id}."); + value["objectId"] = id; + } + scopeCache.EvaluationResults.Add(id, value); + return value; + + bool IsDuplicated(JObject er, out JObject duplicate) + { + var type = er["type"].Value(); + var subtype = er["subtype"].Value(); + var value = er["value"]; + var description = er["description"].Value(); + var className = er["className"].Value(); + duplicate = scopeCache.EvaluationResults.FirstOrDefault( + pair => pair.Value["type"].Value() == type + && pair.Value["subtype"].Value() == subtype + && pair.Value["description"].Value() == description + && pair.Value["className"].Value() == className + && JToken.DeepEquals(pair.Value["value"], value)).Value; + return duplicate != null; + } + } } } From c72009d209f5c5512532c7bb3e5221a97e1dde24 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 24 Mar 2022 13:22:42 +0100 Subject: [PATCH 3/5] Cache arrays that are returned as a method evaluation result. --- .../BrowserDebugProxy/EvaluateExpression.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs index 54b175b7796fa5..ab107e5d2d9c1b 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs @@ -436,6 +436,24 @@ private static object ConvertCSharpToJSType(object v, Type type) return new { type = "number", value = v, description = Convert.ToDouble(v).ToString(CultureInfo.InvariantCulture) }; if (v is JObject) return v; + if (v is Array arr) + { + return resolver.CacheEvaluationResult( + JObject.FromObject( + new + { + type = "object", + subtype = "array", + value = new JArray(arr.Cast().Select((val, idx) => JObject.FromObject( + new + { + value = ConvertCSharpToJSType(resolver, val, val.GetType()), + name = $"{idx}" + }))), + description = v.ToString(), + className = type.ToString() + })); + } return new { type = "object", value = v, description = v.ToString(), className = type.ToString() }; } From ea885eca64522f96d5797517ef1afa176c80b785 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 24 Mar 2022 13:24:26 +0100 Subject: [PATCH 4/5] Enable getting evaluationResults from cache on request. --- .../debugger/BrowserDebugProxy/MemberReferenceResolver.cs | 8 ++++++++ src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs | 5 +++++ src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs | 8 ++++++++ 3 files changed, 21 insertions(+) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs index 2118d646d5dfc5..d243cd51837b77 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs @@ -523,5 +523,13 @@ bool IsDuplicated(JObject er, out JObject duplicate) return duplicate != null; } } + + public JObject TryGetEvaluationResult(string id) + { + JObject val; + if (!scopeCache.EvaluationResults.TryGetValue(id, out val)) + logger.LogError($"EvaluationResult of ID: {id} does not exist in the cache."); + return val; + } } } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index b0e5961a1d14ca..ac8c8ceee0d4be 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -776,6 +776,11 @@ internal async Task RuntimeGetPropertiesInternal(SessionId id, DotnetObj (sortByAccessLevel ? JObject.FromObject(new { result = JArray.Parse(value_json_str) }) : JArray.Parse(value_json_str)) : null; } + case "evaluationResult": + { + var res = context.SdbAgent.GetEvaluationResultProperties(objectId.ToString()); + return sortByAccessLevel ? JObject.FromObject(new { result = res }) : res; + } default: return null; diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index be8a074b35ec7a..5fb393a14a3b02 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -1378,6 +1378,14 @@ public async Task GetAssemblyFromType(int type_id, CancellationToken token) return retDebuggerCmdReader.ReadInt32(); } + public JToken GetEvaluationResultProperties(string id) + { + ExecutionContext context = proxy.GetContext(sessionId); + var resolver = new MemberReferenceResolver(proxy, context, sessionId, context.CallStack.First().Id, logger); + var evaluationResult = resolver.TryGetEvaluationResult(id); + return evaluationResult["value"]; + } + public async Task GetValueFromDebuggerDisplayAttribute(int objectId, int typeId, CancellationToken token) { string expr = ""; From 0b824dfc08c1fd6b092856fe4d8ab2712bb269f3 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 18 May 2022 14:38:28 +0200 Subject: [PATCH 5/5] Fixed Firefox test. --- src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs | 2 +- .../wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs | 2 +- .../debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index f68696c8586c4a..aa8a57706c3f36 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -802,7 +802,7 @@ internal async Task> RuntimeGetObjectMembers(Sess ? ValueOrError.WithValue(GetMembersResult.FromValues(JArray.Parse(value_json_str))) : ValueOrError.WithError(res); case "evaluationResult": - var evaluationRes = context.SdbAgent.GetEvaluationResultProperties(objectId.ToString()); + JArray evaluationRes = (JArray)context.SdbAgent.GetEvaluationResultProperties(objectId.ToString()); return ValueOrError.WithValue(GetMembersResult.FromValues(evaluationRes)); default: return ValueOrError.WithError($"RuntimeGetProperties: unknown object id scheme: {objectId.Scheme}"); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs index e5712b232e191d..c43a1217e0e433 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs @@ -313,7 +313,7 @@ internal override async Task GetProperties(string id, JToken fn_args = n } return ret; } - if (id.StartsWith("dotnet:valuetype:") || id.StartsWith("dotnet:object:") || id.StartsWith("dotnet:array:") || id.StartsWith("dotnet:pointer:")) + if (id.StartsWith("dotnet:evaluationResult:") || id.StartsWith("dotnet:valuetype:") || id.StartsWith("dotnet:object:") || id.StartsWith("dotnet:array:") || id.StartsWith("dotnet:pointer:")) { JArray ret = new (); var o = JObject.FromObject(new diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 116bbd67916ba7..fdc116e95be2b9 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -1192,12 +1192,14 @@ 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") }; - await CheckProps(res["value"], expected_props, "props#1"); + 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(res["value"], expected_props, "props#2"); + await CheckProps(props, expected_props, "props#2"); }); } }