Skip to content

Commit

Permalink
[wasm] MemberReferenceResolver: rework a bit to fix some cases
Browse files Browse the repository at this point in the history
  • Loading branch information
radical committed Nov 13, 2021
1 parent 5f5cdf6 commit d020d36
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 99 deletions.
220 changes: 133 additions & 87 deletions src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.IO;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand Down Expand Up @@ -82,68 +81,86 @@ public async Task<JObject> GetValueFromObject(JToken objRet, CancellationToken t
return null;
}

public async Task<JObject> TryToRunOnLoadedClasses(string varName, CancellationToken token)
public async Task<(JObject containerObject, string remaining)> ResolveStaticMembersInStaticTypes(string varName, CancellationToken token)
{
string classNameToFind = "";
string[] parts = varName.Split(".");
var typeId = -1;
foreach (string part in parts)
var store = await proxy.LoadStore(sessionId, token);
var methodInfo = ctx.CallStack.FirstOrDefault(s => s.Id == scopeId).Method.Info;

int typeId = -1;
for (int i = 0; i < parts.Length; i++)
{
string part = parts[i].Trim();

if (classNameToFind.Length > 0)
classNameToFind += ".";
classNameToFind += part.Trim();
classNameToFind += part;

if (typeId != -1)
{
var fields = await sdbHelper.GetTypeFields(sessionId, typeId, token);
foreach (var field in fields)
{
if (field.Name == part.Trim())
{
var isInitialized = await sdbHelper.TypeIsInitialized(sessionId, typeId, token);
if (isInitialized == 0)
{
isInitialized = await sdbHelper.TypeInitialize(sessionId, typeId, token);
}
var valueRet = await sdbHelper.GetFieldValue(sessionId, typeId, field.Id, token);
return await GetValueFromObject(valueRet, token);
}
}
var methodId = await sdbHelper.GetPropertyMethodIdByName(sessionId, typeId, part.Trim(), token);
if (methodId != -1)
string remaining = null;
JObject memberObject = await FindStaticMemberInType(part, typeId);
if (memberObject != null && i < parts.Length - 1)
remaining = string.Join('.', parts[(i+1)..]);

return (memberObject, remaining);
}

if (!string.IsNullOrEmpty(methodInfo.TypeInfo.Namespace))
typeId = await FindStaticTypeId(methodInfo.TypeInfo.Namespace + "." + classNameToFind);
if (typeId == -1)
typeId = await FindStaticTypeId(classNameToFind);
}

return (null, null);

async Task<JObject> FindStaticMemberInType(string name, int typeId)
{
var fields = await sdbHelper.GetTypeFields(sessionId, typeId, token);
foreach (var field in fields)
{
if (field.Name != name)
continue;

var isInitialized = await sdbHelper.TypeIsInitialized(sessionId, typeId, token);
if (isInitialized == 0)
{
var commandParamsObj = new MemoryStream();
var commandParamsObjWriter = new MonoBinaryWriter(commandParamsObj);
commandParamsObjWriter.Write(0); //param count
var retMethod = await sdbHelper.InvokeMethod(sessionId, commandParamsObj.ToArray(), methodId, "methodRet", token);
return await GetValueFromObject(retMethod, token);
isInitialized = await sdbHelper.TypeInitialize(sessionId, typeId, token);
}
var valueRet = await sdbHelper.GetFieldValue(sessionId, typeId, field.Id, token);

return await GetValueFromObject(valueRet, token);
}
var store = await proxy.LoadStore(sessionId, token);
var info = ctx.CallStack.FirstOrDefault(s => s.Id == scopeId).Method.Info;
var classNameToFindWithNamespace =
string.IsNullOrEmpty(info.TypeInfo.Namespace) ?
classNameToFind :
info.TypeInfo.Namespace + "." + classNameToFind;

foreach (var asm in store.assemblies)
var methodId = await sdbHelper.GetPropertyMethodIdByName(sessionId, typeId, name, token);
if (methodId != -1)
{
if (await TryGetTypeIdFromName(classNameToFindWithNamespace, asm))
break;
if (await TryGetTypeIdFromName(classNameToFind, asm))
break;
var commandParamsObj = new MemoryStream();
var commandParamsObjWriter = new MonoBinaryWriter(commandParamsObj);
commandParamsObjWriter.Write(0); //param count
var retMethod = await sdbHelper.InvokeMethod(sessionId, commandParamsObj.ToArray(), methodId, "methodRet", token);
return await GetValueFromObject(retMethod, token);
}

async Task<bool> TryGetTypeIdFromName(string typeName, AssemblyInfo assembly)
return null;
}

async Task<int> FindStaticTypeId(string typeName)
{
foreach (var asm in store.assemblies)
{
var type = assembly.GetTypeByName(typeName);
var type = asm.GetTypeByName(typeName);
if (type == null)
return false;
continue;

typeId = await sdbHelper.GetTypeIdFromToken(sessionId, assembly.DebugId, type.Token, token);
return true;
int id = await sdbHelper.GetTypeIdFromToken(sessionId, asm.DebugId, type.Token, token);
if (id != -1)
return id;
}

return -1;
}
return null;
}

