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
3 changes: 3 additions & 0 deletions src/mono/mono/component/debugger-agent.c
Original file line number Diff line number Diff line change
Expand Up @@ -8229,6 +8229,9 @@ type_commands_internal (int command, MonoClass *klass, MonoDomain *domain, guint
ERROR_DECL (error);
GPtrArray *array;

if (!klass)
goto invalid_argument;

error_init (error);
array = mono_class_get_methods_by_name (klass, name, flags & ~BINDING_FLAGS_IGNORE_CASE, mlisttype, TRUE, error);
if (!is_ok (error)) {
Expand Down
4 changes: 2 additions & 2 deletions src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1229,7 +1229,7 @@ public IEnumerable<SourceFile> Add(SessionId id, string name, byte[] assembly_da
}
catch (Exception e)
{
logger.LogDebug($"Failed to load assembly: ({e.Message})");
logger.LogError($"Failed to load assembly: ({e.Message})");
yield break;
}

Expand Down Expand Up @@ -1292,7 +1292,7 @@ public async IAsyncEnumerable<SourceFile> Load(SessionId id, string[] loaded_fil
}
catch (Exception e)
{
logger.LogDebug($"Failed to load {step.Url} ({e.Message})");
logger.LogError($"Failed to load {step.Url} ({e.Message})");
}
if (assembly == null)
continue;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

using System;

namespace Microsoft.WebAssembly.Diagnostics;

public class DebuggerAgentException : Exception
{
public DebuggerAgentException(string message) : base(message)
{
}

public DebuggerAgentException(string? message, Exception? innerException) : base(message, innerException)
{
}
}
8 changes: 6 additions & 2 deletions src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,11 @@ public struct Result

private Result(JObject resultOrError, bool isError)
{
bool resultHasError = isError || string.Equals((resultOrError?["result"] as JObject)?["subtype"]?.Value<string>(), "error");
if (resultOrError == null)
throw new ArgumentNullException(nameof(resultOrError));

bool resultHasError = isError || string.Equals((resultOrError["result"] as JObject)?["subtype"]?.Value<string>(), "error");
resultHasError |= resultOrError["exceptionDetails"] != null;
if (resultHasError)
{
Value = null;
Expand All @@ -146,7 +150,7 @@ public static Result FromJson(JObject obj)
var error = obj["error"] as JObject;
if (error != null)
return new Result(error, true);
var result = obj["result"] as JObject;
var result = (obj["result"] as JObject) ?? new JObject();
return new Result(result, false);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

using System;

namespace Microsoft.WebAssembly.Diagnostics;

public class ExpressionEvaluationFailedException : Exception
{
public ExpressionEvaluationFailedException(string message) : base(message)
{
}

public ExpressionEvaluationFailedException(string? message, Exception? innerException) : base(message, innerException)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

using System;

namespace Microsoft.WebAssembly.Diagnostics;

public class InternalErrorException : Exception
{
public InternalErrorException(string message) : base($"Internal error: {message}")
{
}

public InternalErrorException(string message, Exception? innerException) : base($"Internal error: {message}", innerException)
{
}
}
111 changes: 71 additions & 40 deletions src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ public async Task<JObject> Resolve(string varName, CancellationToken token)
(retObject, string remaining) = await ResolveStaticMembersInStaticTypes(varName, token);
if (!string.IsNullOrEmpty(remaining))
{
if (retObject?["subtype"]?.Value<string>() == "null")
if (retObject.IsNullValuedObject())
{
// NRE on null.$remaining
retObject = null;
Expand All @@ -221,7 +221,7 @@ async Task<JObject> ResolveAsLocalOrThisMember(string name)
{
Result scope_res = await proxy.GetScopeProperties(sessionId, scopeId, token);
if (!scope_res.IsOk)
throw new Exception($"BUG: Unable to get properties for scope: {scopeId}. {scope_res}");
throw new ExpressionEvaluationFailedException($"BUG: Unable to get properties for scope: {scopeId}. {scope_res}");
localsFetched = true;
}

Expand All @@ -234,8 +234,14 @@ async Task<JObject> ResolveAsLocalOrThisMember(string name)
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);
ValueOrError<JToken> valueOrError = await proxy.RuntimeGetPropertiesInternal(sessionId, objectId, null, token);
if (valueOrError.IsError)
{
logger.LogDebug($"ResolveAsLocalOrThisMember failed with : {valueOrError.Error}");
return null;
}

JToken objRet = valueOrError.Value.FirstOrDefault(objPropAttr => objPropAttr["name"].Value<string>() == nameTrimmed);
if (objRet != null)
return await GetValueFromObject(objRet, token);

Expand All @@ -255,16 +261,22 @@ async Task<JObject> ResolveAsInstanceMember(string expr, JObject baseObject)
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);
ValueOrError<JToken> valueOrError = await proxy.RuntimeGetPropertiesInternal(sessionId, objectId, null, token);
if (valueOrError.IsError)
{
logger.LogDebug($"ResolveAsInstanceMember failed with : {valueOrError.Error}");
return null;
}

JToken objRet = valueOrError.Value.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")
if (resolvedObject.IsNullValuedObject())
{
if (i < parts.Length - 1)
{
Expand Down Expand Up @@ -360,9 +372,9 @@ public async Task<JObject> Resolve(ElementAccessExpressionSyntax elementAccess,
}
return null;
}
catch (Exception)
catch (Exception ex) when (ex is not ExpressionEvaluationFailedException)
{
throw new Exception($"Unable to evaluate method '{elementAccess}'");
throw new ExpressionEvaluationFailedException($"Unable to evaluate element access '{elementAccess}': {ex.Message}", ex);
}
}

Expand All @@ -379,41 +391,33 @@ public async Task<JObject> Resolve(InvocationExpressionSyntax method, Dictionary
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)
if (scopeCache.ObjectFields.TryGetValue("this", out JObject valueRet)) {
{
if (scopeCache.ObjectFields.TryGetValue("this", out JObject valueRet))
{
rootObject = await GetValueFromObject(valueRet, token);
methodName = expr.ToString();
methodName = expr.ToString();
}
}

if (rootObject != null)
{
DotnetObjectId.TryParse(rootObject?["objectId"]?.Value<string>(), out DotnetObjectId objectId);
if (!DotnetObjectId.TryParse(rootObject?["objectId"]?.Value<string>(), out DotnetObjectId objectId))
throw new ExpressionEvaluationFailedException($"Cannot invoke method '{methodName}' on invalid object id: {rootObject}");

var typeIds = await context.SdbAgent.GetTypeIdFromObject(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
{
if (linqTypeId == -1)
linqTypeId = await context.SdbAgent.GetTypeByName("System.Linq.Enumerable", token);
methodId = await context.SdbAgent.GetMethodIdByName(linqTypeId, methodName, token);
if (methodId != 0)
{
foreach (var typeId in typeIds)
{
var genericTypeArgs = await context.SdbAgent.GetTypeParamsOrArgsForGenericType(typeId, token);
if (genericTypeArgs.Count > 0)
{
isExtensionMethod = true;
methodId = await context.SdbAgent.MakeGenericMethod(methodId, genericTypeArgs, token);
break;
}
}
}
}
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}'", "ArgumentError");
throw new ExpressionEvaluationFailedException($"Method '{methodName}' not found in type '{typeName}'");
}
using var commandParamsObjWriter = new MonoBinaryWriter();
if (!isExtensionMethod)
Expand All @@ -427,20 +431,18 @@ public async Task<JObject> Resolve(InvocationExpressionSyntax method, Dictionary
int passedArgsCnt = method.ArgumentList.Arguments.Count;
int methodParamsCnt = passedArgsCnt;
ParameterInfo[] methodParamsInfo = null;
logger.LogInformation($"passed: {passedArgsCnt}, isExtensionMethod: {isExtensionMethod}");
var methodInfo = await context.SdbAgent.GetMethodInfo(methodId, token);
if (methodInfo != null) //FIXME: #65670
{
methodParamsInfo = methodInfo.Info.GetParametersInfo();
methodParamsCnt = methodParamsInfo.Length;
logger.LogInformation($"got method info with {methodParamsCnt} params");
if (isExtensionMethod)
{
// implicit *this* parameter
methodParamsCnt--;
}
if (passedArgsCnt > methodParamsCnt)
throw new ReturnAsErrorException($"Unable to evaluate method '{methodName}'. Too many arguments passed.", "ArgumentError");
throw new ExpressionEvaluationFailedException($"Cannot invoke method '{method}' - too many arguments passed");
}

if (isExtensionMethod)
Expand All @@ -461,33 +463,62 @@ public async Task<JObject> Resolve(InvocationExpressionSyntax method, Dictionary
if (arg.Expression is LiteralExpressionSyntax literal)
{
if (!await commandParamsObjWriter.WriteConst(literal, context.SdbAgent, token))
throw new ReturnAsErrorException($"Unable to evaluate method '{methodName}'. Unable to write LiteralExpressionSyntax into binary writer.", "ArgumentError");
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 ReturnAsErrorException($"Unable to evaluate method '{methodName}'. Unable to write IdentifierNameSyntax into binary writer.", "ArgumentError");
throw new InternalErrorException($"Unable to write IdentifierNameSyntax ({identifierName}) into binary writer.");
}
else
{
throw new ReturnAsErrorException($"Unable to evaluate method '{methodName}'. Unable to write into binary writer, not recognized expression type: {arg.Expression.GetType().Name}", "ArgumentError");
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 ReturnAsErrorException($"Unable to write optional parameter {methodParamsInfo[argIndex].Name} value in method '{methodName}' to the mono buffer.", "ArgumentError");
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, "methodRet", token);
return await GetValueFromObject(retMethod, token);
}
}
return null;
}
catch (Exception ex) when (ex is not ReturnAsErrorException)
catch (Exception ex) when (ex is not (ExpressionEvaluationFailedException or ReturnAsErrorException))
{
throw new Exception($"Unable to evaluate method '{methodName}'", ex);
throw new ExpressionEvaluationFailedException($"Unable to evaluate method '{method}': {ex.Message}", ex);
}

async Task<int> FindMethodIdOnLinqEnumerable(IList<int> typeIds, string methodName)
{
if (linqTypeId == -1)
{
linqTypeId = await context.SdbAgent.GetTypeByName("System.Linq.Enumerable", token);
if (linqTypeId == 0)
{
logger.LogDebug($"Cannot find type 'System.Linq.Enumerable'");
return 0;
}
}

int newMethodId = await context.SdbAgent.GetMethodIdByName(linqTypeId, methodName, token);
if (newMethodId == 0)
return 0;

foreach (int typeId in typeIds)
{
List<int> genericTypeArgs = await context.SdbAgent.GetTypeParamsOrArgsForGenericType(typeId, token);
if (genericTypeArgs.Count > 0)
{
isExtensionMethod = true;
return await context.SdbAgent.MakeGenericMethod(newMethodId, genericTypeArgs, token);
}
}

return 0;
}
}
}
Expand Down
Loading