未验证 提交 b25b2bc6 编写于 作者: A Ankit Jain 提交者: GitHub

[wasm][debugger] Add support for surfacing inherited members (#41480)

* [wasm][debugger][tests] Update to use `TDateTime`

- this ensures that we check the datetime, and some property getters on
it, wherever we have a datetime.

* [wasm][debugger][tests] Add labels to more checks

* [wasm][debugger] Add support for surfacing inherited members

- surface inherited fields, and properties
- we try to support `Runtime.getProperties`'s two arguments:
    - `ownProperties`, and `accessorsOnly`

    - `ownProperties`: for JS, this means return only the object's own
    members (not inherited ones)
    - `accessorsOnly`: for JS, this means return all the getters

Actual implementation:

- In practice, VSCode, and Chrome debugger seem to only send
`{ ownProperties: true, accessorsOnly: false }`,
and `{ ownProperties: false, accessorsOnly: true }`. The combination of
which means - that we don't return any inherited fields!

- But we want to show inherited fields too, so to get that behavior we
essentially *ignore* `ownProperties`. IOW,

    - `ownProperties`: we return all fields, and properties
    - `accessorsOnly`: we return only the getters, including the
    inherited ones

- Another thing to note is the case for auto-properties
    - these have a backing field
    - and we usually return the backing field's value, instead of
    returning a getter
    - To continue with that, auto-properties are *not* returned for
    `accessorsOnly`

- The code in `mini-wasm-debugger.c` does handle these two arguments,
but that is currently disabled by not passing the args to debugger.c at
all
    - Instead, we get the *full* list of members, and try to filter it
    in `library_mono.js`
    - which includes handling property overrides, or shadowing by new
    properties/fields in derived classes

* [wasm][debugger][tests] Fix simple warnings

* [wasm][debugger][tests] Fix warnings introduced in this PR

* [wasm][debugger][tests] Fix indentation

* [wasm][debugger] Correctly handle local structs in async methods

- When we have a struct local in an async instance method, it doesn't
get expanded, since we have a containerId (the async object), and we can
expand/access it later.

- When the IDE asks us to expand it with `{accessorPropertiesOnly: true}`:
    - we get the expanded json, but `_filter_automatic_properties` tries
    to return just the accessors, but that doesn't handle the expanded
    members of nested structs!
    - That is done in `extract_and_cache_value_types`, which is run *after*
    `_filter_automatic_properties`, but by that time we have already
    lost the expanded members!

    - So, `_get_vt_properties` fails with `Unknown valuetype id`,
    because it doesn't have anything to return at that point.

- This is being solved by ignoring the getProperties args in case of
expanding valuetypes.
    - that means that we can correctly extract, and cache the whole
    object.
    - And after that, we can return accessors/others, based on the args.

* [wasm][debugger] Fix warnings in debugger-test-app, and turn on warnAsError

* For some cases, debugger seems to give the actual method name instead of MoveNext for async methods
上级 740d59ab
......@@ -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
......
......@@ -272,7 +272,7 @@ public async Task RunOnArrayReturnArray(string eval_fn, string bp_loc, int line,
var act_i_props = await GetProperties(act_i["value"]["objectId"]?.Value<string>());
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<System.DateTime>")
}, "obj_own ss_arr[{i}]");
......@@ -320,12 +320,10 @@ public async Task RunOnArrayReturnArray(string eval_fn, string bp_loc, int line,
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<System.DateTime>")
}, $"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 CFOWithSilentReturnsErrors(string eval_fn, string bp_loc, int
// 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 CFOWithSilentReturnsErrors(string eval_fn, string bp_loc, int
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 CFOWithSilentReturnsErrors(string eval_fn, string bp_loc, int
var arr_elems = await GetProperties(res.Value["result"]?["objectId"]?.Value<string>());
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 CFOWithSilentReturnsErrors(string eval_fn, string bp_loc, int
}
});
[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<string>();
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)]
......
......@@ -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 @@ public async Task EvaluateMethodLocals(string type, string method, string bp_fun
var (local_gs, _) = await EvaluateOnCallFrame(id, "local_gs");
await CheckValue(local_gs, TValueType("DebuggerTests.SimpleGenericStruct<int>"), nameof(local_gs));
(local_gs, _) = await EvaluateOnCallFrame(id, " local_gs");
await CheckValue(local_gs, TValueType("DebuggerTests.SimpleGenericStruct<int>"), nameof(local_gs));
var local_gs_props = await GetProperties(local_gs["objectId"]?.Value<string>());
await CheckProps(local_gs_props, new
{
......@@ -206,6 +210,41 @@ public async Task EvaluateExpressionsWithDeepMemberAccesses(string prefix, int b
($"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<string>();
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",
......
......@@ -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();
......
// 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<string, bool?, bool?, string[], Dictionary<string, (JObject, bool)>, bool> ClassGetPropertiesTestData(bool is_async)
{
var data = new TheoryData<string, bool?, bool?, string[], Dictionary<string, (JObject, bool)>, bool>();
var type_name = "DerivedClass";
var all_props = new Dictionary<string, (JObject, bool)>()
{
{"_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<string, bool?, bool?, string[], Dictionary<string, (JObject, bool)>, bool> StructGetPropertiesTestData(bool is_async)
{
var data = new TheoryData<string, bool?, bool?, string[], Dictionary<string, (JObject, bool)>, bool>();
var type_name = "CloneableStruct";
var all_props = new Dictionary<string, (JObject, bool)>()
{
{"_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<string, (JObject, bool)> 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<string>();
var frame_locals = await GetProperties(frame_id);
var this_obj = GetAndAssertObjectWithName(frame_locals, "this");
var this_props = await GetProperties(this_obj["value"]?["objectId"]?.Value<string>(), 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<string>() == "Item");
Assert.Null(item);
// Useful for debugging: AssertHasOnlyExpectedProperties(expected_names, this_props.Values<JObject>());
AssertEqual(expected_names.Length, this_props.Count(), $"expected number of properties");
});
public static IEnumerable<object[]> 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<string, (JObject, bool)> 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<string>(), 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<JObject>());
// indexer properties shouldn't show up here
var item = cs_props.FirstOrDefault(jt => jt["name"]?.Value<string>() == "Item");
Assert.Null(item);
// Useful for debugging: AssertHasOnlyExpectedProperties(expected_names, this_props.Values<JObject>());
AssertEqual(expected_names.Length, cs_props.Count(), $"expected number of properties");
});
public static TheoryData<bool, bool?, bool?, string[]> JSGetPropertiesTestData(bool test_js)=> new TheoryData<bool, bool?, bool?, string[]>
{
// 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<string>();
var frame_locals = await GetProperties(id);
var obj = GetAndAssertObjectWithName(frame_locals, "obj");
var obj_props = await GetProperties(obj["value"]?["objectId"]?.Value<string>(),
own_properties: own_properties, accessors_only: accessors_only);
IEnumerable<JToken> filtered_props;
if (test_js)
{
filtered_props = obj_props.Children().Where(jt => jt["enumerable"]?.Value<bool>() == true);
}
else
{
// we don't set `enumerable` right now
filtered_props = obj_props.Children().Where(jt=> true);
}
var expected_props = new Dictionary<string, (JObject exp_obj, bool is_own)> ()
{
// 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<string> () == name).SingleOrDefault(),
expected_props);
AssertEqual(expected_names.Length, filtered_props.Count(), $"expected number of properties");
});
}
private async Task CheckExpectedProperties(string[] expected_names, Func<string, JToken> get_actual_prop, Dictionary<string, (JObject, bool)> 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<bool> () == true, $"{exp_name}#isOwn");
if (exp_prop["__custom_type"]?.Value<string>() == "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<JObject> actual)
{
var exp = new HashSet<string>(expected_names);
foreach (var obj in actual)
{
if (!exp.Contains(obj["name"]?.Value<string> ()))
Console.WriteLine ($"Unexpected: {obj}");
}
}
}
}
......@@ -18,8 +18,8 @@ public class PointerTests : DebuggerTestBase
new TheoryData<string, string, string, int, string, bool>
{ { $"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "LocalPointers", 32, "LocalPointers", false },
{ $"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "LocalPointers", 32, "LocalPointers", true },
{ $"invoke_static_method_async ('[debugger-test] DebuggerTests.PointerTests:LocalPointersAsync');", "DebuggerTests.PointerTests", "LocalPointersAsync", 32, "MoveNext", false },
{ $"invoke_static_method_async ('[debugger-test] DebuggerTests.PointerTests:LocalPointersAsync');", "DebuggerTests.PointerTests", "LocalPointersAsync", 32, "MoveNext", true }
{ $"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 class PointerTests : DebuggerTestBase
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 class PointerTests : DebuggerTestBase
gsp_null = TPointer("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*")
}, "locals", num_fields: 26);
await CheckDateTime(locals, "dt", dt);
// *dtp
var props = await GetObjectOnLocals(locals, "dtp");
await CheckDateTime(props, "*dtp", dt);
......@@ -178,7 +176,7 @@ public class PointerTests : DebuggerTestBase
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 class PointerTests : DebuggerTestBase
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 class PointerTests : DebuggerTestBase
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 class PointerTests : DebuggerTestBase
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 class PointerTests : DebuggerTestBase
{
var actual_elems = await CheckArrayElements(dtpa_elems, new[]
{
TValueType("System.DateTime", dt.ToString()),
null
TDateTime(dt),
null
});
await CheckDateTime(actual_elems[0], "*[0]", dt);
......
......@@ -342,28 +342,28 @@ internal async Task<JToken> 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<string>(), "className");
AssertEqual(exp_dt.ToString(), v["description"]?.Value<string>(), "description");
AssertEqual("System.DateTime", v["className"]?.Value<string>(), $"{label}#className");
AssertEqual(exp_dt.ToString(), v["description"]?.Value<string>(), $"{label}#description");
var members = await GetProperties(v["objectId"]?.Value<string>());
......@@ -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<string>() == 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<string>(), $"{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<string>();
bool null_or_empty_exp_val = String.IsNullOrEmpty(exp_val_str);
var actual_field_val = actual_val.Values<JProperty>().FirstOrDefault(a_jp => a_jp.Name == jp.Name);
var actual_field_val = actual_val?.Values<JProperty>()?.FirstOrDefault(a_jp => a_jp.Name == jp.Name);
var actual_field_val_str = actual_field_val?.Value?.Value<string>();
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<string>()}\n" +
$"Actual: {actual_field_val.Value.Value<string>()}");
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<JToken> GetObjectOnLocals(JToken locals, string name)
}
/* @fn_args is for use with `Runtime.callFunctionOn` only */
internal async Task<JToken> GetProperties(string id, JToken fn_args = null, bool expect_ok = true)
internal async Task<JToken> 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<JToken> 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}");
......
......@@ -676,12 +676,10 @@ public async Task InspectLocalsInPreviousFramesDuringSteppingIn(bool use_cfo)
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<System.DateTime>")
}, "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 @@ public async Task InspectLocalsWithStructs(bool use_cfo)
{
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<System.DateTime>"),
Kind = TEnum("System.DateTimeKind", "Utc")
}, "ss_local");
......@@ -935,8 +933,6 @@ public async Task InspectLocalsWithStructs(bool use_cfo)
{
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 @@ public async Task InspectLocalsWithStructs(bool use_cfo)
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<System.DateTime>"),
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 @@ public async Task InspectValueTypeMethodArgs(bool use_cfo)
{
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<System.DateTime>"),
Kind = TEnum("System.DateTimeKind", "Local")
};
......@@ -1037,9 +1032,6 @@ public async Task InspectValueTypeMethodArgs(bool use_cfo)
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 @@ public async Task InspectValueTypeMethodArgs(bool use_cfo)
{
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<System.DateTime>"),
Kind = TEnum("System.DateTimeKind", "Utc")
};
......@@ -1078,8 +1070,6 @@ public async Task InspectValueTypeMethodArgs(bool use_cfo)
List = TObject("System.Collections.Generic.List<System.DateTime>"),
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 @@ public async Task InspectLocalsWithStructsStaticAsync(bool use_cfo)
{
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<System.DateTime>"),
Kind = TEnum("System.DateTimeKind", "Utc")
}, "ss_local");
......@@ -1309,9 +1293,6 @@ public async Task InspectLocalsWithStructsStaticAsync(bool use_cfo)
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 @@ public async Task InspectLocalsForToStringDescriptions(int line, int col, string
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 @@ public async Task InspectLocalsForToStringDescriptions(int line, int col, string
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 @@ public async Task InspectLocalsForToStringDescriptions(int line, int col, string
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"),
......
......@@ -168,7 +168,7 @@ public async Task<(T, Point[])> InstanceMethodValueTypeLocalsAsync<T>(T t1)
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<int> { 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>(T sc_arg) where T : SimpleClass
......
// 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");
}
}
}
......@@ -57,7 +57,7 @@ public static unsafe void LocalPointers()
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<DateTime> { Ptr = dtp };
var cwp_null = new GenericClassWithPointers<DateTime>();
Console.WriteLine($"{(int)*ip}, {(int)**ipp}, {ipp_null == null}, {ip_null == null}, {ippa == null}, {ipa}, {(char)*cp}, {(vp == null ? "null" : "not null")}, {dtp->Second}, {gsp->IntField}, {cwp}, {cwp_null}, {gs_null}");
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
......
......@@ -10,6 +10,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputType>Library</OutputType>
<NoWarn>219</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<DebugType>portable</DebugType>
</PropertyGroup>
......@@ -27,7 +28,7 @@
Targets="Build;Publish"/>
</Target>
<UsingTask TaskName="WasmAppBuilder"
<UsingTask TaskName="WasmAppBuilder"
AssemblyFile="$(ArtifactsBinDir)WasmAppBuilder\$(Configuration)\$(NetCoreAppCurrent)\publish\WasmAppBuilder.dll"/>
<Target Name="BuildApp" DependsOnTargets="RebuildWasmAppBuilder;Build">
......
......@@ -190,7 +190,7 @@ public static async Task MethodWithLocalsForToStringTestAsync(bool call_other)
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()
......
......@@ -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`);
}
......@@ -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) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册