diff options
author | Ankit Jain <radical@gmail.com> | 2020-04-09 09:51:15 +0300 |
---|---|---|
committer | Ankit Jain <radical@gmail.com> | 2020-04-10 19:44:30 +0300 |
commit | 5bbca1f930b350b03a2ecdf0c45586e825b5ef20 (patch) | |
tree | d9b27c62c656135181c44adae90ad7fc94cf046e | |
parent | ab15ba3d9979e8c3c5d42eb329940e0d28f15d0d (diff) |
[wasm][debugger] For some types, use `ToString()` for their ..
.. `description`.
- `System.DateTime`
- `System.DateTimeOffset`
- `System.Decimal`
- `System.TimeSpan`
- `System.Guid`
-rw-r--r-- | mono/mini/mini-wasm-debugger.c | 86 | ||||
-rw-r--r-- | sdks/wasm/DebuggerTestSuite/Tests.cs | 103 | ||||
-rw-r--r-- | sdks/wasm/debugger-valuetypes-test.cs | 103 | ||||
-rw-r--r-- | sdks/wasm/src/library_mono.js | 12 |
4 files changed, 278 insertions, 26 deletions
diff --git a/mono/mini/mini-wasm-debugger.c b/mono/mini/mini-wasm-debugger.c index 737028ff611..8586a2e2819 100644 --- a/mono/mini/mini-wasm-debugger.c +++ b/mono/mini/mini-wasm-debugger.c @@ -44,9 +44,9 @@ extern void mono_wasm_fire_bp (void); extern void mono_wasm_add_bool_var (gint8); extern void mono_wasm_add_number_var (double); extern void mono_wasm_add_string_var (const char*); -extern void mono_wasm_add_obj_var (const char*, guint64); -extern void mono_wasm_add_value_type_unexpanded_var (const char*); -extern void mono_wasm_begin_value_type_var (const char*); +extern void mono_wasm_add_obj_var (const char*, const char*, guint64); +extern void mono_wasm_add_value_type_unexpanded_var (const char*, const char*); +extern void mono_wasm_begin_value_type_var (const char*, const char*); extern void mono_wasm_end_value_type_var (void); extern void mono_wasm_add_enum_var (const char*, const char*, guint64); extern void mono_wasm_add_func_var (const char*, guint64); @@ -67,6 +67,15 @@ static GHashTable *objrefs; static GHashTable *obj_to_objref; static int objref_id = 0; +static const char* +to_string_as_descr_names[] = { + "System.DateTime", + "System.DateTimeOffset", + "System.Decimal", + "System.TimeSpan", + "System.Guid" +}; + #define THREAD_TO_INTERNAL(thread) (thread)->internal_thread static void @@ -616,6 +625,62 @@ mono_wasm_enum_frames (void) mono_walk_stack_with_ctx (list_frames, NULL, MONO_UNWIND_NONE, NULL); } +static char* +invoke_to_string (MonoClass *klass, gpointer addr) +{ + MonoObject *exc; + MonoString *mstr; + char *ret_str; + ERROR_DECL (error); + MonoObject *obj; + + // TODO: this is for a specific use case right now, + // (invoke ToString() get a preview/description for *some* types) + // and we don't want to report errors for that. + if (m_class_is_valuetype (klass)) { + MonoObject *boxed_obj = mono_value_box_checked (mono_domain_get (), klass, addr, error); + if (!is_ok (error)) + return NULL; + + obj = boxed_obj; + } else { + obj = *(MonoObject**)addr; + } + + if (!obj) + return NULL; + + mstr = mono_object_try_to_string (obj, &exc, error); + if (exc) + return NULL; + + if (!is_ok (error)) + return NULL; + + ret_str = mono_string_to_utf8_checked_internal (mstr, error); + if (!is_ok (error)) + return NULL; + + // FREE boxed_obj + + return ret_str; +} + +static char* +get_to_string_description (const char* class_name, MonoClass *klass, gpointer addr) +{ + if (!class_name || !klass || !addr) + return NULL; + + for (int i = 0; i < G_N_ELEMENTS (to_string_as_descr_names); i ++) { + if (strcmp (to_string_as_descr_names [i], class_name) == 0) { + return invoke_to_string (klass, addr); + } + } + + return NULL; +} + typedef struct { int cur_frame; int target_frame; @@ -734,7 +799,9 @@ static gboolean describe_value(MonoType * type, gpointer addr, gboolean expandVa } else if (m_class_is_delegate (klass)) { mono_wasm_add_func_var (class_name, obj_id); } else { - mono_wasm_add_obj_var (class_name, obj_id); + char *to_string_val = get_to_string_description (class_name, klass, addr); + mono_wasm_add_obj_var (class_name, to_string_val, obj_id); + g_free (to_string_val); } g_free (class_name); break; @@ -779,12 +846,17 @@ static gboolean describe_value(MonoType * type, gpointer addr, gboolean expandVa mono_wasm_add_enum_var (class_name, enum_members->str, value__); g_string_free (enum_members, TRUE); } else if (expandValueType) { - mono_wasm_begin_value_type_var (class_name); + char *to_string_val = get_to_string_description (class_name, klass, addr); + mono_wasm_begin_value_type_var (class_name, to_string_val); + g_free (to_string_val); + // FIXME: isAsyncLocalThis describe_object_properties_for_klass ((MonoObject*)addr, klass, FALSE, expandValueType); mono_wasm_end_value_type_var (); } else { - mono_wasm_add_value_type_unexpanded_var (class_name); + char *to_string_val = get_to_string_description (class_name, klass, addr); + mono_wasm_add_value_type_unexpanded_var (class_name, to_string_val); + g_free (to_string_val); } g_free (class_name); break; @@ -975,7 +1047,7 @@ describe_non_async_this (InterpFrame *frame, MonoMethod *method) char *class_name = mono_class_full_name (obj->vtable->klass); mono_wasm_add_properties_var ("this", -1); - mono_wasm_add_obj_var (class_name, get_object_id(obj)); + mono_wasm_add_obj_var (class_name, NULL, get_object_id(obj)); g_free (class_name); } } diff --git a/sdks/wasm/DebuggerTestSuite/Tests.cs b/sdks/wasm/DebuggerTestSuite/Tests.cs index 7da8614eb2f..ea0b6fc782e 100644 --- a/sdks/wasm/DebuggerTestSuite/Tests.cs +++ b/sdks/wasm/DebuggerTestSuite/Tests.cs @@ -251,7 +251,12 @@ namespace DebuggerTests } async Task CheckDateTime (JToken locals, string name, DateTime expected) - => await CheckObjectOnLocals (locals, name, + { + var obj = locals.Where (jt => jt ["name"]?.Value<string> () == name) + .FirstOrDefault (); + Assert.Equal (expected.ToString (), obj ["value"]? ["description"]?.Value<string> ()); + + await CheckObjectOnLocals (locals, name, test_fn: (members) => { // not checking everything #if false @@ -273,6 +278,7 @@ namespace DebuggerTests // FIXME: check some float properties too } ); + } JToken CheckBool (JToken locals, string name, bool expected) { @@ -907,7 +913,7 @@ namespace DebuggerTests var ss_local_props = await CompareObjectPropertiesOnFrameLocals (pause_location ["callFrames"][0], "ss_local", new { str_member = TString ("set in MethodWithLocalStructs#SimpleStruct#str_member"), - dt = TValueType ("System.DateTime"), + dt = TValueType ("System.DateTime", "2/3/2021 4:06:07 AM"), gs = TValueType ("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"), Kind = TEnum ("System.DateTimeKind", "Utc") }); @@ -950,7 +956,7 @@ namespace DebuggerTests await CompareObjectPropertiesFor (vt_local_props, "SimpleStructProperty", new { str_member = TString ("SimpleStructProperty#string#0#SimpleStruct#str_member"), - dt = TValueType ("System.DateTime"), + dt = TValueType ("System.DateTime", "3/4/2022 5:07:08 AM"), gs = TValueType ("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"), Kind = TEnum ("System.DateTimeKind", "Utc") }, @@ -959,7 +965,7 @@ namespace DebuggerTests await CompareObjectPropertiesFor (vt_local_props, "SimpleStructField", new { str_member = TString ("SimpleStructField#string#0#SimpleStruct#str_member"), - dt = TValueType ("System.DateTime"), + dt = TValueType ("System.DateTime", "6/7/2025 8:10:11 AM"), gs = TValueType ("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"), Kind = TEnum ("System.DateTimeKind", "Local") }, @@ -998,7 +1004,7 @@ namespace DebuggerTests var ss_local_as_ss_arg = new { str_member = TString ("ss_local#SimpleStruct#string#0#SimpleStruct#str_member"), - dt = TValueType ("System.DateTime"), + dt = TValueType ("System.DateTime", "6/7/2025 8:10:11 AM"), gs = TValueType ("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"), Kind = TEnum ("System.DateTimeKind", "Local") }; @@ -1033,7 +1039,7 @@ namespace DebuggerTests var ss_arg_updated = new { str_member = TString ("ValueTypesTest#MethodWithStructArgs#updated#ss_arg#str_member"), - dt = TValueType ("System.DateTime"), + dt = TValueType ("System.DateTime", "6/7/2025 8:10:11 AM"), gs = TValueType ("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"), Kind = TEnum ("System.DateTimeKind", "Utc") }; @@ -1119,7 +1125,7 @@ namespace DebuggerTests var ss_local_props = await CompareObjectPropertiesOnFrameLocals (pause_location ["callFrames"][0], "ss_local", new { str_member = TString ("set in MethodWithLocalStructsStaticAsync#SimpleStruct#str_member"), - dt = TValueType ("System.DateTime"), + dt = TValueType ("System.DateTime", "2/3/2021 4:06:07 AM"), gs = TValueType ("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"), Kind = TEnum ("System.DateTimeKind", "Utc") }); @@ -1650,6 +1656,74 @@ namespace DebuggerTests }); } + [Theory] + [InlineData (123, 3, "MethodWithLocalsForToStringTest", false, false)] + [InlineData (133, 3, "MethodWithArgumentsForToStringTest", true, false)] + [InlineData (175, 3, "MethodWithArgumentsForToStringTestAsync", true, true)] + [InlineData (165, 3, "MethodWithArgumentsForToStringTestAsync", false, true)] + public async Task InspectLocalsForToStringDescriptions (int line, int col, string method_name, bool call_other, bool invoke_async) + { + var insp = new Inspector (); + //Collect events + var scripts = SubscribeToScripts(insp); + string entry_method_name = $"[debugger-test] DebuggerTests.ValueTypesTest:MethodWithLocalsForToStringTest{(invoke_async ? "Async" : String.Empty)}"; + int frame_idx = 0; + + await Ready(); + await insp.Ready (async (cli, token) => { + ctx = new DebugTestContext (cli, insp, token, scripts); + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; + + await SetBreakpoint (debugger_test_loc, line, col); + + var eval_expr = "window.setTimeout(function() {" + + (invoke_async ? "invoke_static_method_async (" : "invoke_static_method (") + + $"'{entry_method_name}'," + + (call_other ? "true" : "false") + + "); }, 1);"; + Console.WriteLine ($"{eval_expr}"); + + var pause_location = await EvaluateAndCheck (eval_expr, debugger_test_loc, line, col, invoke_async ? "MoveNext" : method_name); + + var frame_locals = await CheckLocalsOnFrame (pause_location ["callFrames"][frame_idx], + new { + call_other = TBool (call_other), + dt0 = TValueType ("System.DateTime", "1/2/2020 3:04:05 AM"), + dt1 = TValueType ("System.DateTime", "5/4/2010 3:02:01 AM"), + dto = TValueType ("System.DateTimeOffset", "1/2/2020 3:04:05 AM +04:05"), + ts = TValueType ("System.TimeSpan", "3530.00:02:04"), + dec = TValueType ("System.Decimal", "123987123"), + guid = TValueType ("System.Guid", "3d36e07e-ac90-48c6-b7ec-a481e289d014"), + dts = TArray ("System.DateTime[]"), + obj = TObject ("DebuggerTests.ClassForToStringTests"), + sst = TObject ("DebuggerTests.StructForToStringTests") + }, + "locals#0"); + + var dts_elements = await CheckObjectOnLocals (frame_locals, "dts"); + await CheckDateTime (dts_elements, "[0]", new DateTime (1983, 6, 7, 5, 6, 10)); + await CheckDateTime (dts_elements, "[1]", new DateTime (1999, 10, 15, 1, 2, 3)); + + var obj_props = await CompareObjectPropertiesFor (frame_locals, "obj", + new { + DT = TValueType ("System.DateTime", "10/15/2004 1:02:03 AM"), + DTO = TValueType ("System.DateTimeOffset", "1/2/2020 3:04:05 AM +02:14"), + TS = TValueType ("System.TimeSpan", "3530.00:02:04"), + Dec = TValueType ("System.Decimal", "1239871"), + Guid = TValueType ("System.Guid", "3d36e07e-ac90-48c6-b7ec-a481e289d014") + }, "obj_props"); + + var sst_props = await CompareObjectPropertiesFor (frame_locals, "sst", + new { + DT = TValueType ("System.DateTime", "10/15/2004 1:02:03 AM"), + DTO = TValueType ("System.DateTimeOffset", "1/2/2020 3:04:05 AM +03:15"), + TS = TValueType ("System.TimeSpan", "3530.00:02:04"), + Dec = TValueType ("System.Decimal", "1239871"), + Guid = TValueType ("System.Guid", "3d36e07e-ac90-48c6-b7ec-a481e289d014") + }, "sst_props"); + }); + } + [Fact] public async Task EvaluateThisProperties () { @@ -2044,7 +2118,7 @@ namespace DebuggerTests } } - async Task<JToken> CheckObjectOnLocals (JToken locals, string name, Action<JToken> test_fn) + async Task<JToken> CheckObjectOnLocals (JToken locals, string name, Action<JToken> test_fn = null) { var obj = locals.Where (jt => jt ["name"]?.Value<string> () == name) .FirstOrDefault (); @@ -2121,16 +2195,16 @@ namespace DebuggerTests static JObject TNumber (int value) => JObject.FromObject (new { type = "number", value = @value.ToString (), description = value.ToString () }); - static JObject TValueType (string className, object members = null) => - JObject.FromObject (new { type = "object", isValueType = true, className = className, description = className }); + static JObject TValueType (string className, string description = null, object members = null) => + JObject.FromObject (new { type = "object", isValueType = true, className = className, description = description ?? className }); static JObject TEnum (string className, string descr, object members = null) => JObject.FromObject (new { type = "object", isEnum = true, className = className, description = descr }); - static JObject TObject (string className, bool is_null = false) => + static JObject TObject (string className, string description = null, bool is_null = false) => is_null - ? JObject.FromObject (new { type = "object", className = className, description = className, subtype = is_null ? "null" : null }) - : JObject.FromObject (new { type = "object", className = className, description = className }); + ? JObject.FromObject (new { type = "object", className = className, description = description ?? className, subtype = is_null ? "null" : null }) + : JObject.FromObject (new { type = "object", className = className, description = description ?? className }); static JObject TArray (string className) => JObject.FromObject (new { type = "object", className = className, description = className, subtype = "array" }); @@ -2138,6 +2212,9 @@ namespace DebuggerTests static JObject TBool (bool value) => JObject.FromObject (new { type = "boolean", value = @value, description = @value ? "true" : "false" }); + static JObject TSymbol (string value) + => JObject.FromObject (new { type = "symbol", value = @value, description = @value }); + //TODO add tests covering basic stepping behavior as step in/out/over } diff --git a/sdks/wasm/debugger-valuetypes-test.cs b/sdks/wasm/debugger-valuetypes-test.cs index 58a15478f4d..9d1ee58d0f8 100644 --- a/sdks/wasm/debugger-valuetypes-test.cs +++ b/sdks/wasm/debugger-valuetypes-test.cs @@ -91,6 +91,109 @@ namespace DebuggerTests { public DateTime DT { get; set; } public RGB RGB; + + public static void MethodWithLocalsForToStringTest (bool call_other) + { + var dt0 = new DateTime (2020, 1, 2, 3, 4, 5); + var dt1 = new DateTime (2010, 5, 4, 3, 2, 1); + var ts = dt0 - dt1; + var dto = new DateTimeOffset (dt0, new TimeSpan(4, 5, 0)); + decimal dec = 123987123; + var guid = new Guid ("3d36e07e-ac90-48c6-b7ec-a481e289d014"); + + var dts = new DateTime [] { + new DateTime (1983, 6, 7, 5, 6, 10), + new DateTime (1999, 10, 15, 1, 2, 3) + }; + + var obj = new ClassForToStringTests { + DT = new DateTime (2004, 10, 15, 1, 2, 3), + DTO = new DateTimeOffset (dt0, new TimeSpan(2, 14, 0)), + TS = ts, + Dec = 1239871, + Guid = guid + }; + + var sst = new StructForToStringTests { + DT = new DateTime (2004, 10, 15, 1, 2, 3), + DTO = new DateTimeOffset (dt0, new TimeSpan (3, 15, 0)), + TS = ts, + Dec = 1239871, + Guid = guid + }; + Console.WriteLine ($"MethodWithLocalsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}"); + if (call_other) + MethodWithArgumentsForToStringTest (call_other, dt0, dt1, ts, dto, dec, guid, dts, obj, sst); + } + + static void MethodWithArgumentsForToStringTest ( + bool call_other, // not really used, just to help with using common code in the tests + DateTime dt0, DateTime dt1, TimeSpan ts, DateTimeOffset dto, decimal dec, + Guid guid, DateTime[] dts, ClassForToStringTests obj, StructForToStringTests sst) + { + Console.WriteLine ($"MethodWithArgumentsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}"); + } + + public static async Task MethodWithLocalsForToStringTestAsync (bool call_other) + { + var dt0 = new DateTime (2020, 1, 2, 3, 4, 5); + var dt1 = new DateTime (2010, 5, 4, 3, 2, 1); + var ts = dt0 - dt1; + var dto = new DateTimeOffset (dt0, new TimeSpan(4, 5, 0)); + decimal dec = 123987123; + var guid = new Guid ("3d36e07e-ac90-48c6-b7ec-a481e289d014"); + + var dts = new DateTime [] { + new DateTime (1983, 6, 7, 5, 6, 10), + new DateTime (1999, 10, 15, 1, 2, 3) + }; + + var obj = new ClassForToStringTests { + DT = new DateTime (2004, 10, 15, 1, 2, 3), + DTO = new DateTimeOffset (dt0, new TimeSpan(2, 14, 0)), + TS = ts, + Dec = 1239871, + Guid = guid + }; + + var sst = new StructForToStringTests { + DT = new DateTime (2004, 10, 15, 1, 2, 3), + DTO = new DateTimeOffset (dt0, new TimeSpan (3, 15, 0)), + TS = ts, + Dec = 1239871, + Guid = guid + }; + Console.WriteLine ($"MethodWithLocalsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}"); + if (call_other) + await MethodWithArgumentsForToStringTestAsync (call_other, dt0, dt1, ts, dto, dec, guid, dts, obj, sst); + } + + static async Task MethodWithArgumentsForToStringTestAsync ( + bool call_other, // not really used, just to help with using common code in the tests + DateTime dt0, DateTime dt1, TimeSpan ts, DateTimeOffset dto, decimal dec, + Guid guid, DateTime[] dts, ClassForToStringTests obj, StructForToStringTests sst) + { + Console.WriteLine ($"MethodWithArgumentsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}"); + } + + } + + class ClassForToStringTests + { + public DateTime DT; + public DateTimeOffset DTO; + public TimeSpan TS; + public decimal Dec; + public Guid Guid; + } + + struct StructForToStringTests + { + public DateTime DT; + public DateTimeOffset DTO; + public TimeSpan TS; + public decimal Dec; + public Guid Guid; } public enum RGB diff --git a/sdks/wasm/src/library_mono.js b/sdks/wasm/src/library_mono.js index 65f6d16be32..fdf6fac6f4e 100644 --- a/sdks/wasm/src/library_mono.js +++ b/sdks/wasm/src/library_mono.js @@ -470,13 +470,13 @@ var MonoSupportLib = { MONO._async_method_objectId = objectId; }, - mono_wasm_begin_value_type_var: function(className) { + mono_wasm_begin_value_type_var: function(className, toString) { fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className)); var vt_obj = { value: { type: "object", className: fixed_class_name, - description: fixed_class_name, + description: (toString == 0 ? fixed_class_name : Module.UTF8ToString (toString)), // objectId will be generated by MonoProxy expanded: true, isValueType: true, @@ -505,13 +505,13 @@ var MonoSupportLib = { } }, - mono_wasm_add_value_type_unexpanded_var: function (className) { + mono_wasm_add_value_type_unexpanded_var: function (className, toString) { fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className)); MONO.var_info.push({ value: { type: "object", className: fixed_class_name, - description: fixed_class_name, + description: (toString == 0 ? fixed_class_name : Module.UTF8ToString (toString)), // objectId added when enumerating object's properties expanded: false, isValueType: true @@ -563,7 +563,7 @@ var MonoSupportLib = { }); }, - mono_wasm_add_obj_var: function(className, objectId) { + mono_wasm_add_obj_var: function(className, toString, objectId) { if (objectId == 0) { MONO.mono_wasm_add_null_var (className); return; @@ -574,7 +574,7 @@ var MonoSupportLib = { value: { type: "object", className: fixed_class_name, - description: fixed_class_name, + description: (toString == 0 ? fixed_class_name : Module.UTF8ToString (toString)), objectId: "dotnet:object:"+ objectId, } }); |