diff --git a/src/mono/mono/component/debugger-agent.c b/src/mono/mono/component/debugger-agent.c index e5fd65378515a..77208dfc2405e 100644 --- a/src/mono/mono/component/debugger-agent.c +++ b/src/mono/mono/component/debugger-agent.c @@ -5353,6 +5353,7 @@ decode_vtype (MonoType *t, MonoDomain *domain, gpointer void_addr, gpointer void gpointer iter = NULL; MonoDomain *d; ErrorCode err; + int inlineArraySize = -1; /* is_enum, ignored */ decode_byte (buf, &buf, limit); @@ -5360,7 +5361,7 @@ decode_vtype (MonoType *t, MonoDomain *domain, gpointer void_addr, gpointer void decode_byte (buf, &buf, limit); klass = decode_typeid (buf, &buf, limit, &d, &err); if (CHECK_PROTOCOL_VERSION(2, 65)) - decode_int (buf, &buf, limit); //ignore inline array + inlineArraySize = decode_int (buf, &buf, limit); if (err != ERR_NONE) return err; @@ -5385,6 +5386,12 @@ decode_vtype (MonoType *t, MonoDomain *domain, gpointer void_addr, gpointer void if (err != ERR_NONE) return err; nfields --; + if (CHECK_PROTOCOL_VERSION(2, 66) && inlineArraySize > 0) + { + int element_size = mono_class_instance_size (mono_class_from_mono_type_internal (f->type)) - MONO_ABI_SIZEOF (MonoObject); + for (int i = 1; i < inlineArraySize; i++) + decode_value (f->type, domain, ((char*)mono_vtype_get_field_addr (addr, f)) + (i*element_size), buf, &buf, limit, check_field_datatype, extra_space, members_in_extra_space); + } } g_assert (nfields == 0); @@ -5459,6 +5466,7 @@ decode_vtype_compute_size (MonoType *t, MonoDomain *domain, gpointer void_buf, g gpointer iter = NULL; MonoDomain *d; ErrorCode err; + int inlineArraySize = -1; /* is_enum, ignored */ decode_byte (buf, &buf, limit); @@ -5466,7 +5474,7 @@ decode_vtype_compute_size (MonoType *t, MonoDomain *domain, gpointer void_buf, g decode_byte (buf, &buf, limit); klass = decode_typeid (buf, &buf, limit, &d, &err); if (CHECK_PROTOCOL_VERSION(2, 65)) - decode_int (buf, &buf, limit); //ignore inline array + inlineArraySize = decode_int (buf, &buf, limit); if (err != ERR_NONE) goto end; @@ -5489,6 +5497,14 @@ decode_vtype_compute_size (MonoType *t, MonoDomain *domain, gpointer void_buf, g if (err != ERR_NONE) return err; nfields --; + if (CHECK_PROTOCOL_VERSION(2, 66) && inlineArraySize > 0) + { + for (int i = 1; i < inlineArraySize; i++) { + field_size = decode_value_compute_size (f->type, 0, domain, buf, &buf, limit, members_in_extra_space); + if (members_in_extra_space) + ret += field_size; + } + } } g_assert (nfields == 0); diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs index e1b9583ddbe3e..df40f5528232f 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs @@ -408,6 +408,12 @@ public async Task Resolve( if (!context.SdbAgent.ValueCreator.TryGetValueTypeById(objectId.Value, out ValueTypeClass valueType)) throw new InvalidOperationException($"Cannot apply indexing with [] to an expression of scheme '{objectId.Scheme}'"); var typeInfo = await context.SdbAgent.GetTypeInfo(valueType.TypeId, token); + if (valueType.InlineArray == null) + { + JObject vtResult = await InvokeGetItemOnJObject(rootObject, valueType.TypeId, objectId, elementIdxInfo, token); + if (vtResult != null) + return vtResult; + } if (int.TryParse(elementIdxInfo.ElementIdxStr, out elementIdx) && elementIdx >= 0 && elementIdx < valueType.InlineArray.Count) return (JObject)valueType.InlineArray[elementIdx]["value"]; throw new InvalidOperationException($"Index is outside the bounds of the inline array"); @@ -436,34 +442,11 @@ public async Task Resolve( if (elementIdxInfo.Indexers is null || elementIdxInfo.Indexers.Count == 0) throw new InternalErrorException($"Unable to write index parameter to invoke the method in the runtime."); - var typeIds = await context.SdbAgent.GetTypeIdsForObject(objectId.Value, true, token); - int[] methodIds = await context.SdbAgent.GetMethodIdsByName(typeIds[0], "get_Item", BindingFlags.Default, token); - if (methodIds == null || methodIds.Length == 0) - throw new InvalidOperationException($"Type '{rootObject?["className"]?.Value()}' cannot be indexed."); - - // ToDo: optimize the loop by choosing the right method at once without trying out them all - for (int i = 0; i < methodIds.Length; i++) - { - MethodInfoWithDebugInformation methodInfo = await context.SdbAgent.GetMethodInfo(methodIds[i], token); - ParameterInfo[] paramInfo = methodInfo.GetParametersInfo(); - if (paramInfo.Length == elementIdxInfo.DimensionsCount) - { - try - { - if (!CheckParametersCompatibility(paramInfo, elementIdxInfo.Indexers)) - continue; - ArraySegment buffer = await WriteIndexObjectAsIndices(objectId, elementIdxInfo.Indexers, paramInfo); - JObject getItemRetObj = await context.SdbAgent.InvokeMethod(buffer, methodIds[i], token); - return (JObject)getItemRetObj["value"]; - } - catch (Exception ex) - { - logger.LogDebug($"Attempt number {i + 1} out of {methodIds.Length} of invoking method {methodInfo.Name} with parameter named {paramInfo[0].Name} on type {type} failed. Method Id = {methodIds[i]}.\nInner exception: {ex}."); - continue; - } - } - } - throw new InvalidOperationException($"Cannot apply indexing with [] to an object of type '{rootObject?["className"]?.Value()}'"); + List typeIds = await context.SdbAgent.GetTypeIdsForObject(objectId.Value, true, token); + JObject objResult = await InvokeGetItemOnJObject(rootObject, typeIds[0], objectId, elementIdxInfo, token); + if (objResult == null) + throw new InvalidOperationException($"Cannot apply indexing with [] to an object of type '{rootObject?["className"]?.Value()}'"); + return objResult; default: throw new InvalidOperationException($"Cannot apply indexing with [] to an expression of scheme '{objectId.Scheme}'"); } @@ -543,6 +526,42 @@ async Task GetElementIndexInfo(List nestedIndexers) ElementIdxStr: elementIdxStr.ToString(), Indexers: indexers); } + } + + private async Task InvokeGetItemOnJObject( + JObject rootObject, + int typeId, + DotnetObjectId objectId, + ElementIndexInfo elementIdxInfo, + CancellationToken token) + { + int[] methodIds = await context.SdbAgent.GetMethodIdsByName(typeId, "get_Item", BindingFlags.Default, token); + if (methodIds == null || methodIds.Length == 0) + throw new InvalidOperationException($"Type '{rootObject?["className"]?.Value()}' cannot be indexed."); + var type = rootObject?["type"]?.Value(); + + // ToDo: optimize the loop by choosing the right method at once without trying out them all + for (int i = 0; i < methodIds.Length; i++) + { + MethodInfoWithDebugInformation methodInfo = await context.SdbAgent.GetMethodInfo(methodIds[i], token); + ParameterInfo[] paramInfo = methodInfo.GetParametersInfo(); + if (paramInfo.Length != elementIdxInfo.DimensionsCount) + continue; + try + { + if (!CheckParametersCompatibility(paramInfo, elementIdxInfo.Indexers)) + continue; + ArraySegment buffer = await WriteIndexObjectAsIndices(objectId, elementIdxInfo.Indexers, paramInfo); + JObject getItemRetObj = await context.SdbAgent.InvokeMethod(buffer, methodIds[i], token); + return (JObject)getItemRetObj["value"]; + } + catch (Exception ex) + { + logger.LogDebug($"Attempt number {i + 1} out of {methodIds.Length} of invoking method {methodInfo.Name} with parameter named {paramInfo[0].Name} on type {type} failed. Method Id = {methodIds[i]}.\nInner exception: {ex}."); + continue; + } + } + return null; async Task> WriteIndexObjectAsIndices(DotnetObjectId rootObjId, List indexObjects, ParameterInfo[] paramInfo) { @@ -578,19 +597,47 @@ private static bool CheckParametersCompatibility(ParameterInfo[] paramInfos, Lis return false; foreach ((ParameterInfo paramInfo, object indexObj) in paramInfos.Zip(indexObjects)) { - // shouldn't we check LiteralExpressionSyntax for compatibility as well? - if (indexObj is JObject indexJObj && !CheckParameterCompatibility(paramInfo.TypeCode, indexJObj)) + string argumentType = "", argumentClassName = ""; + if (indexObj is JObject indexJObj) + { + argumentType = indexJObj["type"]?.Value(); + argumentClassName = indexJObj["className"]?.Value(); + } + else if (indexObj is LiteralExpressionSyntax literal) + { + // any primitive literal is an object + if (paramInfo.TypeCode.Value == ElementType.Object) + continue; + switch (literal.Kind()) + { + case SyntaxKind.NumericLiteralExpression: + argumentType = "number"; + break; + case SyntaxKind.StringLiteralExpression: + argumentType = "string"; + break; + case SyntaxKind.TrueLiteralExpression: + case SyntaxKind.FalseLiteralExpression: + argumentType = "boolean"; + break; + case SyntaxKind.CharacterLiteralExpression: + argumentType = "symbol"; + break; + case SyntaxKind.NullLiteralExpression: + // do not check + continue; + } + } + if (!CheckParameterCompatibility(paramInfo.TypeCode, argumentType, argumentClassName)) return false; } return true; } - private static bool CheckParameterCompatibility(ElementType? paramTypeCode, JObject value) + private static bool CheckParameterCompatibility(ElementType? paramTypeCode, string argumentType, string argumentClassName="") { if (!paramTypeCode.HasValue) return true; - var argumentType = value["type"]?.Value(); - var argumentClassName = value["className"]?.Value(); switch (paramTypeCode.Value) { @@ -624,6 +671,8 @@ private static bool CheckParameterCompatibility(ElementType? paramTypeCode, JObj return false; if (argumentType == "object") return false; + if (argumentType == "string" || argumentType == "symbol") + return false; break; case ElementType.String: if (argumentType != "string") diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index 844ed5b4d5419..9b83089dfd9e2 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -858,7 +858,7 @@ internal sealed partial class MonoSDBHelper private static int debuggerObjectId; private static int cmdId = 1; //cmdId == 0 is used by events which come from runtime - private const int MINOR_VERSION = 65; + private const int MINOR_VERSION = 66; private const int MAJOR_VERSION = 2; private int VmMinorVersion { get; set; } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs index 4f1d99c07ef80..2c559ef98f195 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs @@ -684,7 +684,7 @@ public async Task InspectInlineArray(string varName) $"'[debugger-test] DebuggerTests.InlineArray:run'" + "); }, 1);"; - var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, 441, 12, "DebuggerTests.InlineArray.run"); + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, 442, 12, "DebuggerTests.InlineArray.run"); var id = pause_location["callFrames"][0]["callFrameId"].Value(); await EvaluateOnCallFrameAndCheck(id, ($"{varName}[0]", TObject("DebuggerTests.InlineArray.E")), @@ -714,6 +714,7 @@ await EvaluateOnCallFrameAndCheck(id, o = TObject("DebuggerTests.InlineArray.Two") }, "s_one_props#1"); } + [ConditionalFact(nameof(RunningOnChrome))] public async Task InspectInlineArray2() { @@ -723,13 +724,14 @@ public async Task InspectInlineArray2() $"'[debugger-test] DebuggerTests.InlineArray:run2'" + "); }, 1);"; - var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, 459, 12, "DebuggerTests.InlineArray.run2"); + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, 460, 12, "DebuggerTests.InlineArray.run2"); var id = pause_location["callFrames"][0]["callFrameId"].Value(); await EvaluateOnCallFrameAndCheck(id, ($"a2[0]", TNumber(1)), ($"a3[0]", TNumber(2)), ($"a4[0]", TObject("DebuggerTests.InlineArray.E")), - ($"Arr4.myStaticField", TNumber(50)) + ($"Arr4.myStaticField", TNumber(50)), + ($"a2.InlineMethod(1)", TNumber(101)) ); var (_, res) = await EvaluateOnCallFrame(id,$"a3[1]", expect_ok: false); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrame2Tests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrame2Tests.cs index f76d9a0438e44..b354b8c6caef1 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrame2Tests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrame2Tests.cs @@ -755,13 +755,32 @@ public async Task EvaluateObjectIndexingMultidimensional() => await CheckInspect var id = pause_location["callFrames"][0]["callFrameId"].Value(); await EvaluateOnCallFrameAndCheck(id, - ("f[j, aDouble]", TNumber("3.34")), //only IdentifierNameSyntaxes - ("f[1, aDouble]", TNumber("3.34")), //IdentifierNameSyntax with LiteralExpressionSyntax - ("f[aChar, \"&\", longString]", TString("9-&-longString")), - ("f[f.numArray[j], aDouble]", TNumber("4.34")), //ElementAccessExpressionSyntax - ("f[f.numArray[j], f.numArray[0]]", TNumber("3")), //multiple ElementAccessExpressionSyntaxes - ("f[f.numArray[f.numList[0]], f.numArray[i]]", TNumber("3")), - ("f[f.numArray[f.numList[0]], f.numArray[f.numArray[i]]]", TNumber("4")) + ("c[j, aDouble]", TNumber("3.34")), //only IdentifierNameSyntaxes + ("c[1, aDouble]", TNumber("3.34")), //IdentifierNameSyntax with LiteralExpressionSyntax + ("c[aChar, \"&\", longString]", TString("9-&-longString")), + ("c[cc.numArray[j], aDouble]", TNumber("4.34")), //ElementAccessExpressionSyntax + ("c[cc.numArray[j], cc.numArray[0]]", TNumber("3")), //multiple ElementAccessExpressionSyntaxes + ("c[cc.numArray[cc.numList[0]], cc.numArray[i]]", TNumber("3")), + ("c[cc.numArray[cc.numList[0]], cc.numArray[cc.numArray[i]]]", TNumber("4")) + ); + }); + + [Fact] + public async Task EvaluateValueTypeIndexingMultidimensional() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 12, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + + await EvaluateOnCallFrameAndCheck(id, + ("s[j, aDouble]", TNumber("3.34")), //only IdentifierNameSyntaxes + ("s[1, aDouble]", TNumber("3.34")), //IdentifierNameSyntax with LiteralExpressionSyntax + ("s[aChar, \"&\", longString]", TString("9-&-longString")), + ("s[cc.numArray[j], aDouble]", TNumber("4.34")), //ElementAccessExpressionSyntax + ("s[cc.numArray[j], cc.numArray[0]]", TNumber("3")), //multiple ElementAccessExpressionSyntaxes + ("s[cc.numArray[cc.numList[0]], cc.numArray[i]]", TNumber("3")), + ("s[cc.numArray[cc.numList[0]], cc.numArray[cc.numArray[i]]]", TNumber("4")) ); }); } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index ac9f9d8d1c86b..82ea36992d341 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -576,95 +576,109 @@ await EvaluateOnCallFrameAndCheck(id, [ConditionalFact(nameof(RunningOnChrome))] public async Task EvaluateIndexingNegative() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 6, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); 1 }})", wait_for_event_fn: async (pause_location) => { var id = pause_location["callFrames"][0]["callFrameId"].Value(); - var (_, res) = await EvaluateOnCallFrame(id, "f.idx0[2]", expect_ok: false ); - Assert.Equal("Unable to evaluate element access 'f.idx0[2]': Cannot apply indexing with [] to a primitive object of type 'number'", res.Error["result"]?["description"]?.Value()); + var (_, res) = await EvaluateOnCallFrame(id, "cc.idx0[2]", expect_ok: false ); + Assert.Equal("Unable to evaluate element access 'cc.idx0[2]': Cannot apply indexing with [] to a primitive object of type 'number'", res.Error["result"]?["description"]?.Value()); var exceptionDetailsStack = res.Error["exceptionDetails"]?["stackTrace"]?["callFrames"]?[0]; Assert.Equal("DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", exceptionDetailsStack?["functionName"]?.Value()); - Assert.Equal(562, exceptionDetailsStack?["lineNumber"]?.Value()); + Assert.Equal(576, exceptionDetailsStack?["lineNumber"]?.Value()); Assert.Equal(12, exceptionDetailsStack?["columnNumber"]?.Value()); - (_, res) = await EvaluateOnCallFrame(id, "f[1]", expect_ok: false ); - Assert.Equal( "Unable to evaluate element access 'f[1]': Cannot apply indexing with [] to an object of type 'DebuggerTests.EvaluateLocalsWithIndexingTests.TestEvaluate'", res.Error["result"]?["description"]?.Value()); + (_, res) = await EvaluateOnCallFrame(id, "c[1]", expect_ok: false ); + Assert.Equal( "Unable to evaluate element access 'c[1]': Cannot apply indexing with [] to an object of type 'DebuggerTests.EvaluateLocalsWithIndexingTests.ClassWithIndexers'", res.Error["result"]?["description"]?.Value()); }); [Fact] public async Task EvaluateIndexingsByConstant() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 6, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })", wait_for_event_fn: async (pause_location) => { var id = pause_location["callFrames"][0]["callFrameId"].Value(); await EvaluateOnCallFrameAndCheck(id, - ("f.numList[0]", TNumber(1)), - ("f.textList[1]", TString("2")), - ("f.numArray[1]", TNumber(2)), - ("f.textArray[0]", TString("1"))); + ("cc.numList[0]", TNumber(1)), + ("cc.textList[1]", TString("2")), + ("cc.numArray[1]", TNumber(2)), + ("cc.textArray[0]", TString("1"))); }); [Fact] public async Task EvaluateIndexingByLocalVariable() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 6, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })", wait_for_event_fn: async (pause_location) => { var id = pause_location["callFrames"][0]["callFrameId"].Value(); await EvaluateOnCallFrameAndCheck(id, - ("f.numList[i]", TNumber(1)), - ("f.textList[j]", TString("2")), - ("f.numArray[j]", TNumber(2)), - ("f.textArray[i]", TString("1"))); + ("cc.numList[i]", TNumber(1)), + ("cc.textList[j]", TString("2")), + ("cc.numArray[j]", TNumber(2)), + ("cc.textArray[i]", TString("1"))); }); [ConditionalFact(nameof(RunningOnChrome))] public async Task EvaluateObjectIndexingByNonIntConst() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 6, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })", wait_for_event_fn: async (pause_location) => { var id = pause_location["callFrames"][0]["callFrameId"].Value(); await EvaluateOnCallFrameAndCheck(id, - ("f[\"longstring\"]", TBool(true)), - ("f[\"-\"]", TBool(false)), - ("f[\'-\']", TString("res_-")), - ("f[true]", TString("True")), - //("f[1.23]", TNumber(1)) // Not supported yet - float/double - ("f.indexedByStr[\"1\"]", TBool(true)), - ("f.indexedByStr[\"111\"]", TBool(false)), - ("f.indexedByStr[\"true\"]", TBool(true)), - ("f.indexedByChar[\'i\']", TString("I")), - ("f.indexedByChar[\'5\']", TString("5")), - ("f.indexedByBool[true]", TString("TRUE")), - ("f.indexedByBool[false]", TString("FALSE")) + ("c[\"longstring\"]", TBool(true)), // class + ("c[\"-\"]", TBool(false)), + ("c[\'-\']", TString("res_-")), + ("c[true]", TString("True")), + ("c[1.23]", TNumber(1)), + ("s[\"longstring\"]", TBool(true)), // struct + ("s[\"-\"]", TBool(false)), + ("s[\'-\']", TString("res_-")), + ("s[true]", TString("True")), + ("s[1.23]", TNumber(1)), + ("cc.indexedByStr[\"1\"]", TBool(true)), + ("cc.indexedByStr[\"111\"]", TBool(false)), + ("cc.indexedByStr[\"true\"]", TBool(true)), + ("cc.indexedByChar[\'i\']", TString("I")), + ("cc.indexedByChar[\'5\']", TString("5")), + ("cc.indexedByBool[true]", TString("TRUE")), + ("cc.indexedByBool[false]", TString("FALSE")) ); - var (_, res) = await EvaluateOnCallFrame(id,"f.indexedByStr[\"invalid\"]", expect_ok: false); - Assert.True(res.Error["result"]?["description"]?.Value().StartsWith("Cannot evaluate '(f.indexedByStr[\"invalid\"]", StringComparison.Ordinal)); - (_, res) = await EvaluateOnCallFrame(id,"f.indexedByStr[null]", expect_ok: false); - Assert.True(res.Error["result"]?["description"]?.Value().StartsWith("Cannot evaluate '(f.indexedByStr[null]", StringComparison.Ordinal)); + var (_, res) = await EvaluateOnCallFrame(id,"cc.indexedByStr[\"invalid\"]", expect_ok: false); + Assert.True(res.Error["result"]?["description"]?.Value().StartsWith("Cannot evaluate '(cc.indexedByStr[\"invalid\"]", StringComparison.Ordinal)); + (_, res) = await EvaluateOnCallFrame(id,"cc.indexedByStr[null]", expect_ok: false); + Assert.True(res.Error["result"]?["description"]?.Value().StartsWith("Cannot evaluate '(cc.indexedByStr[null]", StringComparison.Ordinal)); }); [Fact] public async Task EvaluateObjectByNonIntLocals() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 14, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 15, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })", wait_for_event_fn: async (pause_location) => { var id = pause_location["callFrames"][0]["callFrameId"].Value(); 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)), - ("f[aDecimal]", TNumber(3)), - ("f[arr]", TChar('t')), - ("f[objIdx]", TNumber(123)) + ("c[longString]", TBool(true)), + ("c[aBool]", TString("True")), + ("c[aChar]", TString("res_9")), + ("c[shortString]", TBool(false)), + ("c[aFloat]", TNumber(1)), + ("c[aDouble]", TNumber(2)), + ("c[aDecimal]", TNumber(3)), + ("c[arr]", TChar('t')), + ("c[objIdx]", TNumber(123)), + ("s[longString]", TBool(true)), + ("s[aBool]", TString("True")), + ("s[aChar]", TString("res_9")), + ("s[shortString]", TBool(false)), + ("s[aFloat]", TNumber(1)), + ("s[aDouble]", TNumber(2)), + ("s[aDecimal]", TNumber(3)), + ("s[arr]", TChar('t')), + ("s[objIdx]", TNumber(123)) ); }); @@ -676,24 +690,26 @@ public async Task EvaluateNestedObjectIndexingByNonIntLocals() => await CheckIns { var id = pause_location["callFrames"][0]["callFrameId"].Value(); await EvaluateOnCallFrameAndCheck(id, - ("f[f.textArray[0]]", TBool(false)), // f["1"] - ("f[f.textArray[j]]", TBool(false)) // f["2"] + ("c[cc.textArray[0]]", TBool(false)), // c["1"] + ("c[cc.textArray[j]]", TBool(false)), // c["2"] + ("s[cc.textArray[0]]", TBool(false)), // s["1"] + ("s[cc.textArray[j]]", TBool(false)) // s["2"] ); }); // ToDo: https://github.com/dotnet/runtime/issues/76015 [Fact] public async Task EvaluateIndexingByExpression() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 6, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })", wait_for_event_fn: async (pause_location) => { var id = pause_location["callFrames"][0]["callFrameId"].Value(); await EvaluateOnCallFrameAndCheck(id, - ("f.numList[i + 1]", TNumber(2)), - ("f.textList[(2 * j) - 1]", TString("2")), - ("f.textList[j - 1]", TString("1")), - ("f.numArray[f.numList[j - 1]]", TNumber(2)) + ("cc.numList[i + 1]", TNumber(2)), + ("cc.textList[(2 * j) - 1]", TString("2")), + ("cc.textList[j - 1]", TString("1")), + ("cc.numArray[cc.numList[j - 1]]", TNumber(2)) ); }); @@ -714,63 +730,63 @@ await EvaluateOnCallFrameAndCheck(id, [ConditionalFact(nameof(RunningOnChrome))] public async Task EvaluateIndexingByExpressionNegative() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 6, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); 1 }})", wait_for_event_fn: async (pause_location) => { // indexing with expression of a wrong type var id = pause_location["callFrames"][0]["callFrameId"].Value(); - var (_, res) = await EvaluateOnCallFrame(id, "f.numList[\"a\" + 1]", expect_ok: false ); - Assert.Equal("Unable to evaluate element access 'f.numList[\"a\" + 1]': Cannot index with an object of type 'string'", res.Error["result"]?["description"]?.Value()); + var (_, res) = await EvaluateOnCallFrame(id, "cc.numList[\"a\" + 1]", expect_ok: false ); + Assert.Equal("Unable to evaluate element access 'cc.numList[\"a\" + 1]': Cannot index with an object of type 'string'", res.Error["result"]?["description"]?.Value()); var exceptionDetailsStack = res.Error["exceptionDetails"]?["stackTrace"]?["callFrames"]?[0]; Assert.Equal("DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", exceptionDetailsStack?["functionName"]?.Value()); - Assert.Equal(562, exceptionDetailsStack?["lineNumber"]?.Value()); + Assert.Equal(576, exceptionDetailsStack?["lineNumber"]?.Value()); Assert.Equal(12, exceptionDetailsStack?["columnNumber"]?.Value()); }); [ConditionalFact(nameof(RunningOnChrome))] public async Task EvaluateIndexingByExpressionContainingUnknownIdentifier() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 6, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); 1 }})", wait_for_event_fn: async (pause_location) => { // indexing with expression of a wrong type var id = pause_location["callFrames"][0]["callFrameId"].Value(); - var (_, res) = await EvaluateOnCallFrame(id, "f.numList[\"a\" + x]", expect_ok: false); + var (_, res) = await EvaluateOnCallFrame(id, "cc.numList[\"a\" + x]", expect_ok: false); Assert.Equal("The name x does not exist in the current context", res.Error["result"]?["description"]?.Value()); }); [Fact] public async Task EvaluateIndexingByMemberVariables() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 6, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })", wait_for_event_fn: async (pause_location) => { var id = pause_location["callFrames"][0]["callFrameId"].Value(); await EvaluateOnCallFrameAndCheck(id, - ("f.idx0", TNumber(0)), - ("f.idx1", TNumber(1)), - ("f.numList[f.idx0]", TNumber(1)), - ("f.textList[f.idx1]", TString("2")), - ("f.numArray[f.idx1]", TNumber(2)), - ("f.textArray[f.idx0]", TString("1"))); + ("cc.idx0", TNumber(0)), + ("cc.idx1", TNumber(1)), + ("cc.numList[cc.idx0]", TNumber(1)), + ("cc.textList[cc.idx1]", TString("2")), + ("cc.numArray[cc.idx1]", TNumber(2)), + ("cc.textArray[cc.idx0]", TString("1"))); }); [Fact] public async Task EvaluateIndexingNested() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 6, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })", wait_for_event_fn: async (pause_location) => { var id = pause_location["callFrames"][0]["callFrameId"].Value(); await EvaluateOnCallFrameAndCheck(id, - ("f.idx0", TNumber(0)), - ("f.numList[f.numList[f.idx0]]", TNumber(2)), - ("f.textList[f.numList[f.idx0]]", TString("2")), - ("f.numArray[f.numArray[f.idx0]]", TNumber(2)), - ("f.textArray[f.numArray[f.idx0]]", TString("2"))); + ("cc.idx0", TNumber(0)), + ("cc.numList[cc.numList[cc.idx0]]", TNumber(2)), + ("cc.textList[cc.numList[cc.idx0]]", TString("2")), + ("cc.numArray[cc.numArray[cc.idx0]]", TNumber(2)), + ("cc.textArray[cc.numArray[cc.idx0]]", TString("2"))); }); @@ -841,7 +857,7 @@ await EvaluateOnCallFrameAndCheck(id, [ConditionalFact(nameof(RunningOnChrome))] public async Task EvaluateIndexingJagged() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 5, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", + "DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 6, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })", wait_for_event_fn: async (pause_location) => { @@ -849,21 +865,21 @@ public async Task EvaluateIndexingJagged() => await CheckInspectLocalsAtBreakpoi await EvaluateOnCallFrameAndCheck(id, ("j", TNumber(1)), - ("f.idx1", TNumber(1)), - ("f.numArrayOfArrays[1][1]", TNumber(2)), - ("f.numArrayOfArrays[j][j]", TNumber(2)), - ("f.numArrayOfArrays[f.idx1][f.idx1]", TNumber(2)), - ("f.numListOfLists[1][1]", TNumber(2)), - ("f.numListOfLists[j][j]", TNumber(2)), - ("f.numListOfLists[f.idx1][f.idx1]", TNumber(2)), - ("f.textArrayOfArrays[1][1]", TString("2")), - ("f.textArrayOfArrays[j][j]", TString("2")), - ("f.textArrayOfArrays[f.idx1][f.idx1]", TString("2")), - ("f.textListOfLists[1][1]", TString("2")), - ("f.textListOfLists[j][j]", TString("2")), - ("f.textListOfLists[f.idx1][f.idx1]", TString("2")), - ("f.numArrayOfArrays[f.numArray[f.numList[1]]][f.numList[0]]", TNumber(2)) - ); + ("cc.idx1", TNumber(1)), + ("cc.numArrayOfArrays[1][1]", TNumber(2)), + ("cc.numArrayOfArrays[j][j]", TNumber(2)), + ("cc.numArrayOfArrays[cc.idx1][cc.idx1]", TNumber(2)), + ("cc.numListOfLists[1][1]", TNumber(2)), + ("cc.numListOfLists[j][j]", TNumber(2)), + ("cc.numListOfLists[cc.idx1][cc.idx1]", TNumber(2)), + ("cc.textArrayOfArrays[1][1]", TString("2")), + ("cc.textArrayOfArrays[j][j]", TString("2")), + ("cc.textArrayOfArrays[cc.idx1][cc.idx1]", TString("2")), + ("cc.textListOfLists[1][1]", TString("2")), + ("cc.textListOfLists[j][j]", TString("2")), + ("cc.textListOfLists[cc.idx1][cc.idx1]", TString("2")), + ("cc.numArrayOfArrays[cc.numArray[cc.numList[1]]][cc.numList[0]]", TNumber(2)) + ); }); diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-array-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-array-test.cs index c05329b0c3f49..6192c60204da5 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-array-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-array-test.cs @@ -362,7 +362,7 @@ class One {} class Two {} class Three {} class Four {} - struct E + public struct E { public int x; public int y; @@ -370,7 +370,7 @@ struct E } [System.Runtime.CompilerServices.InlineArray(Length)] - struct Arr1 + public struct Arr1 { public const int Length = 42; public E e; @@ -381,6 +381,7 @@ struct Arr2 { public const int Length = 42; public int e; + public int InlineMethod(int n) => n + 100; } [System.Runtime.CompilerServices.InlineArray(1)] diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs index e063020d1a32a..8e3804889a3c7 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs @@ -508,58 +508,72 @@ public class EvaluateLocalsWithIndexingTests { public record Indexer(int index); - public class TestEvaluate + public class CommonCollections { - public List numList; - public List textList; - public int[] numArray; - public string[] textArray; + public List numList = new List { 1, 2 }; + public List textList = new List { "1", "2" }; + public int[] numArray = new int[] { 1, 2, 0 }; + public string[] textArray = new string[] { "1", "2" }; public int[][] numArrayOfArrays; public List> numListOfLists; public string[][] textArrayOfArrays; public List> textListOfLists; - public Dictionary indexedByStr; - public Dictionary indexedByChar; - public Dictionary indexedByBool; - public int idx0; - public int idx1; - - public string this[char key] => "res_" + key; - public string this[bool key] => key.ToString(); - public bool this[string key] => key.Length > 3; - public int this[double key] => (int)key; - public int this[float key] => (int)key; - public int this[decimal key] => (int)key; - public int this[Indexer indexer] => indexer.index; - public char this[char[] arr] => arr.Length == 0 ? '0' : arr[0]; - - public double this[int key1, double key2] => key1 + key2; - public string this[char key1, string key2, string key3] => $"{key1}-{key2}-{key3}"; + public Dictionary indexedByStr = new Dictionary() { { "1", true }, { "111", false }, { "true", true} }; + public Dictionary indexedByChar = new Dictionary() { { 'i', "I" }, { '5', "5" } }; + public Dictionary indexedByBool = new Dictionary() { { true, "TRUE" }, { false, "FALSE" } }; + public int idx0 = 0; + public int idx1 = 1; - public void run() + public CommonCollections() { - numList = new List { 1, 2 }; - textList = new List { "1", "2" }; - numArray = new int[] { 1, 2, 0 }; - textArray = new string[] { "1", "2" }; numArrayOfArrays = new int[][] { numArray, numArray }; numListOfLists = new List> { numList, numList }; textArrayOfArrays = new string[][] { textArray, textArray }; textListOfLists = new List> { textList, textList }; - indexedByStr = new Dictionary() { { "1", true }, { "111", false }, { "true", true} }; - indexedByChar = new Dictionary() { { 'i', "I" }, { '5', "5" } }; - indexedByBool = new Dictionary() { { true, "TRUE" }, { false, "FALSE" } }; - idx0 = 0; - idx1 = 1; } } + public class ClassWithIndexers + { + public string this[char keyChar] => "res_" + keyChar; + public string this[bool keyBool] => keyBool.ToString(); + public bool this[string keyStr] => keyStr.Length > 3; + public int this[double keyDouble] => (int)keyDouble; + public int this[float keyFloat] => (int)keyFloat; + public int this[decimal keyDecimal] => (int)keyDecimal; + public int this[Indexer indexer] => indexer.index; + public char this[char[] arr] => arr.Length == 0 ? '0' : arr[0]; + + public double this[int key1, double key2] => key1 + key2; + public string this[char key1, string key2, string key3] => $"{key1}-{key2}-{key3}"; + + public InlineArray.Arr1 inlineArr; + } + + public struct StructWithIndexers + { + public string this[char keyChar] => "res_" + keyChar; + public string this[bool keyBool] => keyBool.ToString(); + public bool this[string keyStr] => keyStr.Length > 3; + public int this[double keyDouble] => (int)keyDouble; + public int this[float keyFloat] => (int)keyFloat; + public int this[decimal keyDecimal] => (int)keyDecimal; + public int this[Indexer indexer] => indexer.index; + public char this[char[] arr] => arr.Length == 0 ? '0' : arr[0]; + + public double this[int key1, double key2] => key1 + key2; + public string this[char key1, string key2, string key3] => $"{key1}-{key2}-{key3}"; + + public InlineArray.Arr1 inlineArr; + } + public static void EvaluateLocals() { int i = 0; int j = 1; - TestEvaluate f = new TestEvaluate(); - f.run(); + ClassWithIndexers c = new(); + StructWithIndexers s = new(); + CommonCollections cc = new(); string longString = "longString"; string shortString = "9"; char aChar = '9';