Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/mono.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/sdks
diff options
context:
space:
mode:
authorAnkit Jain <radical@gmail.com>2020-07-07 20:39:12 +0300
committerGitHub <noreply@github.com>2020-07-07 20:39:12 +0300
commit1da3a20c8b10f67d521a37ac1c2a32dc604ec484 (patch)
treee6f80c83b93ad1a7c29483c2b58e9a3edb287a8b /sdks
parent618cdf6ff78f6b8f8b519a1b6988f57682f87a76 (diff)
[wasm][debugger] Add support for deref'ing pointers (#19760)
* [wasm][debugger] Send var names also, to `mono_wasm_get_variables` - this will be useful in upcoming commits, which will need to use the names for local pointer vars * [wasm][debugger] Add support for deref'ing pointers * [wasm][debugger] Dereference the pointer in js, instead of debugger.c Based on suggestion from @vargaz * [wasm][debugger][tests] Fix setting async methods when setting .. breakpoint by method name. Async methods themselves don't have the debug information. Instead, that can be found on the generated async implementation type/method. Here, we just look for a type name starting with `{original_type_name}/<{async_method_name}>`, and use the method `MoveNext` from that. * [wasm][debugger][tests] Update to set bp by method name+lineoffset * Address review comments from Katelyn Gadd (@kg) * Add test to check deref'ing invalid pointers - We should really be handling invalid object ids, and report errors for those, where appropriate. This will be done in a future PR - For this PR, I'm adding tests that just check that invalid objects, or trying to deref regular objects, doesn't blow up * Remove old unused test code * [wasm][debugger] Use JsonConvert instead of manually building the json string - addresses review comment from @lewing
Diffstat (limited to 'sdks')
-rw-r--r--sdks/wasm/DebuggerTestSuite/PointerTests.cs520
-rw-r--r--sdks/wasm/DebuggerTestSuite/Support.cs95
-rw-r--r--sdks/wasm/DebuggerTestSuite/Tests.cs34
-rw-r--r--sdks/wasm/Mono.WebAssembly.DebuggerProxy/DebugStore.cs1
-rw-r--r--sdks/wasm/Mono.WebAssembly.DebuggerProxy/DevToolsHelper.cs8
-rw-r--r--sdks/wasm/Mono.WebAssembly.DebuggerProxy/MonoProxy.cs38
-rw-r--r--sdks/wasm/src/library_mono.js147
-rw-r--r--sdks/wasm/tests/debugger/debugger-pointers-test.cs109
-rw-r--r--sdks/wasm/tests/debugger/debugger-test.cs23
9 files changed, 854 insertions, 121 deletions
diff --git a/sdks/wasm/DebuggerTestSuite/PointerTests.cs b/sdks/wasm/DebuggerTestSuite/PointerTests.cs
new file mode 100644
index 00000000000..932f02a0c80
--- /dev/null
+++ b/sdks/wasm/DebuggerTestSuite/PointerTests.cs
@@ -0,0 +1,520 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Newtonsoft.Json.Linq;
+using Xunit;
+using WebAssembly.Net.Debugging;
+
+namespace DebuggerTests
+{
+
+ public class PointerTests : DebuggerTestBase {
+
+ public static TheoryData<string, string, string, int, string, bool> PointersTestData =>
+ new TheoryData<string, string, string, int, string, bool> {
+ {$"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "LocalPointers", 32, "LocalPointers", false},
+ {$"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "LocalPointers", 32, "LocalPointers", true},
+ {$"invoke_static_method_async ('[debugger-test] DebuggerTests.PointerTests:LocalPointersAsync');", "DebuggerTests.PointerTests", "LocalPointersAsync", 32, "MoveNext", false},
+ {$"invoke_static_method_async ('[debugger-test] DebuggerTests.PointerTests:LocalPointersAsync');", "DebuggerTests.PointerTests", "LocalPointersAsync", 32, "MoveNext", true}
+ };
+
+ [Theory]
+ [MemberDataAttribute (nameof (PointersTestData))]
+ public async Task InspectLocalPointersToPrimitiveTypes (string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo)
+ => await CheckInspectLocalsAtBreakpointSite (
+ type, method, line_offset, bp_function_name,
+ "window.setTimeout(function() { " + eval_fn + " })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) => {
+ var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
+
+ var dt = new DateTime (5, 6, 7, 8, 9, 10);
+ await CheckProps (locals, new {
+ ip = TPointer ("int*"),
+ ip_null = TPointer ("int*", is_null: true),
+ ipp = TPointer ("int**"),
+ ipp_null = TPointer ("int**"),
+
+ cvalue0 = TSymbol ("113 'q'"),
+ cp = TPointer ("char*"),
+
+ vp = TPointer ("void*"),
+ vp_null = TPointer ("void*", is_null: true),
+ }, "locals", num_fields: 26);
+
+ var props = await GetObjectOnLocals (locals, "ip");
+ await CheckPointerValue (props, "*ip", TNumber (5), "locals");
+
+ {
+ var ipp_props = await GetObjectOnLocals (locals, "ipp");
+ await CheckPointerValue (ipp_props, "*ipp", TPointer ("int*"));
+
+ ipp_props = await GetObjectOnLocals (ipp_props, "*ipp");
+ await CheckPointerValue (ipp_props, "**ipp", TNumber (5));
+ }
+
+ {
+ var ipp_props = await GetObjectOnLocals (locals, "ipp_null");
+ await CheckPointerValue (ipp_props, "*ipp_null", TPointer ("int*", is_null: true));
+ }
+
+ // *cp
+ props = await GetObjectOnLocals (locals, "cp");
+ await CheckPointerValue (props, "*cp", TSymbol ("113 'q'"));
+ });
+
+ [Theory]
+ [MemberDataAttribute (nameof (PointersTestData))]
+ public async Task InspectLocalPointerArrays (string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo)
+ => await CheckInspectLocalsAtBreakpointSite (
+ type, method, line_offset, bp_function_name,
+ "window.setTimeout(function() { " + eval_fn + " })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) => {
+ var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
+
+ var dt = new DateTime (5, 6, 7, 8, 9, 10);
+ await CheckProps (locals, new {
+ ipa = TArray ("int*[]", 3)
+ }, "locals", num_fields: 26);
+
+ var ipa_elems = await CompareObjectPropertiesFor (locals, "ipa", new [] {
+ TPointer ("int*"),
+ TPointer ("int*"),
+ TPointer ("int*", is_null: true)
+ });
+
+ await CheckArrayElements (ipa_elems, new [] {
+ TNumber (5),
+ TNumber (10),
+ null
+ });
+ });
+
+ [Theory]
+ [MemberDataAttribute (nameof (PointersTestData))]
+ public async Task InspectLocalDoublePointerToPrimitiveTypeArrays (string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo)
+ => await CheckInspectLocalsAtBreakpointSite (
+ type, method, line_offset, bp_function_name,
+ "window.setTimeout(function() { " + eval_fn + " })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) => {
+ var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
+
+ var dt = new DateTime (5, 6, 7, 8, 9, 10);
+ await CheckProps (locals, new {
+ ippa = TArray ("int**[]", 5)
+ }, "locals", num_fields: 26);
+
+ var ippa_elems = await CompareObjectPropertiesFor (locals, "ippa", new [] {
+ TPointer ("int**"),
+ TPointer ("int**"),
+ TPointer ("int**"),
+ TPointer ("int**"),
+ TPointer ("int**", is_null: true)
+ });
+
+ {
+ var actual_elems = await CheckArrayElements (ippa_elems, new [] {
+ TPointer ("int*"),
+ TPointer ("int*", is_null: true),
+ TPointer ("int*"),
+ TPointer ("int*", is_null: true),
+ null
+ });
+
+ var val = await GetObjectOnLocals (actual_elems [0], "*[0]");
+ await CheckPointerValue (val, "**[0]", TNumber (5));
+
+ val = await GetObjectOnLocals (actual_elems [2], "*[2]");
+ await CheckPointerValue (val, "**[2]", TNumber (5));
+ }
+ });
+
+ [Theory]
+ [MemberDataAttribute (nameof (PointersTestData))]
+ public async Task InspectLocalPointersToValueTypes (string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo)
+ => await CheckInspectLocalsAtBreakpointSite (
+ type, method, line_offset, bp_function_name,
+ "window.setTimeout(function() { " + eval_fn + " })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) => {
+ var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
+
+ var dt = new DateTime (5, 6, 7, 8, 9, 10);
+ await CheckProps (locals, new {
+ dt = TValueType ("System.DateTime", dt.ToString ()),
+ dtp = TPointer ("System.DateTime*"),
+ dtp_null = TPointer ("System.DateTime*", is_null: true),
+
+ gsp = TPointer ("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*"),
+ gsp_null = TPointer ("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*")
+ }, "locals", num_fields: 26);
+
+ await CheckDateTime (locals, "dt", dt);
+
+ // *dtp
+ var props = await GetObjectOnLocals (locals, "dtp");
+ await CheckDateTime (props, "*dtp", dt);
+
+ var gsp_props = await GetObjectOnLocals (locals, "gsp");
+ await CheckPointerValue (gsp_props, "*gsp", TValueType ("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>"), "locals#gsp");
+
+ {
+ var gs_dt = new DateTime (1, 2, 3, 4, 5, 6);
+
+ var gsp_deref_props = await GetObjectOnLocals (gsp_props, "*gsp");
+ await CheckProps (gsp_deref_props, new {
+ Value = TValueType ("System.DateTime", gs_dt.ToString ()),
+ IntField = TNumber (4),
+ DTPP = TPointer ("System.DateTime**")
+ }, "locals#gsp#deref");
+ {
+ var dtpp_props = await GetObjectOnLocals (gsp_deref_props, "DTPP");
+ await CheckPointerValue (dtpp_props, "*DTPP", TPointer ("System.DateTime*"), "locals#*gsp");
+
+ var dtpp_deref_props = await GetObjectOnLocals (dtpp_props, "*DTPP");
+ await CheckDateTime (dtpp_deref_props, "**DTPP", dt);
+ }
+ }
+
+ // gsp_null
+ var gsp_w_n_props = await GetObjectOnLocals (locals, "gsp_null");
+ await CheckPointerValue (gsp_w_n_props, "*gsp_null", TValueType ("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>"), "locals#gsp");
+
+ {
+ var gs_dt = new DateTime (1, 2, 3, 4, 5, 6);
+
+ var gsp_deref_props = await GetObjectOnLocals (gsp_w_n_props, "*gsp_null");
+ await CheckProps (gsp_deref_props, new {
+ Value = TValueType ("System.DateTime", gs_dt.ToString ()),
+ IntField = TNumber (4),
+ DTPP = TPointer ("System.DateTime**")
+ }, "locals#gsp#deref");
+ {
+ var dtpp_props = await GetObjectOnLocals (gsp_deref_props, "DTPP");
+ await CheckPointerValue (dtpp_props, "*DTPP", TPointer ("System.DateTime*", is_null: true), "locals#*gsp");
+ }
+ }
+ });
+
+ [Theory]
+ [MemberDataAttribute (nameof (PointersTestData))]
+ public async Task InspectLocalPointersToValueTypeArrays (string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo)
+ => await CheckInspectLocalsAtBreakpointSite (
+ type, method, line_offset, bp_function_name,
+ "window.setTimeout(function() { " + eval_fn + " })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) => {
+ var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
+
+ var dt = new DateTime (5, 6, 7, 8, 9, 10);
+ await CheckProps (locals, new {
+ dtpa = TArray ("System.DateTime*[]", 2)
+ }, "locals", num_fields: 26);
+
+ // dtpa
+ var dtpa_elems = (await CompareObjectPropertiesFor (locals, "dtpa", new [] {
+ TPointer ("System.DateTime*"),
+ TPointer ("System.DateTime*", is_null: true)
+ }));
+ {
+ var actual_elems = await CheckArrayElements (dtpa_elems, new [] {
+ TValueType ("System.DateTime", dt.ToString ()),
+ null
+ });
+
+ await CheckDateTime (actual_elems [0], "*[0]", dt);
+ }
+ });
+
+ [Theory]
+ [MemberDataAttribute (nameof (PointersTestData))]
+ public async Task InspectLocalPointersToGenericValueTypeArrays (string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo)
+ => await CheckInspectLocalsAtBreakpointSite (
+ type, method, line_offset, bp_function_name,
+ "window.setTimeout(function() { " + eval_fn + " })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) => {
+ var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
+
+ var dt = new DateTime (5, 6, 7, 8, 9, 10);
+ await CheckProps (locals, new {
+ gspa = TArray ("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*[]", 3),
+ }, "locals", num_fields: 26);
+
+ // dtpa
+ var gspa_elems = await CompareObjectPropertiesFor (locals, "gspa", new [] {
+ TPointer ("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*", is_null: true),
+ TPointer ("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*"),
+ TPointer ("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*"),
+ });
+ {
+ var gs_dt = new DateTime (1, 2, 3, 4, 5, 6);
+ var actual_elems = await CheckArrayElements (gspa_elems, new [] {
+ null,
+ TValueType ("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>"),
+ TValueType ("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>")
+ });
+
+ // *[1]
+ {
+ var gsp_deref_props = await GetObjectOnLocals (actual_elems [1], "*[1]");
+ await CheckProps (gsp_deref_props, new {
+ Value = TValueType ("System.DateTime", gs_dt.ToString ()),
+ IntField = TNumber (4),
+ DTPP = TPointer ("System.DateTime**")
+ }, "locals#gsp#deref");
+ {
+ var dtpp_props = await GetObjectOnLocals (gsp_deref_props, "DTPP");
+ await CheckPointerValue (dtpp_props, "*DTPP", TPointer ("System.DateTime*"), "locals#*gsp");
+
+ dtpp_props = await GetObjectOnLocals (dtpp_props, "*DTPP");
+ await CheckDateTime (dtpp_props, "**DTPP", dt);
+ }
+ }
+
+ // *[2]
+ {
+ var gsp_deref_props = await GetObjectOnLocals (actual_elems [2], "*[2]");
+ await CheckProps (gsp_deref_props, new {
+ Value = TValueType ("System.DateTime", gs_dt.ToString ()),
+ IntField = TNumber (4),
+ DTPP = TPointer ("System.DateTime**")
+ }, "locals#gsp#deref");
+ {
+ var dtpp_props = await GetObjectOnLocals (gsp_deref_props, "DTPP");
+ await CheckPointerValue (dtpp_props, "*DTPP", TPointer ("System.DateTime*", is_null: true), "locals#*gsp");
+ }
+ }
+ }
+ });
+
+ [Theory]
+ [MemberDataAttribute (nameof (PointersTestData))]
+ public async Task InspectLocalDoublePointersToValueTypeArrays (string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo)
+ => await CheckInspectLocalsAtBreakpointSite (
+ type, method, line_offset, bp_function_name,
+ "window.setTimeout(function() { " + eval_fn + " })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) => {
+ var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
+
+ var dt = new DateTime (5, 6, 7, 8, 9, 10);
+ await CheckProps (locals, new {
+ dtppa = TArray ("System.DateTime**[]", 3),
+ }, "locals", num_fields: 26);
+
+ // DateTime**[] dtppa = new DateTime**[] { &dtp, &dtp_null, null };
+ var dtppa_elems = (await CompareObjectPropertiesFor (locals, "dtppa", new [] {
+ TPointer ("System.DateTime**"),
+ TPointer ("System.DateTime**"),
+ TPointer ("System.DateTime**", is_null: true)
+ }));
+
+ var exp_elems = new [] {
+ TPointer ("System.DateTime*"),
+ TPointer ("System.DateTime*", is_null: true),
+ null
+ };
+
+ var actual_elems = new JToken [exp_elems.Length];
+ for (int i = 0; i < exp_elems.Length; i ++) {
+ if (exp_elems [i] != null) {
+ actual_elems [i] = await GetObjectOnLocals (dtppa_elems, i.ToString ());
+ await CheckPointerValue (actual_elems [i], $"*[{i}]", exp_elems [i], $"dtppa->");
+ }
+ }
+ });
+
+ [Theory]
+ [MemberDataAttribute (nameof (PointersTestData))]
+ public async Task InspectLocalPointersInClasses (string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo)
+ => await CheckInspectLocalsAtBreakpointSite (
+ type, method, line_offset, bp_function_name,
+ "window.setTimeout(function() { " + eval_fn + " })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) => {
+ var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
+
+ var dt = new DateTime (5, 6, 7, 8, 9, 10);
+ await CheckProps (locals, new {
+ cwp = TObject ("DebuggerTests.GenericClassWithPointers<System.DateTime>"),
+ cwp_null = TObject ("DebuggerTests.GenericClassWithPointers<System.DateTime>")
+ }, "locals", num_fields: 26);
+
+ var cwp_props = await GetObjectOnLocals (locals, "cwp");
+ var ptr_props = await GetObjectOnLocals (cwp_props, "Ptr");
+ await CheckDateTime (ptr_props, "*Ptr", dt);
+ });
+
+ public static TheoryData<string, string, string, int, string, bool> PointersAsMethodArgsTestData =>
+ new TheoryData<string, string, string, int, string, bool> {
+ {$"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "PointersAsArgsTest", 2, "PointersAsArgsTest", false},
+ {$"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "PointersAsArgsTest", 2, "PointersAsArgsTest", true},
+ };
+
+ [Theory]
+ [MemberDataAttribute (nameof (PointersAsMethodArgsTestData))]
+ public async Task InspectPrimitiveTypePointersAsMethodArgs (string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo)
+ => await CheckInspectLocalsAtBreakpointSite (
+ type, method, line_offset, bp_function_name,
+ "window.setTimeout(function() { " + eval_fn + " })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) => {
+ var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
+
+ var dt = new DateTime (5, 6, 7, 8, 9, 10);
+ await CheckProps (locals, new {
+ ip = TPointer ("int*"),
+ ipp = TPointer ("int**"),
+ ipa = TArray ("int*[]", 3),
+ ippa = TArray ("int**[]", 5)
+ }, "locals", num_fields: 8);
+
+ // ip
+ var props = await GetObjectOnLocals (locals, "ip");
+ await CheckPointerValue (props, "*ip", TNumber (5), "locals");
+
+ // ipp
+ var ipp_props = await GetObjectOnLocals (locals, "ipp");
+ await CheckPointerValue (ipp_props, "*ipp", TPointer ("int*"));
+
+ ipp_props = await GetObjectOnLocals (ipp_props, "*ipp");
+ await CheckPointerValue (ipp_props, "**ipp", TNumber (5));
+
+ // ipa
+ var ipa_elems = await CompareObjectPropertiesFor (locals, "ipa", new [] {
+ TPointer ("int*"),
+ TPointer ("int*"),
+ TPointer ("int*", is_null: true)
+ });
+
+ await CheckArrayElements (ipa_elems, new [] {
+ TNumber (5),
+ TNumber (10),
+ null
+ });
+
+ // ippa
+ var ippa_elems = await CompareObjectPropertiesFor (locals, "ippa", new [] {
+ TPointer ("int**"),
+ TPointer ("int**"),
+ TPointer ("int**"),
+ TPointer ("int**"),
+ TPointer ("int**", is_null: true)
+ });
+
+ {
+ var actual_elems = await CheckArrayElements (ippa_elems, new [] {
+ TPointer ("int*"),
+ TPointer ("int*", is_null: true),
+ TPointer ("int*"),
+ TPointer ("int*", is_null: true),
+ null
+ });
+
+ var val = await GetObjectOnLocals (actual_elems [0], "*[0]");
+ await CheckPointerValue (val, "**[0]", TNumber (5));
+
+ val = await GetObjectOnLocals (actual_elems [2], "*[2]");
+ await CheckPointerValue (val, "**[2]", TNumber (5));
+ }
+ });
+
+ [Theory]
+ [MemberDataAttribute (nameof (PointersAsMethodArgsTestData))]
+ public async Task InspectValueTypePointersAsMethodArgs (string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo)
+ => await CheckInspectLocalsAtBreakpointSite (
+ type, method, line_offset, bp_function_name,
+ "window.setTimeout(function() { " + eval_fn + " })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) => {
+ var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
+
+ var dt = new DateTime (5, 6, 7, 8, 9, 10);
+ await CheckProps (locals, new {
+ dtp = TPointer ("System.DateTime*"),
+ dtpp = TPointer ("System.DateTime**"),
+ dtpa = TArray ("System.DateTime*[]", 2),
+ dtppa = TArray ("System.DateTime**[]", 3)
+ }, "locals", num_fields: 8);
+
+ // *dtp
+ var dtp_props = await GetObjectOnLocals (locals, "dtp");
+ await CheckDateTime (dtp_props, "*dtp", dt);
+
+ // *dtpp
+ var dtpp_props = await GetObjectOnLocals (locals, "dtpp");
+ await CheckPointerValue (dtpp_props, "*dtpp", TPointer ("System.DateTime*"), "locals");
+
+ dtpp_props = await GetObjectOnLocals (dtpp_props, "*dtpp");
+ await CheckDateTime (dtpp_props, "**dtpp", dt);
+
+ // dtpa
+ var dtpa_elems = (await CompareObjectPropertiesFor (locals, "dtpa", new [] {
+ TPointer ("System.DateTime*"),
+ TPointer ("System.DateTime*", is_null: true)
+ }));
+ {
+ var actual_elems = await CheckArrayElements (dtpa_elems, new [] {
+ TValueType ("System.DateTime", dt.ToString ()),
+ null
+ });
+
+ await CheckDateTime (actual_elems [0], "*[0]", dt);
+ }
+
+ // dtppa = new DateTime**[] { &dtp, &dtp_null, null };
+ var dtppa_elems = (await CompareObjectPropertiesFor (locals, "dtppa", new [] {
+ TPointer ("System.DateTime**"),
+ TPointer ("System.DateTime**"),
+ TPointer ("System.DateTime**", is_null: true)
+ }));
+
+ var exp_elems = new [] {
+ TPointer ("System.DateTime*"),
+ TPointer ("System.DateTime*", is_null: true),
+ null
+ };
+
+ await CheckArrayElements (dtppa_elems, exp_elems);
+ });
+
+ [Theory]
+ [InlineData ("invoke_static_method ('[debugger-test] Math:UseComplex', 0, 0);", "Math", "UseComplex", 3, "UseComplex", false)]
+ [InlineData ("invoke_static_method ('[debugger-test] Math:UseComplex', 0, 0);", "Math", "UseComplex", 3, "UseComplex", true)]
+ public async Task DerefNonPointerObject (string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo)
+ => await CheckInspectLocalsAtBreakpointSite (
+ type, method, line_offset, bp_function_name,
+ "window.setTimeout(function() { " + eval_fn + " })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) => {
+
+ // this will generate the object ids
+ var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
+ var complex = GetAndAssertObjectWithName (locals, "complex");
+
+ // try to deref the non-pointer object, as a pointer
+ var props = await GetProperties (complex ["value"]["objectId"].Value<string> ().Replace (":object:", ":pointer:"));
+ Assert.Empty (props.Values ());
+
+ // try to deref an invalid pointer id
+ props = await GetProperties ("dotnet:pointer:123897");
+ Assert.Empty (props.Values ());
+ });
+
+ async Task<JToken[]> CheckArrayElements (JToken array, JToken[] exp_elems)
+ {
+ var actual_elems = new JToken [exp_elems.Length];
+ for (int i = 0; i < exp_elems.Length; i ++) {
+ if (exp_elems [i] != null) {
+ actual_elems [i] = await GetObjectOnLocals (array, i.ToString ());
+ await CheckPointerValue (actual_elems [i], $"*[{i}]", exp_elems [i], $"dtppa->");
+ }
+ }
+
+ return actual_elems;
+ }
+ }
+} \ No newline at end of file
diff --git a/sdks/wasm/DebuggerTestSuite/Support.cs b/sdks/wasm/DebuggerTestSuite/Support.cs
index 56db77df516..efa16109087 100644
--- a/sdks/wasm/DebuggerTestSuite/Support.cs
+++ b/sdks/wasm/DebuggerTestSuite/Support.cs
@@ -214,6 +214,50 @@ namespace DebuggerTests
});
}
+ // sets breakpoint by method name and line offset
+ internal async Task CheckInspectLocalsAtBreakpointSite (string type, string method, int line_offset, string bp_function_name, string eval_expression,
+ Action<JToken> locals_fn = null, Func<JObject, Task> wait_for_event_fn = null, bool use_cfo = false, string assembly="debugger-test.dll", int col = 0)
+ {
+ var insp = new Inspector ();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready ();
+ await insp.Ready (async (cli, token) => {
+ ctx = new DebugTestContext (cli, insp, token, scripts);
+ ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
+
+ var bp = await SetBreakpointInMethod (assembly, type, method, line_offset, col);
+
+ var args = JObject.FromObject (new { expression = eval_expression });
+ var res = await ctx.cli.SendCommand ("Runtime.evaluate", args, ctx.token);
+ if (!res.IsOk) {
+ Console.WriteLine ($"Failed to run command {method} with args: {args?.ToString ()}\nresult: {res.Error.ToString ()}");
+ Assert.True (false, $"SendCommand for {method} failed with {res.Error.ToString ()}");
+ }
+
+ var pause_location = await ctx.insp.WaitFor (Inspector.PAUSE);
+
+ if (bp_function_name != null)
+ Assert.Equal (bp_function_name, pause_location ["callFrames"]?[0]?["functionName"]?.Value<string> ());
+
+ Assert.Equal (bp.Value ["breakpointId"]?.ToString (), pause_location ["hitBreakpoints"]?[0]?.Value<string> ());
+
+ var top_frame = pause_location ["callFrames"][0];
+
+ var scope = top_frame ["scopeChain"][0];
+ Assert.Equal ("dotnet:scope:0", scope ["object"]["objectId"]);
+
+ if (wait_for_event_fn != null)
+ await wait_for_event_fn (pause_location);
+
+ if (locals_fn != null) {
+ var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string> ());
+ locals_fn (locals);
+ }
+ });
+ }
+
internal void CheckLocation (string script_loc, int line, int column, Dictionary<string, string> scripts, JToken location)
{
var loc_str = $"{ scripts[location["scriptId"].Value<string>()] }"
@@ -277,6 +321,13 @@ namespace DebuggerTests
return l;
}
+ internal async Task<JToken> CheckPointerValue (JToken locals, string name, JToken expected, string label = null)
+ {
+ var l = GetAndAssertObjectWithName (locals, name);
+ await CheckValue (l ["value"], expected, $"{label ?? String.Empty}-{name}");
+ return l;
+ }
+
internal async Task CheckDateTime (JToken locals, string name, DateTime expected)
{
var obj = GetAndAssertObjectWithName(locals, name);
@@ -486,9 +537,8 @@ namespace DebuggerTests
}
}
- internal async Task CheckCustomType (JToken actual, JToken exp_val, string label)
+ internal async Task CheckCustomType (JToken actual_val, JToken exp_val, string label)
{
- var actual_val = actual ["value"];
var ctype = exp_val["__custom_type"].Value<string>();
switch (ctype) {
case "delegate":
@@ -496,23 +546,35 @@ namespace DebuggerTests
break;
case "pointer": {
- AssertEqual ("symbol", actual_val ["type"]?.Value<string>(), $"{label}-type");
- if (exp_val ["is_null"]?.Value<bool>() == false) {
+ if (exp_val ["is_null"]?.Value<bool>() == true) {
+ AssertEqual ("symbol", actual_val ["type"]?.Value<string>(), $"{label}-type");
+
+ var exp_val_str = $"({exp_val ["type_name"]?.Value<string>()}) 0";
+ AssertEqual (exp_val_str, actual_val ["value"]?.Value<string> (), $"{label}-value");
+ AssertEqual (exp_val_str, actual_val ["description"]?.Value<string> (), $"{label}-description");
+ } else if (exp_val ["is_void"]?.Value<bool> () == true) {
+ AssertEqual ("symbol", actual_val ["type"]?.Value<string>(), $"{label}-type");
+
+ var exp_val_str = $"({exp_val ["type_name"]?.Value<string>()})";
+ AssertStartsWith (exp_val_str, actual_val ["value"]?.Value<string> (), $"{label}-value");
+ AssertStartsWith (exp_val_str, actual_val ["description"]?.Value<string> (), $"{label}-description");
+ } else {
+ AssertEqual ("object", actual_val ["type"]?.Value<string>(), $"{label}-type");
+
var exp_prefix = $"({exp_val ["type_name"]?.Value<string>()})";
- AssertStartsWith (exp_prefix, actual_val ["value"]?.Value<string> (), $"{label}-value");
+ AssertStartsWith (exp_prefix, actual_val ["className"]?.Value<string> (), $"{label}-className");
AssertStartsWith (exp_prefix, actual_val ["description"]?.Value<string> (), $"{label}-description");
- } else {
- var exp_prefix = $"({exp_val ["type_name"]?.Value<string>()}) 0";
- AssertEqual (exp_prefix, actual_val ["value"]?.Value<string> (), $"{label}-value");
- AssertEqual (exp_prefix, actual_val ["description"]?.Value<string> (), $"{label}-description");
+ Assert.False (actual_val ["className"]?.Value<string> () == $"{exp_prefix} 0", $"[{label}] Expected a non-null value, but got {actual_val}");
}
break;
}
case "getter": {
- var get = actual ["get"];
- Assert.True (get != null, $"[{label}] No `get` found");
+ // For getter, `actual_val` is not `.value`, instead it's the container object
+ // which has a `.get` instead of a `.value`
+ var get = actual_val ["get"];
+ Assert.True (get != null, $"[{label}] No `get` found. {(actual_val != null ? "Make sure to pass the container object for testing getters, and not the ['value']": String.Empty)}");
AssertEqual ("Function", get ["className"]?.Value<string> (), $"{label}-className");
AssertStartsWith ($"get {exp_val ["type_name"]?.Value<string> ()} ()", get ["description"]?.Value<string> (), $"{label}-description");
@@ -546,12 +608,8 @@ namespace DebuggerTests
var act_i = actual_arr [i];
AssertEqual (i.ToString (), act_i ["name"]?.Value<string> (), $"{label}-[{i}].name");
-
- if (exp_i ["__custom_type"] != null) {
- await CheckCustomType (act_i, exp_i, $"{label}-{i}th value");
- } else {
+ if (exp_i != null)
await CheckValue (act_i["value"], exp_i, $"{label}-{i}th value");
- }
}
return;
@@ -580,7 +638,8 @@ namespace DebuggerTests
if (exp_val.Type == JTokenType.Array) {
var actual_props = await GetProperties(actual_val["objectId"]?.Value<string>());
await CheckProps (actual_props, exp_val, $"{label}-{exp_name}");
- } else if (exp_val ["__custom_type"] != null) {
+ } else if (exp_val ["__custom_type"] != null && exp_val ["__custom_type"]?.Value<string> () == "getter") {
+ // hack: for getters, actual won't have a .value
await CheckCustomType (actual_obj, exp_val, $"{label}#{exp_name}");
} else {
await CheckValue (actual_val, exp_val, $"{label}#{exp_name}");
@@ -820,7 +879,7 @@ namespace DebuggerTests
});
internal static JObject TPointer (string type_name, bool is_null = false)
- => JObject.FromObject (new { __custom_type = "pointer", type_name = type_name, is_null = is_null });
+ => JObject.FromObject (new { __custom_type = "pointer", type_name = type_name, is_null = is_null, is_void = type_name.StartsWith ("void*") });
internal static JObject TIgnore ()
=> JObject.FromObject (new { __custom_type = "ignore_me" });
diff --git a/sdks/wasm/DebuggerTestSuite/Tests.cs b/sdks/wasm/DebuggerTestSuite/Tests.cs
index fdd97c017db..4ded1e46b6f 100644
--- a/sdks/wasm/DebuggerTestSuite/Tests.cs
+++ b/sdks/wasm/DebuggerTestSuite/Tests.cs
@@ -1311,38 +1311,6 @@ namespace DebuggerTests
[Theory]
[InlineData (false)]
[InlineData (true)]
- public async Task InspectLocalsWithPointers (bool use_cfo)
- => await CheckInspectLocalsAtBreakpointSite (
- "dotnet://debugger-test.dll/debugger-test.cs", 294, 2,
- "PointersTest",
- "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] Math:PointersTest'); })",
- use_cfo: use_cfo,
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
-
- var dt = new DateTime (5, 6, 7, 8, 9, 10);
- await CheckProps (locals, new {
- ivalue0 = TNumber (5),
- ivalue1 = TNumber (10),
- ip = TPointer ("int*"),
- ip_null = TPointer ("int*", is_null: true),
- ipp = TPointer ("int**"),
-
- ipa = TArray ("int*[]", 3),
- cvalue0 = TSymbol ("113 'q'"),
- cp = TPointer ("char*"),
- dt = TValueType ("System.DateTime", dt.ToString ()),
- vp = TPointer ("void*"),
- vp_null = TPointer ("void*", is_null: true),
- dtp = TPointer ("System.DateTime*"),
- dtp_null = TPointer ("System.DateTime*", is_null: true)
- }, "locals");
-
- });
-
- [Theory]
- [InlineData (false)]
- [InlineData (true)]
public async Task InspectLocalsForStructInstanceMethod (bool use_cfo)
=> await CheckInspectLocalsAtBreakpointSite (
"dotnet://debugger-test.dll/debugger-array-test.cs", 236, 3,
@@ -1376,8 +1344,6 @@ namespace DebuggerTests
label: "this#0");
});
- //
//TODO add tests covering basic stepping behavior as step in/out/over
}
-
}
diff --git a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/DebugStore.cs b/sdks/wasm/Mono.WebAssembly.DebuggerProxy/DebugStore.cs
index be349392ab6..f633fe786d5 100644
--- a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/DebugStore.cs
+++ b/sdks/wasm/Mono.WebAssembly.DebuggerProxy/DebugStore.cs
@@ -502,6 +502,7 @@ namespace WebAssembly.Net.Debugging {
public IEnumerable<SourceFile> Sources
=> this.sources;
+ public Dictionary<string, TypeInfo> TypesByName => this.typesByName;
public int Id => id;
public string Name => image.Name;
diff --git a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/DevToolsHelper.cs b/sdks/wasm/Mono.WebAssembly.DebuggerProxy/DevToolsHelper.cs
index 08cebf1b1b7..a615540e450 100644
--- a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/DevToolsHelper.cs
+++ b/sdks/wasm/Mono.WebAssembly.DebuggerProxy/DevToolsHelper.cs
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
+using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Threading;
@@ -190,8 +191,11 @@ namespace WebAssembly.Net.Debugging {
public static MonoCommands GetDetails (DotnetObjectId objectId, JToken args = null)
=> new MonoCommands ($"MONO.mono_wasm_get_details ('{objectId}', {(args ?? "{}")})");
- public static MonoCommands GetScopeVariables (int scopeId, params int[] vars)
- => new MonoCommands ($"MONO.mono_wasm_get_variables({scopeId}, [ {string.Join (",", vars)} ])");
+ public static MonoCommands GetScopeVariables (int scopeId, params VarInfo[] vars)
+ {
+ var var_ids = vars.Select (v => new { index = v.Index, name = v.Name }).ToArray ();
+ return new MonoCommands ($"MONO.mono_wasm_get_variables({scopeId}, {JsonConvert.SerializeObject (var_ids)})");
+ }
public static MonoCommands SetBreakpoint (string assemblyName, uint methodToken, int ilOffset)
=> new MonoCommands ($"MONO.mono_wasm_set_breakpoint (\"{assemblyName}\", {methodToken}, {ilOffset})");
diff --git a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/MonoProxy.cs b/sdks/wasm/Mono.WebAssembly.DebuggerProxy/MonoProxy.cs
index e0e6c3e528f..2637053db11 100644
--- a/sdks/wasm/Mono.WebAssembly.DebuggerProxy/MonoProxy.cs
+++ b/sdks/wasm/Mono.WebAssembly.DebuggerProxy/MonoProxy.cs
@@ -319,6 +319,14 @@ namespace WebAssembly.Net.Debugging {
var methodInfo = type.Methods.FirstOrDefault (m => m.Name == methodName);
if (methodInfo == null) {
+ // Maybe this is an async method, in which case the debug info is attached
+ // to the async method implementation, in class named:
+ // `{type_name}/<method_name>::MoveNext`
+ methodInfo = assembly.TypesByName.Values.SingleOrDefault (t => t.FullName.StartsWith ($"{typeName}/<{methodName}>"))
+ ?.Methods.FirstOrDefault (mi => mi.Name == "MoveNext");
+ }
+
+ if (methodInfo == null) {
SendResponse (id, Result.Err ($"Method '{typeName}:{methodName}' not found."), token);
return true;
}
@@ -372,7 +380,7 @@ namespace WebAssembly.Net.Debugging {
var value_json_str = res.Value ["result"]?["value"]?["__value_as_json_string__"]?.Value<string> ();
if (value_json_str != null) {
res = Result.OkFromObject (new {
- result = JArray.Parse (value_json_str.Replace (@"\""", "\""))
+ result = JArray.Parse (value_json_str)
});
} else {
res = Result.OkFromObject (new { result = new {} });
@@ -582,7 +590,7 @@ namespace WebAssembly.Net.Debugging {
var scope = context.CallStack.FirstOrDefault (s => s.Id == scope_id);
var live_vars = scope.Method.GetLiveVarsAt (scope.Location.CliLocation.Offset);
//get_this
- var res = await SendMonoCommand (msg_id, MonoCommands.GetScopeVariables (scope.Id, live_vars.Select (lv => lv.Index).ToArray ()), token);
+ var res = await SendMonoCommand (msg_id, MonoCommands.GetScopeVariables (scope.Id, live_vars), token);
var scope_values = res.Value? ["result"]? ["value"]?.Values<JObject> ()?.ToArray ();
thisValue = scope_values?.FirstOrDefault (v => v ["name"]?.Value<string> () == "this");
@@ -650,9 +658,7 @@ namespace WebAssembly.Net.Debugging {
if (scope == null)
return Result.Err (JObject.FromObject (new { message = $"Could not find scope with id #{scope_id}" }));
- var vars = scope.Method.GetLiveVarsAt (scope.Location.CliLocation.Offset);
-
- var var_ids = vars.Select (v => v.Index).ToArray ();
+ var var_ids = scope.Method.GetLiveVarsAt (scope.Location.CliLocation.Offset);
var res = await SendMonoCommand (msg_id, MonoCommands.GetScopeVariables (scope.Id, var_ids), token);
//if we fail we just buble that to the IDE (and let it panic over it)
@@ -661,27 +667,13 @@ namespace WebAssembly.Net.Debugging {
var values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray ();
- if(values == null)
+ if(values == null || values.Length == 0)
return Result.OkFromObject (new { result = Array.Empty<object> () });
- var var_list = new List<object> ();
- int i = 0;
- for (; i < vars.Length && i < values.Length; i ++) {
- // For async methods, we get locals with names, unlike non-async methods
- // and the order may not match the var_ids, so, use the names that they
- // come with
- if (values [i]["name"] != null)
- continue;
-
- ctx.LocalsCache[vars [i].Name] = values [i];
- var_list.Add (new { name = vars [i].Name, value = values [i]["value"] });
- }
- for (; i < values.Length; i ++) {
- ctx.LocalsCache[values [i]["name"].ToString()] = values [i];
- var_list.Add (values [i]);
- }
+ foreach (var value in values)
+ ctx.LocalsCache [value ["name"]?.Value<string> ()] = value;
- return Result.OkFromObject (new { result = var_list });
+ return Result.OkFromObject (new { result = values });
} catch (Exception exception) {
Log ("verbose", $"Error resolving scope properties {exception.Message}");
return Result.Exception (exception);
diff --git a/sdks/wasm/src/library_mono.js b/sdks/wasm/src/library_mono.js
index 92960f45479..6569fd985d0 100644
--- a/sdks/wasm/src/library_mono.js
+++ b/sdks/wasm/src/library_mono.js
@@ -141,6 +141,27 @@ var MonoSupportLib = {
return final_var_list;
},
+ // code from https://stackoverflow.com/a/5582308
+ //
+ // Given `dotnet:object:foo:bar`,
+ // returns [ 'dotnet', 'object', 'foo:bar']
+ _split_object_id: function (id, delim = ':', count = 3) {
+ if (id === undefined || id == "")
+ return [];
+
+ if (delim === undefined) delim = ':';
+ if (count === undefined) count = 3;
+
+ var arr = id.split (delim);
+ var result = arr.splice (0, count - 1);
+
+ if (arr.length > 0)
+ result.push (arr.join (delim));
+ return result;
+ },
+
+ //
+ // @var_list: [ { index: <var_id>, name: <var_name> }, .. ]
mono_wasm_get_variables: function(scope, var_list) {
if (!this.mono_wasm_get_var_info)
this.mono_wasm_get_var_info = Module.cwrap ("mono_wasm_get_var_info", null, [ 'number', 'number', 'number']);
@@ -150,7 +171,7 @@ var MonoSupportLib = {
var ptr = Module._malloc(numBytes);
var heapBytes = new Int32Array(Module.HEAP32.buffer, ptr, numBytes);
for (let i=0; i<var_list.length; i++) {
- heapBytes[i] = var_list[i]
+ heapBytes[i] = var_list[i].index;
}
this._async_method_objectId = 0;
@@ -158,18 +179,25 @@ var MonoSupportLib = {
Module._free(heapBytes.byteOffset);
var res = MONO._fixup_name_value_objects (this.var_info);
- //Async methods are special in the way that local variables can be lifted to generated class fields
- //value of "this" comes here either
for (let i in res) {
- var name = res [i].name;
- if (name != undefined && name.indexOf ('>') > 0)
- res [i].name = name.substring (1, name.indexOf ('>'));
- }
+ var res_name = res [i].name;
+
+ var value = res[i].value;
+ if (this._async_method_objectId != 0) {
+ //Async methods are special in the way that local variables can be lifted to generated class fields
+ //value of "this" comes here either
+ if (res_name !== undefined && res_name.indexOf ('>') > 0) {
+ // For async methods, we get the names too, so use that
+ // ALTHOUGH, the name wouldn't have `<>` for method args
+ res [i].name = res_name.substring (1, res_name.indexOf ('>'));
+ }
- if (this._async_method_objectId != 0) {
- for (let i in res) {
- if (res [i].value.isValueType != undefined && res [i].value.isValueType)
- res [i].value.objectId = `dotnet:valuetype:${this._async_method_objectId}:${res [i].fieldOffset}`;
+ if (value.isValueType)
+ value.objectId = `dotnet:valuetype:${this._async_method_objectId}:${res [i].fieldOffset}`;
+ } else if (res_name === undefined && var_list [i] !== undefined) {
+ // For non-async methods, we just have the var id, but we have the name
+ // from the caller
+ res [i].name = var_list [i].name;
}
}
@@ -208,8 +236,14 @@ var MonoSupportLib = {
var res = MONO._fixup_name_value_objects (this.var_info);
for (var i = 0; i < res.length; i++) {
- if (res [i].value.isValueType != undefined && res [i].value.isValueType)
+ var prop_value = res [i].value;
+ if (prop_value.isValueType) {
res [i].value.objectId = `dotnet:array:${objId}:${i}`;
+ } else if (prop_value.objectId !== undefined && prop_value.objectId.startsWith("dotnet:pointer")) {
+ prop_value.objectId = this._get_updated_ptr_id (prop_value.objectId, {
+ varName: `[${i}]`
+ });
+ }
}
this.var_info = [];
@@ -254,12 +288,26 @@ var MonoSupportLib = {
for (let i in var_list) {
var value = var_list [i].value;
- if (value == undefined || value.type != "object")
+ if (value === undefined)
continue;
- if (value.isValueType != true || value.expanded != true) // undefined would also give us false
+ if (value.objectId !== undefined && value.objectId.startsWith ("dotnet:pointer:")) {
+ var ptr_args = this._get_ptr_args (value.objectId);
+ if (ptr_args.varName === undefined) {
+ // It might have been already set in some cases, like arrays
+ // where the name would be `0`, but we want `[0]` for pointers,
+ // so the deref would look like `*[0]`
+ value.objectId = this._get_updated_ptr_id (value.objectId, {
+ varName: var_list [i].name
+ });
+ }
+ }
+
+ if (value.type != "object" || value.isValueType != true || value.expanded != true) // undefined would also give us false
continue;
+ // Generate objectId for expanded valuetypes
+
var objectId = value.objectId;
if (objectId == undefined)
objectId = `dotnet:valuetype:${this._next_value_type_id ()}`;
@@ -366,6 +414,46 @@ var MonoSupportLib = {
return { __value_as_json_string__: JSON.stringify (res_details) };
},
+ _get_ptr_args: function (objectId) {
+ var parts = this._split_object_id (objectId);
+ if (parts.length != 3)
+ throw new Error (`Bug: Unexpected objectId format for a pointer, expected 3 parts: ${objectId}`);
+ return JSON.parse (parts [2]);
+ },
+
+ _get_updated_ptr_id: function (objectId, new_args) {
+ var old_args = {};
+ if (typeof (objectId) === 'string' && objectId.length)
+ old_args = this._get_ptr_args (objectId);
+
+ return `dotnet:pointer:${JSON.stringify ( Object.assign (old_args, new_args) )}`;
+ },
+
+ _get_deref_ptr_value: function (objectId) {
+ if (!this.mono_wasm_get_deref_ptr_value_info)
+ this.mono_wasm_get_deref_ptr_value_info = Module.cwrap("mono_wasm_get_deref_ptr_value", null, ['number', 'number']);
+
+ var ptr_args = this._get_ptr_args (objectId);
+ if (ptr_args.ptr_addr == 0 || ptr_args.klass_addr == 0)
+ throw new Error (`Both ptr_addr and klass_addr need to be non-zero, to dereference a pointer. objectId: ${objectId}`);
+
+ this.var_info = [];
+ var value_addr = new DataView (Module.HEAPU8.buffer).getUint32 (ptr_args.ptr_addr, /* littleEndian */ true);
+ this.mono_wasm_get_deref_ptr_value_info (value_addr, ptr_args.klass_addr);
+
+ var res = MONO._fixup_name_value_objects(this.var_info);
+ if (res.length > 0) {
+ if (ptr_args.varName === undefined)
+ throw new Error (`Bug: no varName found for the pointer. objectId: ${objectId}`);
+
+ res [0].name = `*${ptr_args.varName}`;
+ }
+
+ res = this._post_process_details (res);
+ this.var_info = [];
+ return res;
+ },
+
mono_wasm_get_details: function (objectId, args) {
var parts = objectId.split(":");
if (parts[0] != "dotnet")
@@ -394,6 +482,10 @@ var MonoSupportLib = {
case "cfo_res":
return this._get_cfo_res_details (objectId, args);
+ case "pointer": {
+ return this._get_deref_ptr_value (objectId);
+ }
+
default:
throw new Error(`Unknown object id format: ${objectId}`);
}
@@ -835,13 +927,26 @@ var MonoSupportLib = {
break;
case "pointer": {
- MONO.var_info.push ({
- value: {
- type: "symbol",
- value: str_value,
- description: str_value
- }
- });
+ var fixed_value_str = MONO._mono_csharp_fixup_class_name (str_value);
+ if (value.klass_addr == 0 || value.ptr_addr == 0 || fixed_value_str.startsWith ('(void*')) {
+ // null or void*, which we can't deref
+ MONO.var_info.push({
+ value: {
+ type: "symbol",
+ value: fixed_value_str,
+ description: fixed_value_str
+ }
+ });
+ } else {
+ MONO.var_info.push({
+ value: {
+ type: "object",
+ className: fixed_value_str,
+ description: fixed_value_str,
+ objectId: this._get_updated_ptr_id ('', value)
+ }
+ });
+ }
}
break;
diff --git a/sdks/wasm/tests/debugger/debugger-pointers-test.cs b/sdks/wasm/tests/debugger/debugger-pointers-test.cs
new file mode 100644
index 00000000000..6cca499566e
--- /dev/null
+++ b/sdks/wasm/tests/debugger/debugger-pointers-test.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Threading.Tasks;
+
+namespace DebuggerTests
+{
+ public class PointerTests
+ {
+
+ public static unsafe void LocalPointers ()
+ {
+ int ivalue0 = 5;
+ int ivalue1 = 10;
+
+ int* ip = &ivalue0;
+ int* ip_null = null;
+ int** ipp = &ip;
+ int** ipp_null = &ip_null;
+ int*[] ipa = new int*[] { &ivalue0, &ivalue1, null };
+ int**[] ippa = new int**[] { &ip, &ip_null, ipp, ipp_null, null };
+ char cvalue0 = 'q';
+ char* cp = &cvalue0;
+
+ DateTime dt = new DateTime(5, 6, 7, 8, 9, 10);
+ void* vp = &dt;
+ void* vp_null = null;
+ void** vpp = &vp;
+ void** vpp_null = &vp_null;
+
+ DateTime* dtp = &dt;
+ DateTime* dtp_null = null;
+ DateTime*[] dtpa = new DateTime*[] { dtp, dtp_null };
+ DateTime**[] dtppa = new DateTime**[] { &dtp, &dtp_null, null };
+ Console.WriteLine($"-- break here: ip_null==null: {ip_null == null}, ipp_null: {ipp_null == null}, *ipp_null==ip_null: {*ipp_null == ip_null}, *ipp_null==null: {*ipp_null == null}");
+
+ var gs = new GenericStructWithUnmanagedT<DateTime> { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp };
+ var gs_null = new GenericStructWithUnmanagedT<DateTime> { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp_null };
+ var gsp = &gs;
+ var gsp_null = &gs_null;
+ var gspa = new GenericStructWithUnmanagedT<DateTime>*[] { null, gsp, gsp_null };
+
+ var cwp = new GenericClassWithPointers<DateTime> { Ptr = dtp };
+ var cwp_null = new GenericClassWithPointers<DateTime>();
+ Console.WriteLine($"{(int)*ip}, {(int)**ipp}, {ipp_null == null}, {ip_null == null}, {ippa == null}, {ipa}, {(char)*cp}, {(vp == null ? "null" : "not null")}, {dtp->Second}, {gsp->IntField}, {cwp}, {cwp_null}, {gs_null}");
+
+ PointersAsArgsTest (ip, ipp, ipa, ippa, &dt, &dtp, dtpa, dtppa);
+ }
+
+ static unsafe void PointersAsArgsTest(int* ip, int** ipp, int*[] ipa, int**[] ippa,
+ DateTime* dtp, DateTime** dtpp, DateTime*[] dtpa, DateTime**[] dtppa)
+ {
+ Console.WriteLine($"break here!");
+ if (ip == null)
+ Console.WriteLine($"ip is null");
+ Console.WriteLine($"done!");
+ }
+
+ public static unsafe async Task LocalPointersAsync ()
+ {
+ int ivalue0 = 5;
+ int ivalue1 = 10;
+
+ int* ip = &ivalue0;
+ int* ip_null = null;
+ int** ipp = &ip;
+ int** ipp_null = &ip_null;
+ int*[] ipa = new int*[] { &ivalue0, &ivalue1, null };
+ int**[] ippa = new int**[] { &ip, &ip_null, ipp, ipp_null, null };
+ char cvalue0 = 'q';
+ char* cp = &cvalue0;
+
+ DateTime dt = new DateTime(5, 6, 7, 8, 9, 10);
+ void* vp = &dt;
+ void* vp_null = null;
+ void** vpp = &vp;
+ void** vpp_null = &vp_null;
+
+ DateTime* dtp = &dt;
+ DateTime* dtp_null = null;
+ DateTime*[] dtpa = new DateTime*[] { dtp, dtp_null };
+ DateTime**[] dtppa = new DateTime**[] { &dtp, &dtp_null, null };
+ Console.WriteLine($"-- break here: ip_null==null: {ip_null == null}, ipp_null: {ipp_null == null}, *ipp_null==ip_null: {*ipp_null == ip_null}, *ipp_null==null: {*ipp_null == null}");
+
+ var gs = new GenericStructWithUnmanagedT<DateTime> { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp };
+ var gs_null = new GenericStructWithUnmanagedT<DateTime> { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp_null };
+ var gsp = &gs;
+ var gsp_null = &gs_null;
+ var gspa = new GenericStructWithUnmanagedT<DateTime>*[] { null, gsp, gsp_null };
+
+ var cwp = new GenericClassWithPointers<DateTime> { Ptr = dtp };
+ var cwp_null = new GenericClassWithPointers<DateTime>();
+ Console.WriteLine($"{(int)*ip}, {(int)**ipp}, {ipp_null == null}, {ip_null == null}, {ippa == null}, {ipa}, {(char)*cp}, {(vp == null ? "null" : "not null")}, {dtp->Second}, {gsp->IntField}, {cwp}, {cwp_null}, {gs_null}");
+ }
+
+ // async methods cannot have unsafe params, so no test for that
+ }
+
+ public unsafe struct GenericStructWithUnmanagedT<T> where T : unmanaged
+ {
+ public T Value;
+ public int IntField;
+
+ public DateTime** DTPP;
+ }
+
+ public unsafe class GenericClassWithPointers<T> where T : unmanaged
+ {
+ public unsafe T* Ptr;
+ }
+} \ No newline at end of file
diff --git a/sdks/wasm/tests/debugger/debugger-test.cs b/sdks/wasm/tests/debugger/debugger-test.cs
index bd38e5edd8f..84e3bcfe0a7 100644
--- a/sdks/wasm/tests/debugger/debugger-test.cs
+++ b/sdks/wasm/tests/debugger/debugger-test.cs
@@ -272,29 +272,6 @@ public partial class Math { //Only append content to this class as the test suit
delegate GenericStruct<bool[]> DelegateForSignatureTest (Math m, GenericStruct<GenericStruct<int[]>> gs);
static bool DelegateTargetForNestedFunc<T>(T arg) => true;
- public static unsafe void PointersTest ()
- {
- int ivalue0 = 5;
- int ivalue1 = 10;
-
- int* ip = &ivalue0;
- int* ip_null = null;
- int** ipp = &ip;
-
- int*[] ipa = new int*[] { &ivalue0, &ivalue1, null };
-
- char cvalue0 = 'q';
- char* cp = &cvalue0;
-
- DateTime dt = new DateTime(5, 6, 7, 8, 9, 10);
- void* vp = &dt;
- void* vp_null = null;
-
- DateTime* dtp = &dt;
- DateTime* dtp_null = null;
- Console.WriteLine ($"-- break here");
- }
-
public struct SimpleStruct
{
public DateTime dt;