// Checks Locals, followed by `this`
Expand All @@ -153,9 +170,6 @@ public async Task<JObject> Resolve(string varName, CancellationToken token)
if (varName.Contains('('))
return null;

string[] parts = varName.Split(".");
JObject rootObject = null;

if (scopeCache.MemberReferences.TryGetValue(varName, out JObject ret)) {
return ret;
}
Expand All @@ -164,66 +178,98 @@ public async Task<JObject> Resolve(string varName, CancellationToken token)
return await GetValueFromObject(valueRet, token);
}

foreach (string part in parts)
string[] parts = varName.Split(".");
if (parts.Length == 0)
return null;

JObject retObject = await ResolveAsLocalOrThisMember(parts[0]);
if (retObject != null && parts.Length > 1)
retObject = await ResolveAsInstanceMember(string.Join('.', parts[1..]), retObject);

if (retObject == null)
{
string partTrimmed = part.Trim();
if (partTrimmed == "")
return null;
if (rootObject != null)
(retObject, string remaining) = await ResolveStaticMembersInStaticTypes(varName, token);
if (!string.IsNullOrEmpty(remaining))
{
if (rootObject?["subtype"]?.Value<string>() == "null")
return null;
if (DotnetObjectId.TryParse(rootObject?["objectId"]?.Value<string>(), out DotnetObjectId objectId))
if (retObject?["subtype"]?.Value<string>() == "null")
{
var rootResObj = await proxy.RuntimeGetPropertiesInternal(sessionId, objectId, null, token);
var objRet = rootResObj.FirstOrDefault(objPropAttr => objPropAttr["name"].Value<string>() == partTrimmed);
if (objRet == null)
return null;

rootObject = await GetValueFromObject(objRet, token);
// NRE on null.$remaining
retObject = null;
}
else
{
retObject = await ResolveAsInstanceMember(remaining, retObject);
}
continue;
}
}

scopeCache.MemberReferences[varName] = retObject;
return retObject;

async Task<JObject> ResolveAsLocalOrThisMember(string name)
{
if (scopeCache.Locals.Count == 0 && !localsFetched)
{
Result scope_res = await proxy.GetScopeProperties(sessionId, scopeId, token);
if (scope_res.IsErr)
throw new Exception($"BUG: Unable to get properties for scope: {scopeId}. {scope_res}");
localsFetched = true;
}
if (scopeCache.Locals.TryGetValue(partTrimmed, out JObject obj))
{
rootObject = obj["value"]?.Value<JObject>();
}
else if (scopeCache.Locals.TryGetValue("this", out JObject objThis))

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

if (!scopeCache.Locals.TryGetValue("this", out JObject objThis))
return null;

if (!DotnetObjectId.TryParse(objThis?["value"]?["objectId"]?.Value<string>(), out DotnetObjectId objectId))
return null;

var rootResObj = await proxy.RuntimeGetPropertiesInternal(sessionId, objectId, null, token);
var objRet = rootResObj.FirstOrDefault(objPropAttr => objPropAttr["name"].Value<string>() == name);
if (objRet != null)
return await GetValueFromObject(objRet, token);

return null;
}

async Task<JObject> ResolveAsInstanceMember(string expr, JObject baseObject)
{
JObject resolvedObject = baseObject;
string[] parts = expr.Split('.');
for (int i = 0; i < parts.Length; i++)
{
if (partTrimmed == "this")
{
rootObject = objThis?["value"].Value<JObject>();
}
else if (DotnetObjectId.TryParse(objThis?["value"]?["objectId"]?.Value<string>(), out DotnetObjectId objectId))
string partTrimmed = parts[i].Trim();
if (partTrimmed.Length == 0)
return null;

if (!DotnetObjectId.TryParse(resolvedObject?["objectId"]?.Value<string>(), out DotnetObjectId objectId))
return null;

var resolvedResObj = await proxy.RuntimeGetPropertiesInternal(sessionId, objectId, null, token);
var objRet = resolvedResObj.FirstOrDefault(objPropAttr => objPropAttr["name"].Value<string>() == partTrimmed);
if (objRet == null)
return null;

resolvedObject = await GetValueFromObject(objRet, token);
if (resolvedObject == null)
return null;

if (resolvedObject["subtype"]?.Value<string>() == "null")
{
var rootResObj = await proxy.RuntimeGetPropertiesInternal(sessionId, objectId, null, token);
var objRet = rootResObj.FirstOrDefault(objPropAttr => objPropAttr["name"].Value<string>() == partTrimmed);
if (objRet != null)
if (i < parts.Length - 1)
{
rootObject = await GetValueFromObject(objRet, token);
}
else
{
rootObject = await TryToRunOnLoadedClasses(varName, token);
return rootObject;
// there is some parts remaining, and can't
// do null.$remaining
return null;
}

return resolvedObject;
}
}

return resolvedObject;
}
if (rootObject == null)
{
rootObject = await TryToRunOnLoadedClasses(varName, token);
return rootObject;
}
scopeCache.MemberReferences[varName] = rootObject;
return rootObject;
}

