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] Enable onCallFrame evaluation of static fields in classes/interfaces from a static method. #70560

Merged
107 changes: 72 additions & 35 deletions src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,53 +77,73 @@ public async Task<JObject> GetValueFromObject(JToken objRet, CancellationToken t
return null;
}

public async Task<(JObject containerObject, ArraySegment<string> remaining)> ResolveStaticMembersInStaticTypes(ArraySegment<string> parts, CancellationToken token)
public async Task<(JObject containerObject, ArraySegment<string> remaining)> ResolveStaticMembersInStaticTypes(ArraySegment<string> expressionParts, CancellationToken token)
{
string classNameToFind = "";
var store = await proxy.LoadStore(sessionId, false, token);
var methodInfo = context.CallStack.FirstOrDefault(s => s.Id == scopeId)?.Method?.Info;

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

int typeId = -1;
for (int i = 0; i < parts.Count; i++)
string[] parts = expressionParts.ToArray();

string fullName = methodInfo.IsAsync == 0 ? methodInfo.TypeInfo.FullName : StripAsyncPartOfFullName(methodInfo.TypeInfo.FullName);
string[] fullNameParts = fullName.Split(".", StringSplitOptions.TrimEntries).ToArray();
for (int i = 0; i < fullNameParts.Length; i++)
{
string part = parts[i];
string[] fullNamePrefix = fullNameParts[..^i];
var (memberObject, remaining) = await FindStaticMemberMatchingParts(parts, fullNamePrefix);
if (memberObject != null)
return (memberObject, remaining);
}
return await FindStaticMemberMatchingParts(parts);

if (typeId != -1)
async Task<(JObject, ArraySegment<string>)> FindStaticMemberMatchingParts(string[] parts, string[] fullNameParts = null)
{
string classNameToFind = fullNameParts == null ? "" : string.Join(".", fullNameParts);
int typeId = -1;
for (int i = 0; i < parts.Length; i++)
{
JObject memberObject = await FindStaticMemberInType(classNameToFind, part, typeId);
if (memberObject != null)
if (!string.IsNullOrEmpty(methodInfo.TypeInfo.Namespace))
{
ArraySegment<string> remaining = null;
if (i < parts.Count - 1)
remaining = parts[i..];

return (memberObject, remaining);
typeId = await FindStaticTypeId(methodInfo.TypeInfo.Namespace + "." + classNameToFind);
if (typeId != -1)
continue;
}
typeId = await FindStaticTypeId(classNameToFind);

// 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
string part = parts[i];
if (typeId != -1)
{
JObject memberObject = await FindStaticMemberInType(classNameToFind, part, typeId);
if (memberObject != null)
{
ArraySegment<string> remaining = null;
if (i < parts.Length - 1)
remaining = parts[i..];
return (memberObject, remaining);
}

typeId = -1;
}
// 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

if (classNameToFind.Length > 0)
classNameToFind += ".";
classNameToFind += part;
typeId = -1;
}

if (!string.IsNullOrEmpty(methodInfo?.TypeInfo?.Namespace))
{
typeId = await FindStaticTypeId(methodInfo?.TypeInfo?.Namespace + "." + classNameToFind);
if (typeId != -1)
continue;
if (classNameToFind.Length > 0)
classNameToFind += ".";
classNameToFind += part;
}
typeId = await FindStaticTypeId(classNameToFind);
return (null, null);
}

return (null, null);
// async function full name has a form: namespaceName.<currentFrame'sMethodName>d__integer
static string StripAsyncPartOfFullName(string fullName)
=> fullName.IndexOf(".<") is int index && index < 0
? fullName
: fullName.Substring(0, index);


async Task<JObject> FindStaticMemberInType(string classNameToFind, string name, int typeId)
{
Expand All @@ -138,20 +158,37 @@ async Task<JObject> FindStaticMemberInType(string classNameToFind, string name,
{
isInitialized = await context.SdbAgent.TypeInitialize(typeId, token);
}
var staticFieldValue = await context.SdbAgent.GetFieldValue(typeId, field.Id, token);
var valueRet = await GetValueFromObject(staticFieldValue, token);
// we need the full name here
valueRet["className"] = classNameToFind;
return valueRet;
try
{
var staticFieldValue = await context.SdbAgent.GetFieldValue(typeId, field.Id, token);
var valueRet = await GetValueFromObject(staticFieldValue, token);
// we need the full name here
valueRet["className"] = classNameToFind;
return valueRet;
}
catch (Exception ex)
{
logger.LogDebug(ex, $"Failed to get value of field {field.Name} on {classNameToFind} " +
$"because {field.Name} is not a static member of {classNameToFind}.");
}
return null;
}

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, token);
return await GetValueFromObject(retMethod, token);
try
{
var retMethod = await context.SdbAgent.InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, token);
return await GetValueFromObject(retMethod, token);
}
catch (Exception ex)
{
logger.LogDebug(ex, $"Failed to invoke getter of id={methodId} on {classNameToFind}.{name} " +
$"because {name} is not a static member of {classNameToFind}.");
}
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1414,6 +1414,15 @@ internal async Task SetJustMyCode(bool enabled)
Assert.True(res.IsOk);
Assert.Equal(res.Value["justMyCodeEnabled"], enabled);
}

internal async Task CheckEvaluateFail(string id, params (string expression, string message)[] args)
{
foreach (var arg in args)
{
(_, Result _res) = await EvaluateOnCallFrame(id, arg.expression, expect_ok: false).ConfigureAwait(false);;
AssertEqual(arg.message, _res.Error["result"]?["description"]?.Value<string>(), $"Expression '{arg.expression}' - wrong error message");
}
}
}

class DotnetObjectId
Expand Down
Loading