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
25 changes: 23 additions & 2 deletions src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ public static MonoCommands GetScopeVariables(int scopeId, params VarInfo[] vars)
return new MonoCommands($"MONO.mono_wasm_get_variables({scopeId}, {JsonConvert.SerializeObject(var_ids)})");
}

public static MonoCommands EvaluateMemberAccess(int scopeId, string expr, params VarInfo[] vars)
{
var var_ids = vars.Select(v => new { index = v.Index, name = v.Name }).ToArray();
return new MonoCommands($"MONO.mono_wasm_eval_member_access({scopeId}, {JsonConvert.SerializeObject(var_ids)}, '', '{expr}')");
}

public static MonoCommands SetBreakpoint(string assemblyName, uint methodToken, int ilOffset) => new MonoCommands($"MONO.mono_wasm_set_breakpoint (\"{assemblyName}\", {methodToken}, {ilOffset})");

public static MonoCommands RemoveBreakpoint(int breakpointId) => new MonoCommands($"MONO.mono_wasm_remove_breakpoint({breakpointId})");
Expand Down Expand Up @@ -285,7 +291,7 @@ internal class ExecutionContext
internal DebugStore store;
public TaskCompletionSource<DebugStore> Source { get; } = new TaskCompletionSource<DebugStore>();

public Dictionary<string, JToken> LocalsCache = new Dictionary<string, JToken>();
Dictionary<int, PerScopeCache> perScopeCaches { get; } = new Dictionary<int, PerScopeCache>();

public DebugStore Store
{
Expand All @@ -298,11 +304,26 @@ public DebugStore Store
}
}

public PerScopeCache GetCacheForScope(int scope_id)
{
if (perScopeCaches.TryGetValue(scope_id, out var cache))
return cache;

cache = new PerScopeCache();
perScopeCaches[scope_id] = cache;
return cache;
}

public void ClearState()
{
CallStack = null;
LocalsCache.Clear();
perScopeCaches.Clear();
}
}

