Skip to content

Commit

Permalink
Add EvaluationResult Scheme for arrays. (#67095)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
ilonatommy committed May 26, 2022
1 parent 5d3288d commit 4e9ac49
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs
Expand Up @@ -473,6 +473,7 @@ internal sealed class PerScopeCache
public Dictionary<string, JObject> Locals { get; } = new Dictionary<string, JObject>();
public Dictionary<string, JObject> MemberReferences { get; } = new Dictionary<string, JObject>();
public Dictionary<string, JObject> ObjectFields { get; } = new Dictionary<string, JObject>();
public Dictionary<string, JObject> EvaluationResults { get; } = new();
public PerScopeCache(JArray objectValues)
{
foreach (var objectValue in objectValues)
Expand Down
Expand Up @@ -453,7 +453,7 @@ private static async Task<IList<JObject>> ResolveElementAccess(IEnumerable<Eleme
newScript = script.ContinueWith(
string.Join("\n", replacer.variableDefinitions) + "\nreturn " + syntaxTree.ToString());
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)
{
Expand Down
Expand Up @@ -12,11 +12,13 @@
using System.Collections.Generic;
using System.Net.WebSockets;
using BrowserDebugProxy;
using System.Globalization;

namespace Microsoft.WebAssembly.Diagnostics
{
internal sealed class MemberReferenceResolver
{
private static int evaluationResultObjectId;
private SessionId sessionId;
private int scopeId;
private MonoProxy proxy;
Expand Down Expand Up @@ -543,5 +545,88 @@ async Task<int> FindMethodIdOnLinqEnumerable(IList<int> typeIds, string methodNa
return 0;
}
}

private static readonly HashSet<Type> NumericTypes = new HashSet<Type>
{
typeof(decimal), typeof(byte), typeof(sbyte),
typeof(short), typeof(ushort),
typeof(int), typeof(uint),
typeof(float), typeof(double)
};

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(
JObject.FromObject(
new
{
type = "object",
subtype = "array",
value = new JArray(arr.Cast<object>().Select((val, idx) => JObject.FromObject(
new
{
value = ConvertCSharpToJSType(val, val.GetType()),
name = $"{idx}"
}))),
description = v.ToString(),
className = type.ToString()
}));
}
return new { type = "object", value = v, description = v.ToString(), className = type.ToString() };
}

private 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<string>()}. 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<string>();
var subtype = er["subtype"].Value<string>();
var value = er["value"];
var description = er["description"].Value<string>();
var className = er["className"].Value<string>();
duplicate = scopeCache.EvaluationResults.FirstOrDefault(
pair => pair.Value["type"].Value<string>() == type
&& pair.Value["subtype"].Value<string>() == subtype
&& pair.Value["description"].Value<string>() == description
&& pair.Value["className"].Value<string>() == className
&& JToken.DeepEquals(pair.Value["value"], value)).Value;
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;
}
}
}
3 changes: 3 additions & 0 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
Expand Up @@ -802,6 +802,9 @@ internal async Task<ValueOrError<GetMembersResult>> RuntimeGetObjectMembers(Sess
return value_json_str != null
? ValueOrError<GetMembersResult>.WithValue(GetMembersResult.FromValues(JArray.Parse(value_json_str)))
: ValueOrError<GetMembersResult>.WithError(res);
case "evaluationResult":
JArray evaluationRes = (JArray)context.SdbAgent.GetEvaluationResultProperties(objectId.ToString());
return ValueOrError<GetMembersResult>.WithValue(GetMembersResult.FromValues(evaluationRes));
default:
return ValueOrError<GetMembersResult>.WithError($"RuntimeGetProperties: unknown object id scheme: {objectId.Scheme}");
}
Expand Down
8 changes: 8 additions & 0 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs
Expand Up @@ -1387,6 +1387,14 @@ public async Task<int> 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<string> GetValueFromDebuggerDisplayAttribute(DotnetObjectId dotnetObjectId, int typeId, CancellationToken token)
{
string expr = "";
Expand Down
Expand Up @@ -313,7 +313,7 @@ internal override async Task<JToken> 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
Expand Down
Expand Up @@ -1201,5 +1201,24 @@ public async Task EvaluateLocalObjectFromAssemblyNotRelatedButLoaded()
("localFloat.ToString()", TString(floatLocalVal["description"]?.Value<string>())),
("localDouble.ToString()", TString(doubleLocalVal["description"]?.Value<string>())));
});

[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<string>();
var (res, _) = await EvaluateOnCallFrame(id, "test.propString.Split('_', 3, System.StringSplitOptions.TrimEntries)");
var props = res["value"] ?? await GetProperties(res["objectId"]?.Value<string>()); // in firefox getProps is necessary
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<string>());
expected_props = new [] { TString("S"), TString("T"), TString("R") };
await CheckProps(props, expected_props, "props#2");
});
}
}

0 comments on commit 4e9ac49

Please sign in to comment.