public async Task<JObject> Resolve(ElementAccessExpressionSyntax elementAccess, Dictionary<string, JObject> memberAccessValues, JObject indexObject, CancellationToken token)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,10 @@ await EvaluateOnCallFrameFail(id,
//BUG: TODO:
//("a)", "CompilationError"),

("this.c.e", "ReferenceError"), // this.<int-field>.<field>
("this.e.a", "ReferenceError"), // this.<int-local>.<field>
("this.dt.e", "ReferenceError"), // this.<field>.<local>

("this.a.", "ReferenceError"),
("a.", "ReferenceError"),

Expand All @@ -465,6 +469,9 @@ public async Task NegativeTestsInStaticMethod() => await CheckInspectLocalsAtBre
var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();

await EvaluateOnCallFrameFail(id,
("EvaluateStaticClass.f_s.c", "ReferenceError"), // <static-type>.<local>.<field>
("EvaluateStaticClass.NonExistant.f_s.c", "ReferenceError"), // <static-type>.<non-existant>.<local>.<field>
("NonExistant.f_s.dateTime", "ReferenceError"), // <non-existant>.<local>...
("me.foo", "ReferenceError"),
("this", "ReferenceError"),
("this.NullIfAIsNotZero.foo", "ReferenceError"));
Expand Down Expand Up @@ -693,10 +700,11 @@ public async Task EvaluateStaticClass() => await CheckInspectLocalsAtBreakpointS
var frame = pause_location["callFrames"][0];

await EvaluateOnCallFrameAndCheck(id,
("DebuggerTests.EvaluateStaticClass.StaticField1", TNumber(10)));
await EvaluateOnCallFrameAndCheck(id,
("DebuggerTests.EvaluateStaticClass.StaticProperty1", TString("StaticProperty1")));
await EvaluateOnCallFrameAndCheck(id,
("EvaluateStaticClass.StaticDTProperty1.Hour", TNumber(4)), // <static-type>.<static-prop>.<instance member>
("EvaluateStaticClass.StaticDTField1.Hour", TNumber(3)), // <static-type>.<static-field>.<instance member>
("EvaluateStaticClass.StaticDTField1.Date.Year", TNumber(2000)), // <static-type>.<static-field>.<instance-member>.<instance member>
("DebuggerTests.EvaluateStaticClass.StaticField1", TNumber(10)),
("DebuggerTests.EvaluateStaticClass.StaticProperty1", TString("StaticProperty1")),
("DebuggerTests.EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented")));
});

Expand Down Expand Up @@ -769,16 +777,16 @@ public async Task EvaluateStaticClassesFromDifferentNamespaceInDifferentFrames()
var frame = pause_location["callFrames"][0];

await EvaluateOnCallFrameAndCheck(id_top,
("EvaluateStaticClass.StaticField1", TNumber(20)),
("EvaluateStaticClass.StaticProperty1", TString("StaticProperty2")),
("EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented")));
("EvaluateStaticClass.StaticField1", TNumber(20)),
("EvaluateStaticClass.StaticProperty1", TString("StaticProperty2")),
("EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented")));

var id_second = pause_location["callFrames"][1]["callFrameId"].Value<string>();

await EvaluateOnCallFrameAndCheck(id_second,
("EvaluateStaticClass.StaticField1", TNumber(10)),
("EvaluateStaticClass.StaticProperty1", TString("StaticProperty1")),
("EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented")));
("EvaluateStaticClass.StaticField1", TNumber(10)),
("EvaluateStaticClass.StaticProperty1", TString("StaticProperty1")),
("EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented")));
});

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,9 @@ public static void EvaluateAsyncMethods()
public static class EvaluateStaticClass
{
public static int StaticField1 = 10;
public static DateTime StaticDTField1 = new DateTime(2000, 5, 4, 3, 2, 1);
public static string StaticProperty1 => "StaticProperty1";
public static DateTime StaticDTProperty1 => new DateTime(2000, 8, 1, 4, 7, 1);
public static string StaticPropertyWithError => throw new Exception("not implemented");
}

Expand Down Expand Up @@ -466,7 +468,7 @@ public void run()
textListOfLists = new List<List<string>> { textList, textList };
idx0 = 0;
idx1 = 1;
}
}
}

public static void EvaluateLocals()
Expand All @@ -493,4 +495,4 @@ public static void Run()
var a = 0;
}
}
}
}

0 comments on commit d020d36

Please sign in to comment.