Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wasm] [debugger] Eval fixes for static class eval #61591

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
fab24e5
Using current namespace as the default place to serach for the resolv…
ilonatommy Nov 5, 2021
66b56a7
Add tests for static class, static fields and pausing in async method.
ilonatommy Nov 5, 2021
782a107
Added tests for class evaluation.
ilonatommy Nov 8, 2021
4119de3
Fixing support to the current namespace and adding tests for it
thaystg Nov 8, 2021
e275144
Merge branch 'add-static-attribute-support' of https://github.com/ilo…
ilonatommy Nov 9, 2021
ee19014
Assuing that we search within the current assembly first. Removed tes…
ilonatommy Nov 9, 2021
89bdc49
Remove a test-duplicate that was not testing static class or static f…
ilonatommy Nov 9, 2021
5ce0f57
Fixing indentation.
ilonatommy Nov 9, 2021
62d18b6
Refixing indentation.
ilonatommy Nov 9, 2021
ce177fd
Refix indentations again.
ilonatommy Nov 9, 2021
cb32402
Applied the advice about adding new blank lines.
ilonatommy Nov 10, 2021
ed2577e
Changed the current assembly check.
ilonatommy Nov 10, 2021
01f46d5
Extracting the check from the loop. One time check is enough.
ilonatommy Nov 10, 2021
d14367d
Simplifying multiple test cases into one call.
ilonatommy Nov 10, 2021
8a82380
Using local function as per review suggestion.
ilonatommy Nov 11, 2021
7f24d47
Added test that was skipped by mistake.
ilonatommy Nov 11, 2021
367c431
Added looking for the namespace in all assemblies because there is a …
ilonatommy Nov 11, 2021
55479c8
Extracting value based on the current frame, not the top of stack loc…
ilonatommy Nov 11, 2021
5f5cdf6
Test for classes evaluated from different frames.
ilonatommy Nov 11, 2021
d020d36
[wasm] MemberReferenceResolver: rework a bit to fix some cases
radical Nov 13, 2021
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
17 changes: 10 additions & 7 deletions src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,8 @@ internal class MethodInfo
public bool IsStatic() => (methodDef.Attributes & MethodAttributes.Static) != 0;
public int IsAsync { get; set; }
public bool IsHiddenFromDebugger { get; }
public TypeInfo TypeInfo { get; }

public MethodInfo(AssemblyInfo assembly, MethodDefinitionHandle methodDefHandle, int token, SourceFile source, TypeInfo type, MetadataReader asmMetadataReader, MetadataReader pdbMetadataReader)
{
this.IsAsync = -1;
Expand All @@ -343,6 +345,7 @@ public MethodInfo(AssemblyInfo assembly, MethodDefinitionHandle methodDefHandle,
this.Name = asmMetadataReader.GetString(methodDef.Name);
this.pdbMetadataReader = pdbMetadataReader;
this.IsEnCMethod = false;
this.TypeInfo = type;
if (!DebugInformation.SequencePointsBlob.IsNil)
{
var sps = DebugInformation.GetSequencePoints();
Expand Down Expand Up @@ -475,6 +478,7 @@ internal class TypeInfo
private TypeDefinition type;
private List<MethodInfo> methods;
internal int Token { get; }
internal string Namespace { get; }

public TypeInfo(AssemblyInfo assembly, TypeDefinitionHandle typeHandle, TypeDefinition type)
{
Expand All @@ -484,21 +488,20 @@ public TypeInfo(AssemblyInfo assembly, TypeDefinitionHandle typeHandle, TypeDefi
this.type = type;
methods = new List<MethodInfo>();
Name = metadataReader.GetString(type.Name);
var namespaceName = "";
if (type.IsNested)
{
var declaringType = metadataReader.GetTypeDefinition(type.GetDeclaringType());
Name = metadataReader.GetString(declaringType.Name) + "/" + Name;
namespaceName = metadataReader.GetString(declaringType.Namespace);
Namespace = metadataReader.GetString(declaringType.Namespace);
}
else
{
namespaceName = metadataReader.GetString(type.Namespace);
Namespace = metadataReader.GetString(type.Namespace);
}

if (namespaceName.Length > 0)
namespaceName += ".";
FullName = namespaceName + Name;
if (Namespace.Length > 0)
FullName = Namespace + "." + Name;
else
FullName = Name;
}

public TypeInfo(AssemblyInfo assembly, string name)
Expand Down
210 changes: 138 additions & 72 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,53 +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 methodId = await sdbHelper.GetPropertyMethodIdByName(sessionId, typeId, name, token);
if (methodId != -1)
{
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);
}

return null;
}

async Task<int> FindStaticTypeId(string typeName)
{
foreach (var asm in store.assemblies)
{
var type = asm.GetTypeByName(classNameToFind);
if (type != null)
{
typeId = await sdbHelper.GetTypeIdFromToken(sessionId, asm.DebugId, type.Token, token);
}
var type = asm.GetTypeByName(typeName);
if (type == null)
continue;

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 @@ -138,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 @@ -149,61 +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;
}
scopeCache.MemberReferences[varName] = rootObject;
return rootObject;
}

public async Task<JObject> Resolve(ElementAccessExpressionSyntax elementAccess, Dictionary<string, JObject> memberAccessValues, JObject indexObject, CancellationToken token)
Expand Down
Loading