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 #61660

Merged
merged 88 commits into from
Nov 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 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
4ba6b58
Merge branch 'add-static-attribute-support' into add-nested-static-at…
ilonatommy Nov 11, 2021
c5d0153
Tests for nested static classes.
ilonatommy Nov 12, 2021
13a4a38
Fix for nested static classes.
ilonatommy Nov 12, 2021
c5e1791
Fixed 9 tests from EvaluateOnCallFrame.
ilonatommy Nov 12, 2021
7151177
Fixing indentation and spaces.
ilonatommy Nov 15, 2021
5fc759e
Applied review comments for values evaluation.
ilonatommy Nov 15, 2021
3661c0e
Compressed two tests into one with MemberData.
ilonatommy Nov 15, 2021
66ed5c2
Added test case of type without namespace (failing).
ilonatommy Nov 15, 2021
382899f
Merge branch 'add-static-attribute-support' into add-nested-static-at…
ilonatommy Nov 15, 2021
4c3afed
Merge branch 'add-nested-static-attribute-support' into nested-static…
ilonatommy Nov 15, 2021
136f69a
Addressed Ankit advices from the review.
ilonatommy Nov 16, 2021
f254d3f
Merge branch 'add-static-attribute-support' into add-nested-static-at…
ilonatommy Nov 16, 2021
425b769
Revert merged nested evaluation changes.
ilonatommy Nov 16, 2021
8c00bc4
Incorporate Ankit's changes from d020d36.
ilonatommy Nov 16, 2021
525f5c8
Fix - when both valuesare null we should keep checking (e.g. for nest…
ilonatommy Nov 16, 2021
02d094e
Added nested tests.
ilonatommy Nov 16, 2021
9f2d8e2
Redo changes after reverting them in merge.
ilonatommy Nov 16, 2021
dc8a95d
Merge branch 'add-nested-static-attribute-support' into nested-static…
ilonatommy Nov 16, 2021
894d6f4
Fixed - works with and without namespace.
ilonatommy Nov 16, 2021
219a79a
Merge branch 'add-nested-static-attribute-support' into nested-static…
ilonatommy Nov 16, 2021
59c3060
Merge with main.
ilonatommy Nov 18, 2021
869bd29
Fix merge.
ilonatommy Nov 18, 2021
5ff27f0
Using current namespace as the default place to serach for the resolv…
ilonatommy Nov 5, 2021
250ad0b
Add tests for static class, static fields and pausing in async method.
ilonatommy Nov 5, 2021
640e416
Added tests for class evaluation.
ilonatommy Nov 8, 2021
9dec5bc
Fixing support to the current namespace and adding tests for it
thaystg Nov 8, 2021
0a4da06
Assuing that we search within the current assembly first. Removed tes…
ilonatommy Nov 9, 2021
64756b2
Remove a test-duplicate that was not testing static class or static f…
ilonatommy Nov 9, 2021
47474c7
Fixing indentation.
ilonatommy Nov 9, 2021
298be4b
Extracting the check from the loop. One time check is enough.
ilonatommy Nov 10, 2021
d85a0c2
Simplifying multiple test cases into one call.
ilonatommy Nov 10, 2021
4b45d63
Added test that was skipped by mistake.
ilonatommy Nov 11, 2021
01b1991
Test for classes evaluated from different frames.
ilonatommy Nov 11, 2021
5f88089
Tests for nested static classes.
ilonatommy Nov 12, 2021
cdbe1c7
Fix for nested static classes.
ilonatommy Nov 12, 2021
cada76c
Fixing indentation and spaces.
ilonatommy Nov 15, 2021
a840c24
Applied review comments for values evaluation.
ilonatommy Nov 15, 2021
59bdf92
Compressed two tests into one with MemberData.
ilonatommy Nov 15, 2021
7e06d5a
Addressed Ankit advices from the review.
ilonatommy Nov 16, 2021
fd1f980
Revert merged nested evaluation changes.
ilonatommy Nov 16, 2021
35f33c9
Incorporate Ankit's changes from d020d36.
ilonatommy Nov 16, 2021
ad8cc3a
Fix - when both valuesare null we should keep checking (e.g. for nest…
ilonatommy Nov 16, 2021
d3928d9
Fix merge.
ilonatommy Nov 18, 2021
c322461
Cleanup after rebase.
ilonatommy Nov 18, 2021
ecadc20
Merge.
ilonatommy Nov 18, 2021
15a6636
Merge branch 'main' into fix-static-attribute-support
ilonatommy Nov 18, 2021
1169797
Added nested tests.
ilonatommy Nov 16, 2021
a096846
Fixed - works with and without namespace.
ilonatommy Nov 16, 2021
421673c
Merge branch 'add-nested-static-attribute-support' of https://github.…
ilonatommy Nov 18, 2021
4b03b37
Clean-up after rebasing with fix-static-attribute-support.
ilonatommy Nov 18, 2021
4b8ac74
Fixed 9 tests from EvaluateOnCallFrame.
ilonatommy Nov 12, 2021
244546d
Merge branch 'nested-static-classes-fix-broken-tests' of https://gith…
ilonatommy Nov 18, 2021
f736557
Fixed 18 test types.
ilonatommy Nov 18, 2021
67efb17
Fix test cases with spaces in the names, e.g. " this" evaluation.
ilonatommy Nov 18, 2021
fac543d
Update src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolv…
ilonatommy Nov 19, 2021
b8e48c3
Merge branch 'add-nested-static-attribute-support' into nested-static…
ilonatommy Nov 19, 2021
61336b6
Avoid resolving fields of a null value.
ilonatommy Nov 19, 2021
26512dc
Merge pull request #1 from ilonatommy/nested-static-classes-fix-broke…
ilonatommy Nov 19, 2021
c55a350
Fix for 4 failing tests of Count evaluation.
ilonatommy Nov 19, 2021
e7e4e5b
Merge pull request #2 from ilonatommy/fix-count-in-description-evalua…
ilonatommy Nov 19, 2021
ba9e6e8
Update src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolv…
ilonatommy Nov 24, 2021
15cb1a0
Update src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolv…
ilonatommy Nov 24, 2021
f9afb46
Add null safety.
ilonatommy Nov 24, 2021
6c20935
Exchanged multiple trims for one.
ilonatommy Nov 24, 2021
6a1b7ad
Undo nested static changes that will be sumbitted in another PR.
ilonatommy Nov 24, 2021
a223689
Revert "Undo nested static changes that will be sumbitted in another …
ilonatommy Nov 24, 2021
acd9613
Merge pull request #3 from ilonatommy/add-nested-static-attribute-sup…
ilonatommy Nov 24, 2021
dc6ec6b
Hiding test that has to be fixed in the future.
ilonatommy Nov 24, 2021
139ec95
Applying Ankit's suggestion about code simplification.
ilonatommy Nov 24, 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
13 changes: 5 additions & 8 deletions src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -488,16 +488,13 @@ public TypeInfo(AssemblyInfo assembly, TypeDefinitionHandle typeHandle, TypeDefi
this.type = type;
methods = new List<MethodInfo>();
Name = metadataReader.GetString(type.Name);
if (type.IsNested)
var declaringType = type;
while (declaringType.IsNested)
{
var declaringType = metadataReader.GetTypeDefinition(type.GetDeclaringType());
Name = metadataReader.GetString(declaringType.Name) + "/" + Name;
Namespace = metadataReader.GetString(declaringType.Namespace);
}
else
{
Namespace = metadataReader.GetString(type.Namespace);
declaringType = metadataReader.GetTypeDefinition(declaringType.GetDeclaringType());
Name = metadataReader.GetString(declaringType.Name) + "." + Name;
radical marked this conversation as resolved.
Show resolved Hide resolved
}
Namespace = metadataReader.GetString(declaringType.Namespace);
if (Namespace.Length > 0)
FullName = Namespace + "." + Name;
else
Expand Down
249 changes: 153 additions & 96 deletions src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,74 +77,99 @@ 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)
string[] parts = varName.Split(".", StringSplitOptions.TrimEntries);
var store = await proxy.LoadStore(sessionId, token);
var methodInfo = context.CallStack.FirstOrDefault(s => s.Id == scopeId)?.Method?.Info;
ilonatommy marked this conversation as resolved.
Show resolved Hide resolved

if (methodInfo == null)
return (null, null);

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

if (typeId != -1)
{
var fields = await context.SdbAgent.GetTypeFields(typeId, token);
foreach (var field in fields)
{
if (field.Name == part.Trim())
{
var isInitialized = await context.SdbAgent.TypeIsInitialized(typeId, token);
if (isInitialized == 0)
{
isInitialized = await context.SdbAgent.TypeInitialize(typeId, token);
}
var valueRet = await context.SdbAgent.GetFieldValue(typeId, field.Id, token);
return await GetValueFromObject(valueRet, token);
}
}
var methodId = await context.SdbAgent.GetPropertyMethodIdByName(typeId, part.Trim(), token);
if (methodId != -1)
JObject memberObject = await FindStaticMemberInType(part, typeId);
if (memberObject != null)
{
using var commandParamsObjWriter = new MonoBinaryWriter();
commandParamsObjWriter.Write(0); //param count
var retMethod = await context.SdbAgent.InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, "methodRet", token);
return await GetValueFromObject(retMethod, token);
string remaining = null;
if (i < parts.Length - 1)
remaining = string.Join('.', parts[(i + 1)..]);

return (memberObject, remaining);
}

// Didn't find a member named `part` in `typeId`.
// Could be a nested type. Let's continue the search
// with `part` added to the type name

typeId = -1;
}
var store = await proxy.LoadStore(sessionId, token);
var methodInfo = context.CallStack.FirstOrDefault(s => s.Id == scopeId)?.Method?.Info;
var classNameToFindWithNamespace =
string.IsNullOrEmpty(methodInfo?.TypeInfo?.Namespace) ?
classNameToFind :
methodInfo.TypeInfo.Namespace + "." + classNameToFind;

var searchResult = await TryFindNameInAssembly(store.assemblies, classNameToFindWithNamespace);
if (searchResult == null)
searchResult = await TryFindNameInAssembly(store.assemblies, classNameToFind);
if (searchResult != null)
typeId = (int)searchResult;

async Task<int?> TryGetTypeIdFromName(string typeName, AssemblyInfo assembly)

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

if (!string.IsNullOrEmpty(methodInfo?.TypeInfo?.Namespace))
{
var type = assembly.GetTypeByName(typeName);
if (type == null)
return null;
return await context.SdbAgent.GetTypeIdFromToken(assembly.DebugId, type.Token, token);
typeId = await FindStaticTypeId(methodInfo?.TypeInfo?.Namespace + "." + classNameToFind);
if (typeId != -1)
continue;
}
typeId = await FindStaticTypeId(classNameToFind);
}

