diff options
author | Ankit Jain <radical@gmail.com> | 2020-07-07 20:39:12 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-07 20:39:12 +0300 |
commit | 1da3a20c8b10f67d521a37ac1c2a32dc604ec484 (patch) | |
tree | e6f80c83b93ad1a7c29483c2b58e9a3edb287a8b /sdks | |
parent | 618cdf6ff78f6b8f8b519a1b6988f57682f87a76 (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.cs | 520 | ||||
-rw-r--r-- | sdks/wasm/DebuggerTestSuite/Support.cs | 95 | ||||
-rw-r--r-- | sdks/wasm/DebuggerTestSuite/Tests.cs | 34 | ||||
-rw-r--r-- | sdks/wasm/Mono.WebAssembly.DebuggerProxy/DebugStore.cs | 1 | ||||
-rw-r--r-- | sdks/wasm/Mono.WebAssembly.DebuggerProxy/DevToolsHelper.cs | 8 | ||||
-rw-r--r-- | sdks/wasm/Mono.WebAssembly.DebuggerProxy/MonoProxy.cs | 38 | ||||
-rw-r--r-- | sdks/wasm/src/library_mono.js | 147 | ||||
-rw-r--r-- | sdks/wasm/tests/debugger/debugger-pointers-test.cs | 109 | ||||
-rw-r--r-- | sdks/wasm/tests/debugger/debugger-test.cs | 23 |
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; |