internal class PerScopeCache
{
public Dictionary<string, JObject> Locals { get; } = new Dictionary<string, JObject>();
public Dictionary<string, JObject> MemberReferences { get; } = new Dictionary<string, JObject>();
}
}
300 changes: 219 additions & 81 deletions src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Microsoft.WebAssembly.Diagnostics
{
internal class MemberReferenceResolver
{
private MessageId messageId;
private int scopeId;
private MonoProxy proxy;
private ExecutionContext ctx;
private PerScopeCache scopeCache;
private VarInfo[] varIds;
private ILogger logger;
private bool locals_fetched = false;

public MemberReferenceResolver(MonoProxy proxy, ExecutionContext ctx, MessageId msg_id, int scope_id, ILogger logger)
{
messageId = msg_id;
scopeId = scope_id;
this.proxy = proxy;
this.ctx = ctx;
this.logger = logger;
scopeCache = ctx.GetCacheForScope(scope_id);
}

// Checks Locals, followed by `this`
public async Task<JObject> Resolve(string var_name, CancellationToken token)
{
if (scopeCache.Locals.Count == 0 && !locals_fetched)
{
var scope_res = await proxy.GetScopeProperties(messageId, scopeId, token);
if (scope_res.IsErr)
throw new Exception($"BUG: Unable to get properties for scope: {scopeId}. {scope_res}");
locals_fetched = true;
}

if (scopeCache.Locals.TryGetValue(var_name, out var obj))
{
return obj["value"]?.Value<JObject>();
}

if (scopeCache.MemberReferences.TryGetValue(var_name, out var ret))
return ret;

if (varIds == null)
{
var scope = ctx.CallStack.FirstOrDefault(s => s.Id == scopeId);
varIds = scope.Method.GetLiveVarsAt(scope.Location.CliLocation.Offset);
}

var res = await proxy.SendMonoCommand(messageId, MonoCommands.EvaluateMemberAccess(scopeId, var_name, varIds), token);
if (res.IsOk)
{
ret = res.Value?["result"]?["value"]?["value"]?.Value<JObject>();
scopeCache.MemberReferences[var_name] = ret;
}
else
{
logger.LogDebug(res.Error.ToString());
}

return ret;
}

}
}
102 changes: 27 additions & 75 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,9 @@ protected override async Task<bool> AcceptCommand(MessageId id, string method, J
async Task<Result> RuntimeGetProperties(MessageId id, DotnetObjectId objectId, JToken args, CancellationToken token)
{
if (objectId.Scheme == "scope")
{
return await GetScopeProperties(id, int.Parse(objectId.Value), token);
}

var res = await SendMonoCommand(id, MonoCommands.GetDetails(objectId, args), token);
if (res.IsErr)
Expand Down Expand Up @@ -673,64 +675,6 @@ async Task<bool> Step(MessageId msg_id, StepKind kind, CancellationToken token)
return true;
}

internal bool TryFindVariableValueInCache(ExecutionContext ctx, string expression, bool only_search_on_this, out JToken obj)
{
if (ctx.LocalsCache.TryGetValue(expression, out obj))
{
if (only_search_on_this && obj["fromThis"] == null)
return false;
return true;
}
return false;
}

internal async Task<JToken> TryGetVariableValue(MessageId msg_id, int scope_id, string expression, bool only_search_on_this, CancellationToken token)
{
JToken thisValue = null;
var context = GetContext(msg_id);
if (context.CallStack == null)
return null;

if (TryFindVariableValueInCache(context, expression, only_search_on_this, out JToken obj))
return obj;

var scope = context.CallStack.FirstOrDefault(s => s.Id == scope_id);
var live_vars = scope.Method.GetLiveVarsAt(scope.Location.CliLocation.Offset);
//get_this
var res = await SendMonoCommand(msg_id, MonoCommands.GetScopeVariables(scope.Id, live_vars), token);

var scope_values = res.Value?["result"]?["value"]?.Values<JObject>()?.ToArray();
thisValue = scope_values?.FirstOrDefault(v => v["name"]?.Value<string>() == "this");

if (!only_search_on_this)
{
if (thisValue != null && expression == "this")
return thisValue;

var value = scope_values.SingleOrDefault(sv => sv["name"]?.Value<string>() == expression);
if (value != null)
return value;
}

//search in scope
if (thisValue != null)
{
if (!DotnetObjectId.TryParse(thisValue["value"]["objectId"], out var objectId))
return null;

res = await SendMonoCommand(msg_id, MonoCommands.GetDetails(objectId), token);
scope_values = res.Value?["result"]?["value"]?.Values<JObject>().ToArray();
var foundValue = scope_values.FirstOrDefault(v => v["name"].Value<string>() == expression);
if (foundValue != null)
{
foundValue["fromThis"] = true;
context.LocalsCache[foundValue["name"].Value<string>()] = foundValue;
return foundValue;
}
}
return null;
}

async Task<bool> OnEvaluateOnCallFrame(MessageId msg_id, int scope_id, string expression, CancellationToken token)
{
try
Expand All @@ -739,35 +683,40 @@ async Task<bool> OnEvaluateOnCallFrame(MessageId msg_id, int scope_id, string ex
if (context.CallStack == null)
return false;

var varValue = await TryGetVariableValue(msg_id, scope_id, expression, false, token);
var resolver = new MemberReferenceResolver(this, context, msg_id, scope_id, logger);

if (varValue != null)
JObject retValue = await resolver.Resolve(expression, token);
if (retValue == null)
{
retValue = await EvaluateExpression.CompileAndRunTheExpression(expression, resolver, token);
}

if (retValue != null)
{
SendResponse(msg_id, Result.OkFromObject(new
{
result = varValue["value"]
result = retValue
}), token);
return true;
}

string retValue = await EvaluateExpression.CompileAndRunTheExpression(this, msg_id, scope_id, expression, token);
SendResponse(msg_id, Result.OkFromObject(new
else
{
result = new
{
value = retValue
}
}), token);
return true;
SendResponse(msg_id, Result.Err($"Unable to evaluate {expression}"), token);
}
}
catch (ReturnAsErrorException ree)
{
SendResponse(msg_id, ree.Error, token);
}
catch (Exception e)
{
logger.LogDebug(e, $"Error in EvaluateOnCallFrame for expression '{expression}.");
logger.LogDebug($"Error in EvaluateOnCallFrame for expression '{expression}' with '{e}.");
SendResponse(msg_id, Result.Exception(e), token);
}
return false;

return true;
}

async Task<Result> GetScopeProperties(MessageId msg_id, int scope_id, CancellationToken token)
internal async Task<Result> GetScopeProperties(MessageId msg_id, int scope_id, CancellationToken token)
{
try
{
Expand All @@ -788,8 +737,11 @@ async Task<Result> GetScopeProperties(MessageId msg_id, int scope_id, Cancellati
if (values == null || values.Length == 0)
return Result.OkFromObject(new { result = Array.Empty<object>() });

var frameCache = ctx.GetCacheForScope(scope_id);
foreach (var value in values)
ctx.LocalsCache[value["name"]?.Value<string>()] = value;
{
frameCache.Locals[value["name"]?.Value<string>()] = value;
}

return Result.OkFromObject(new { result = values });
}
Expand Down
Loading