return (null, null);

async Task<int?> TryFindNameInAssembly(List<AssemblyInfo> assemblies, string name)
async Task<JObject> FindStaticMemberInType(string name, int typeId)
{
var fields = await context.SdbAgent.GetTypeFields(typeId, token);
foreach (var field in fields)
{
foreach (var asm in assemblies)
if (field.Name != name)
continue;

var isInitialized = await context.SdbAgent.TypeIsInitialized(typeId, token);
if (isInitialized == 0)
{
var typeId = await TryGetTypeIdFromName(name, asm);
if (typeId != null)
return typeId;
isInitialized = await context.SdbAgent.TypeInitialize(typeId, token);
}
return null;
var valueRet = await context.SdbAgent.GetFieldValue(typeId, field.Id, token);

return await GetValueFromObject(valueRet, token);
}

var methodId = await context.SdbAgent.GetPropertyMethodIdByName(typeId, name, token);
if (methodId != -1)
{
using var commandParamsObjWriter = new MonoBinaryWriter();
commandParamsObjWriter.Write(0); //param count
var retMethod = await context.SdbAgent.InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), 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(typeName);
if (type == null)
continue;

int id = await context.SdbAgent.GetTypeIdFromToken(asm.DebugId, type.Token, token);
if (id != -1)
return id;
}

