diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs index 42a0405bb7e8a8..2482026315ba4a 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs @@ -358,6 +358,7 @@ internal sealed class MethodInfo public int KickOffMethod { get; } internal bool IsCompilerGenerated { get; } private AsyncScopeDebugInformation[] _asyncScopes { get; set; } + private static SignatureTypeProvider _signatureTypeProvider = new(); public MethodInfo(AssemblyInfo assembly, string methodName, int methodToken, TypeInfo type, MethodAttributes attrs) { @@ -436,6 +437,11 @@ public ParameterInfo[] GetParametersInfo() if (_parametersInfo != null) return _parametersInfo; + var signature = methodDef.Signature; + var sigReader = Assembly.asmMetadataReader.GetBlobReader(signature); + var decoder = new SignatureDecoder(_signatureTypeProvider, Assembly.asmMetadataReader, genericContext: null); + var methodSignature = decoder.DecodeMethodSignature(ref sigReader); + var paramsHandles = methodDef.GetParameters().ToArray(); var paramsCnt = paramsHandles.Length; var paramsInfo = new ParameterInfo[paramsCnt]; @@ -447,7 +453,7 @@ public ParameterInfo[] GetParametersInfo() var isOptional = parameter.Attributes.HasFlag(ParameterAttributes.Optional) && parameter.Attributes.HasFlag(ParameterAttributes.HasDefault); if (!isOptional) { - paramsInfo[i] = new ParameterInfo(paramName); + paramsInfo[i] = new ParameterInfo(paramName, methodSignature.ParameterTypes[i]); continue; } var constantHandle = parameter.GetDefaultValue(); @@ -653,7 +659,11 @@ internal sealed class ParameterInfo public ElementType? TypeCode { get; init; } public object Value { get; init; } - + public ParameterInfo(string name, ElementType type) + { + Name = name; + TypeCode = type; + } public ParameterInfo(string name, ConstantTypeCode? typeCode = null, byte[] value = null) { Name = name; @@ -670,11 +680,11 @@ public ParameterInfo(string name, ConstantTypeCode? typeCode = null, byte[] valu TypeCode = ElementType.Char; break; case ConstantTypeCode.Byte: - Value = (int)value[0]; + Value = (uint)value[0]; TypeCode = ElementType.U1; break; case ConstantTypeCode.SByte: - Value = (uint)value[0]; + Value = (int)value[0]; TypeCode = ElementType.I1; break; case ConstantTypeCode.Int16: diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs b/src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs index 0a9f53c793a7ba..5fed2b2b858dd5 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs @@ -365,7 +365,7 @@ public void ClearCache() public bool TryGetValueTypeById(int valueTypeId, out ValueTypeClass vt) => _valueTypes.TryGetValue(valueTypeId, out vt); public PointerValue GetPointerValue(int pointerId) => _pointerValues.TryGetValue(pointerId, out PointerValue pv) ? pv : null; - private static JObject CreateJObjectForNumber(T value) => Create(value, "number", value.ToString(), writable: true); + private static JObject CreateJObjectForNumber(T value) => Create(value, "number", value.ToString(), writable: true, className: typeof(T).Name); private static JObject CreateJObjectForChar(int value) { diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs index 5205ed5438310f..738c8c695794a5 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs @@ -439,15 +439,15 @@ public async Task Resolve(ElementAccessExpressionSyntax elementAccess, { MethodInfoWithDebugInformation methodInfo = await context.SdbAgent.GetMethodInfo(methodIds[i], token); ParameterInfo[] paramInfo = methodInfo.GetParametersInfo(); - - // get_Item should not have an overload, but if user defined it, take the default one: with one param (key) if (paramInfo.Length == 1) { try { + if (indexObject != null && !CheckParametersCompatibility(paramInfo[0].TypeCode, indexObject)) + continue; ArraySegment buffer = indexObject is null ? - await WriteLiteralExpressionAsIndex(objectId, elementIdxInfo.IndexingExpression, elementIdxInfo.ElementIdxStr) : - await WriteJObjectAsIndex(objectId, indexObject, elementIdxInfo.ElementIdxStr); + await WriteLiteralExpressionAsIndex(objectId, elementIdxInfo.IndexingExpression, elementIdxInfo.ElementIdxStr) : + await WriteJObjectAsIndex(objectId, indexObject, elementIdxInfo.ElementIdxStr, paramInfo[0].TypeCode); JObject getItemRetObj = await context.SdbAgent.InvokeMethod(buffer, methodIds[i], token); return (JObject)getItemRetObj["value"]; } @@ -524,12 +524,12 @@ async Task GetElementIndexInfo() IndexingExpression: indexingExpression); } - async Task> WriteJObjectAsIndex(DotnetObjectId rootObjId, JObject indexObject, string elementIdxStr) + async Task> WriteJObjectAsIndex(DotnetObjectId rootObjId, JObject indexObject, string elementIdxStr, ElementType? expectedType) { using var writer = new MonoBinaryWriter(); writer.WriteObj(rootObjId, context.SdbAgent); writer.Write(1); // number of method args - if (!await writer.WriteJsonValue(indexObject, context.SdbAgent, token)) + if (!await writer.WriteJsonValue(indexObject, context.SdbAgent, expectedType, token)) throw new InternalErrorException($"Parsing index of type {indexObject["type"].Value()} to write it into the buffer failed."); return writer.GetParameterBuffer(); } @@ -545,6 +545,56 @@ async Task> WriteLiteralExpressionAsIndex(DotnetObjectId root } } + private static bool CheckParametersCompatibility(ElementType? paramTypeCode, JObject value) + { + if (!paramTypeCode.HasValue) + return true; + var argumentType = value["type"]?.Value(); + var argumentClassName = value["className"]?.Value(); + + switch (paramTypeCode.Value) + { + case ElementType.Object: + if (argumentType != "object") + return false; + break; + case ElementType.I2: + case ElementType.I4: + case ElementType.I8: + case ElementType.R4: + case ElementType.R8: + case ElementType.U2: + case ElementType.U4: + case ElementType.U8: + if (argumentType != "number") + return false; + if (argumentType == "object") + return false; + break; + case ElementType.Char: + if (argumentType != "string" && argumentType != "symbol") + return false; + if (argumentType == "object") + return false; + break; + case ElementType.Boolean: + if (argumentType == "boolean") + return true; + if (argumentType == "number" && (argumentClassName == "Single" || argumentClassName == "Double")) + return false; + if (argumentType == "object") + return false; + break; + case ElementType.String: + if (argumentType != "string") + return false; + break; + default: + return true; + } + return true; + } + public async Task<(JObject, string)> ResolveInvocationInfo(InvocationExpressionSyntax method, CancellationToken token) { var methodName = ""; @@ -664,7 +714,7 @@ public async Task Resolve(InvocationExpressionSyntax method, Dictionary } else if (arg.Expression is IdentifierNameSyntax identifierName) { - if (!await commandParamsObjWriter.WriteJsonValue(memberAccessValues[identifierName.Identifier.Text], context.SdbAgent, token)) + if (!await commandParamsObjWriter.WriteJsonValue(memberAccessValues[identifierName.Identifier.Text], context.SdbAgent, methodParamsInfo[argIndex].TypeCode, token)) throw new InternalErrorException($"Unable to evaluate method '{methodName}'. Unable to write IdentifierNameSyntax into binary writer."); } else diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index b714233b8e3372..ce35b553f129d3 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -587,7 +587,7 @@ public async Task WriteConst(ElementType? type, object value, MonoSDBHelpe { switch (type) { - case ElementType.U1: + case ElementType.I1: case ElementType.I2: case ElementType.I4: Write((ElementType)type, (int)value); @@ -604,7 +604,7 @@ public async Task WriteConst(ElementType? type, object value, MonoSDBHelpe intBoolVal = (bool)value ? 1 : 0; Write((ElementType)type, intBoolVal); return true; - case ElementType.I1: + case ElementType.U1: case ElementType.U2: case ElementType.U4: Write((ElementType)type, (uint)value); @@ -715,14 +715,41 @@ public async Task WriteConst(LiteralExpressionSyntax constValue, MonoSDBHe return false; } - public async Task WriteJsonValue(JObject objValue, MonoSDBHelper SdbHelper, CancellationToken token) + public async Task WriteJsonValue(JObject objValue, MonoSDBHelper SdbHelper, ElementType? expectedType, CancellationToken token) { switch (objValue["type"].Value()) { case "number": { - // FixMe: what if the number is not int but single/double? - Write(ElementType.I4, objValue["value"].Value()); + var expected = expectedType is not null ? expectedType.Value : ElementType.I4; + switch (expected) + { + case ElementType.I1: + case ElementType.I2: + case ElementType.I4: + Write(expected, objValue["value"].Value()); + break; + case ElementType.U1: + case ElementType.U2: + case ElementType.U4: + Write(expected, objValue["value"].Value()); + break; + case ElementType.I8: + Write(expected, objValue["value"].Value()); + break; + case ElementType.U8: + Write(expected, objValue["value"].Value()); + break; + case ElementType.R4: + Write(expected, objValue["value"].Value()); + break; + case ElementType.R8: + Write(expected, objValue["value"].Value()); + break; + default: + objValue["value"].Value(); + break; + }; return true; } case "symbol": diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/SignatureTypeProvider.cs b/src/mono/wasm/debugger/BrowserDebugProxy/SignatureTypeProvider.cs new file mode 100644 index 00000000000000..9353dafacc80e4 --- /dev/null +++ b/src/mono/wasm/debugger/BrowserDebugProxy/SignatureTypeProvider.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection.Metadata; +using System.Text; +using System.Threading.Tasks; +using Microsoft.WebAssembly.Diagnostics; + +namespace Microsoft.WebAssembly.Diagnostics; + +internal sealed class SignatureTypeProvider : ISignatureTypeProvider +{ + public ElementType GetPrimitiveType(PrimitiveTypeCode typeCode) + => typeCode switch + { + PrimitiveTypeCode.Boolean => ElementType.Boolean, + PrimitiveTypeCode.Byte => ElementType.U1, + PrimitiveTypeCode.Char => ElementType.Char, + PrimitiveTypeCode.Double => ElementType.R8, + PrimitiveTypeCode.Int16 => ElementType.I2, + PrimitiveTypeCode.Int32 => ElementType.I4, + PrimitiveTypeCode.Int64 => ElementType.I8, + PrimitiveTypeCode.IntPtr => ElementType.Ptr, + PrimitiveTypeCode.Object => ElementType.Object, + PrimitiveTypeCode.SByte => ElementType.I1, + PrimitiveTypeCode.Single => ElementType.R4, + PrimitiveTypeCode.String => ElementType.String, + PrimitiveTypeCode.TypedReference => ElementType.ValueType, + PrimitiveTypeCode.UInt16 => ElementType.U2, + PrimitiveTypeCode.UInt32 => ElementType.U4, + PrimitiveTypeCode.UInt64 => ElementType.U8, + PrimitiveTypeCode.UIntPtr => ElementType.Ptr, + PrimitiveTypeCode.Void => ElementType.Void, + _ => ElementType.End, + }; + + ElementType ISignatureTypeProvider.GetFunctionPointerType(MethodSignature signature) => ElementType.FnPtr; + ElementType ISignatureTypeProvider.GetModifiedType(ElementType modifier, ElementType unmodifiedType, bool isRequired) => ElementType.Object; + ElementType ISignatureTypeProvider.GetPinnedType(ElementType elementType) => ElementType.Object; + ElementType IConstructedTypeProvider.GetArrayType(ElementType elementType, ArrayShape shape) => ElementType.Array; + ElementType IConstructedTypeProvider.GetByReferenceType(ElementType elementType) => ElementType.Object; + ElementType IConstructedTypeProvider.GetGenericInstantiation(ElementType genericType, ImmutableArray typeArguments) => ElementType.Object; + ElementType IConstructedTypeProvider.GetPointerType(ElementType elementType) => ElementType.Ptr; + ElementType ISZArrayTypeProvider.GetSZArrayType(ElementType elementType) => ElementType.SzArray; + ElementType ISignatureTypeProvider.GetGenericMethodParameter(object genericContext, int index) => ElementType.Object; + ElementType ISignatureTypeProvider.GetGenericTypeParameter(object genericContext, int index) => ElementType.Object; + ElementType ISignatureTypeProvider.GetTypeFromSpecification(MetadataReader reader, object genericContext, TypeSpecificationHandle handle, byte rawTypeKind) => ElementType.Object; + ElementType ISimpleTypeProvider.GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) => ElementType.Object; + ElementType ISimpleTypeProvider.GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) => ElementType.Object; +} diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 784ba1d6933401..b9fab326b118fe 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -659,12 +659,10 @@ await EvaluateOnCallFrameAndCheck(id, ("f[longString]", TBool(true)), ("f[aBool]", TString("True")), ("f[aChar]", TString("res_9")), - ("f[shortString]", TBool(false)) - // ("f[aFloat]", TNumber(1)), - // ("f[aDouble]", TNumber(2)), - - // FixMe: https://github.com/dotnet/runtime/issues/76014 - // ("f[aDecimal]", TNumber(3)) // object + ("f[shortString]", TBool(false)), + ("f[aFloat]", TNumber(1)), + ("f[aDouble]", TNumber(2)), + ("f[aDecimal]", TNumber(3)) // object ); });