Skip to content

Commit 4e9ac49

Browse files
authored
Add EvaluationResult Scheme for arrays. (#67095)
* Test expanding the properties of a result returned by a method evaluated on a primitive type. * Enable caching evaluationResult in scopeCache. * Cache arrays that are returned as a method evaluation result. * Enable getting evaluationResults from cache on request. * Fixed Firefox test.
1 parent 5d3288d commit 4e9ac49

File tree

7 files changed

+118
-2
lines changed

7 files changed

+118
-2
lines changed

src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,7 @@ internal sealed class PerScopeCache
473473
public Dictionary<string, JObject> Locals { get; } = new Dictionary<string, JObject>();
474474
public Dictionary<string, JObject> MemberReferences { get; } = new Dictionary<string, JObject>();
475475
public Dictionary<string, JObject> ObjectFields { get; } = new Dictionary<string, JObject>();
476+
public Dictionary<string, JObject> EvaluationResults { get; } = new();
476477
public PerScopeCache(JArray objectValues)
477478
{
478479
foreach (var objectValue in objectValues)

src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@ internal static async Task<JObject> CompileAndRunTheExpression(
453453
newScript = script.ContinueWith(
454454
string.Join("\n", replacer.variableDefinitions) + "\nreturn " + syntaxTree.ToString());
455455
var state = await newScript.RunAsync(cancellationToken: token);
456-
return JObject.FromObject(ConvertCLRToJSType(state.ReturnValue));
456+
return JObject.FromObject(resolver.ConvertCSharpToJSType(state.ReturnValue, state.ReturnValue.GetType()));
457457
}
458458
catch (CompilationErrorException cee)
459459
{

src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
using System.Collections.Generic;
1313
using System.Net.WebSockets;
1414
using BrowserDebugProxy;
15+
using System.Globalization;
1516

1617
namespace Microsoft.WebAssembly.Diagnostics
1718
{
1819
internal sealed class MemberReferenceResolver
1920
{
21+
private static int evaluationResultObjectId;
2022
private SessionId sessionId;
2123
private int scopeId;
2224
private MonoProxy proxy;
@@ -543,5 +545,88 @@ async Task<int> FindMethodIdOnLinqEnumerable(IList<int> typeIds, string methodNa
543545
return 0;
544546
}
545547
}
548+
549+
private static readonly HashSet<Type> NumericTypes = new HashSet<Type>
550+
{
551+
typeof(decimal), typeof(byte), typeof(sbyte),
552+
typeof(short), typeof(ushort),
553+
typeof(int), typeof(uint),
554+
typeof(float), typeof(double)
555+
};
556+
557+
public object ConvertCSharpToJSType(object v, Type type)
558+
{
559+
if (v == null)
560+
return new { type = "object", subtype = "null", className = type?.ToString(), description = type?.ToString() };
561+
if (v is string s)
562+
return new { type = "string", value = s, description = s };
563+
if (v is char c)
564+
return new { type = "symbol", value = c, description = $"{(int)c} '{c}'" };
565+
if (NumericTypes.Contains(v.GetType()))
566+
return new { type = "number", value = v, description = Convert.ToDouble(v).ToString(CultureInfo.InvariantCulture) };
567+
if (v is bool)
568+
return new { type = "boolean", value = v, description = v.ToString().ToLowerInvariant(), className = type.ToString() };
569+
if (v is JObject)
570+
return v;
571+
if (v is Array arr)
572+
{
573+
return CacheEvaluationResult(
574+
JObject.FromObject(
575+
new
576+
{
577+
type = "object",
578+
subtype = "array",
579+
value = new JArray(arr.Cast<object>().Select((val, idx) => JObject.FromObject(
580+
new
581+
{
582+
value = ConvertCSharpToJSType(val, val.GetType()),
583+
name = $"{idx}"
584+
}))),
585+
description = v.ToString(),
586+
className = type.ToString()
587+
}));
588+
}
589+
return new { type = "object", value = v, description = v.ToString(), className = type.ToString() };
590+
}
591+
592+
private JObject CacheEvaluationResult(JObject value)
593+
{
594+
if (IsDuplicated(value, out JObject duplicate))
595+
return value;
596+
597+
var evalResultId = Interlocked.Increment(ref evaluationResultObjectId);
598+
string id = $"dotnet:evaluationResult:{evalResultId}";
599+
if (!value.TryAdd("objectId", id))
600+
{
601+
logger.LogWarning($"EvaluationResult cache request passed with ID: {value["objectId"].Value<string>()}. Overwritting it with a automatically assigned ID: {id}.");
602+
value["objectId"] = id;
603+
}
604+
scopeCache.EvaluationResults.Add(id, value);
605+
return value;
606+
607+
bool IsDuplicated(JObject er, out JObject duplicate)
608+
{
609+
var type = er["type"].Value<string>();
610+
var subtype = er["subtype"].Value<string>();
611+
var value = er["value"];
612+
var description = er["description"].Value<string>();
613+
var className = er["className"].Value<string>();
614+
duplicate = scopeCache.EvaluationResults.FirstOrDefault(
615+
pair => pair.Value["type"].Value<string>() == type
616+
&& pair.Value["subtype"].Value<string>() == subtype
617+
&& pair.Value["description"].Value<string>() == description
618+
&& pair.Value["className"].Value<string>() == className
619+
&& JToken.DeepEquals(pair.Value["value"], value)).Value;
620+
return duplicate != null;
621+
}
622+
}
623+
624+
public JObject TryGetEvaluationResult(string id)
625+
{
626+
JObject val;
627+
if (!scopeCache.EvaluationResults.TryGetValue(id, out val))
628+
logger.LogError($"EvaluationResult of ID: {id} does not exist in the cache.");
629+
return val;
630+
}
546631
}
547632
}

src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,9 @@ internal async Task<ValueOrError<GetMembersResult>> RuntimeGetObjectMembers(Sess
802802
return value_json_str != null
803803
? ValueOrError<GetMembersResult>.WithValue(GetMembersResult.FromValues(JArray.Parse(value_json_str)))
804804
: ValueOrError<GetMembersResult>.WithError(res);
805+
case "evaluationResult":
806+
JArray evaluationRes = (JArray)context.SdbAgent.GetEvaluationResultProperties(objectId.ToString());
807+
return ValueOrError<GetMembersResult>.WithValue(GetMembersResult.FromValues(evaluationRes));
805808
default:
806809
return ValueOrError<GetMembersResult>.WithError($"RuntimeGetProperties: unknown object id scheme: {objectId.Scheme}");
807810
}

src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1387,6 +1387,14 @@ public async Task<int> GetAssemblyFromType(int type_id, CancellationToken token)
13871387
return retDebuggerCmdReader.ReadInt32();
13881388
}
13891389

1390+
public JToken GetEvaluationResultProperties(string id)
1391+
{
1392+
ExecutionContext context = proxy.GetContext(sessionId);
1393+
var resolver = new MemberReferenceResolver(proxy, context, sessionId, context.CallStack.First().Id, logger);
1394+
var evaluationResult = resolver.TryGetEvaluationResult(id);
1395+
return evaluationResult["value"];
1396+
}
1397+
13901398
public async Task<string> GetValueFromDebuggerDisplayAttribute(DotnetObjectId dotnetObjectId, int typeId, CancellationToken token)
13911399
{
13921400
string expr = "";

src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestFirefox.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ internal override async Task<JToken> GetProperties(string id, JToken fn_args = n
313313
}
314314
return ret;
315315
}
316-
if (id.StartsWith("dotnet:valuetype:") || id.StartsWith("dotnet:object:") || id.StartsWith("dotnet:array:") || id.StartsWith("dotnet:pointer:"))
316+
if (id.StartsWith("dotnet:evaluationResult:") || id.StartsWith("dotnet:valuetype:") || id.StartsWith("dotnet:object:") || id.StartsWith("dotnet:array:") || id.StartsWith("dotnet:pointer:"))
317317
{
318318
JArray ret = new ();
319319
var o = JObject.FromObject(new

src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1201,5 +1201,24 @@ await EvaluateOnCallFrameAndCheck(id,
12011201
("localFloat.ToString()", TString(floatLocalVal["description"]?.Value<string>())),
12021202
("localDouble.ToString()", TString(doubleLocalVal["description"]?.Value<string>())));
12031203
});
1204+
1205+
[Fact]
1206+
public async Task EvaluateMethodsOnPrimitiveTypesReturningObjects() => await CheckInspectLocalsAtBreakpointSite(
1207+
"DebuggerTests.PrimitiveTypeMethods", "Evaluate", 11, "Evaluate",
1208+
"window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.PrimitiveTypeMethods:Evaluate'); })",
1209+
wait_for_event_fn: async (pause_location) =>
1210+
{
1211+
var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
1212+
1213+
var (res, _) = await EvaluateOnCallFrame(id, "test.propString.Split('_', 3, System.StringSplitOptions.TrimEntries)");
1214+
var props = res["value"] ?? await GetProperties(res["objectId"]?.Value<string>()); // in firefox getProps is necessary
1215+
var expected_props = new [] { TString("s"), TString("t"), TString("r") };
1216+
await CheckProps(props, expected_props, "props#1");
1217+
1218+
(res, _) = await EvaluateOnCallFrame(id, "localString.Split('*', 3, System.StringSplitOptions.RemoveEmptyEntries)");
1219+
props = res["value"] ?? await GetProperties(res["objectId"]?.Value<string>());
1220+
expected_props = new [] { TString("S"), TString("T"), TString("R") };
1221+
await CheckProps(props, expected_props, "props#2");
1222+
});
12041223
}
12051224
}

0 commit comments

Comments
 (0)