return -1;
}
return null;
}

// Checks Locals, followed by `this`
Expand All @@ -154,73 +179,105 @@ 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)) {
if (scopeCache.MemberReferences.TryGetValue(varName, out JObject ret))
return ret;
}

if (scopeCache.ObjectFields.TryGetValue(varName, out JObject valueRet)) {
if (scopeCache.ObjectFields.TryGetValue(varName, out JObject valueRet))
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)
{
var nameTrimmed = name.Trim();
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(nameTrimmed, 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>() == nameTrimmed);
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
{
break;
// 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);
scopeCache.MemberReferences[varName] = rootObject;
return rootObject;
}

public async Task<JObject> Resolve(ElementAccessExpressionSyntax elementAccess, Dictionary<string, JObject> memberAccessValues, JObject indexObject, CancellationToken token)
Expand Down
4 changes: 2 additions & 2 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -500,8 +500,8 @@ protected override async Task<bool> AcceptCommand(MessageId id, string method, J
{
// Maybe this is an async method, in which case the debug info is attached
// to the async method implementation, in class named:
// `{type_name}/<method_name>::MoveNext`
methodInfo = assembly.TypesByName.Values.SingleOrDefault(t => t.FullName.StartsWith($"{typeName}/<{methodName}>"))?
// `{type_name}.<method_name>::MoveNext`
methodInfo = assembly.TypesByName.Values.SingleOrDefault(t => t.FullName.StartsWith($"{typeName}.<{methodName}>"))?
.Methods.FirstOrDefault(mi => mi.Name == "MoveNext");
}

Expand Down
Loading