diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs index ee7def9514c54ccda1907e48c4efc7a62fc87731..e948c6abe0ebf7d2ecdcc20c34ddf9b750797061 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs @@ -60,36 +60,59 @@ public MessageId(string sessionId, int id) internal class DotnetObjectId { public string Scheme { get; } - public string Value { get; } + public int Value { get; } + public int SubValue { get; set; } public static bool TryParse(JToken jToken, out DotnetObjectId objectId) => TryParse(jToken?.Value(), out objectId); public static bool TryParse(string id, out DotnetObjectId objectId) { objectId = null; - if (id == null) - return false; - - if (!id.StartsWith("dotnet:")) - return false; + try { + if (id == null) + return false; - string[] parts = id.Split(":", 3); + if (!id.StartsWith("dotnet:")) + return false; - if (parts.Length < 3) - return false; + string[] parts = id.Split(":"); - objectId = new DotnetObjectId(parts[1], parts[2]); + if (parts.Length < 3) + return false; - return true; + objectId = new DotnetObjectId(parts[1], int.Parse(parts[2])); + switch (objectId.Scheme) + { + case "methodId": + { + parts = id.Split(":"); + objectId.SubValue = int.Parse(parts[3]); + break; + } + } + return true; + } + catch (Exception) + { + return false; + } } - public DotnetObjectId(string scheme, string value) + public DotnetObjectId(string scheme, int value) { Scheme = scheme; Value = value; } - public override string ToString() => $"dotnet:{Scheme}:{Value}"; + public override string ToString() + { + switch (Scheme) + { + case "methodId": + return $"dotnet:{Scheme}:{Value}:{SubValue}"; + } + return $"dotnet:{Scheme}:{Value}"; + } } public struct Result diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs index 8516b349f4cf23a328f15a24a9ce09226120aac4..bcef7dbed5272d387aa4218aeed0e7b4fbbc04a4 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs @@ -53,7 +53,7 @@ public async Task GetValueFromObject(JToken objRet, CancellationToken t { if (DotnetObjectId.TryParse(objRet?["value"]?["objectId"]?.Value(), out DotnetObjectId objectId)) { - var exceptionObject = await context.SdbAgent.GetObjectValues(int.Parse(objectId.Value), GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.OwnProperties, token); + var exceptionObject = await context.SdbAgent.GetObjectValues(objectId.Value, GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.OwnProperties, token); var exceptionObjectMessage = exceptionObject.FirstOrDefault(attr => attr["name"].Value().Equals("_message")); exceptionObjectMessage["value"]["value"] = objRet["value"]?["className"]?.Value() + ": " + exceptionObjectMessage["value"]?["value"]?.Value(); return exceptionObjectMessage["value"]?.Value(); @@ -342,17 +342,15 @@ public async Task Resolve(ElementAccessExpressionSyntax elementAccess, switch (objectId.Scheme) { case "array": - rootObject["value"] = await context.SdbAgent.GetArrayValues(int.Parse(objectId.Value), token); + rootObject["value"] = await context.SdbAgent.GetArrayValues(objectId.Value, token); return (JObject)rootObject["value"][elementIdx]["value"]; case "object": - var typeIds = await context.SdbAgent.GetTypeIdFromObject(int.Parse(objectId.Value), true, token); + var typeIds = await context.SdbAgent.GetTypeIdFromObject(objectId.Value, true, token); int methodId = await context.SdbAgent.GetMethodIdByName(typeIds[0], "ToArray", token); - var commandParamsObjWriter = new MonoBinaryWriter(); - commandParamsObjWriter.WriteObj(objectId, context.SdbAgent); - var toArrayRetMethod = await context.SdbAgent.InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, elementAccess.Expression.ToString(), token); + var toArrayRetMethod = await context.SdbAgent.InvokeMethodInObject(objectId.Value, methodId, elementAccess.Expression.ToString(), token); rootObject = await GetValueFromObject(toArrayRetMethod, token); DotnetObjectId.TryParse(rootObject?["objectId"]?.Value(), out DotnetObjectId arrayObjectId); - rootObject["value"] = await context.SdbAgent.GetArrayValues(int.Parse(arrayObjectId.Value), token); + rootObject["value"] = await context.SdbAgent.GetArrayValues(arrayObjectId.Value, token); return (JObject)rootObject["value"][elementIdx]["value"]; default: throw new InvalidOperationException($"Cannot apply indexing with [] to an expression of type '{objectId.Scheme}'"); @@ -391,7 +389,7 @@ public async Task Resolve(InvocationExpressionSyntax method, Dictionary if (rootObject != null) { DotnetObjectId.TryParse(rootObject?["objectId"]?.Value(), out DotnetObjectId objectId); - var typeIds = await context.SdbAgent.GetTypeIdFromObject(int.Parse(objectId.Value), true, token); + 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 diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index e0f796e09c5ad29a185d4542f0f3b453f147f360..b12de2655f70fde033ac998a917e489bc3fb9289 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -411,7 +411,7 @@ protected override async Task AcceptCommand(MessageId id, string method, J { case "scope": return await OnSetVariableValue(id, - int.Parse(objectId.Value), + objectId.Value, args?["variableName"]?.Value(), args?["newValue"], token); @@ -449,7 +449,7 @@ protected override async Task AcceptCommand(MessageId id, string method, J { case "scope": return await OnEvaluateOnCallFrame(id, - int.Parse(objectId.Value), + objectId.Value, args?["expression"]?.Value(), token); default: return false; @@ -586,16 +586,17 @@ private async Task CallOnFunction(MessageId id, JObject args, Cancellation switch (objectId.Scheme) { case "object": - args["details"] = await context.SdbAgent.GetObjectProxy(int.Parse(objectId.Value), token); + case "methodId": + args["details"] = await context.SdbAgent.GetObjectProxy(objectId.Value, token); break; case "valuetype": - args["details"] = await context.SdbAgent.GetValueTypeProxy(int.Parse(objectId.Value), token); + args["details"] = await context.SdbAgent.GetValueTypeProxy(objectId.Value, token); break; case "pointer": - args["details"] = await context.SdbAgent.GetPointerContent(int.Parse(objectId.Value), token); + args["details"] = await context.SdbAgent.GetPointerContent(objectId.Value, token); break; case "array": - args["details"] = await context.SdbAgent.GetArrayValuesProxy(int.Parse(objectId.Value), token); + args["details"] = await context.SdbAgent.GetArrayValuesProxy(objectId.Value, token); break; case "cfo_res": { @@ -680,20 +681,25 @@ internal async Task RuntimeGetPropertiesInternal(SessionId id, DotnetObj { case "scope": { - var res = await GetScopeProperties(id, int.Parse(objectId.Value), token); + var res = await GetScopeProperties(id, objectId.Value, token); return res.Value?["result"]; } case "valuetype": - return await context.SdbAgent.GetValueTypeValues(int.Parse(objectId.Value), accessorPropertiesOnly, token); + return await context.SdbAgent.GetValueTypeValues(objectId.Value, accessorPropertiesOnly, token); case "array": - return await context.SdbAgent.GetArrayValues(int.Parse(objectId.Value), token); + return await context.SdbAgent.GetArrayValues(objectId.Value, token); + case "methodId": + { + var objRet = await context.SdbAgent.InvokeMethodInObject(objectId.Value, objectId.SubValue, "", token); + return new JArray(objRet); + } case "object": - return await context.SdbAgent.GetObjectValues(int.Parse(objectId.Value), objectValuesOpt, token); + return await context.SdbAgent.GetObjectValues(objectId.Value, objectValuesOpt, token); case "pointer": - return new JArray{await context.SdbAgent.GetPointerContent(int.Parse(objectId.Value), token)}; + return new JArray{await context.SdbAgent.GetPointerContent(objectId.Value, token)}; case "cfo_res": { - Result res = await SendMonoCommand(id, MonoCommands.GetDetails(RuntimeId, int.Parse(objectId.Value), args), token); + Result res = await SendMonoCommand(id, MonoCommands.GetDetails(RuntimeId, objectId.Value, args), token); string value_json_str = res.Value["result"]?["value"]?["__value_as_json_string__"]?.Value(); return value_json_str != null ? JArray.Parse(value_json_str) : null; } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index 216f78a53af564b112088ad7bf2a34b763df956e..35d1ef4fceba1266dce7cd4a17a852357e8fa13a 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -502,7 +502,7 @@ public override void Write(string val) base.Write(data); } - private void Write(ElementType type, T value) where T : struct => Write((byte)type, value); + internal void Write(ElementType type, T value) where T : struct => Write((byte)type, value); private void Write(T1 type, T2 value) where T1 : struct where T2 : struct { @@ -514,11 +514,11 @@ public void WriteObj(DotnetObjectId objectId, MonoSDBHelper SdbHelper) { if (objectId.Scheme == "object") { - Write(ElementType.Class, int.Parse(objectId.Value)); + Write(ElementType.Class, objectId.Value); } else if (objectId.Scheme == "valuetype") { - Write(SdbHelper.valueTypes[int.Parse(objectId.Value)].valueTypeBuffer); + Write(SdbHelper.valueTypes[objectId.Value].valueTypeBuffer); } } public async Task WriteConst(LiteralExpressionSyntax constValue, MonoSDBHelper SdbHelper, CancellationToken token) @@ -1537,6 +1537,13 @@ public async Task InvokeMethod(ArraySegment valueTypeBuffer, int return await CreateJObjectForVariableValue(retDebuggerCmdReader, varName, false, -1, false, token); } + public async Task InvokeMethodInObject(int objectId, int methodId, string varName, CancellationToken token) + { + using var commandParamsObjWriter = new MonoBinaryWriter(); + commandParamsObjWriter.Write(ElementType.Class, objectId); + return await InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, varName, token); + } + public async Task GetPropertyMethodIdByName(int typeId, string propertyName, CancellationToken token) { using var retDebuggerCmdReader = await GetTypePropertiesReader(typeId, token); @@ -1559,13 +1566,14 @@ public async Task GetPropertyMethodIdByName(int typeId, string propertyName return -1; } - public async Task CreateJArrayForProperties(int typeId, ArraySegment object_buffer, JArray attributes, bool isAutoExpandable, string objectId, bool isOwn, CancellationToken token) + public async Task CreateJArrayForProperties(int typeId, ArraySegment object_buffer, JArray attributes, bool isAutoExpandable, string objectIdStr, bool isOwn, CancellationToken token) { JArray ret = new JArray(); using var retDebuggerCmdReader = await GetTypePropertiesReader(typeId, token); if (retDebuggerCmdReader == null) return null; - + if (!DotnetObjectId.TryParse(objectIdStr, out DotnetObjectId objectId)) + return null; var nProperties = retDebuggerCmdReader.ReadInt32(); for (int i = 0 ; i < nProperties; i++) { @@ -1595,11 +1603,11 @@ public async Task CreateJArrayForProperties(int typeId, ArraySegment GetHoistedLocalVariables(int objectId, JArray asyncLoc { if (DotnetObjectId.TryParse(asyncLocal?["value"]?["objectId"]?.Value(), out DotnetObjectId dotnetObjectId)) { - if (int.TryParse(dotnetObjectId.Value, out int objectIdToGetInfo) && !objectsAlreadyRead.Contains(objectIdToGetInfo)) + if (!objectsAlreadyRead.Contains(dotnetObjectId.Value)) { - var asyncLocalsFromObject = await GetObjectValues(objectIdToGetInfo, GetObjectCommandOptions.WithProperties, token); - var hoistedLocalVariable = await GetHoistedLocalVariables(objectIdToGetInfo, asyncLocalsFromObject, token); + var asyncLocalsFromObject = await GetObjectValues(dotnetObjectId.Value, GetObjectCommandOptions.WithProperties, token); + var hoistedLocalVariable = await GetHoistedLocalVariables(dotnetObjectId.Value, asyncLocalsFromObject, token); asyncLocalsFull = new JArray(asyncLocalsFull.Union(hoistedLocalVariable)); } } @@ -2295,7 +2303,7 @@ public async Task GetValuesFromDebuggerProxyAttribute(int objectId, int var retMethod = await InvokeMethod(invokeParamsWriter.GetParameterBuffer(), methodId, "methodRet", token); DotnetObjectId.TryParse(retMethod?["value"]?["objectId"]?.Value(), out DotnetObjectId dotnetObjectId); - var displayAttrs = await GetObjectValues(int.Parse(dotnetObjectId.Value), GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.ForDebuggerProxyAttribute, token); + var displayAttrs = await GetObjectValues(dotnetObjectId.Value, GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.ForDebuggerProxyAttribute, token); return displayAttrs; } } @@ -2385,7 +2393,7 @@ public async Task GetObjectValues(int objectId, GetObjectCommandOptions if (!getCommandType.HasFlag(GetObjectCommandOptions.WithProperties)) return ret; using var commandParamsObjWriter = new MonoBinaryWriter(); - commandParamsObjWriter.WriteObj(new DotnetObjectId("object", $"{objectId}"), this); + commandParamsObjWriter.WriteObj(new DotnetObjectId("object", objectId), this); var props = await CreateJArrayForProperties(typeId[i], commandParamsObjWriter.GetParameterBuffer(), ret, getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute), $"dotnet:object:{objectId}", i == 0, token); ret = new JArray(ret.Union(props)); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs index fbc176dc2ca5e340559d60a6a6ac5f7a406a351c..f22b4ae3196befed79806e3c5483b41e020f4c07 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs @@ -623,7 +623,12 @@ internal async Task CheckCustomType(JToken actual_val, JToken exp_val, string la AssertEqual("Function", get["className"]?.Value(), $"{label}-className"); AssertStartsWith($"get {exp_val["type_name"]?.Value()} ()", get["description"]?.Value(), $"{label}-description"); AssertEqual("function", get["type"]?.Value(), $"{label}-type"); - + var expectedValue = exp_val["value"]; + if (expectedValue.Type != JTokenType.Null) + { + var valueAfterRunGet = await GetProperties(get["objectId"]?.Value()); + await CheckValue(valueAfterRunGet[0]?["value"], expectedValue, exp_val["type_name"]?.Value()); + } break; } @@ -1059,7 +1064,7 @@ internal void AssertEqual(object expected, object actual, string label) internal static JObject TIgnore() => JObject.FromObject(new { __custom_type = "ignore_me" }); - internal static JObject TGetter(string type) => JObject.FromObject(new { __custom_type = "getter", type_name = type }); + internal static JObject TGetter(string type, JObject value = null) => JObject.FromObject(new { __custom_type = "getter", type_name = type, value = value}); internal static JObject TDateTime(DateTime dt) => JObject.FromObject(new { diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs index ca5afa742cfb3ff0780673702b78f3f7a57554a3..f91ba3c012effb98e37ea5869dfb2dea51cfc2d4 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs @@ -340,7 +340,7 @@ public async Task GetObjectValueWithInheritance() { var pause_location = await EvaluateAndCheck( "window.setTimeout(function() { invoke_static_method('[debugger-test] TestChild:TestWatchWithInheritance'); }, 1);", - "dotnet://debugger-test.dll/debugger-test2.cs", 127, 8, + "dotnet://debugger-test.dll/debugger-test2.cs", 128, 8, "TestWatchWithInheritance"); var frame_id = pause_location["callFrames"][0]["callFrameId"].Value(); var frame_locals = await GetProperties(frame_id); @@ -350,9 +350,10 @@ public async Task GetObjectValueWithInheritance() j = TNumber(20), i = TNumber(50), k = TNumber(30), - GetJ = TGetter("GetJ"), - GetI = TGetter("GetI"), - GetK = TGetter("GetK") + GetJ = TGetter("GetJ", TNumber(20)), + GetI = TGetter("GetI", TNumber(50)), + GetK = TGetter("GetK", TNumber(30)), + GetD = TGetter("GetD", TDateTime(new DateTime(2020, 7, 6, 5, 4, 3))) }, "test_props"); await EvaluateOnCallFrameAndCheck(frame_id, ($"test.GetJ", TNumber(20)), diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-test2.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-test2.cs index 9756cbe26de44958bf839d27c8d6e2a391edbdfa..28c908261a6aa8fe2da0b18fac22963731ec901b 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-test2.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-test2.cs @@ -118,6 +118,7 @@ public class TestChild : TestParent { public int i = 50; public int GetI => i; + public DateTime GetD => new DateTime(2020, 7, 6, 5, 4, 3); public TestChild() { Console.WriteLine("Hi");