diff --git a/src/mono/mono/mini/mini-wasm-debugger.c b/src/mono/mono/mini/mini-wasm-debugger.c index 52a79089ad590..352bb81deb923 100644 --- a/src/mono/mono/mini/mini-wasm-debugger.c +++ b/src/mono/mono/mini/mini-wasm-debugger.c @@ -541,7 +541,7 @@ assembly_loaded (MonoProfiler *prof, MonoAssembly *assembly) MonoDebugHandle *handle = mono_debug_get_handle (assembly_image); if (handle) { MonoPPDBFile *ppdb = handle->ppdb; - if (!mono_ppdb_is_embedded (ppdb)) { //if it's an embedded pdb we don't need to send pdb extrated to DebuggerProxy. + if (ppdb && !mono_ppdb_is_embedded (ppdb)) { //if it's an embedded pdb we don't need to send pdb extrated to DebuggerProxy. pdb_image = mono_ppdb_get_image (ppdb); mono_wasm_asm_loaded (assembly_image->assembly_name, assembly_image->raw_data, assembly_image->raw_data_len, pdb_image->raw_data, pdb_image->raw_data_len); return; @@ -1023,6 +1023,10 @@ describe_value(MonoType * type, gpointer addr, int gpflags) case MONO_TYPE_OBJECT: { MonoObject *obj = *(MonoObject**)addr; + if (!obj) { + mono_wasm_add_obj_var ("object", NULL, 0); + break; + } MonoClass *klass = obj->vtable->klass; if (!klass) { // boxed null @@ -1070,6 +1074,12 @@ describe_value(MonoType * type, gpointer addr, int gpflags) case MONO_TYPE_ARRAY: case MONO_TYPE_CLASS: { MonoObject *obj = *(MonoObject**)addr; + if (!obj) { + char *class_name = mono_type_full_name (type); + mono_wasm_add_func_var (class_name, NULL, 0); + g_free (class_name); + return TRUE; + } MonoClass *klass = type->data.klass; if (m_class_is_valuetype (mono_object_class (obj))) { @@ -1523,7 +1533,7 @@ describe_variable (InterpFrame *frame, MonoMethod *method, MonoMethodHeader *hea addr = mini_get_interp_callbacks ()->frame_get_local (frame, pos); } - PRINT_DEBUG_MSG (2, "adding val %p type [%p] %s\n", addr, type, mono_type_full_name (type)); + PRINT_DEBUG_MSG (2, "adding val %p type 0x%x %s\n", addr, type->type, mono_type_full_name (type)); return describe_value(type, addr, gpflags); } diff --git a/src/mono/wasm/README.md b/src/mono/wasm/README.md index 913330093012c..6a5dff401ab21 100644 --- a/src/mono/wasm/README.md +++ b/src/mono/wasm/README.md @@ -137,7 +137,7 @@ To run a test with `FooBar` in the name: (See https://docs.microsoft.com/en-us/dotnet/core/testing/selective-unit-tests?pivots=xunit for filter options) -Additional arguments for `dotnet test` can be passed via `TEST_ARGS`. Though only one of `TEST_ARGS`, or `TEST_FILTER` can be used at a time. +Additional arguments for `dotnet test` can be passed via `MSBUILD_ARGS` or `TEST_ARGS`. For example `MSBUILD_ARGS="/p:WasmDebugLevel=5"`. Though only one of `TEST_ARGS`, or `TEST_FILTER` can be used at a time. ## Run samples diff --git a/src/mono/wasm/build/WasmApp.InTree.targets b/src/mono/wasm/build/WasmApp.InTree.targets index cde8075f0874c..f92f2abd62bc6 100644 --- a/src/mono/wasm/build/WasmApp.InTree.targets +++ b/src/mono/wasm/build/WasmApp.InTree.targets @@ -25,7 +25,7 @@ + Targets="Build"/> + @@ -222,7 +223,7 @@ $([MSBuild]::NormalizeDirectory($(WasmAppDir))) $([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidDir))) $([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidDir), 'native')) - $([MSBuild]::NormalizePath($(MicrosoftNetCoreAppRuntimePackRidNativeDir), 'cross', $(PackageRID), 'mono-aot-cross$(_ExeExt)')) + $([MSBuild]::NormalizePath($(MicrosoftNetCoreAppRuntimePackRidNativeDir), 'cross', $(RuntimeIdentifier), 'mono-aot-cross$(_ExeExt)')) <_WasmIntermediateOutputPath>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'wasm')) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/AssignmentTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/AssignmentTests.cs new file mode 100644 index 0000000000000..00f917c8dc573 --- /dev/null +++ b/src/mono/wasm/debugger/DebuggerTestSuite/AssignmentTests.cs @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Linq; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace DebuggerTests +{ + public class AssignmentTests : DebuggerTestBase + { + public static TheoryData GetTestData => new TheoryData + { + { "MONO_TYPE_OBJECT", TObject("object", is_null: true), TObject("object") }, + { "MONO_TYPE_CLASS", TObject("DebuggerTests.MONO_TYPE_CLASS", is_null: true), TObject("DebuggerTests.MONO_TYPE_CLASS") }, + { "MONO_TYPE_BOOLEAN", TBool(default), TBool(true) }, + { "MONO_TYPE_CHAR", TSymbol("0 '\u0000'"), TSymbol("97 'a'") }, + { "MONO_TYPE_STRING", TString(default), TString("hello") }, + { "MONO_TYPE_ENUM", TEnum("DebuggerTests.RGB", "Red"), TEnum("DebuggerTests.RGB", "Blue") }, + { "MONO_TYPE_ARRAY", TObject("byte[]", is_null: true), TArray("byte[]", 2) }, + { "MONO_TYPE_VALUETYPE", TValueType("DebuggerTests.Point"), TValueType("DebuggerTests.Point") }, + { "MONO_TYPE_VALUETYPE2", TValueType("System.Decimal","0"), TValueType("System.Decimal", "1.1") }, + { "MONO_TYPE_GENERICINST", TObject("System.Func", is_null: true), TDelegate("System.Func", "int Prepare ()") }, + { "MONO_TYPE_FNPTR", TPointer("*()", is_null: true), TPointer("*()") }, + { "MONO_TYPE_PTR", TPointer("int*", is_null: true), TPointer("int*") }, + { "MONO_TYPE_I1", TNumber(0), TNumber(-1) }, + { "MONO_TYPE_I2", TNumber(0), TNumber(-1) }, + { "MONO_TYPE_I4", TNumber(0), TNumber(-1) }, + { "MONO_TYPE_I8", TNumber(0), TNumber(-1) }, + { "MONO_TYPE_U1", TNumber(0), TNumber(1) }, + { "MONO_TYPE_U2", TNumber(0), TNumber(1) }, + { "MONO_TYPE_U4", TNumber(0), TNumber(1) }, + { "MONO_TYPE_U8", TNumber(0), TNumber(1) }, + { "MONO_TYPE_R4", TNumber(0), TNumber("3.1414999961853027") }, + { "MONO_TYPE_R8", TNumber(0), TNumber("3.1415") }, + }; + + [Theory] + [MemberData("GetTestData")] + async Task InspectVariableBeforeAndAfterAssignment(string clazz, JObject checkDefault, JObject checkValue) + { + await SetBreakpointInMethod("debugger-test", "DebuggerTests." + clazz, "Prepare", 2); + await EvaluateAndCheck("window.setTimeout(function() { invoke_static_method('[debugger-test] DebuggerTests." + clazz + ":Prepare'); })", null, -1, -1, "Prepare"); + + // 1) check un-assigned variables + await StepAndCheck(StepKind.Into, "dotnet://debugger-test.dll/debugger-assignment-test.cs", -1, -1, "TestedMethod", + locals_fn: (locals) => + { + Assert.Equal(2, locals.Count()); + Check(locals, "r", checkDefault); + } + ); + + // 2) check assigned variables + await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-assignment-test.cs", -1, -1, "TestedMethod", times: 3, + locals_fn: (locals) => + { + Assert.Equal(2, locals.Count()); + Check(locals, "r", checkValue); + } + ); + } + } +} diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs index 30a1a804621bd..9b41983812074 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs @@ -53,6 +53,7 @@ static protected string FindTestPath() "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge", "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary", "/usr/bin/chromium", + "C:/Program Files/Google/Chrome/Application/chrome.exe", "/usr/bin/chromium-browser", }; static string chrome_path; @@ -89,19 +90,19 @@ public DebuggerTestBase(string driver = "debugger-driver.html") public virtual async Task InitializeAsync() { - Func)>> fn = (client, token) => - { - Func)> getInitCmdFn = (cmd) => (cmd, client.SendCommand(cmd, null, token)); - var init_cmds = new List<(string, Task)> - { + Func)>> fn = (client, token) => + { + Func)> getInitCmdFn = (cmd) => (cmd, client.SendCommand(cmd, null, token)); + var init_cmds = new List<(string, Task)> + { getInitCmdFn("Profiler.enable"), getInitCmdFn("Runtime.enable"), getInitCmdFn("Debugger.enable"), getInitCmdFn("Runtime.runIfWaitingForDebugger") - }; + }; - return init_cmds; - }; + return init_cmds; + }; await Ready(); await insp.OpenSessionAsync(fn); @@ -153,9 +154,9 @@ await EvaluateAndCheck( function_name, wait_for_event_fn: async (pause_location) => { - //make sure we're on the right bp + //make sure we're on the right bp - Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); + Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); var top_frame = pause_location!["callFrames"]?[0]; @@ -262,11 +263,18 @@ internal JToken CheckSymbol(JToken locals, string name, string value) return l; } - internal JToken CheckObject(JToken locals, string name, string class_name, string subtype = null, bool is_null = false) + internal JToken Check(JToken locals, string name, JObject expected) + { + var l = GetAndAssertObjectWithName(locals, name); + CheckValue(l["value"], expected, name).Wait(); + return l; + } + + internal JToken CheckObject(JToken locals, string name, string class_name, string subtype = null, bool is_null = false, string description = null) { var l = GetAndAssertObjectWithName(locals, name); var val = l["value"]; - CheckValue(val, TObject(class_name, is_null: is_null), name).Wait(); + CheckValue(val, TObject(class_name, is_null: is_null, description: description), name).Wait(); Assert.True(val["isValueType"] == null || !val["isValueType"].Value()); return l; @@ -330,10 +338,10 @@ internal void CheckContentValue(JToken token, string value) Assert.Equal(value, val); } - internal JToken CheckValueType(JToken locals, string name, string class_name) + internal JToken CheckValueType(JToken locals, string name, string class_name, string description=null) { var l = GetAndAssertObjectWithName(locals, name); - CheckValue(l["value"], TValueType(class_name), name).Wait(); + CheckValue(l["value"], TValueType(class_name, description: description), name).Wait(); return l; } @@ -413,7 +421,7 @@ internal async Task InvokeGetter(JToken obj, object arguments, string fn { functionDeclaration = fn, objectId = obj["value"]?["objectId"]?.Value(), - arguments = new[] { new { value = property } , new { value = newvalue } }, + arguments = new[] { new { value = property }, new { value = newvalue } }, silent = true }); var res = await cli.SendCommand("Runtime.callFunctionOn", req, token); @@ -471,7 +479,14 @@ internal async Task SendCommandAndCheck(JObject args, string method, st if (locals_fn != null) { var locals = await GetProperties(wait_res["callFrames"][0]["callFrameId"].Value()); - locals_fn(locals); + try + { + locals_fn(locals); + } + catch (System.AggregateException ex) + { + throw new AggregateException(ex.Message + " \n" + locals.ToString(), ex); + } } return wait_res; @@ -701,9 +716,9 @@ internal async Task CheckValue(JToken actual_val, JToken exp_val, string label) AssertEqual(exp_val_str, actual_field_val_str, $"[{label}] Value for json property named {jp.Name} didn't match."); } } - catch + catch (Exception ex) { - Console.WriteLine($"Expected: {exp_val}. Actual: {actual_val}"); + Console.WriteLine($"{ex.Message} \nExpected: {exp_val} \nActual: {actual_val}"); throw; } } @@ -848,8 +863,8 @@ internal async Task RemoveBreakpoint(string id, bool expect_ok = true) internal async Task SetBreakpoint(string url_key, int line, int column, bool expect_ok = true, bool use_regex = false, string condition = "") { var bp1_req = !use_regex ? - JObject.FromObject(new { lineNumber = line, columnNumber = column, url = dicFileToUrl[url_key], condition}) : - JObject.FromObject(new { lineNumber = line, columnNumber = column, urlRegex = url_key, condition}); + JObject.FromObject(new { lineNumber = line, columnNumber = column, url = dicFileToUrl[url_key], condition }) : + JObject.FromObject(new { lineNumber = line, columnNumber = column, urlRegex = url_key, condition }); var bp1_res = await cli.SendCommand("Debugger.setBreakpointByUrl", bp1_req, token); Assert.True(expect_ok ? bp1_res.IsOk : bp1_res.IsErr); @@ -929,6 +944,9 @@ internal static JObject TNumber(int value) => internal static JObject TNumber(uint value) => JObject.FromObject(new { type = "number", value = @value.ToString(), description = value.ToString() }); + internal static JObject TNumber(string value) => + JObject.FromObject(new { type = "number", value = @value.ToString(), description = value }); + internal static JObject TValueType(string className, string description = null, object members = null) => JObject.FromObject(new { type = "object", isValueType = true, className = className, description = description ?? className }); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs b/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs index c555cd0439767..826d860c21e4d 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs @@ -147,6 +147,7 @@ private static string FormatConsoleAPICalled(JObject args) async Task OnMessage(string method, JObject args, CancellationToken token) { + bool fail = false; switch (method) { case "Debugger.paused": @@ -158,14 +159,22 @@ async Task OnMessage(string method, JObject args, CancellationToken token) case "Runtime.consoleAPICalled": _logger.LogInformation(FormatConsoleAPICalled(args)); break; + case "Inspector.detached": + case "Inspector.targetCrashed": + case "Inspector.targetReloadedAfterCrash": + fail = true; + break; + case "Runtime.exceptionThrown": + _logger.LogDebug($"Failing all waiters because: {method}: {args}"); + fail = true; + break; } if (eventListeners.TryGetValue(method, out var listener)) { await listener(args, token).ConfigureAwait(false); } - else if (String.Compare(method, "Runtime.exceptionThrown") == 0) + else if (fail) { - _logger.LogDebug($"Failing all waiters because: {method}: {args}"); FailAllWaiters(new ArgumentException(args.ToString())); } } diff --git a/src/mono/wasm/debugger/tests/Directory.Build.props b/src/mono/wasm/debugger/tests/Directory.Build.props index 2506fb59a8214..0cff1f10a52d4 100644 --- a/src/mono/wasm/debugger/tests/Directory.Build.props +++ b/src/mono/wasm/debugger/tests/Directory.Build.props @@ -5,7 +5,7 @@ $(AspNetCoreAppCurrent) Library Debug - Release + Release true 219 diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-assignment-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-assignment-test.cs new file mode 100644 index 0000000000000..e316c2a658bf1 --- /dev/null +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-assignment-test.cs @@ -0,0 +1,234 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +namespace DebuggerTests +{ + public class StepInTest + { + public static int TestedMethod(T value) + { + // 1) break here and check un-assigned variables + T r; + r = value; + // 2) break here and check assigned variables + return 0; + } + } + + public class MONO_TYPE_OBJECT + { + public static int Prepare() + { + var value = new object(); + return StepInTest.TestedMethod(value); + } + } + + public class MONO_TYPE_CLASS + { + public static int Prepare() + { + var value = new MONO_TYPE_CLASS(); + return StepInTest.TestedMethod(value); + } + } + + public class MONO_TYPE_BOOLEAN + { + public static int Prepare() + { + var value = true; + return StepInTest.TestedMethod(value); + } + } + + public class MONO_TYPE_CHAR + { + public static int Prepare() + { + var value = 'a'; + return StepInTest.TestedMethod(value); + } + } + + public class MONO_TYPE_I1 + { + public static int Prepare() + { + sbyte value = -1; + return StepInTest.TestedMethod(value); + } + } + + public class MONO_TYPE_I2 + { + public static int Prepare() + { + short value = -1; + return StepInTest.TestedMethod(value); + } + } + + public class MONO_TYPE_I4 + { + public static int Prepare() + { + int value = -1; + return StepInTest.TestedMethod(value); + } + } + + public class MONO_TYPE_I8 + { + public static int Prepare() + { + long value = -1; + return StepInTest.TestedMethod(value); + } + } + + public class MONO_TYPE_U1 + { + public static int Prepare() + { + byte value = 1; + return StepInTest.TestedMethod(value); + } + } + + public class MONO_TYPE_U2 + { + public static int Prepare() + { + ushort value = 1; + return StepInTest.TestedMethod(value); + } + } + + public class MONO_TYPE_U4 + { + public static int Prepare() + { + uint value = 1; + return StepInTest.TestedMethod(value); + } + } + + public class MONO_TYPE_U8 + { + public static int Prepare() + { + ulong value = 1; + return StepInTest.TestedMethod(value); + } + } + + public class MONO_TYPE_R4 + { + public static int Prepare() + { + float value = 3.1415F; + return StepInTest.TestedMethod(value); + } + } + + public class MONO_TYPE_R8 + { + public static int Prepare() + { + double value = 3.1415D; + return StepInTest.TestedMethod(value); + } + } + + public class MONO_TYPE_STRING + { + public static int Prepare() + { + string value = "hello"; + return StepInTest.TestedMethod(value); + } + } + + public class MONO_TYPE_ENUM + { + public static int Prepare() + { + RGB value = RGB.Blue; + return StepInTest.TestedMethod(value); + } + } + + public class MONO_TYPE_ARRAY + { + public static int Prepare() + { + byte[] value = new byte[2] { 1, 2 }; + return StepInTest.TestedMethod(value); + } + } + + public class MONO_TYPE_VALUETYPE + { + public static int Prepare() + { + Point value = new Point(); + return StepInTest.TestedMethod(value); + } + } + + public class MONO_TYPE_VALUETYPE2 + { + public static int Prepare() + { + Decimal value = 1.1m; + return StepInTest.TestedMethod(value); + } + } + + public class MONO_TYPE_GENERICINST + { + public static int Prepare() + { + Func value = MONO_TYPE_GENERICINST.Prepare; + return StepInTest>.TestedMethod(value); + } + } + + public class MONO_TYPE_FNPTR + { + public unsafe static int Prepare() + { + delegate* value = &MONO_TYPE_FNPTR.Prepare; + return TestedMethod(value); + } + + public unsafe static int TestedMethod(delegate* value) + { + delegate* r; + r = value; + return 0; + } + } + + public class MONO_TYPE_PTR + { + public unsafe static int Prepare() + { + int a = 1; int* value = &a; + return TestedMethod(value); + } + + public unsafe static int TestedMethod(int* value) + { + int* r; + r = value; + return 0; + } + } +} diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj index 880220a06ffa3..648dbcc64fc6d 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj @@ -24,7 +24,7 @@ $(AppDir) $(MonoProjectRoot)wasm\runtime-test.js - 1 + 1 true