Update dbgproxy to 70ff920a89 (#21075)

This commit is contained in:
Pranav K 2020-04-21 16:09:45 -07:00 committed by GitHub
parent b8f7dde377
commit 25f89e22f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 136 additions and 199 deletions

View File

@ -61,6 +61,42 @@ namespace WebAssembly.Net.Debugging {
=> (obj is MessageId) ? ((MessageId) obj).sessionId == sessionId && ((MessageId) obj).id == id : false;
}
internal class DotnetObjectId {
public string Scheme { get; }
public string Value { get; }
public static bool TryParse (JToken jToken, out DotnetObjectId objectId)
=> TryParse (jToken?.Value<string>(), out objectId);
public static bool TryParse (string id, out DotnetObjectId objectId)
{
objectId = null;
if (id == null)
return false;
if (!id.StartsWith ("dotnet:"))
return false;
var parts = id.Split (":", 3);
if (parts.Length < 3)
return false;
objectId = new DotnetObjectId (parts[1], parts[2]);
return true;
}
public DotnetObjectId (string scheme, string value)
{
Scheme = scheme;
Value = value;
}
public override string ToString ()
=> $"dotnet:{Scheme}:{Value}";
}
internal struct Result {
public JObject Value { get; private set; }
public JObject Error { get; private set; }
@ -151,8 +187,8 @@ namespace WebAssembly.Net.Debugging {
public static MonoCommands ClearAllBreakpoints ()
=> new MonoCommands ("MONO.mono_wasm_clear_all_breakpoints()");
public static MonoCommands GetObjectProperties (int objectId, bool expandValueTypes)
=> new MonoCommands ($"MONO.mono_wasm_get_object_properties({objectId}, { (expandValueTypes ? "true" : "false") })");
public static MonoCommands GetObjectProperties (DotnetObjectId objectId, bool expandValueTypes)
=> new MonoCommands ($"MONO.mono_wasm_get_object_properties({int.Parse (objectId.Value)}, { (expandValueTypes ? "true" : "false") })");
public static MonoCommands GetArrayValues (int objectId)
=> new MonoCommands ($"MONO.mono_wasm_get_array_values({objectId})");
@ -241,8 +277,6 @@ namespace WebAssembly.Net.Debugging {
internal DebugStore store;
public TaskCompletionSource<DebugStore> Source { get; } = new TaskCompletionSource<DebugStore> ();
int nextValueTypeId = 0;
public Dictionary<string, JToken> ValueTypesCache = new Dictionary<string, JToken> ();
public Dictionary<string, JToken> LocalsCache = new Dictionary<string, JToken> ();
public DebugStore Store {
@ -257,11 +291,8 @@ namespace WebAssembly.Net.Debugging {
public void ClearState ()
{
CallStack = null;
ValueTypesCache.Clear ();
LocalsCache.Clear ();
nextValueTypeId = 0;
}
public int NextValueTypeId () => Interlocked.Increment (ref nextValueTypeId);
}
}

View File

@ -246,7 +246,7 @@ namespace WebAssembly.Net.Debugging {
{
JObject o = result.ToJObject (id);
if (result.IsErr)
logger.LogError ("sending error response {result}", result);
logger.LogError ($"sending error response for id: {id} -> {result}");
Send (this.ide, o, token);
}

View File

@ -17,7 +17,7 @@ namespace WebAssembly.Net.Debugging {
HashSet<SessionId> sessions = new HashSet<SessionId> ();
Dictionary<SessionId, ExecutionContext> contexts = new Dictionary<SessionId, ExecutionContext> ();
public MonoProxy (ILoggerFactory loggerFactory, bool hideWebDriver = true) : base(loggerFactory) { }
public MonoProxy (ILoggerFactory loggerFactory, bool hideWebDriver = true) : base(loggerFactory) { this.hideWebDriver = hideWebDriver; }
readonly bool hideWebDriver;
@ -166,7 +166,7 @@ namespace WebAssembly.Net.Debugging {
//FIXME support variant where restrictToFunction=true and end is omitted
var end = SourceLocation.Parse (args? ["end"] as JObject);
if (start != null && end != null && await GetPossibleBreakpoints (id, start, end, token))
return true;
return true;
SendResponse (id, resp, token);
return true;
@ -220,57 +220,35 @@ namespace WebAssembly.Net.Debugging {
}
case "Debugger.evaluateOnCallFrame": {
var objId = args? ["callFrameId"]?.Value<string> ();
if (objId.StartsWith ("dotnet:", StringComparison.Ordinal)) {
var parts = objId.Split (new char [] { ':' });
if (parts.Length < 3)
return true;
switch (parts [1]) {
case "scope": {
await GetEvaluateOnCallFrame (id, int.Parse (parts [2]), args? ["expression"]?.Value<string> (), token);
break;
}
}
return true;
if (!DotnetObjectId.TryParse (args? ["callFrameId"], out var objectId))
return false;
switch (objectId.Scheme) {
case "scope":
return await OnEvaluateOnCallFrame (id,
int.Parse (objectId.Value),
args? ["expression"]?.Value<string> (), token);
default:
return false;
}
return false;
}
case "Runtime.getProperties": {
var objId = args? ["objectId"]?.Value<string> ();
if (objId.StartsWith ("dotnet:", StringComparison.Ordinal)) {
var parts = objId.Split (new char [] { ':' });
if (parts.Length < 3)
return true;
switch (parts[1]) {
case "scope": {
await GetScopeProperties (id, int.Parse (parts[2]), token);
break;
}
case "object": {
await GetDetails (id, MonoCommands.GetObjectProperties (int.Parse (parts[2]), expandValueTypes: false), token);
break;
}
case "array": {
await GetArrayDetails (id, objId, parts, token);
break;
}
case "valuetype": {
await GetDetailsForValueType (id, objId,
get_props_cmd_fn: () => {
if (parts.Length < 4)
return null;
if (!DotnetObjectId.TryParse (args? ["objectId"], out var objectId))
break;
var containerObjId = int.Parse (parts[2]);
return MonoCommands.GetObjectProperties (containerObjId, expandValueTypes: true);
}, token);
break;
}
}
var result = await RuntimeGetProperties (id, objectId, args, token);
SendResponse (id, result, token);
return true;
}
return true;
}
break;
case "Runtime.releaseObject": {
if (!(DotnetObjectId.TryParse (args ["objectId"], out var objectId) && objectId.Scheme == "cfo_res"))
break;
await SendMonoCommand (id, new MonoCommands ($"MONO.mono_wasm_release_object('{objectId}')"), token);
SendResponse (id, Result.OkFromObject (new{}), token);
return true;
}
// Protocol extensions
@ -334,33 +312,62 @@ namespace WebAssembly.Net.Debugging {
return true;
}
case "Runtime.callFunctionOn": {
if (!DotnetObjectId.TryParse (args ["objectId"], out var objectId))
return false;
var silent = args ["silent"]?.Value<bool> () ?? false;
if (objectId.Scheme == "scope") {
var fail = silent ? Result.OkFromObject (new { result = new { } }) : Result.Exception (new ArgumentException ($"Runtime.callFunctionOn not supported with scope ({objectId})."));
SendResponse (id, fail, token);
return true;
}
var returnByValue = args ["returnByValue"]?.Value<bool> () ?? false;
var cmd = new MonoCommands ($"MONO.mono_wasm_call_function_on ({args.ToString ()}, {(returnByValue ? "true" : "false")})");
var res = await SendMonoCommand (id, cmd, token);
if (!returnByValue &&
DotnetObjectId.TryParse (res.Value?["result"]?["value"]?["objectId"], out var resultObjectId) &&
resultObjectId.Scheme == "cfo_res")
res = Result.OkFromObject (new { result = res.Value ["result"]["value"] });
if (res.IsErr && silent)
res = Result.OkFromObject (new { result = new { } });
SendResponse (id, res, token);
return true;
}
}
return false;
}
async Task GetArrayDetails (MessageId id, string objId, string[] objIdParts, CancellationToken token)
async Task<Result> RuntimeGetProperties (MessageId id, DotnetObjectId objectId, JToken args, CancellationToken token)
{
switch (objIdParts.Length)
{
case 3: {
await GetDetails (id, MonoCommands.GetArrayValues (int.Parse (objIdParts [2])), token);
break;
}
case 4: {
// This form of the id is being used only for valuetypes right now
await GetDetailsForValueType(id, objId,
get_props_cmd_fn: () => {
var arrayObjectId = int.Parse (objIdParts [2]);
var idx = int.Parse (objIdParts [3]);
return MonoCommands.GetArrayValueExpanded (arrayObjectId, idx);
}, token);
break;
}
default:
SendResponse (id, Result.Exception (new ArgumentException ($"Unknown objectId format for array: {objId}")), token);
break;
if (objectId.Scheme == "scope")
return await GetScopeProperties (id, int.Parse (objectId.Value), token);
var res = await SendMonoCommand (id, new MonoCommands ($"MONO.mono_wasm_get_details ('{objectId}', {args})"), token);
if (res.IsErr)
return res;
if (objectId.Scheme == "cfo_res") {
// Runtime.callFunctionOn result object
var value_json_str = res.Value ["result"]?["value"]?["__value_as_json_string__"]?.Value<string> ();
if (value_json_str != null) {
res = Result.OkFromObject (new {
result = JArray.Parse (value_json_str.Replace (@"\""", "\""))
});
} else {
res = Result.OkFromObject (new { result = new {} });
}
} else {
res = Result.Ok (JObject.FromObject (new { result = res.Value ["result"] ["value"] }));
}
return res;
}
//static int frame_id=0;
@ -530,37 +537,6 @@ namespace WebAssembly.Net.Debugging {
return true;
}
async Task GetDetails(MessageId msg_id, MonoCommands cmd, CancellationToken token, bool send_response = true)
{
var res = await SendMonoCommand(msg_id, cmd, token);
//if we fail we just buble that to the IDE (and let it panic over it)
if (res.IsErr) {
SendResponse(msg_id, res, token);
return;
}
try {
var var_list = res.Value?["result"]?["value"]?.Values<JObject>().ToArray() ?? Array.Empty<JObject>();
if (var_list.Length > 0)
ExtractAndCacheValueTypes (GetContext (msg_id), var_list);
if (!send_response)
return;
var response = JObject.FromObject(new
{
result = var_list
});
SendResponse(msg_id, Result.Ok(response), token);
} catch (Exception e) when (send_response) {
Log ("verbose", $"failed to parse {res.Value} - {e.Message}");
SendResponse(msg_id, Result.Exception(e), token);
}
}
internal bool TryFindVariableValueInCache(ExecutionContext ctx, string expression, bool only_search_on_this, out JToken obj)
{
if (ctx.LocalsCache.TryGetValue (expression, out obj)) {
@ -577,9 +553,9 @@ namespace WebAssembly.Net.Debugging {
var context = GetContext (msg_id);
if (context.CallStack == null)
return null;
if (TryFindVariableValueInCache(context, expression, only_search_on_this, out JToken obj))
return obj;
return obj;
var scope = context.CallStack.FirstOrDefault (s => s.Id == scope_id);
var vars = scope.Method.GetLiveVarsAt (scope.Location.CliLocation.Offset);
@ -588,7 +564,7 @@ namespace WebAssembly.Net.Debugging {
var res = await SendMonoCommand (msg_id, MonoCommands.GetScopeVariables (scope.Id, var_ids), token);
var values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray ();
thisValue = values.FirstOrDefault (v => v ["name"].Value<string> () == "this");
if (!only_search_on_this) {
if (thisValue != null && expression == "this") {
return thisValue;
@ -604,9 +580,10 @@ namespace WebAssembly.Net.Debugging {
//search in scope
if (thisValue != null) {
var objectId = thisValue ["value"] ["objectId"].Value<string> ();
var parts = objectId.Split (new char [] { ':' });
res = await SendMonoCommand (msg_id, MonoCommands.GetObjectProperties (int.Parse (parts [2]), expandValueTypes: false), token);
if (!DotnetObjectId.TryParse (thisValue ["value"] ["objectId"], out var objectId))
return null;
res = await SendMonoCommand (msg_id, MonoCommands.GetObjectProperties (objectId, expandValueTypes: false), token);
values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray ();
var foundValue = values.FirstOrDefault (v => v ["name"].Value<string> () == expression);
if (foundValue != null) {
@ -618,12 +595,12 @@ namespace WebAssembly.Net.Debugging {
return null;
}
async Task GetEvaluateOnCallFrame (MessageId msg_id, int scope_id, string expression, CancellationToken token)
async Task<bool> OnEvaluateOnCallFrame (MessageId msg_id, int scope_id, string expression, CancellationToken token)
{
try {
var context = GetContext (msg_id);
if (context.CallStack == null)
return;
return false;
var varValue = await TryGetVariableValue (msg_id, scope_id, expression, false, token);
@ -632,7 +609,7 @@ namespace WebAssembly.Net.Debugging {
SendResponse (msg_id, Result.OkFromObject (new {
result = varValue ["value"]
}), token);
return;
return true;
}
string retValue = await EvaluateExpression.CompileAndRunTheExpression (this, msg_id, scope_id, expression, token);
@ -641,43 +618,34 @@ namespace WebAssembly.Net.Debugging {
value = retValue
}
}), token);
return true;
} catch (Exception e) {
logger.LogTrace (e.Message, expression);
SendResponse (msg_id, Result.OkFromObject (new {}), token);
logger.LogDebug (e, $"Error in EvaluateOnCallFrame for expression '{expression}.");
}
return false;
}
async Task GetScopeProperties (MessageId msg_id, int scope_id, CancellationToken token)
async Task<Result> GetScopeProperties (MessageId msg_id, int scope_id, CancellationToken token)
{
try {
var ctx = GetContext (msg_id);
var scope = ctx.CallStack.FirstOrDefault (s => s.Id == scope_id);
if (scope == null) {
SendResponse (msg_id,
Result.Err (JObject.FromObject (new { message = $"Could not find scope with id #{scope_id}" })),
token);
return;
}
if (scope == null)
return Result.Err (JObject.FromObject (new { message = $"Could not find scope with id #{scope_id}" }));
var vars = scope.Method.GetLiveVarsAt (scope.Location.CliLocation.Offset);
var var_ids = vars.Select (v => v.Index).ToArray ();
var res = await SendMonoCommand (msg_id, MonoCommands.GetScopeVariables (scope.Id, var_ids), token);
//if we fail we just buble that to the IDE (and let it panic over it)
if (res.IsErr) {
SendResponse (msg_id, res, token);
return;
}
if (res.IsErr)
return res;
var values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray ();
if(values == null) {
SendResponse (msg_id, Result.OkFromObject (new {result = Array.Empty<object> ()}), token);
return;
}
ExtractAndCacheValueTypes (ctx, values);
if(values == null)
return Result.OkFromObject (new { result = Array.Empty<object> () });
var var_list = new List<object> ();
int i = 0;
@ -690,72 +658,10 @@ namespace WebAssembly.Net.Debugging {
var_list.Add (values [i]);
}
SendResponse (msg_id, Result.OkFromObject (new { result = var_list }), token);
return Result.OkFromObject (new { result = var_list });
} catch (Exception exception) {
Log ("verbose", $"Error resolving scope properties {exception.Message}");
SendResponse (msg_id, Result.Exception (exception), token);
}
}
IEnumerable<JObject> ExtractAndCacheValueTypes (ExecutionContext ctx, IEnumerable<JObject> var_list)
{
foreach (var jo in var_list) {
var value = jo["value"]?.Value<JObject> ();
if (value ["type"]?.Value<string> () != "object")
continue;
if (!(value ["isValueType"]?.Value<bool> () ?? false) || // not a valuetype
!(value ["expanded"]?.Value<bool> () ?? false)) // not expanded
continue;
// Expanded ValueType
var members = value ["members"]?.Values<JObject>().ToArray() ?? Array.Empty<JObject>();
var objectId = value ["objectId"]?.Value<string> () ?? $"dotnet:valuetype:{ctx.NextValueTypeId ()}";
value ["objectId"] = objectId;
ExtractAndCacheValueTypes (ctx, members);
ctx.ValueTypesCache [objectId] = JArray.FromObject (members);
value.Remove ("members");
}
return var_list;
}
async Task<bool> GetDetailsForValueType (MessageId msg_id, string object_id, Func<MonoCommands> get_props_cmd_fn, CancellationToken token)
{
var ctx = GetContext (msg_id);
if (!ctx.ValueTypesCache.ContainsKey (object_id)) {
var cmd = get_props_cmd_fn ();
if (cmd == null) {
SendResponse (msg_id, Result.Exception (new ArgumentException (
"Could not find a cached value for {object_id}, and cant' expand it.")),
token);
return false;
} else {
await GetDetails (msg_id, cmd, token, send_response: false);
}
}
if (ctx.ValueTypesCache.TryGetValue (object_id, out var var_list)) {
var response = JObject.FromObject(new
{
result = var_list
});
SendResponse(msg_id, Result.Ok(response), token);
return true;
} else {
var response = JObject.FromObject(new
{
result = $"Unable to get details for {object_id}"
});
SendResponse(msg_id, Result.Err(response), token);
return false;
return Result.Exception (exception);
}
}