Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ internal static async Task<JObject> CompileAndRunTheExpression(
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -1201,5 +1201,24 @@ await EvaluateOnCallFrameAndCheck(id,
("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");
});
}
}