diff --git a/src/mono/mono/mini/mini-wasm-debugger.c b/src/mono/mono/mini/mini-wasm-debugger.c index 43e61020b1f4d3..3c60fcdd6cd8cb 100644 --- a/src/mono/mono/mini/mini-wasm-debugger.c +++ b/src/mono/mono/mini/mini-wasm-debugger.c @@ -30,6 +30,12 @@ enum { EXCEPTION_MODE_ALL }; +// Flags for get_*_properties +#define GPFLAG_NONE 0x0000 +#define GPFLAG_OWN_PROPERTIES 0x0001 +#define GPFLAG_ACCESSORS_ONLY 0x0002 +#define GPFLAG_EXPAND_VALUETYPES 0x0004 + //functions exported to be used by JS G_BEGIN_DECLS @@ -41,8 +47,8 @@ EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_get_local_vars (int scope, int* pos, int EMSCRIPTEN_KEEPALIVE void mono_wasm_clear_all_breakpoints (void); EMSCRIPTEN_KEEPALIVE int mono_wasm_setup_single_step (int kind); EMSCRIPTEN_KEEPALIVE int mono_wasm_pause_on_exceptions (int state); -EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_get_object_properties (int object_id, gboolean expand_value_types); -EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_get_array_values (int object_id, int start_idx, int count, gboolean expand_value_types); +EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_get_object_properties (int object_id, int gpflags); +EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_get_array_values (int object_id, int start_idx, int count, int gpflags); EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_invoke_getter_on_object (int object_id, const char* name); EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_invoke_getter_on_value (void *value, MonoClass *klass, const char *name); EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_get_deref_ptr_value (void *value_addr, MonoClass *klass); @@ -61,7 +67,7 @@ extern void mono_wasm_add_typed_value (const char *type, const char *str_value, G_END_DECLS -static void describe_object_properties_for_klass (void *obj, MonoClass *klass, gboolean isAsyncLocalThis, gboolean expandValueType); +static void describe_object_properties_for_klass (void *obj, MonoClass *klass, gboolean isAsyncLocalThis, int gpflags); static void handle_exception (MonoException *exc, MonoContext *throw_ctx, MonoContext *catch_ctx, StackFrameInfo *catch_frame); //FIXME move all of those fields to the profiler object @@ -823,7 +829,8 @@ read_enum_value (const char *mem, int type) return 0; } -static gboolean describe_value(MonoType * type, gpointer addr, gboolean expandValueType) +static gboolean +describe_value(MonoType * type, gpointer addr, int gpflags) { ERROR_DECL (error); switch (type->type) { @@ -984,7 +991,7 @@ static gboolean describe_value(MonoType * type, gpointer addr, gboolean expandVa } else { char *to_string_val = get_to_string_description (class_name, klass, addr); - if (expandValueType) { + if (gpflags & GPFLAG_EXPAND_VALUETYPES) { int32_t size = mono_class_value_size (klass, NULL); void *value_buf = g_malloc0 (size); mono_value_copy_internal (value_buf, addr, klass); @@ -996,7 +1003,7 @@ static gboolean describe_value(MonoType * type, gpointer addr, gboolean expandVa g_free (value_buf); // FIXME: isAsyncLocalThis - describe_object_properties_for_klass (addr, klass, FALSE, expandValueType); + describe_object_properties_for_klass (addr, klass, FALSE, gpflags); mono_wasm_add_typed_value ("end_vt", NULL, 0); } else { EM_ASM ({ @@ -1043,35 +1050,45 @@ invoke_and_describe_getter_value (MonoObject *obj, MonoProperty *p) if (!is_ok (error) && exc == NULL) exc = (MonoObject*) mono_error_convert_to_exception (error); if (exc) - describe_value (mono_get_object_type (), &exc, TRUE); + describe_value (mono_get_object_type (), &exc, GPFLAG_EXPAND_VALUETYPES); else if (!res || !m_class_is_valuetype (mono_object_class (res))) - describe_value (sig->ret, &res, TRUE); + describe_value (sig->ret, &res, GPFLAG_EXPAND_VALUETYPES); else - describe_value (sig->ret, mono_object_unbox_internal (res), TRUE); + describe_value (sig->ret, mono_object_unbox_internal (res), GPFLAG_EXPAND_VALUETYPES); } static void -describe_object_properties_for_klass (void *obj, MonoClass *klass, gboolean isAsyncLocalThis, gboolean expandValueType) +describe_object_properties_for_klass (void *obj, MonoClass *klass, gboolean isAsyncLocalThis, int gpflags) { MonoClassField *f; MonoProperty *p; MonoMethodSignature *sig; - gpointer iter = NULL; gboolean is_valuetype; int pnum; char *klass_name; gboolean auto_invoke_getters; + gboolean is_own; + gboolean only_backing_fields; g_assert (klass); + MonoClass *start_klass = klass; + + only_backing_fields = gpflags & GPFLAG_ACCESSORS_ONLY; is_valuetype = m_class_is_valuetype(klass); + if (is_valuetype) + gpflags |= GPFLAG_EXPAND_VALUETYPES; +handle_parent: + is_own = (start_klass == klass); + klass_name = mono_class_full_name (klass); + gpointer iter = NULL; while (obj && (f = mono_class_get_fields_internal (klass, &iter))) { if (isAsyncLocalThis && f->name[0] == '<' && f->name[1] == '>') { if (g_str_has_suffix (f->name, "__this")) { mono_wasm_add_properties_var ("this", f->offset); gpointer field_value = (guint8*)obj + f->offset; - describe_value (f->type, field_value, is_valuetype | expandValueType); + describe_value (f->type, field_value, gpflags); } continue; @@ -1081,20 +1098,23 @@ describe_object_properties_for_klass (void *obj, MonoClass *klass, gboolean isAs if (mono_field_is_deleted (f)) continue; - mono_wasm_add_properties_var (f->name, f->offset); + if (only_backing_fields && !g_str_has_suffix(f->name, "k__BackingField")) + continue; + + EM_ASM ({ + MONO.mono_wasm_add_properties_var ($0, { field_offset: $1, is_own: $2, attr: $3, owner_class: $4 }); + }, f->name, f->offset, is_own, f->type->attrs, klass_name); gpointer field_addr; if (is_valuetype) field_addr = mono_vtype_get_field_addr (obj, f); else field_addr = (guint8*)obj + f->offset; - - describe_value (f->type, field_addr, is_valuetype | expandValueType); + + describe_value (f->type, field_addr, gpflags); } - klass_name = mono_class_full_name (klass); auto_invoke_getters = are_getters_allowed (klass_name); - iter = NULL; pnum = 0; while ((p = mono_class_get_properties (klass, &iter))) { @@ -1102,8 +1122,15 @@ describe_object_properties_for_klass (void *obj, MonoClass *klass, gboolean isAs if (isAsyncLocalThis && (p->name[0] != '<' || (p->name[0] == '<' && p->name[1] == '>'))) continue; - mono_wasm_add_properties_var (p->name, pnum); sig = mono_method_signature_internal (p->get); + if (sig->param_count != 0) { + // getters with params are not shown + continue; + } + + EM_ASM ({ + MONO.mono_wasm_add_properties_var ($0, { field_offset: $1, is_own: $2, attr: $3, owner_class: $4 }); + }, p->name, pnum, is_own, p->attrs, klass_name); gboolean vt_self_type_getter = is_valuetype && mono_class_from_mono_type_internal (sig->ret) == klass; if (auto_invoke_getters && !vt_self_type_getter) { @@ -1112,8 +1139,7 @@ describe_object_properties_for_klass (void *obj, MonoClass *klass, gboolean isAs // not allowed to call the getter here char *ret_class_name = mono_class_full_name (mono_class_from_mono_type_internal (sig->ret)); - gboolean invokable = sig->param_count == 0; - mono_wasm_add_typed_value ("getter", ret_class_name, invokable); + mono_wasm_add_typed_value ("getter", ret_class_name, -1); g_free (ret_class_name); continue; @@ -1123,6 +1149,14 @@ describe_object_properties_for_klass (void *obj, MonoClass *klass, gboolean isAs } g_free (klass_name); + + // ownProperties + // Note: ownProperties should mean that we return members of the klass itself, + // but we are going to ignore that here, because otherwise vscode/chrome don't + // seem to ask for inherited fields at all. + // if (!is_valuetype && !(gpflags & GPFLAG_OWN_PROPERTIES) && (klass = m_class_get_parent (klass))) + if (!is_valuetype && (klass = m_class_get_parent (klass))) + goto handle_parent; } /* @@ -1148,9 +1182,9 @@ describe_delegate_properties (MonoObject *obj) } static gboolean -describe_object_properties (guint64 objectId, gboolean isAsyncLocalThis, gboolean expandValueType) +describe_object_properties (guint64 objectId, gboolean isAsyncLocalThis, int gpflags) { - DEBUG_PRINTF (2, "describe_object_properties %llu\n", objectId); + DEBUG_PRINTF (2, "describe_object_properties %llu, gpflags: %d\n", objectId, gpflags); MonoObject *obj = get_object_from_id (objectId); if (!obj) @@ -1160,7 +1194,7 @@ describe_object_properties (guint64 objectId, gboolean isAsyncLocalThis, gboolea // delegates get the same id format as regular objects describe_delegate_properties (obj); } else { - describe_object_properties_for_klass (obj, obj->vtable->klass, isAsyncLocalThis, expandValueType); + describe_object_properties_for_klass (obj, obj->vtable->klass, isAsyncLocalThis, gpflags); } return TRUE; @@ -1174,7 +1208,9 @@ invoke_getter (void *obj_or_value, MonoClass *klass, const char *name) return FALSE; } - gpointer iter = NULL; + gpointer iter; +handle_parent: + iter = NULL; MonoProperty *p; while ((p = mono_class_get_properties (klass, &iter))) { //if get doesn't have name means that doesn't have a getter implemented and we don't want to show value, like VS debug @@ -1185,11 +1221,14 @@ invoke_getter (void *obj_or_value, MonoClass *klass, const char *name) return TRUE; } + if ((klass = m_class_get_parent(klass))) + goto handle_parent; + return FALSE; } -static gboolean -describe_array_values (guint64 objectId, int startIdx, int count, gboolean expandValueType) +static gboolean +describe_array_values (guint64 objectId, int startIdx, int count, int gpflags) { if (count == 0) return TRUE; @@ -1229,7 +1268,7 @@ describe_array_values (guint64 objectId, int startIdx, int count, gboolean expan for (int i = startIdx; i < endIdx; i ++) { mono_wasm_add_array_item(i); elem = (gpointer*)((char*)arr->vector + (i * esize)); - describe_value (m_class_get_byval_arg (m_class_get_element_class (klass)), elem, expandValueType); + describe_value (m_class_get_byval_arg (m_class_get_element_class (klass)), elem, gpflags); } return TRUE; } @@ -1237,14 +1276,14 @@ describe_array_values (guint64 objectId, int startIdx, int count, gboolean expan static void describe_async_method_locals (InterpFrame *frame, MonoMethod *method) { - //Async methods are special in the way that local variables can be lifted to generated class fields + //Async methods are special in the way that local variables can be lifted to generated class fields gpointer addr = NULL; if (mono_debug_lookup_method_async_debug_info (method)) { addr = mini_get_interp_callbacks ()->frame_get_this (frame); MonoObject *obj = *(MonoObject**)addr; int objId = get_object_id (obj); mono_wasm_set_is_async_method (objId); - describe_object_properties (objId, TRUE, FALSE); + describe_object_properties (objId, TRUE, GPFLAG_NONE); } } @@ -1264,17 +1303,17 @@ describe_non_async_this (InterpFrame *frame, MonoMethod *method) mono_wasm_add_properties_var ("this", -1); if (m_class_is_valuetype (klass)) { - describe_value (type, obj, TRUE); + describe_value (type, obj, GPFLAG_EXPAND_VALUETYPES); } else { // this is an object, and we can retrieve the valuetypes in it later // through the object id - describe_value (type, addr, FALSE); + describe_value (type, addr, GPFLAG_NONE); } } } static gboolean -describe_variable (InterpFrame *frame, MonoMethod *method, int pos, gboolean expandValueType) +describe_variable (InterpFrame *frame, MonoMethod *method, int pos, int gpflags) { ERROR_DECL (error); MonoMethodHeader *header = NULL; @@ -1295,7 +1334,7 @@ describe_variable (InterpFrame *frame, MonoMethod *method, int pos, gboolean exp DEBUG_PRINTF (2, "adding val %p type [%p] %s\n", addr, type, mono_type_full_name (type)); - describe_value(type, addr, expandValueType); + describe_value(type, addr, gpflags); if (header) mono_metadata_free_mh (header); @@ -1324,7 +1363,7 @@ describe_variables_on_frame (MonoStackFrameInfo *info, MonoContext *ctx, gpointe for (int i = 0; i < data->len; i++) { - describe_variable (frame, method, data->pos[i], TRUE); + describe_variable (frame, method, data->pos[i], GPFLAG_EXPAND_VALUETYPES); } describe_async_method_locals (frame, method); @@ -1343,7 +1382,7 @@ mono_wasm_get_deref_ptr_value (void *value_addr, MonoClass *klass) } mono_wasm_add_properties_var ("deref", -1); - describe_value (type->data.type, value_addr, TRUE); + describe_value (type->data.type, value_addr, GPFLAG_EXPAND_VALUETYPES); return TRUE; } @@ -1363,19 +1402,19 @@ mono_wasm_get_local_vars (int scope, int* pos, int len) } EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_get_object_properties (int object_id, gboolean expand_value_types) +mono_wasm_get_object_properties (int object_id, int gpflags) { - DEBUG_PRINTF (2, "getting properties of object %d\n", object_id); + DEBUG_PRINTF (2, "getting properties of object %d, gpflags: %d\n", object_id, gpflags); - return describe_object_properties (object_id, FALSE, expand_value_types); + return describe_object_properties (object_id, FALSE, gpflags); } EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_get_array_values (int object_id, int start_idx, int count, gboolean expand_value_types) +mono_wasm_get_array_values (int object_id, int start_idx, int count, int gpflags) { - DEBUG_PRINTF (2, "getting array values %d, startIdx: %d, count: %d, expandValueType: %d\n", object_id, start_idx, count, expand_value_types); + DEBUG_PRINTF (2, "getting array values %d, startIdx: %d, count: %d, gpflags: 0x%x\n", object_id, start_idx, count, gpflags); - return describe_array_values (object_id, start_idx, count, expand_value_types); + return describe_array_values (object_id, start_idx, count, gpflags); } EMSCRIPTEN_KEEPALIVE gboolean diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/CallFunctionOnTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/CallFunctionOnTests.cs index d6169102bb12b8..09094cea2212ff 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/CallFunctionOnTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/CallFunctionOnTests.cs @@ -272,7 +272,7 @@ public async Task RunOnVTArray(bool roundtrip) => await RunCallFunctionOn( var act_i_props = await GetProperties(act_i["value"]["objectId"]?.Value()); await CheckProps(act_i_props, new { - dt = TValueType("System.DateTime", new DateTime(2020 + (i * 2), 1, 2, 3, 4, 5).ToString()), + dt = TDateTime(new DateTime(2020 + (i * 2), 1, 2, 3, 4, 5)), gs = TValueType("Math.GenericStruct") }, "obj_own ss_arr[{i}]"); @@ -320,12 +320,10 @@ public async Task RunOnCFOValueTypeResult(bool roundtrip) => await RunCallFuncti var dt = new DateTime(2020, 1, 2, 3, 4, 5); await CheckProps(obj_own_val, new { - dt = TValueType("System.DateTime", dt.ToString()), + dt = TDateTime(dt), gs = TValueType("Math.GenericStruct") }, $"obj_own-props"); - await CheckDateTime(obj_own_val, "dt", dt); - var gs_props = await GetObjectOnLocals(obj_own_val, "gs"); await CheckProps(gs_props, new { @@ -649,16 +647,9 @@ public async Task PropertyGettersTest(string eval_fn, string method_name, int li // Auto properties show w/o getters, because they have // a backing field - DTAutoProperty = TValueType("System.DateTime", dt.ToString()) + DTAutoProperty = TDateTime(dt) }, local_name); - // Automatic properties don't have invokable getters, because we can get their - // value from the backing field directly - { - var dt_auto_props = await GetObjectOnLocals(obj_props, "DTAutoProperty"); - await CheckDateTime(obj_props, "DTAutoProperty", dt); - } - // Invoke getters, and check values dt = new DateTime(3, 4, 5, 6, 7, 8); @@ -669,8 +660,7 @@ public async Task PropertyGettersTest(string eval_fn, string method_name, int li await CheckValue(res.Value["result"], JObject.FromObject(new { type = "string", value = $"String property, V: 0xDEADBEEF" }), $"{local_name}.String"); res = await InvokeGetter(obj, get_args_fn(new[] { "DT" }), cfo_fn); - await CheckValue(res.Value["result"], TValueType("System.DateTime", dt.ToString()), $"{local_name}.DT"); - await CheckDateTimeValue(res.Value["result"], dt); + await CheckValue(res.Value["result"], TDateTime(dt), $"{local_name}.DT"); // Check arrays through getters @@ -696,8 +686,8 @@ public async Task PropertyGettersTest(string eval_fn, string method_name, int li var arr_elems = await GetProperties(res.Value["result"]?["objectId"]?.Value()); var exp_elems = new[] { - TValueType("System.DateTime", dt0.ToString()), - TValueType("System.DateTime", dt1.ToString()), + TDateTime(dt0), + TDateTime(dt1) }; await CheckProps(arr_elems, exp_elems, $"{local_name}.DTArray"); @@ -707,6 +697,38 @@ public async Task PropertyGettersTest(string eval_fn, string method_name, int li } }); + [Fact] + public async Task InvokeInheritedAndPrivateGetters() => await CheckInspectLocalsAtBreakpointSite( + $"DebuggerTests.GetPropertiesTests.DerivedClass", "InstanceMethod", 1, "InstanceMethod", + $"window.setTimeout(function() {{ invoke_static_method_async ('[debugger-test] DebuggerTests.GetPropertiesTests.DerivedClass:run'); }})", + wait_for_event_fn: async (pause_location) => + { + var frame_id = pause_location["callFrames"][0]["callFrameId"].Value(); + var frame_locals = await GetProperties(frame_id); + var this_obj = GetAndAssertObjectWithName(frame_locals, "this"); + + var args = new[] + { + // private + ("_DTProp", TDateTime(new DateTime(2200, 5, 6, 7, 8, 9))), + + // overridden + ("DateTimeForOverride", TDateTime(new DateTime(2190, 9, 7, 5, 3, 2))), + ("FirstName", TString("DerivedClass#FirstName")), + ("StringPropertyForOverrideWithAutoProperty", TString("DerivedClass#StringPropertyForOverrideWithAutoProperty")), + + // inherited + ("_base_dateTime", TDateTime(new DateTime(2134, 5, 7, 1, 9, 2))) + }; + + foreach (var (name, expected) in args) + { + var res = await InvokeGetter(this_obj, name); + await CheckValue(res.Value["result"], expected, name); + } + }); + + [Theory] [InlineData("invoke_static_method_async ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTestAsync');", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 38, 12, true)] [InlineData("invoke_static_method_async ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTestAsync');", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 38, 12, false)] diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 14da2b0d2be6d2..2218b9a7945114 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -107,6 +107,7 @@ public async Task EvaluateMethodLocals(string type, string method, string bp_fun var dt = new DateTime(2025, 3, 5, 7, 9, 11); await EvaluateOnCallFrameAndCheck(id, + (" d ", TNumber(401)), ("d", TNumber(401)), (" d", TNumber(401)), ("e", TNumber(402)), @@ -171,6 +172,9 @@ await EvaluateOnCallFrameAndCheck(id, var (local_gs, _) = await EvaluateOnCallFrame(id, "local_gs"); await CheckValue(local_gs, TValueType("DebuggerTests.SimpleGenericStruct"), nameof(local_gs)); + (local_gs, _) = await EvaluateOnCallFrame(id, " local_gs"); + await CheckValue(local_gs, TValueType("DebuggerTests.SimpleGenericStruct"), nameof(local_gs)); + var local_gs_props = await GetProperties(local_gs["objectId"]?.Value()); await CheckProps(local_gs_props, new { @@ -206,6 +210,41 @@ await EvaluateOnCallFrameAndCheck(id, ($"local_dt.Date.Year * 10", TNumber(10))); }); + [Theory] + [InlineData("")] + [InlineData("this.")] + public async Task InheritedAndPrivateMembersInAClass(string prefix) + => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.GetPropertiesTests.DerivedClass", "InstanceMethod", 1, "InstanceMethod", + $"window.setTimeout(function() {{ invoke_static_method_async('[debugger-test] DebuggerTests.GetPropertiesTests.DerivedClass:run');}}, 1);", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + + foreach(var pad in new [] { String.Empty, " "}) + { + var padded_prefix = pad + prefix; + await EvaluateOnCallFrameAndCheck(id, + // overridden + ($"{padded_prefix}FirstName + \"_foo\"", TString("DerivedClass#FirstName_foo")), + ($"{padded_prefix}DateTimeForOverride.Date.Year", TNumber(2190)), + ($"{padded_prefix}DateTimeForOverride.Date.Year - 10", TNumber(2180)), + ($"\"foo_\" + {padded_prefix}StringPropertyForOverrideWithAutoProperty", TString("foo_DerivedClass#StringPropertyForOverrideWithAutoProperty")), + + // private + ($"{padded_prefix}_stringField + \"_foo\"", TString("DerivedClass#_stringField_foo")), + ($"{padded_prefix}_stringField", TString("DerivedClass#_stringField")), + ($"{padded_prefix}_dateTime.Second + 4", TNumber(7)), + ($"{padded_prefix}_DTProp.Second + 4", TNumber(13)), + + // inherited public + ($"\"foo_\" + {padded_prefix}Base_AutoStringProperty", TString("foo_base#Base_AutoStringProperty")), + // inherited private + ($"{padded_prefix}_base_dateTime.Date.Year - 10", TNumber(2124)) + ); + } + }); + [Fact] public async Task EvaluateSimpleExpressions() => await CheckInspectLocalsAtBreakpointSite( "DebuggerTests.EvaluateTestsClass/TestEvaluate", "run", 9, "run", diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/ExceptionTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/ExceptionTests.cs index 415956a6c2e3ba..6f339c28ea90fd 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/ExceptionTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/ExceptionTests.cs @@ -19,8 +19,6 @@ public async Task ExceptionTestAll() var insp = new Inspector(); //Collect events var scripts = SubscribeToScripts(insp); - int line = 15; - int col = 20; string entry_method_name = "[debugger-test] DebuggerTests.ExceptionTestsClass:TestExceptions"; await Ready(); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs new file mode 100644 index 00000000000000..2f35e8cfdc0e09 --- /dev/null +++ b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs @@ -0,0 +1,388 @@ +// 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.Threading.Tasks; +using Microsoft.WebAssembly.Diagnostics; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace DebuggerTests +{ + public class GetPropertiesTests : DebuggerTestBase + { + public static TheoryData, bool> ClassGetPropertiesTestData(bool is_async) + { + var data = new TheoryData, bool>(); + + var type_name = "DerivedClass"; + var all_props = new Dictionary() + { + {"_stringField", (TString("DerivedClass#_stringField"), true)}, + {"_dateTime", (TDateTime(new DateTime(2020, 7, 6, 5, 4, 3)), true)}, + {"_DTProp", (TGetter("_DTProp"), true)}, + + // own public + {"a", (TNumber(4), true)}, + {"DateTime", (TGetter("DateTime"), true)}, + {"AutoStringProperty", (TString("DerivedClass#AutoStringProperty"), true)}, + {"FirstName", (TGetter("FirstName"), true)}, + {"DateTimeForOverride", (TGetter("DateTimeForOverride"), true)}, + + {"StringPropertyForOverrideWithAutoProperty", (TString("DerivedClass#StringPropertyForOverrideWithAutoProperty"), true)}, + {"Base_AutoStringPropertyForOverrideWithField", (TString("DerivedClass#Base_AutoStringPropertyForOverrideWithField"), true)}, + {"Base_GetterForOverrideWithField", (TString("DerivedClass#Base_GetterForOverrideWithField"), true)}, + {"BaseBase_MemberForOverride", (TString("DerivedClass#BaseBase_MemberForOverride"), true)}, + + // indexers don't show up in getprops + // {"Item", (TSymbol("int { get; }"), true)}, + + // inherited private + {"_base_name", (TString("private_name"), false)}, + {"_base_dateTime", (TGetter("_base_dateTime"), false)}, + + // inherited public + {"Base_AutoStringProperty", (TString("base#Base_AutoStringProperty"), false)}, + {"base_num", (TNumber(5), false)}, + {"LastName", (TGetter("LastName"), false)} + }; + + // default, all properties + // n, n + data.Add(type_name, null, null, all_props.Keys.ToArray(), all_props, is_async); + // f, f + data.Add(type_name, false, false, all_props.Keys.ToArray(), all_props, is_async); + // f, n + data.Add(type_name, false, null, all_props.Keys.ToArray(), all_props, is_async); + // n, f + data.Add(type_name, null, false, all_props.Keys.ToArray(), all_props, is_async); + + // all own + // t, f + // t, n + foreach (bool? accessors in new bool?[] { false, null }) + { + // Breaking from JS behavior, we return *all* members irrespective of `ownMembers` + data.Add(type_name, true, accessors, all_props.Keys.ToArray(), all_props, is_async); + // data.Add(type_name, true, accessors, new[] + // { + // "_stringField", + // "_dateTime", + // "_DTProp", + // "a", + // "DateTime", + // "AutoStringProperty", + // "FirstName", + // "DateTimeForOverride", + // "StringPropertyForOverrideWithAutoProperty" + // }, all_props, is_async); + } + + var all_accessors = new[] + { + "_DTProp", + "DateTime", + "_base_dateTime", + "FirstName", + "LastName", + "DateTimeForOverride" + }; + + var only_own_accessors = new[] + { + "_DTProp", + "DateTime", + "FirstName", + "DateTimeForOverride" + }; + + // all own, only accessors + // t, t + + // Breaking from JS behavior, we return *all* members irrespective of `ownMembers` + // data.Add(type_name, true, true, only_own_accessors, all_props, is_async); + data.Add(type_name, true, true, all_accessors, all_props, is_async); + + // all accessors + // f, t + // n, t + foreach (bool? own in new bool?[] { false, null }) + { + data.Add(type_name, own, true, all_accessors, all_props, is_async); + } + + return data; + } + + public static TheoryData, bool> StructGetPropertiesTestData(bool is_async) + { + var data = new TheoryData, bool>(); + + var type_name = "CloneableStruct"; + var all_props = new Dictionary() + { + {"_stringField", (TString("CloneableStruct#_stringField"), true)}, + {"_dateTime", (TDateTime(new DateTime(2020, 7, 6, 5, 4, 3 + 3)), true)}, + {"_DTProp", (TGetter("_DTProp"), true)}, + + // own public + {"a", (TNumber(4), true)}, + {"DateTime", (TGetter("DateTime"), true)}, + {"AutoStringProperty", (TString("CloneableStruct#AutoStringProperty"), true)}, + {"FirstName", (TGetter("FirstName"), true)}, + {"LastName", (TGetter("LastName"), true)}, + + // indexers don't show up in getprops + // {"Item", (TSymbol("int { get; }"), true)} + }; + + // default, all properties + data.Add(type_name, null, null, all_props.Keys.ToArray(), all_props, is_async); + data.Add(type_name, false, false, all_props.Keys.ToArray(), all_props, is_async); + + // all own + data.Add(type_name, true, false, all_props.Keys.ToArray(), all_props, is_async); + + var all_accessor_names = new[] + { + "_DTProp", + "DateTime", + "FirstName", + "LastName" + }; + + // all own, only accessors + data.Add(type_name, true, true, all_accessor_names, all_props, is_async); + // all accessors + data.Add(type_name, false, true, all_accessor_names, all_props, is_async); + + return data; + } + + [Theory] + [MemberData(nameof(ClassGetPropertiesTestData), parameters: true)] + [MemberData(nameof(ClassGetPropertiesTestData), parameters: false)] + [MemberData(nameof(StructGetPropertiesTestData), parameters: true)] + [MemberData(nameof(StructGetPropertiesTestData), parameters: false)] + public async Task InspectTypeInheritedMembers(string type_name, bool? own_properties, bool? accessors_only, string[] expected_names, Dictionary all_props, bool is_async) => await CheckInspectLocalsAtBreakpointSite( + $"DebuggerTests.GetPropertiesTests.{type_name}", + $"InstanceMethod{(is_async ? "Async" : "")}", 1, (is_async ? "MoveNext" : "InstanceMethod"), + $"window.setTimeout(function() {{ invoke_static_method_async ('[debugger-test] DebuggerTests.GetPropertiesTests.{type_name}:run'); }})", + wait_for_event_fn: async (pause_location) => + { + var frame_id = pause_location["callFrames"][0]["callFrameId"].Value(); + var frame_locals = await GetProperties(frame_id); + var this_obj = GetAndAssertObjectWithName(frame_locals, "this"); + var this_props = await GetProperties(this_obj["value"]?["objectId"]?.Value(), own_properties: own_properties, accessors_only: accessors_only); + + await CheckExpectedProperties(expected_names, name => GetAndAssertObjectWithName(this_props, name), all_props); + + // indexer properties shouldn't show up here + var item = this_props.FirstOrDefault(jt => jt["name"]?.Value() == "Item"); + Assert.Null(item); + + // Useful for debugging: AssertHasOnlyExpectedProperties(expected_names, this_props.Values()); + AssertEqual(expected_names.Length, this_props.Count(), $"expected number of properties"); + }); + + public static IEnumerable MembersForLocalNestedStructData(bool is_async) + => StructGetPropertiesTestData(false).Select (datum => datum [1..]); + + [Theory] + [MemberData(nameof(MembersForLocalNestedStructData), parameters: false)] + [MemberData(nameof(MembersForLocalNestedStructData), parameters: true)] + public async Task MembersForLocalNestedStruct(bool? own_properties, bool? accessors_only, string[] expected_names, Dictionary all_props, bool is_async) => await CheckInspectLocalsAtBreakpointSite( + $"DebuggerTests.GetPropertiesTests.NestedStruct", + is_async ? $"TestNestedStructStaticAsync" : "TestNestedStructStatic", + 2, + is_async ? "MoveNext" : $"TestNestedStructStatic", + $"window.setTimeout(function() {{ invoke_static_method_async ('[debugger-test] DebuggerTests.GetPropertiesTests.NestedStruct:run'); }})", + wait_for_event_fn: async (pause_location) => + { + var ns_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ns"); + + var cs_obj = GetAndAssertObjectWithName(ns_props, "cloneableStruct"); + var cs_props = await GetProperties(cs_obj["value"]?["objectId"]?.Value(), own_properties: own_properties, accessors_only: accessors_only); + + await CheckExpectedProperties(expected_names, name => GetAndAssertObjectWithName(cs_props, name), all_props); + AssertHasOnlyExpectedProperties(expected_names, cs_props.Values()); + + // indexer properties shouldn't show up here + var item = cs_props.FirstOrDefault(jt => jt["name"]?.Value() == "Item"); + Assert.Null(item); + + // Useful for debugging: AssertHasOnlyExpectedProperties(expected_names, this_props.Values()); + AssertEqual(expected_names.Length, cs_props.Count(), $"expected number of properties"); + }); + + public static TheoryData JSGetPropertiesTestData(bool test_js)=> new TheoryData + { + // default, no args set + { + test_js, + null, null, new[] + { + "owner_name", + "owner_last_name", + "kind", + "make", + "available" + } + }, + + // all props + { + test_js, + false, false, new[] + { + "owner_name", + "owner_last_name", + "kind", + "make", + "available" + } + }, + + // all own + { + test_js, + true, false, new[] + { + "owner_name", + "owner_last_name" + } + }, + + // all own accessors + { + test_js, + true, true, new[] + { + "owner_last_name" + } + }, + + // all accessors + { + test_js, + false, true, new[] + { + "available", + "owner_last_name" + } + } + }; + + [Theory] + [MemberData(nameof(JSGetPropertiesTestData), parameters: true)] + // Note: Disabled because we don't match JS's behavior here! + // We return inherited members too for `ownProperties:true` + // [MemberData(nameof(JSGetPropertiesTestData), parameters: false)] + public async Task GetPropertiesTestJSAndManaged(bool test_js, bool? own_properties, bool? accessors_only, string[] expected_names) + { + 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); + string eval_expr; + if (test_js) + { + await SetBreakpoint("/other.js", 93, 1); + eval_expr = "window.setTimeout(function() { get_properties_test (); }, 1)"; + } + else + { + await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.GetPropertiesTests.DerivedClassForJSTest", "run", 2); + eval_expr = "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.GetPropertiesTests.DerivedClassForJSTest:run'); }, 1)"; + } + + var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token); + var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + + var id = pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value(); + + var frame_locals = await GetProperties(id); + var obj = GetAndAssertObjectWithName(frame_locals, "obj"); + var obj_props = await GetProperties(obj["value"]?["objectId"]?.Value(), + own_properties: own_properties, accessors_only: accessors_only); + + IEnumerable filtered_props; + if (test_js) + { + filtered_props = obj_props.Children().Where(jt => jt["enumerable"]?.Value() == true); + } + else + { + // we don't set `enumerable` right now + filtered_props = obj_props.Children().Where(jt=> true); + } + + var expected_props = new Dictionary () + { + // own + {"owner_name", (TString("foo"), true)}, + {"owner_last_name", (TGetter("owner_last_name"), true)}, + + // inherited + {"kind", (TString("car"), false)}, + {"make", (TString("mini"), false)}, + {"available", (TGetter("available"), false)}, + }; + + await CheckExpectedProperties( + expected_names, + name => filtered_props.Where(jt => jt["name"]?.Value () == name).SingleOrDefault(), + expected_props); + + AssertEqual(expected_names.Length, filtered_props.Count(), $"expected number of properties"); + }); + } + + private async Task CheckExpectedProperties(string[] expected_names, Func get_actual_prop, Dictionary all_props) + { + foreach (var exp_name in expected_names) + { + if (!all_props.TryGetValue(exp_name, out var expected)) + { + Assert.True(false, $"Test Bug: Could not find property named {exp_name}"); + } + var (exp_prop, is_own) = expected; + var actual_prop = get_actual_prop(exp_name); + + AssertEqual(is_own, actual_prop["isOwn"]?.Value () == true, $"{exp_name}#isOwn"); + + if (exp_prop["__custom_type"]?.Value() == "getter") + { + // HACK! CheckValue normally expects to get a value:{} + // from `{name: "..", value: {}, ..} + // but for getters we actually have: `{name: "..", get: {..} }` + // and no `value` + await CheckValue(actual_prop, exp_prop, exp_name); + } + else + { + await CheckValue(actual_prop["value"], exp_prop, exp_name); + } + } + } + + private static void AssertHasOnlyExpectedProperties (string[] expected_names, IEnumerable actual) + { + var exp = new HashSet(expected_names); + + foreach (var obj in actual) + { + if (!exp.Contains(obj["name"]?.Value ())) + Console.WriteLine ($"Unexpected: {obj}"); + } + } + + } +} diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/PointerTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/PointerTests.cs index d18f054d9d7875..ee703b29648bb1 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/PointerTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/PointerTests.cs @@ -18,8 +18,8 @@ public class PointerTests : DebuggerTestBase new TheoryData { { $"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 } + { $"invoke_static_method_async ('[debugger-test] DebuggerTests.PointerTests:LocalPointersAsync');", "DebuggerTests.PointerTests", "LocalPointersAsync", 32, "LocalPointersAsync", false }, + { $"invoke_static_method_async ('[debugger-test] DebuggerTests.PointerTests:LocalPointersAsync');", "DebuggerTests.PointerTests", "LocalPointersAsync", 32, "LocalPointersAsync", true } }; [Theory] @@ -155,7 +155,7 @@ public async Task InspectLocalPointersToValueTypes(string eval_fn, string type, var dt = new DateTime(5, 6, 7, 8, 9, 10); await CheckProps(locals, new { - dt = TValueType("System.DateTime", dt.ToString()), + dt = TDateTime(dt), dtp = TPointer("System.DateTime*"), dtp_null = TPointer("System.DateTime*", is_null: true), @@ -163,8 +163,6 @@ public async Task InspectLocalPointersToValueTypes(string eval_fn, string type, gsp_null = TPointer("DebuggerTests.GenericStructWithUnmanagedT*") }, "locals", num_fields: 26); - await CheckDateTime(locals, "dt", dt); - // *dtp var props = await GetObjectOnLocals(locals, "dtp"); await CheckDateTime(props, "*dtp", dt); @@ -178,7 +176,7 @@ public async Task InspectLocalPointersToValueTypes(string eval_fn, string type, var gsp_deref_props = await GetObjectOnLocals(gsp_props, "*gsp"); await CheckProps(gsp_deref_props, new { - Value = TValueType("System.DateTime", gs_dt.ToString()), + Value = TDateTime(gs_dt), IntField = TNumber(4), DTPP = TPointer("System.DateTime**") }, "locals#gsp#deref"); @@ -201,7 +199,7 @@ public async Task InspectLocalPointersToValueTypes(string eval_fn, string type, 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()), + Value = TDateTime(gs_dt), IntField = TNumber(4), DTPP = TPointer("System.DateTime**") }, "locals#gsp#deref"); @@ -282,7 +280,7 @@ public async Task InspectLocalPointersToGenericValueTypeArrays(string eval_fn, s var gsp_deref_props = await GetObjectOnLocals(actual_elems[1], "*[1]"); await CheckProps(gsp_deref_props, new { - Value = TValueType("System.DateTime", gs_dt.ToString()), + Value = TDateTime(gs_dt), IntField = TNumber(4), DTPP = TPointer("System.DateTime**") }, "locals#gsp#deref"); @@ -300,7 +298,7 @@ public async Task InspectLocalPointersToGenericValueTypeArrays(string eval_fn, s var gsp_deref_props = await GetObjectOnLocals(actual_elems[2], "*[2]"); await CheckProps(gsp_deref_props, new { - Value = TValueType("System.DateTime", gs_dt.ToString()), + Value = TDateTime(gs_dt), IntField = TNumber(4), DTPP = TPointer("System.DateTime**") }, "locals#gsp#deref"); @@ -494,8 +492,8 @@ public async Task InspectValueTypePointersAsMethodArgs(string eval_fn, string ty { var actual_elems = await CheckArrayElements(dtpa_elems, new[] { - TValueType("System.DateTime", dt.ToString()), - null + TDateTime(dt), + null }); await CheckDateTime(actual_elems[0], "*[0]", dt); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/Support.cs b/src/mono/wasm/debugger/DebuggerTestSuite/Support.cs index bd0987c0e43877..e1970e8ab48b09 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/Support.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/Support.cs @@ -342,28 +342,28 @@ internal async Task CheckPointerValue(JToken locals, string name, JToken internal async Task CheckDateTime(JToken value, DateTime expected, string label = "") { await CheckValue(value, TValueType("System.DateTime", expected.ToString()), label); - await CheckDateTimeValue(value, expected); + await CheckDateTimeValue(value, expected, label); } - internal async Task CheckDateTime(JToken locals, string name, DateTime expected) + internal async Task CheckDateTime(JToken locals, string name, DateTime expected, string label="") { - var obj = GetAndAssertObjectWithName(locals, name); - await CheckDateTimeValue(obj["value"], expected); + var obj = GetAndAssertObjectWithName(locals, name, label); + await CheckDateTimeValue(obj["value"], expected, label); } - internal async Task CheckDateTimeValue(JToken value, DateTime expected) + internal async Task CheckDateTimeValue(JToken value, DateTime expected, string label="") { - await CheckDateTimeMembers(value, expected); + await CheckDateTimeMembers(value, expected, label); var res = await InvokeGetter(JObject.FromObject(new { value = value }), "Date"); - await CheckDateTimeMembers(res.Value["result"], expected.Date); + await CheckDateTimeMembers(res.Value["result"], expected.Date, label); // FIXME: check some float properties too - async Task CheckDateTimeMembers(JToken v, DateTime exp_dt) + async Task CheckDateTimeMembers(JToken v, DateTime exp_dt, string label="") { - AssertEqual("System.DateTime", v["className"]?.Value(), "className"); - AssertEqual(exp_dt.ToString(), v["description"]?.Value(), "description"); + AssertEqual("System.DateTime", v["className"]?.Value(), $"{label}#className"); + AssertEqual(exp_dt.ToString(), v["description"]?.Value(), $"{label}#description"); var members = await GetProperties(v["objectId"]?.Value()); @@ -409,11 +409,11 @@ internal void CheckArray(JToken locals, string name, string class_name, int leng GetAndAssertObjectWithName(locals, name)["value"], TArray(class_name, length), name).Wait(); - internal JToken GetAndAssertObjectWithName(JToken obj, string name) + internal JToken GetAndAssertObjectWithName(JToken obj, string name, string label="") { var l = obj.FirstOrDefault(jt => jt["name"]?.Value() == name); if (l == null) - Assert.True(false, $"Could not find variable '{name}'"); + Assert.True(false, $"[{label}] Could not find variable '{name}'"); return l; } @@ -613,7 +613,7 @@ internal async Task CheckCustomType(JToken actual_val, JToken exp_val, string la { // 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"]; + 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(), $"{label}-className"); @@ -732,17 +732,13 @@ internal async Task CheckValue(JToken actual_val, JToken exp_val, string label) var exp_val_str = jp.Value.Value(); bool null_or_empty_exp_val = String.IsNullOrEmpty(exp_val_str); - var actual_field_val = actual_val.Values().FirstOrDefault(a_jp => a_jp.Name == jp.Name); + var actual_field_val = actual_val?.Values()?.FirstOrDefault(a_jp => a_jp.Name == jp.Name); var actual_field_val_str = actual_field_val?.Value?.Value(); if (null_or_empty_exp_val && String.IsNullOrEmpty(actual_field_val_str)) continue; Assert.True(actual_field_val != null, $"[{label}] Could not find value field named {jp.Name}"); - - Assert.True(exp_val_str == actual_field_val_str, - $"[{label}] Value for json property named {jp.Name} didn't match.\n" + - $"Expected: {jp.Value.Value()}\n" + - $"Actual: {actual_field_val.Value.Value()}"); + AssertEqual(exp_val_str, actual_field_val_str, $"[{label}] Value for json property named {jp.Name} didn't match."); } } @@ -788,7 +784,7 @@ internal async Task GetObjectOnLocals(JToken locals, string name) } /* @fn_args is for use with `Runtime.callFunctionOn` only */ - internal async Task GetProperties(string id, JToken fn_args = null, bool expect_ok = true) + internal async Task GetProperties(string id, JToken fn_args = null, bool? own_properties = null, bool? accessors_only = null, bool expect_ok = true) { if (ctx.UseCallFunctionOnBeforeGetProperties && !id.StartsWith("dotnet:scope:")) { @@ -812,6 +808,14 @@ internal async Task GetProperties(string id, JToken fn_args = null, bool { objectId = id }); + if (own_properties.HasValue) + { + get_prop_req["ownProperties"] = own_properties.Value; + } + if (accessors_only.HasValue) + { + get_prop_req["accessorPropertiesOnly"] = accessors_only.Value; + } var frame_props = await ctx.cli.SendCommand("Runtime.getProperties", get_prop_req, ctx.token); AssertEqual(expect_ok, frame_props.IsOk, $"Runtime.getProperties returned {frame_props.IsOk} instead of {expect_ok}, for {get_prop_req}, with Result: {frame_props}"); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs index 03fe684e110892..958b1761a56861 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs @@ -676,12 +676,10 @@ await insp.Ready(async (cli, token) => var dt = new DateTime(2020, 1, 2, 3, 4, 5); await CheckProps(ss_props, new { - dt = TValueType("System.DateTime", dt.ToString()), + dt = TDateTime(dt), gs = TValueType("Math.GenericStruct") }, "ss_props"); - await CheckDateTime(ss_props, "dt", new DateTime(2020, 1, 2, 3, 4, 5)); - // Check OuterMethod frame var locals_m1 = await GetLocalsForFrame(wait_res["callFrames"][1], debugger_test_loc, 87, 8, "OuterMethod"); Assert.Equal(5, locals_m1.Count()); @@ -927,7 +925,7 @@ await insp.Ready(async (cli, token) => { V = TGetter("V"), str_member = TString("set in MethodWithLocalStructs#SimpleStruct#str_member"), - dt = TValueType("System.DateTime", dt.ToString()), + dt = TDateTime(dt), gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), Kind = TEnum("System.DateTimeKind", "Utc") }, "ss_local"); @@ -935,8 +933,6 @@ await insp.Ready(async (cli, token) => { var gres = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_local"), "V"); await CheckValue(gres.Value["result"], TNumber(0xDEADBEEF + 2), $"ss_local#V"); - // Check ss_local.dt - await CheckDateTime(ss_local_props, "dt", dt); // Check ss_local.gs var gs_props = await GetObjectOnLocals(ss_local_props, "gs"); @@ -964,18 +960,17 @@ await insp.Ready(async (cli, token) => foreach (var (name, bias, dt_kind) in exp) { dt = new DateTime(2020 + bias, 1 + bias, 2 + bias, 3 + bias, 5 + bias, 6 + bias); - var ssp_props = await CompareObjectPropertiesFor(vt_local_props, name, + await CompareObjectPropertiesFor(vt_local_props, name, new { V = TGetter("V"), str_member = TString($"{name}#string#0#SimpleStruct#str_member"), - dt = TValueType("System.DateTime", dt.ToString()), + dt = TDateTime(dt), gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), Kind = TEnum("System.DateTimeKind", dt_kind) }, label: $"vt_local_props.{name}"); - await CheckDateTime(ssp_props, "dt", dt); var gres = await InvokeGetter(GetAndAssertObjectWithName(vt_local_props, name), "V"); await CheckValue(gres.Value["result"], TNumber(0xDEADBEEF + (uint)dt.Month), $"{name}#V"); } @@ -1018,7 +1013,7 @@ await insp.Ready(async (cli, token) => { V = TGetter("V"), str_member = TString("ss_local#SimpleStruct#string#0#SimpleStruct#str_member"), - dt = TValueType("System.DateTime", dt.ToString()), + dt = TDateTime(dt), gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), Kind = TEnum("System.DateTimeKind", "Local") }; @@ -1037,9 +1032,6 @@ await insp.Ready(async (cli, token) => await CheckValue(res.Value["result"], TNumber(0xDEADBEEF + (uint)dt.Month), "ss_arg#V"); { - // Check ss_local.dt - await CheckDateTime(ss_arg_props, "dt", dt); - // Check ss_local.gs await CompareObjectPropertiesFor(ss_arg_props, "gs", ss_local_gs); } @@ -1059,7 +1051,7 @@ await insp.Ready(async (cli, token) => { V = TGetter("V"), str_member = TString("ValueTypesTest#MethodWithStructArgs#updated#ss_arg#str_member"), - dt = TValueType("System.DateTime", dt.ToString()), + dt = TDateTime(dt), gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), Kind = TEnum("System.DateTimeKind", "Utc") }; @@ -1078,8 +1070,6 @@ await insp.Ready(async (cli, token) => List = TObject("System.Collections.Generic.List"), Options = TEnum("DebuggerTests.Options", "Option1") }); - - await CheckDateTime(ss_arg_props, "dt", dt); } // Check locals on previous frame, same as earlier in this test @@ -1161,20 +1151,16 @@ async Task CheckLocals(JToken pause_location, DateTime obj_dt, DateTime vt_dt) { await CheckProps(obj_props, new { - DT = TValueType("System.DateTime", obj_dt.ToString()) + DT = TDateTime(obj_dt) }, "locals#obj.DT", num_fields: 5); - - await CheckDateTime(obj_props, "DT", obj_dt); } var vt_props = await GetObjectOnLocals(locals, "vt"); { await CheckProps(vt_props, new { - DT = TValueType("System.DateTime", vt_dt.ToString()) + DT = TDateTime(vt_dt) }, "locals#obj.DT", num_fields: 5); - - await CheckDateTime(vt_props, "DT", vt_dt); } } } @@ -1253,10 +1239,8 @@ async Task CheckArrayElements(JToken pause_location, DateTime dt) var sst0 = await GetObjectOnLocals(ssta, "0"); await CheckProps(sst0, new { - DT = TValueType("System.DateTime", dt.ToString()) + DT = TDateTime(dt) }, "dta [0]", num_fields: 5); - - await CheckDateTime(sst0, "DT", dt); } } @@ -1300,7 +1284,7 @@ await insp.Ready(async (cli, token) => { V = TGetter("V"), str_member = TString("set in MethodWithLocalStructsStaticAsync#SimpleStruct#str_member"), - dt = TValueType("System.DateTime", dt.ToString()), + dt = TDateTime(dt), gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), Kind = TEnum("System.DateTimeKind", "Utc") }, "ss_local"); @@ -1309,9 +1293,6 @@ await insp.Ready(async (cli, token) => var gres = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_local"), "V"); await CheckValue(gres.Value["result"], TNumber(0xDEADBEEF + 2), $"ss_local#V"); - // Check ss_local.dt - await CheckDateTime(ss_local_props, "dt", dt); - // Check ss_local.gs await CompareObjectPropertiesFor(ss_local_props, "gs", new @@ -1375,8 +1356,8 @@ await insp.Ready(async (cli, token) => await CheckProps(frame_locals, new { call_other = TBool(call_other), - dt0 = TValueType("System.DateTime", dt0.ToString()), - dt1 = TValueType("System.DateTime", dt1.ToString()), + dt0 = TDateTime(dt0), + dt1 = TDateTime(dt1), dto = TValueType("System.DateTimeOffset", dto.ToString()), ts = TValueType("System.TimeSpan", ts.ToString()), dec = TValueType("System.Decimal", "123987123"), @@ -1413,10 +1394,10 @@ await CompareObjectPropertiesFor(frame_locals, "dto", var DT = new DateTime(2004, 10, 15, 1, 2, 3); var DTO = new DateTimeOffset(dt0, new TimeSpan(2, 14, 0)); - var obj_props = await CompareObjectPropertiesFor(frame_locals, "obj", + await CompareObjectPropertiesFor(frame_locals, "obj", new { - DT = TValueType("System.DateTime", DT.ToString()), + DT = TDateTime(DT), DTO = TValueType("System.DateTimeOffset", DTO.ToString()), TS = TValueType("System.TimeSpan", ts.ToString()), Dec = TValueType("System.Decimal", "1239871"), @@ -1427,7 +1408,7 @@ await CompareObjectPropertiesFor(frame_locals, "dto", var sst_props = await CompareObjectPropertiesFor(frame_locals, "sst", new { - DT = TValueType("System.DateTime", DT.ToString()), + DT = TDateTime(DT), DTO = TValueType("System.DateTimeOffset", DTO.ToString()), TS = TValueType("System.TimeSpan", ts.ToString()), Dec = TValueType("System.Decimal", "1239871"), diff --git a/src/mono/wasm/debugger/tests/debugger-array-test.cs b/src/mono/wasm/debugger/tests/debugger-array-test.cs index 282db743e882f5..26acccf68dc767 100644 --- a/src/mono/wasm/debugger/tests/debugger-array-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-array-test.cs @@ -168,7 +168,7 @@ public static async Task ValueTypeLocalsAsync(bool call_other = false) var point = new Point { X = 45, Y = 51, Id = "point#Id", Color = RGB.Green }; Console.WriteLine($"point_arr: {point_arr.Length}, T: {t1}, point: {point}"); - return (t1, new Point[] { point_arr[0], point_arr[1], point }); + return await Task.FromResult((t1, new Point[] { point_arr[0], point_arr[1], point })); } // A workaround for method invocations on structs not working right now @@ -249,7 +249,7 @@ public async Task AsyncInstanceMethod(SimpleClass sc_arg) { var local_gs = new SimpleGenericStruct { Id = "local_gs#Id", Color = RGB.Green, Value = 4 }; sc_arg.Id = "sc_arg#Id"; - Console.WriteLine($"AsyncInstanceMethod sc_arg: {sc_arg.Id}, local_gs: {local_gs.Id}"); + Console.WriteLine($"AsyncInstanceMethod sc_arg: {sc_arg.Id}, local_gs: {local_gs.Id}"); await Task.CompletedTask; } public void GenericInstanceMethod(T sc_arg) where T : SimpleClass diff --git a/src/mono/wasm/debugger/tests/debugger-get-properties-test.cs b/src/mono/wasm/debugger/tests/debugger-get-properties-test.cs new file mode 100644 index 00000000000000..60e2d485becd44 --- /dev/null +++ b/src/mono/wasm/debugger/tests/debugger-get-properties-test.cs @@ -0,0 +1,206 @@ +// 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.Threading.Tasks; +namespace DebuggerTests.GetPropertiesTests +{ + + public interface IFirstName + { + string FirstName { get; } + } + + public interface ILastName + { + string LastName { get; } + } + + public interface IName : IFirstName, ILastName + {} + + public class BaseBaseClass + { + public string BaseBase_MemberForOverride { get; set; } + } + + public class BaseClass : BaseBaseClass, IName + { + private string _base_name; + private DateTime _base_dateTime => new DateTime(2134, 5, 7, 1, 9, 2); + protected int base_num; + + public string Base_AutoStringProperty { get; set; } + public virtual DateTime DateTimeForOverride { get; set; } + public string Base_AutoStringPropertyForOverrideWithField { get; set; } + + public virtual string StringPropertyForOverrideWithAutoProperty => "base#StringPropertyForOverrideWithAutoProperty"; + public virtual string Base_GetterForOverrideWithField => "base#Base_GetterForOverrideWithField"; + public new string BaseBase_MemberForOverride => "Base#BaseBase_MemberForOverride"; + + public string this[string s] => s + "_hello"; + + public BaseClass() + { + _base_name = "private_name"; + base_num = 5; + Base_AutoStringProperty = "base#Base_AutoStringProperty"; + DateTimeForOverride = new DateTime(2250, 4, 5, 6, 7, 8); + //AutoStringPropertyForOverride = "base#AutoStringPropertyForOverride"; + } + + public string GetBaseName() => _base_name; + + public virtual string FirstName => "BaseClass#FirstName"; + public virtual string LastName => "BaseClass#LastName"; + } + + public class DerivedClass : BaseClass, ICloneable + { + // public string _base_name = "DerivedClass#_base_name"; + private string _stringField = "DerivedClass#_stringField"; + private DateTime _dateTime = new DateTime(2020, 7, 6, 5, 4, 3); + private DateTime _DTProp => new DateTime(2200, 5, 6, 7, 8, 9); + + public int a; + public DateTime DateTime => _DTProp.AddMinutes(10); + public string AutoStringProperty { get; set; } + public override string FirstName => "DerivedClass#FirstName"; + + // Overrides an auto-property with a getter + public override DateTime DateTimeForOverride => new DateTime(2190, 9, 7, 5, 3, 2); + public override string StringPropertyForOverrideWithAutoProperty { get; } + public new string Base_AutoStringPropertyForOverrideWithField = "DerivedClass#Base_AutoStringPropertyForOverrideWithField"; + public new string Base_GetterForOverrideWithField = "DerivedClass#Base_GetterForOverrideWithField"; + public new string BaseBase_MemberForOverride = "DerivedClass#BaseBase_MemberForOverride"; + + public int this[int i, string s] => i + 1 + s.Length; + + object ICloneable.Clone() + { + // not meant to be used! + return new DerivedClass(); + } + + public DerivedClass() + { + a = 4; + AutoStringProperty = "DerivedClass#AutoStringProperty"; + StringPropertyForOverrideWithAutoProperty = "DerivedClass#StringPropertyForOverrideWithAutoProperty"; + } + + public static void run() + { + new DerivedClass().InstanceMethod (); + new DerivedClass().InstanceMethodAsync ().Wait(); + } + + public string GetStringField() => _stringField; + + public void InstanceMethod() + { + Console.WriteLine ($"break here"); + } + + public async Task InstanceMethodAsync() + { + Console.WriteLine ($"break here"); + await Task.CompletedTask; + } + } + + public struct CloneableStruct : ICloneable, IName + { + private string _stringField; + private DateTime _dateTime; + private DateTime _DTProp => new DateTime(2200, 5, 6, 7, 8, 9); + + public int a; + public DateTime DateTime => _DTProp.AddMinutes(10); + public string AutoStringProperty { get; set; } + public string FirstName => "CloneableStruct#FirstName"; + public string LastName => "CloneableStruct#LastName"; + public int this[int i] => i + 1; + + object ICloneable.Clone() + { + // not meant to be used! + return new CloneableStruct(0); + } + + public CloneableStruct(int bias) + { + a = 4; + _stringField = "CloneableStruct#_stringField"; + _dateTime = new DateTime(2020, 7, 6, 5, 4, 3 + bias); + AutoStringProperty = "CloneableStruct#AutoStringProperty"; + } + + public static void run() + { + new CloneableStruct(3).InstanceMethod (); + new CloneableStruct(3).InstanceMethodAsync ().Wait(); + } + + public string GetStringField() => _stringField; + + public void InstanceMethod() + { + Console.WriteLine ($"break here"); + } + + public async Task InstanceMethodAsync() + { + Console.WriteLine ($"break here"); + await Task.CompletedTask; + } + } + + public struct NestedStruct + { + public CloneableStruct cloneableStruct; + + public NestedStruct(int bias) + { + cloneableStruct = new CloneableStruct(bias); + } + + public static void run() + { + TestNestedStructStatic(); + TestNestedStructStaticAsync().Wait(); + } + + public static void TestNestedStructStatic() + { + var ns = new NestedStruct(3); + Console.WriteLine ($"break here"); + } + + public static async Task TestNestedStructStaticAsync() + { + var ns = new NestedStruct(3); + Console.WriteLine ($"break here"); + await Task.CompletedTask; + } + } + + class BaseClassForJSTest + { + public string kind = "car"; + public string make = "mini"; + public bool available => true; + } + + class DerivedClassForJSTest : BaseClassForJSTest + { + public string owner_name = "foo"; + public string owner_last_name => "bar"; + + public static void run() + { + var obj = new DerivedClassForJSTest(); + Console.WriteLine ($"break here"); + } + } +} diff --git a/src/mono/wasm/debugger/tests/debugger-pointers-test.cs b/src/mono/wasm/debugger/tests/debugger-pointers-test.cs index a82973e5ac37fe..e5ed9d65f9c004 100644 --- a/src/mono/wasm/debugger/tests/debugger-pointers-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-pointers-test.cs @@ -57,7 +57,7 @@ static unsafe void PointersAsArgsTest(int* ip, int** ipp, int*[] ipa, int**[] ip Console.WriteLine($"done!"); } - public static unsafe async Task LocalPointersAsync() + public static unsafe Task LocalPointersAsync() { int ivalue0 = 5; int ivalue1 = 10; @@ -91,7 +91,7 @@ public static unsafe async Task LocalPointersAsync() var cwp = new GenericClassWithPointers { Ptr = dtp }; var cwp_null = new GenericClassWithPointers(); - 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}"); + 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}"); return Task.CompletedTask; } // async methods cannot have unsafe params, so no test for that diff --git a/src/mono/wasm/debugger/tests/debugger-test.csproj b/src/mono/wasm/debugger/tests/debugger-test.csproj index 27f295c0de4dc7..045cf4c35fe7d8 100644 --- a/src/mono/wasm/debugger/tests/debugger-test.csproj +++ b/src/mono/wasm/debugger/tests/debugger-test.csproj @@ -10,6 +10,7 @@ true Library 219 + true portable @@ -27,7 +28,7 @@ Targets="Build;Publish"/> - diff --git a/src/mono/wasm/debugger/tests/debugger-valuetypes-test.cs b/src/mono/wasm/debugger/tests/debugger-valuetypes-test.cs index f557f4e9c3ee63..d1330e0742e90b 100644 --- a/src/mono/wasm/debugger/tests/debugger-valuetypes-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-valuetypes-test.cs @@ -190,7 +190,7 @@ static async Task MethodWithArgumentsForToStringTestAsync( 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}"); + Console.WriteLine($"MethodWithArgumentsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}"); await Task.CompletedTask; } public static void MethodUpdatingValueTypeMembers() @@ -214,7 +214,7 @@ public static async Task MethodUpdatingValueTypeLocalsAsync() var dt = new DateTime(1, 2, 3, 4, 5, 6); Console.WriteLine($"#1"); dt = new DateTime(9, 8, 7, 6, 5, 4); - Console.WriteLine($"#2"); + Console.WriteLine($"#2"); await Task.CompletedTask; } public static void MethodUpdatingVTArrayMembers() diff --git a/src/mono/wasm/debugger/tests/other.js b/src/mono/wasm/debugger/tests/other.js index d3a6d398972697..c32eaa064b033f 100644 --- a/src/mono/wasm/debugger/tests/other.js +++ b/src/mono/wasm/debugger/tests/other.js @@ -76,3 +76,20 @@ function eval_call_on_frame_test () { let obj_undefined = undefined; console.log(`break here`); } + +function get_properties_test () { + let vehicle = { + kind: "car", + make: "mini", + get available () { return true; } + }; + + let obj = { + owner_name: "foo", + get owner_last_name () { return "bar"; }, + } + // obj.prototype.this_vehicle = vehicle; + Object.setPrototypeOf(obj, vehicle); + + console.log(`break here`); +} diff --git a/src/mono/wasm/runtime/library_mono.js b/src/mono/wasm/runtime/library_mono.js index 67ae4c1c9cdb44..9783e7d027137b 100644 --- a/src/mono/wasm/runtime/library_mono.js +++ b/src/mono/wasm/runtime/library_mono.js @@ -380,25 +380,70 @@ var MonoSupportLib = { return out_list; }, - _filter_automatic_properties: function (props) { - let names_found = {}; - let final_var_list = []; + _filter_automatic_properties: function (props, accessors_only=false) { + // Note: members in @props, have derived class members, followed by + // those from parent classes + + // Note: Auto-properties have backing fields, named with a special suffix. + // @props here will have the backing field, *and* the getter. + // + // But we want to return only one name/value pair: + // [name of the auto-property] = value of the backing field + + let getters = {}; + let all_fields_except_backing_fields = {}; + let backing_fields = {}; + + // Split props into the 3 groups - backing_fields, getters, and all_fields_except_backing_fields + props.forEach(p => { + if (p.name.endsWith('k__BackingField')) { + const auto_prop_name = p.name.replace ('k__BackingField', '') + .replace ('<', '') + .replace ('>', ''); + + // Only take the first one, as that is overriding others + if (!(auto_prop_name in backing_fields)) + backing_fields[auto_prop_name] = Object.assign(p, { name: auto_prop_name }); + + } else if (p.get !== undefined) { + // if p wasn't overridden by a getter or a field, + // from a more derived class + if (!(p.name in getters) && !(p.name in all_fields_except_backing_fields)) + getters[p.name] = p; + + } else if (!(p.name in all_fields_except_backing_fields)) { + all_fields_except_backing_fields[p.name] = p; + } + }); - for (var i in props) { - var p = props [i]; - if (p.name in names_found) - continue; + // Filter/merge backing fields, and getters + Object.values(backing_fields).forEach(backing_field => { + const auto_prop_name = backing_field.name; + const getter = getters[auto_prop_name]; - if (p.name.endsWith ("k__BackingField")) - p.name = p.name.replace ("k__BackingField", "") - .replace ('<', '') - .replace ('>', ''); + if (getter === undefined) { + // backing field with no getter + // eg. when a field overrides/`new string foo=..` + // an autoproperty + return; + } - names_found [p.name] = p.name; - final_var_list.push (p); - } + if (auto_prop_name in all_fields_except_backing_fields) { + delete getters[auto_prop_name]; + } else if (getter.__args.owner_class === backing_field.__args.owner_class) { + // getter+backing_field are from the same class. + // Add the backing_field value as a field + all_fields_except_backing_fields[auto_prop_name] = backing_field; - return final_var_list; + // .. and drop the auto-prop getter + delete getters[auto_prop_name]; + } + }); + + if (accessors_only) + return Object.values(getters); + + return Object.values(all_fields_except_backing_fields).concat(Object.values(getters)); }, /** Given `dotnet:object:foo:bar`, @@ -507,23 +552,32 @@ var MonoSupportLib = { * @param {WasmId} id * @returns {object[]} */ - _get_vt_properties: function (id) { - let entry = this._id_table [id.idStr]; - if (entry !== undefined && entry.members !== undefined) - return entry.members; - - if (!isNaN (id.o.containerId)) - this._get_object_properties (id.o.containerId, true); - else if (!isNaN (id.o.arrayId)) - this._get_array_values (id, Number (id.o.arrayIdx), 1, true); - else - throw new Error (`Invalid valuetype id (${id.idStr}). Can't get properties for it.`); + _get_vt_properties: function (id, args={}) { + let entry = this._get_id_props (id.idStr); + + if (entry === undefined || entry.members === undefined) { + if (!isNaN (id.o.containerId)) { + // We are expanding, so get *all* the members. + // Which ones to return based on @args, can be determined + // at the time of return + this._get_object_properties (id.o.containerId, { expandValueTypes: true }); + } else if (!isNaN (id.o.arrayId)) + this._get_array_values (id, Number (id.o.arrayIdx), 1, true); + else + throw new Error (`Invalid valuetype id (${id.idStr}). Can't get properties for it.`); + } + // Let's try again entry = this._get_id_props (id.idStr); - if (entry !== undefined && entry.members !== undefined) + + if (entry !== undefined && entry.members !== undefined) { + if (args.accessorPropertiesOnly === true) + return entry.accessors; + return entry.members; + } - throw new Error (`Unknown valuetype id: ${id.idStr}`); + throw new Error (`Unknown valuetype id: ${id.idStr}. Failed to get properties for it.`); }, /** @@ -597,17 +651,37 @@ var MonoSupportLib = { return res; }, + // Keep in sync with the flags in mini-wasm-debugger.c + _get_properties_args_to_gpflags: function (args) { + let gpflags =0; + /* + Disabled for now. Instead, we ask debugger.c to return + ~all~ the members, and then handle the filtering in mono.js . + + if (args.ownProperties) + gpflags |= 1; + if (args.accessorPropertiesOnly) + gpflags |= 2; + */ + if (args.expandValueTypes) + gpflags |= 4; + + return gpflags; + }, + /** * @param {number} idNum * @param {boolean} expandValueTypes * @returns {object} */ - _get_object_properties: function(idNum, expandValueTypes) { - let { res_ok, res } = this.mono_wasm_get_object_properties_info (idNum, expandValueTypes); + _get_object_properties: function(idNum, args={}) { + let gpflags = this._get_properties_args_to_gpflags (args); + + let { res_ok, res } = this.mono_wasm_get_object_properties_info (idNum, gpflags); if (!res_ok) throw new Error (`Failed to get properties for ${idNum}`); - res = MONO._filter_automatic_properties (res); + res = MONO._filter_automatic_properties (res, args.accessorPropertiesOnly === true); res = this._assign_vt_ids (res, v => ({ containerId: idNum, fieldOffset: v.fieldOffset })); res = this._post_process_details (res); @@ -625,7 +699,8 @@ var MonoSupportLib = { if (isNaN (id.o.arrayId) || isNaN (startIdx)) throw new Error (`Invalid array id: ${id.idStr}`); - let { res_ok, res } = this.mono_wasm_get_array_values_info (id.o.arrayId, startIdx, count, expandValueTypes); + let gpflags = this._get_properties_args_to_gpflags({ expandValueTypes }); + let { res_ok, res } = this.mono_wasm_get_array_values_info (id.o.arrayId, startIdx, count, gpflags); if (!res_ok) throw new Error (`Failed to get properties for array id ${id.idStr}`); @@ -647,6 +722,8 @@ var MonoSupportLib = { if (details.length > 0) this._extract_and_cache_value_types(details); + // remove __args added by add_properties_var + details.forEach(d => delete d.__args); return details; }, @@ -687,7 +764,8 @@ var MonoSupportLib = { this._extract_and_cache_value_types (value.members); - const new_props = Object.assign ({ members: value.members }, value.__extra_vt_props); + const accessors = value.members.filter(m => m.get !== undefined); + const new_props = Object.assign ({ members: value.members, accessors }, value.__extra_vt_props); this._new_or_add_id_props ({ objectId: value.objectId, props: new_props }); delete value.members; @@ -837,7 +915,7 @@ var MonoSupportLib = { return res; }, - mono_wasm_get_details: function (objectId, args) { + mono_wasm_get_details: function (objectId, args={}) { let id = this._parse_object_id (objectId, true); switch (id.scheme) { @@ -845,14 +923,15 @@ var MonoSupportLib = { if (isNaN (id.value)) throw new Error (`Invalid objectId: ${objectId}. Expected a numeric id.`); - return this._get_object_properties(id.value, false); + args.expandValueTypes = false; + return this._get_object_properties(id.value, args); } case "array": return this._get_array_values (id); case "valuetype": - return this._get_vt_properties(id); + return this._get_vt_properties(id, args); case "cfo_res": return this._get_cfo_res_details (objectId, args); @@ -1082,8 +1161,8 @@ var MonoSupportLib = { this._call_function_res_cache = {}; this._c_fn_table = {}; - this._register_c_var_fn ('mono_wasm_get_object_properties', 'bool', [ 'number', 'bool' ]); - this._register_c_var_fn ('mono_wasm_get_array_values', 'bool', [ 'number', 'number', 'number', 'bool' ]); + this._register_c_var_fn ('mono_wasm_get_object_properties', 'bool', [ 'number', 'number' ]); + this._register_c_var_fn ('mono_wasm_get_array_values', 'bool', [ 'number', 'number', 'number', 'number' ]); this._register_c_var_fn ('mono_wasm_invoke_getter_on_object', 'bool', [ 'number', 'string' ]); this._register_c_var_fn ('mono_wasm_invoke_getter_on_value', 'bool', [ 'number', 'number', 'string' ]); this._register_c_var_fn ('mono_wasm_get_local_vars', 'bool', [ 'number', 'number', 'number']); @@ -1623,31 +1702,20 @@ var MonoSupportLib = { }); }, - _mono_wasm_add_getter_var: function(className, invokable) { + _mono_wasm_add_getter_var: function(className) { const fixed_class_name = MONO._mono_csharp_fixup_class_name (className); - if (invokable != 0) { - var name; - if (MONO.var_info.length > 0) - name = MONO.var_info [MONO.var_info.length - 1].name; - name = (name === undefined) ? "" : name; - - MONO.var_info.push({ - get: { - className: "Function", - description: `get ${name} () {}`, - type: "function", - } - }); - } else { - var value = `${fixed_class_name} { get; }`; - MONO.var_info.push({ - value: { - type: "symbol", - description: value, - value: value, - } - }); - } + var name; + if (MONO.var_info.length > 0) + name = MONO.var_info [MONO.var_info.length - 1].name; + name = (name === undefined) ? "" : name; + + MONO.var_info.push({ + get: { + className: "Function", + description: `get ${name} () {}`, + type: "function", + } + }); }, _mono_wasm_add_array_var: function(className, objectId, length) { @@ -1740,6 +1808,23 @@ var MonoSupportLib = { }); }, + mono_wasm_add_properties_var: function (name, args) { + if (typeof args !== 'object') + args = { field_offset: args }; + + if (args.owner_class !== undefined && args.owner_class !== 0) + args.owner_class = Module.UTF8ToString(args.owner_class); + + let name_obj = { + name: Module.UTF8ToString (name), + fieldOffset: args.field_offset, + __args: args + }; + if (args.is_own) + name_obj.isOwn = true; + + MONO.var_info.push(name_obj); + }, mono_wasm_add_typed_value: function (type, str_value, value) { let type_str = type; @@ -1789,7 +1874,7 @@ var MonoSupportLib = { break; case "getter": - MONO._mono_wasm_add_getter_var (str_value, value); + MONO._mono_wasm_add_getter_var (str_value); break; case "array": @@ -1912,11 +1997,8 @@ var MonoSupportLib = { MONO.mono_wasm_add_typed_value (type, str_value, value); }, - mono_wasm_add_properties_var: function(name, field_offset) { - MONO.var_info.push({ - name: Module.UTF8ToString (name), - fieldOffset: field_offset - }); + mono_wasm_add_properties_var: function(name, args) { + MONO.mono_wasm_add_properties_var (name, args); }, mono_wasm_set_is_async_method: function(objectId) {