Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
202 changes: 127 additions & 75 deletions src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ protected override async Task<bool> AcceptCommand(MessageId sessionId, JObject a
var resolver = new MemberReferenceResolver(this, context, sessionId, scope.Id, logger);
JObject retValue = await resolver.Resolve(args?["text"]?.Value<string>(), token);
if (retValue == null)
retValue = await EvaluateExpression.CompileAndRunTheExpression(args?["text"]?.Value<string>(), resolver, token);
retValue = await ExpressionEvaluator.CompileAndRunTheExpression(args?["text"]?.Value<string>(), resolver, logger, token);
var osend = JObject.FromObject(new
{
type = "evaluationResult",
Expand Down
197 changes: 100 additions & 97 deletions src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public async Task<JObject> GetValueFromObject(JToken objRet, CancellationToken t

if (typeId != -1)
{
JObject memberObject = await FindStaticMemberInType(part, typeId);
JObject memberObject = await FindStaticMemberInType(classNameToFind, part, typeId);
if (memberObject != null)
{
string remaining = null;
Expand Down Expand Up @@ -124,7 +124,7 @@ public async Task<JObject> GetValueFromObject(JToken objRet, CancellationToken t

return (null, null);

async Task<JObject> FindStaticMemberInType(string name, int typeId)
async Task<JObject> FindStaticMemberInType(string classNameToFind, string name, int typeId)
{
var fields = await context.SdbAgent.GetTypeFields(typeId, token);
foreach (var field in fields)
Expand All @@ -137,9 +137,11 @@ async Task<JObject> FindStaticMemberInType(string name, int typeId)
{
isInitialized = await context.SdbAgent.TypeInitialize(typeId, token);
}
var valueRet = await context.SdbAgent.GetFieldValue(typeId, field.Id, token);

return await GetValueFromObject(valueRet, 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;
}

var methodId = await context.SdbAgent.GetPropertyMethodIdByName(typeId, name, token);
Expand Down Expand Up @@ -375,125 +377,126 @@ public async Task<JObject> Resolve(ElementAccessExpressionSyntax elementAccess,
}
}

public async Task<JObject> Resolve(InvocationExpressionSyntax method, Dictionary<string, JObject> memberAccessValues, CancellationToken token)
public async Task<(JObject, string)> ResolveInvokationInfo(InvocationExpressionSyntax method, CancellationToken token)
{
var methodName = "";
bool isExtensionMethod = false;
try
{
JObject rootObject = null;
var expr = method.Expression;
if (expr is MemberAccessExpressionSyntax)
if (expr is MemberAccessExpressionSyntax memberAccessExpressionSyntax)
{
var memberAccessExpressionSyntax = expr as MemberAccessExpressionSyntax;
rootObject = await Resolve(memberAccessExpressionSyntax.Expression.ToString(), token);
methodName = memberAccessExpressionSyntax.Name.ToString();

if (rootObject.IsNullValuedObject())
throw new ExpressionEvaluationFailedException($"Expression '{memberAccessExpressionSyntax}' evaluated to null");
}
else if (expr is IdentifierNameSyntax)
else if (expr is IdentifierNameSyntax && scopeCache.ObjectFields.TryGetValue("this", out JObject thisValue))
{
if (scopeCache.ObjectFields.TryGetValue("this", out JObject valueRet))
{
rootObject = await GetValueFromObject(valueRet, token);
methodName = expr.ToString();
}
rootObject = await GetValueFromObject(thisValue, token);
methodName = expr.ToString();
}
return (rootObject, methodName);
}
catch (Exception ex) when (ex is not (ExpressionEvaluationFailedException or ReturnAsErrorException))
{
throw new Exception($"Unable to evaluate method '{methodName}'", ex);
}
}

if (rootObject != null)
private static readonly string[] primitiveTypes = new string[] { "string", "number", "boolean", "symbol" };

public async Task<JObject> Resolve(InvocationExpressionSyntax method, Dictionary<string, JObject> memberAccessValues, CancellationToken token)
{
(JObject rootObject, string methodName) = await ResolveInvokationInfo(method, token);
if (rootObject == null)
throw new ReturnAsErrorException($"Failed to resolve root object for {method}", "ReferenceError");

// primitives don't have objectId
if (!DotnetObjectId.TryParse(rootObject["objectId"]?.Value<string>(), out DotnetObjectId objectId) &&
primitiveTypes.Contains(rootObject["type"]?.Value<string>()))
return null;

if (method.ArgumentList == null)
throw new InternalErrorException($"Failed to resolve method call for {method}, list of arguments is null.");

bool isExtensionMethod = false;
try
{
var typeIds = await context.SdbAgent.GetTypeIdsForObject(objectId.Value, true, token);
int methodId = await context.SdbAgent.GetMethodIdByName(typeIds[0], methodName, token);
var className = await context.SdbAgent.GetTypeNameOriginal(typeIds[0], token);
if (methodId == 0) //try to search on System.Linq.Enumerable
methodId = await FindMethodIdOnLinqEnumerable(typeIds, methodName);

if (methodId == 0)
{
var typeName = await context.SdbAgent.GetTypeName(typeIds[0], token);
throw new ReturnAsErrorException($"Method '{methodName}' not found in type '{typeName}'", "ReferenceError");
}
using var commandParamsObjWriter = new MonoBinaryWriter();
if (!isExtensionMethod)
{
if (!DotnetObjectId.TryParse(rootObject?["objectId"]?.Value<string>(), out DotnetObjectId objectId))
throw new ExpressionEvaluationFailedException($"Cannot invoke method '{methodName}' on invalid object id: {rootObject}");
// instance method
commandParamsObjWriter.WriteObj(objectId, context.SdbAgent);
}

List<int> typeIds;
if (objectId.IsValueType)
int passedArgsCnt = method.ArgumentList.Arguments.Count;
int methodParamsCnt = passedArgsCnt;
ParameterInfo[] methodParamsInfo = null;
var methodInfo = await context.SdbAgent.GetMethodInfo(methodId, token);
if (methodInfo != null)
{
methodParamsInfo = methodInfo.Info.GetParametersInfo();
methodParamsCnt = methodParamsInfo.Length;
if (isExtensionMethod)
{
if (!context.SdbAgent.valueTypes.TryGetValue(objectId.Value, out ValueTypeClass valueType))
throw new Exception($"Could not find valuetype {objectId}");

typeIds = new List<int>(1) { valueType.TypeId };
// implicit *this* parameter
methodParamsCnt--;
}
else
if (passedArgsCnt > methodParamsCnt)
throw new ReturnAsErrorException($"Unable to evaluate method '{methodName}'. Too many arguments passed.", "ArgumentError");
}

if (isExtensionMethod)
{
commandParamsObjWriter.Write(methodParamsCnt + 1);
commandParamsObjWriter.WriteObj(objectId, context.SdbAgent);
}
else
{
commandParamsObjWriter.Write(methodParamsCnt);
}

int argIndex = 0;
// explicitly passed arguments
for (; argIndex < passedArgsCnt; argIndex++)
{
var arg = method.ArgumentList.Arguments[argIndex];
if (arg.Expression is LiteralExpressionSyntax literal)
{
typeIds = await context.SdbAgent.GetTypeIdsForObject(objectId.Value, true, token);
if (!await commandParamsObjWriter.WriteConst(literal, context.SdbAgent, token))
throw new InternalErrorException($"Unable to evaluate method '{methodName}'. Unable to write LiteralExpressionSyntax into binary writer.");
}
int methodId = await context.SdbAgent.GetMethodIdByName(typeIds[0], methodName, token);
var className = await context.SdbAgent.GetTypeNameOriginal(typeIds[0], token);
if (methodId == 0) //try to search on System.Linq.Enumerable
methodId = await FindMethodIdOnLinqEnumerable(typeIds, methodName);

if (methodId == 0) {
var typeName = await context.SdbAgent.GetTypeName(typeIds[0], token);
throw new ExpressionEvaluationFailedException($"Method '{methodName}' not found in type '{typeName}'");
}
using var commandParamsObjWriter = new MonoBinaryWriter();
if (!isExtensionMethod)
else if (arg.Expression is IdentifierNameSyntax identifierName)
{
// instance method
commandParamsObjWriter.WriteObj(objectId, context.SdbAgent);
if (!await commandParamsObjWriter.WriteJsonValue(memberAccessValues[identifierName.Identifier.Text], context.SdbAgent, token))
throw new InternalErrorException($"Unable to evaluate method '{methodName}'. Unable to write IdentifierNameSyntax into binary writer.");
}

if (method.ArgumentList != null)
else
{
int passedArgsCnt = method.ArgumentList.Arguments.Count;
int methodParamsCnt = passedArgsCnt;
ParameterInfo[] methodParamsInfo = null;
var methodInfo = await context.SdbAgent.GetMethodInfo(methodId, token);
if (methodInfo != null) //FIXME: #65670
{
methodParamsInfo = methodInfo.Info.GetParametersInfo();
methodParamsCnt = methodParamsInfo.Length;
if (isExtensionMethod)
{
// implicit *this* parameter
methodParamsCnt--;
}
if (passedArgsCnt > methodParamsCnt)
throw new ExpressionEvaluationFailedException($"Cannot invoke method '{method}' - too many arguments passed");
}

if (isExtensionMethod)
{
commandParamsObjWriter.Write(methodParamsCnt + 1);
commandParamsObjWriter.WriteObj(objectId, context.SdbAgent);
}
else
{
commandParamsObjWriter.Write(methodParamsCnt);
}

int argIndex = 0;
// explicitly passed arguments
for (; argIndex < passedArgsCnt; argIndex++)
{
var arg = method.ArgumentList.Arguments[argIndex];
if (arg.Expression is LiteralExpressionSyntax literal)
{
if (!await commandParamsObjWriter.WriteConst(literal, context.SdbAgent, token))
throw new InternalErrorException($"Unable to write LiteralExpressionSyntax ({literal}) into binary writer.");
}
else if (arg.Expression is IdentifierNameSyntax identifierName)
{
if (!await commandParamsObjWriter.WriteJsonValue(memberAccessValues[identifierName.Identifier.Text], context.SdbAgent, token))
throw new InternalErrorException($"Unable to write IdentifierNameSyntax ({identifierName}) into binary writer.");
}
else
{
throw new InternalErrorException($"Unable to write into binary writer, not recognized expression type: {arg.Expression.GetType().Name}");
}
}
// optional arguments that were not overwritten
for (; argIndex < methodParamsCnt; argIndex++)
{
if (!await commandParamsObjWriter.WriteConst(methodParamsInfo[argIndex].TypeCode, methodParamsInfo[argIndex].Value, context.SdbAgent, token))
throw new InternalErrorException($"Unable to write optional parameter {methodParamsInfo[argIndex].Name} value in method '{methodName}' to the mono buffer.");
}
var retMethod = await context.SdbAgent.InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, token);
return await GetValueFromObject(retMethod, token);
throw new InternalErrorException($"Unable to evaluate method '{methodName}'. Unable to write into binary writer, not recognized expression type: {arg.Expression.GetType().Name}");
}
}
return null;
// optional arguments that were not overwritten
for (; argIndex < methodParamsCnt; argIndex++)
{
if (!await commandParamsObjWriter.WriteConst(methodParamsInfo[argIndex].TypeCode, methodParamsInfo[argIndex].Value, context.SdbAgent, token))
throw new InternalErrorException($"Unable to write optional parameter {methodParamsInfo[argIndex].Name} value in method '{methodName}' to the mono buffer.");
}
var retMethod = await context.SdbAgent.InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, token);
return await GetValueFromObject(retMethod, token);
}
catch (Exception ex) when (ex is not (ExpressionEvaluationFailedException or ReturnAsErrorException))
{
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 @@ -821,7 +821,7 @@ protected async Task<bool> EvaluateCondition(SessionId sessionId, ExecutionConte
var resolver = new MemberReferenceResolver(this, context, sessionId, mono_frame.Id, logger);
JObject retValue = await resolver.Resolve(condition, token);
if (retValue == null)
retValue = await EvaluateExpression.CompileAndRunTheExpression(condition, resolver, token);
retValue = await ExpressionEvaluator.CompileAndRunTheExpression(condition, resolver, logger, token);
if (retValue?["value"]?.Type == JTokenType.Boolean ||
retValue?["value"]?.Type == JTokenType.Integer ||
retValue?["value"]?.Type == JTokenType.Float) {
Expand Down Expand Up @@ -1314,7 +1314,7 @@ private async Task<bool> OnEvaluateOnCallFrame(MessageId msg_id, int scopeId, st
JObject retValue = await resolver.Resolve(expression, token);
if (retValue == null)
{
retValue = await EvaluateExpression.CompileAndRunTheExpression(expression, resolver, token);
retValue = await ExpressionEvaluator.CompileAndRunTheExpression(expression, resolver, logger, token);
}

if (retValue != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1430,7 +1430,7 @@ public async Task<string> GetValueFromDebuggerDisplayAttribute(DotnetObjectId do
expr = "$\"" + dispAttrStr + "\"";
JObject retValue = await resolver.Resolve(expr, token);
if (retValue == null)
retValue = await EvaluateExpression.CompileAndRunTheExpression(expr, resolver, token);
retValue = await ExpressionEvaluator.CompileAndRunTheExpression(expr, resolver, logger, token);

return retValue?["value"]?.Value<string>();
}
Expand Down
12 changes: 9 additions & 3 deletions src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,15 @@ public async Task<JObject> ToJObject(MonoSDBHelper sdbAgent, bool forDebuggerDis
if (displayString != null)
description = displayString;
}

var obj = MonoSDBHelper.CreateJObject<string>(null, "object", description, false, className, Id.ToString(), null, null, true, true, IsEnum);
return obj;
return MonoSDBHelper.CreateJObject(
IsEnum ? fields[0]["value"] : null,
"object",
description,
false,
className,
Id.ToString(),
null, null, true, true,
IsEnum);
}

public async Task<JArray> GetProxy(MonoSDBHelper sdbHelper, CancellationToken token)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ internal override async Task<Result> SetBreakpointInMethod(string assembly, stri
description = res.Value["result"]["value"]["description"],
objectId = actor
});
if (actor.StartsWith("dotnet:valuetype:"))
if (actor?.StartsWith("dotnet:valuetype:") == true)
resObj["isValueType"] = true;
return (resObj, res);
}
Expand Down
Loading