EMSCRIPTEN_KEEPALIVE int mono_wasm_remove_breakpoint (int bp_id);
EMSCRIPTEN_KEEPALIVE int mono_wasm_current_bp_id (void);
EMSCRIPTEN_KEEPALIVE void mono_wasm_enum_frames (void);
-EMSCRIPTEN_KEEPALIVE void mono_wasm_get_var_info (int scope, int* pos, int len);
+EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_get_local_vars (int scope, int* pos, int len);
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 void mono_wasm_get_object_properties (int object_id, gboolean expand_value_types);
-EMSCRIPTEN_KEEPALIVE void mono_wasm_get_array_values (int object_id);
-EMSCRIPTEN_KEEPALIVE void mono_wasm_get_array_value_expanded (int object_id, int idx);
-EMSCRIPTEN_KEEPALIVE void mono_wasm_invoke_getter_on_object (int object_id, const char* name);
-EMSCRIPTEN_KEEPALIVE void mono_wasm_get_deref_ptr_value (void *value_addr, MonoClass *klass);
+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_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);
//JS functions imported that we use
extern void mono_wasm_add_frame (int il_offset, int method_token, const char *assembly_name, const char *method_name);
extern void mono_wasm_fire_bp (void);
extern void mono_wasm_fire_exception (int exception_obj_id, const char* message, const char* class_name, gboolean uncaught);
extern void mono_wasm_add_obj_var (const char*, const char*, guint64);
-extern void mono_wasm_add_value_type_unexpanded_var (const char*, const char*);
-extern void mono_wasm_begin_value_type_var (const char*, const char*);
-extern void mono_wasm_end_value_type_var (void);
extern void mono_wasm_add_enum_var (const char*, const char*, guint64);
extern void mono_wasm_add_func_var (const char*, const char*, guint64);
extern void mono_wasm_add_properties_var (const char*, gint32);
return evt->id;
}
+static MonoObject*
+get_object_from_id (int objectId)
+{
+ ObjRef *ref = (ObjRef *)g_hash_table_lookup (objrefs, GINT_TO_POINTER (objectId));
+ if (!ref) {
+ DEBUG_PRINTF (2, "get_object_from_id !ref: %d\n", objectId);
+ return NULL;
+ }
+
+ MonoObject *obj = mono_gchandle_get_target_internal (ref->handle);
+ if (!obj)
+ DEBUG_PRINTF (2, "get_object_from_id !obj: %d\n", objectId);
+
+ return obj;
+}
+
static gboolean
list_frames (MonoStackFrameInfo *info, MonoContext *ctx, gpointer data)
{
mono_wasm_add_enum_var (class_name, enum_members->str, value__);
g_string_free (enum_members, TRUE);
- } else if (expandValueType) {
- char *to_string_val = get_to_string_description (class_name, klass, addr);
- mono_wasm_begin_value_type_var (class_name, to_string_val);
- g_free (to_string_val);
-
- // FIXME: isAsyncLocalThis
- describe_object_properties_for_klass ((MonoObject*)addr, klass, FALSE, expandValueType);
- mono_wasm_end_value_type_var ();
} else {
char *to_string_val = get_to_string_description (class_name, klass, addr);
- mono_wasm_add_value_type_unexpanded_var (class_name, to_string_val);
+
+ if (expandValueType) {
+ int32_t size = mono_class_value_size (klass, NULL);
+ void *value_buf = g_malloc0 (size);
+ mono_value_copy_internal (value_buf, addr, klass);
+
+ EM_ASM ({
+ MONO.mono_wasm_add_typed_value ($0, $1, { toString: $2, value_addr: $3, value_size: $4, klass: $5 });
+ }, "begin_vt", class_name, to_string_val, value_buf, size, klass);
+
+ g_free (value_buf);
+
+ // FIXME: isAsyncLocalThis
+ describe_object_properties_for_klass (addr, klass, FALSE, expandValueType);
+ mono_wasm_add_typed_value ("end_vt", NULL, 0);
+ } else {
+ EM_ASM ({
+ MONO.mono_wasm_add_typed_value ($0, $1, { toString: $2 });
+ }, "unexpanded_vt", class_name, to_string_val);
+ }
g_free (to_string_val);
}
g_free (class_name);
gboolean is_valuetype;
int pnum;
char *klass_name;
- gboolean getters_allowed;
+ gboolean auto_invoke_getters;
g_assert (klass);
is_valuetype = m_class_is_valuetype(klass);
}
klass_name = mono_class_full_name (klass);
- getters_allowed = are_getters_allowed (klass_name);
+ auto_invoke_getters = are_getters_allowed (klass_name);
iter = NULL;
pnum = 0;
mono_wasm_add_properties_var (p->name, pnum);
sig = mono_method_signature_internal (p->get);
- // automatic properties will get skipped
- if (!getters_allowed) {
+ gboolean vt_self_type_getter = is_valuetype && mono_class_from_mono_type_internal (sig->ret) == klass;
+ if (auto_invoke_getters && !vt_self_type_getter) {
+ invoke_and_describe_getter_value (obj, p);
+ } else {
// not allowed to call the getter here
char *ret_class_name = mono_class_full_name (mono_class_from_mono_type_internal (sig->ret));
- // getters not supported for valuetypes, yet
- gboolean invokable = !is_valuetype && sig->param_count == 0;
+ gboolean invokable = sig->param_count == 0;
mono_wasm_add_typed_value ("getter", ret_class_name, invokable);
g_free (ret_class_name);
continue;
}
-
- if (is_valuetype && mono_class_from_mono_type_internal (sig->ret) == klass) {
- // Property of the same valuetype, avoid endlessly recursion!
- mono_wasm_add_typed_value ("getter", klass_name, 0);
- continue;
- }
-
- invoke_and_describe_getter_value (obj, p);
}
pnum ++;
}
+
+ g_free (klass_name);
}
/*
describe_object_properties (guint64 objectId, gboolean isAsyncLocalThis, gboolean expandValueType)
{
DEBUG_PRINTF (2, "describe_object_properties %llu\n", objectId);
- ObjRef *ref = (ObjRef *)g_hash_table_lookup (objrefs, GINT_TO_POINTER (objectId));
- if (!ref) {
- DEBUG_PRINTF (2, "describe_object_properties !ref\n");
- return FALSE;
- }
- MonoObject *obj = mono_gchandle_get_target_internal (ref->handle);
- if (!obj) {
- DEBUG_PRINTF (2, "describe_object_properties !obj\n");
+ MonoObject *obj = get_object_from_id (objectId);
+ if (!obj)
return FALSE;
- }
if (m_class_is_delegate (mono_object_class (obj))) {
// delegates get the same id format as regular objects
}
static gboolean
-invoke_getter_on_object (guint64 objectId, const char *name)
+invoke_getter (void *obj_or_value, MonoClass *klass, const char *name)
{
- ObjRef *ref = (ObjRef *)g_hash_table_lookup (objrefs, GINT_TO_POINTER (objectId));
- if (!ref) {
- DEBUG_PRINTF (1, "invoke_getter_on_object no objRef found for id %llu\n", objectId);
- return FALSE;
- }
-
- MonoObject *obj = mono_gchandle_get_target_internal (ref->handle);
- if (!obj) {
- DEBUG_PRINTF (1, "invoke_getter_on_object !obj\n");
+ if (!obj_or_value || !klass || !name) {
+ DEBUG_PRINTF (2, "invoke_getter: none of the arguments can be null");
return FALSE;
}
- MonoClass *klass = mono_object_class (obj);
gpointer iter = NULL;
MonoProperty *p;
while ((p = mono_class_get_properties (klass, &iter))) {
if (!p->get->name || strcasecmp (p->name, name) != 0)
continue;
- invoke_and_describe_getter_value (obj, p);
+ invoke_and_describe_getter_value (obj_or_value, p);
return TRUE;
}
}
static gboolean
-describe_array_values (guint64 objectId)
+describe_array_values (guint64 objectId, int startIdx, int count, gboolean expandValueType)
{
+ if (count == 0)
+ return TRUE;
+
int esize;
gpointer elem;
- ObjRef *ref = (ObjRef *)g_hash_table_lookup (objrefs, GINT_TO_POINTER (objectId));
- if (!ref) {
+ MonoArray *arr = (MonoArray*) get_object_from_id (objectId);
+ if (!arr)
return FALSE;
- }
- MonoArray *arr = (MonoArray *)mono_gchandle_get_target_internal (ref->handle);
- MonoObject *obj = &arr->obj;
- if (!obj) {
+
+ MonoClass *klass = mono_object_class (arr);
+ MonoTypeEnum type = m_class_get_byval_arg (klass)->type;
+ if (type != MONO_TYPE_SZARRAY && type != MONO_TYPE_ARRAY) {
+ DEBUG_PRINTF (1, "describe_array_values: object is not an array. type: 0x%x\n", type);
return FALSE;
}
- esize = mono_array_element_size (obj->vtable->klass);
- for (int i = 0; i < arr->max_length; 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 (arr->obj.vtable->klass)), elem, FALSE);
+
+ int len = arr->max_length;
+ if (len == 0 && startIdx == 0 && count <= 0) {
+ // Nothing to do
+ return TRUE;
}
- return TRUE;
-}
-/* Expands valuetypes */
-static gboolean
-describe_array_value_expanded (guint64 objectId, guint64 idx)
-{
- int esize;
- gpointer elem;
- ObjRef *ref = (ObjRef *)g_hash_table_lookup (objrefs, GINT_TO_POINTER (objectId));
- if (!ref) {
+ if (startIdx < 0 || (len > 0 && startIdx >= len)) {
+ DEBUG_PRINTF (1, "describe_array_values: invalid startIdx (%d) for array of length %d\n", startIdx, len);
return FALSE;
}
- MonoArray *arr = (MonoArray *)mono_gchandle_get_target_internal (ref->handle);
- MonoObject *obj = &arr->obj;
- if (!obj) {
+
+ if (count > 0 && (startIdx + count) > len) {
+ DEBUG_PRINTF (1, "describe_array_values: invalid count (%d) for startIdx: %d, and array of length %d\n", count, startIdx, len);
return FALSE;
}
- if (idx >= arr->max_length)
- return FALSE;
- esize = mono_array_element_size (obj->vtable->klass);
- elem = (gpointer*)((char*)arr->vector + (idx * esize));
- describe_value (m_class_get_byval_arg (m_class_get_element_class (arr->obj.vtable->klass)), elem, TRUE);
+ esize = mono_array_element_size (klass);
+ int endIdx = count < 0 ? len : startIdx + count;
+ 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);
+ }
return TRUE;
}
return TRUE;
}
-EMSCRIPTEN_KEEPALIVE void
+EMSCRIPTEN_KEEPALIVE gboolean
mono_wasm_get_deref_ptr_value (void *value_addr, MonoClass *klass)
{
MonoType *type = m_class_get_byval_arg (klass);
if (type->type != MONO_TYPE_PTR && type->type != MONO_TYPE_FNPTR) {
DEBUG_PRINTF (2, "BUG: mono_wasm_get_deref_ptr_value: Expected to get a ptr type, but got 0x%x\n", type->type);
- return;
+ return FALSE;
}
mono_wasm_add_properties_var ("deref", -1);
describe_value (type->data.type, value_addr, TRUE);
+ return TRUE;
}
//FIXME this doesn't support getting the return value pseudo-var
-EMSCRIPTEN_KEEPALIVE void
-mono_wasm_get_var_info (int scope, int* pos, int len)
+EMSCRIPTEN_KEEPALIVE gboolean
+mono_wasm_get_local_vars (int scope, int* pos, int len)
{
FrameDescData data;
data.target_frame = scope;
data.pos = pos;
mono_walk_stack_with_ctx (describe_variables_on_frame, NULL, MONO_UNWIND_NONE, &data);
+
+ return TRUE;
}
-EMSCRIPTEN_KEEPALIVE void
+EMSCRIPTEN_KEEPALIVE gboolean
mono_wasm_get_object_properties (int object_id, gboolean expand_value_types)
{
DEBUG_PRINTF (2, "getting properties of object %d\n", object_id);
- describe_object_properties (object_id, FALSE, expand_value_types);
+ return describe_object_properties (object_id, FALSE, expand_value_types);
}
-EMSCRIPTEN_KEEPALIVE void
-mono_wasm_get_array_values (int object_id)
+EMSCRIPTEN_KEEPALIVE gboolean
+mono_wasm_get_array_values (int object_id, int start_idx, int count, gboolean expand_value_types)
{
- DEBUG_PRINTF (2, "getting array values %d\n", object_id);
+ DEBUG_PRINTF (2, "getting array values %d, startIdx: %d, count: %d, expandValueType: %d\n", object_id, start_idx, count, expand_value_types);
- describe_array_values(object_id);
+ return describe_array_values (object_id, start_idx, count, expand_value_types);
}
-EMSCRIPTEN_KEEPALIVE void
-mono_wasm_get_array_value_expanded (int object_id, int idx)
+EMSCRIPTEN_KEEPALIVE gboolean
+mono_wasm_invoke_getter_on_object (int object_id, const char* name)
{
- DEBUG_PRINTF (2, "getting array value %d for idx %d\n", object_id, idx);
+ MonoObject *obj = get_object_from_id (object_id);
+ if (!obj)
+ return FALSE;
- describe_array_value_expanded (object_id, idx);
+ return invoke_getter (obj, mono_object_class (obj), name);
}
-EMSCRIPTEN_KEEPALIVE void
-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)
{
- invoke_getter_on_object (object_id, name);
+ DEBUG_PRINTF (2, "mono_wasm_invoke_getter_on_value: v: %p klass: %p, name: %s\n", value, klass, name);
+ if (!klass || !value)
+ return FALSE;
+
+ if (!m_class_is_valuetype (klass)) {
+ DEBUG_PRINTF (2, "mono_wasm_invoke_getter_on_value: klass is not a valuetype. name: %s\n", mono_class_full_name (klass));
+ return FALSE;
+ }
+
+ return invoke_getter (value, klass, name);
}
+
// Functions required by debugger-state-machine.
gsize
mono_debugger_tls_thread_id (DebuggerTlsData *debuggerTlsData)
etype_name: "int",
local_var_name_prefix: "int",
array : new [] { TNumber(4), TNumber(70), TNumber(1) },
- array_elements : null,
+ array_elem_props: null,
test_prev_frame : test_prev_frame,
frame_idx : frame_idx,
use_cfo : use_cfo);
TValueType("DebuggerTests.Point"),
TValueType("DebuggerTests.Point"),
},
- array_elements : new []
+ array_elem_props: new []
{
TPoint(5, -2, "point_arr#Id#0", "Green"),
TPoint(123, 0, "point_arr#Id#1", "Blue")
TObject("DebuggerTests.SimpleClass", is_null : true),
TObject("DebuggerTests.SimpleClass")
},
- array_elements : new []
+ array_elem_props: new []
{
TSimpleClass(5, -2, "class_arr#Id#0", "Green"),
null, // Element is null
TObject("DebuggerTests.GenericClass<int>"),
TObject("DebuggerTests.GenericClass<int>")
},
- array_elements : new []
+ array_elem_props : new []
{
null, // Element is null
new
TValueType("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>"),
TValueType("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>")
},
- array_elements : new []
+ array_elem_props : new []
{
new
{
TValueType("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point[]>"),
TValueType("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point[]>")
},
- array_elements : new []
+ array_elem_props : new []
{
new
{
use_cfo : use_cfo);
async Task TestSimpleArrayLocals(int line, int col, string entry_method_name, string method_name, string etype_name,
- string local_var_name_prefix, object[] array, object[] array_elements,
+ string local_var_name_prefix, object[] array, object[] array_elem_props,
bool test_prev_frame = false, int frame_idx = 0, bool use_cfo = false)
{
var insp = new Inspector();
var locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value<string>());
Assert.Equal(4, locals.Count());
- CheckArray(locals, $"{local_var_name_prefix}_arr", $"{etype_name}[]");
- CheckArray(locals, $"{local_var_name_prefix}_arr_empty", $"{etype_name}[]");
+ CheckArray(locals, $"{local_var_name_prefix}_arr", $"{etype_name}[]", array?.Length ?? 0);
+ CheckArray(locals, $"{local_var_name_prefix}_arr_empty", $"{etype_name}[]", 0);
CheckObject(locals, $"{local_var_name_prefix}_arr_null", $"{etype_name}[]", is_null : true);
CheckBool(locals, "call_other", test_prev_frame);
await CheckProps(prefix_arr, array, local_arr_name);
- if (array_elements?.Length > 0)
+ if (array_elem_props?.Length > 0)
{
- for (int i = 0; i < array_elements.Length; i++)
+ for (int i = 0; i < array_elem_props.Length; i++)
{
var i_str = i.ToString();
var label = $"{local_var_name_prefix}_arr[{i}]";
- if (array_elements[i] == null)
+ if (array_elem_props[i] == null)
{
var act_i = prefix_arr.FirstOrDefault(jt => jt["name"]?.Value<string>() == i_str);
Assert.True(act_i != null, $"[{label}] Couldn't find array element [{i_str}]");
}
else
{
- await CompareObjectPropertiesFor(prefix_arr, i_str, array_elements[i], label : label);
+ await CompareObjectPropertiesFor(prefix_arr, i_str, array_elem_props[i], label : label);
}
}
}
});
}
+ [Fact]
+ public async Task InvalidArrayId() => await CheckInspectLocalsAtBreakpointSite(
+ "DebuggerTests.Container", "PlaceholderMethod", 1, "PlaceholderMethod",
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ArrayTestsClass:ObjectArrayMembers'); }, 1);",
+ wait_for_event_fn : async(pause_location) =>
+ {
+
+ int frame_idx = 1;
+ var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value<string>());
+ var c_obj = GetAndAssertObjectWithName(frame_locals, "c");
+ var c_obj_id = c_obj["value"] ? ["objectId"]?.Value<string>();
+ Assert.NotNull(c_obj_id);
+
+ // Invalid format
+ await GetProperties("dotnet:array:4123", expect_ok : false);
+
+ // Invalid object id
+ await GetProperties("dotnet:array:{ \"arrayId\": 234980 }", expect_ok : false);
+
+ // Trying to access object as an array
+ if (!DotnetObjectId.TryParse (c_obj_id, out var id) || id.Scheme != "object")
+ Assert.True(false, "Unexpected object id format. Maybe this test is out of sync with the object id format in library_mono.js?");
+
+ if (!int.TryParse(id.Value, out var idNum))
+ Assert.True(false, "Expected a numeric value part of the object id: {c_obj_id}");
+ await GetProperties($"dotnet:array:{{\"arrayId\":{idNum}}}", expect_ok : false);
+ });
+
+ [Fact]
+ public async Task InvalidValueTypeArrayIndex() => await CheckInspectLocalsAtBreakpointSite(
+ "DebuggerTests.Container", "PlaceholderMethod", 1, "PlaceholderMethod",
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ArrayTestsClass:ObjectArrayMembers'); }, 1);",
+ locals_fn : async(locals) =>
+ {
+ var this_obj = GetAndAssertObjectWithName(locals, "this");
+ var c_obj = GetAndAssertObjectWithName(await GetProperties(this_obj["value"]["objectId"].Value<string>()), "c");
+ var c_obj_id = c_obj["value"] ? ["objectId"]?.Value<string>();
+ Assert.NotNull(c_obj_id);
+
+ var c_props = await GetProperties(c_obj_id);
+
+ var pf_arr = GetAndAssertObjectWithName(c_props, "PointsField");
+ var pf_arr_elems = await GetProperties(pf_arr["value"]["objectId"].Value<string>());
+
+ if (!DotnetObjectId.TryParse(pf_arr_elems[0]["value"] ? ["objectId"]?.Value<string>(), out var id))
+ Assert.True(false, "Couldn't parse objectId for PointsFields' elements");
+
+ AssertEqual("valuetype", id.Scheme, "Expected a valuetype id");
+ var id_args = id.ValueAsJson;
+ Assert.True(id_args["arrayId"] != null, "ObjectId format for array seems to have changed. Expected to find 'arrayId' in the value. Update this test");
+ Assert.True(id_args != null, "Expected to get a json as the value part of {id}");
+
+ // Try one valid query, to confirm that the id format hasn't changed!
+ id_args["arrayIdx"] = 0;
+ await GetProperties($"dotnet:valuetype:{id_args.ToString (Newtonsoft.Json.Formatting.None)}", expect_ok : true);
+
+ id_args["arrayIdx"] = 12399;
+ await GetProperties($"dotnet:valuetype:{id_args.ToString (Newtonsoft.Json.Formatting.None)}", expect_ok : false);
+
+ id_args["arrayIdx"] = -1;
+ await GetProperties($"dotnet:valuetype:{id_args.ToString (Newtonsoft.Json.Formatting.None)}", expect_ok : false);
+
+ id_args["arrayIdx"] = "qwe";
+ await GetProperties($"dotnet:valuetype:{id_args.ToString (Newtonsoft.Json.Formatting.None)}", expect_ok : false);
+ });
+
+ [Fact]
+ public async Task InvalidAccessors() => await CheckInspectLocalsAtBreakpointSite(
+ "DebuggerTests.Container", "PlaceholderMethod", 1, "PlaceholderMethod",
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ArrayTestsClass:ObjectArrayMembers'); }, 1);",
+ locals_fn : async(locals) =>
+ {
+ var this_obj = GetAndAssertObjectWithName(locals, "this");
+ var c_obj = GetAndAssertObjectWithName(await GetProperties(this_obj["value"]["objectId"].Value<string>()), "c");
+ var c_obj_id = c_obj["value"] ? ["objectId"]?.Value<string>();
+ Assert.NotNull(c_obj_id);
+
+ var c_props = await GetProperties(c_obj_id);
+
+ var pf_arr = GetAndAssertObjectWithName(c_props, "PointsField");
+
+ var invalid_accessors = new object[] { "NonExistant", "10000", "-2", 10000, -2, null, String.Empty };
+ foreach (var invalid_accessor in invalid_accessors)
+ {
+ // var res = await InvokeGetter (JObject.FromObject (new { value = new { objectId = obj_id } }), invalid_accessor, expect_ok: true);
+ var res = await InvokeGetter(pf_arr, invalid_accessor, expect_ok : true);
+ AssertEqual("undefined", res.Value["result"] ? ["type"]?.ToString(), "Expected to get undefined result for non-existant accessor");
+ }
+ });
+
}
-}
\ No newline at end of file
+}
// callFunctionOn
result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token);
await CheckValue(result.Value["result"], TNumber(5), "cfo-res");
+
+ cfo_args = JObject.FromObject(new
+ {
+ functionDeclaration = "function () { return 'test value'; }",
+ objectId = obj_id
+ });
+
+ // value of @returnByValue doesn't matter, as the returned value
+ // is a primitive
+ if (return_by_val)
+ cfo_args["returnByValue"] = return_by_val;
+
+ // callFunctionOn
+ result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token);
+ await CheckValue(result.Value["result"], JObject.FromObject(new { type = "string", value = "test value" }), "cfo-res");
+
+ cfo_args = JObject.FromObject(new
+ {
+ functionDeclaration = "function () { return null; }",
+ objectId = obj_id
+ });
+
+ // value of @returnByValue doesn't matter, as the returned value
+ // is a primitive
+ if (return_by_val)
+ cfo_args["returnByValue"] = return_by_val;
+
+ // callFunctionOn
+ result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token);
+ await CheckValue(result.Value["result"], JObject.Parse("{ type: 'object', subtype: 'null', value: null }"), "cfo-res");
});
}
});
}
- public static TheoryData<string, string, int, int, string, Func<string[], object>, bool> GettersTestData(bool use_cfo) => new TheoryData<string, string, int, int, string, Func<string[], object>, bool>
+ public static TheoryData<string, string, int, int, string, Func<string[], object>, string, bool> GettersTestData(string local_name, bool use_cfo) => new TheoryData<string, string, int, int, string, Func<string[], object>, string, bool>
{
// Chrome sends this one
{
12,
"function invokeGetter(arrayStr){ let result=this; const properties=JSON.parse(arrayStr); for(let i=0,n=properties.length;i<n;++i){ result=result[properties[i]]; } return result; }",
(arg_strs) => JArray.FromObject(arg_strs).ToString(),
+ local_name,
use_cfo
},
{
12,
"function invokeGetter(arrayStr){ let result=this; const properties=JSON.parse(arrayStr); for(let i=0,n=properties.length;i<n;++i){ result=result[properties[i]]; } return result; }",
(arg_strs) => JArray.FromObject(arg_strs).ToString(),
+ local_name,
use_cfo
},
12,
"function(e){return this[e]}",
(args_str) => args_str?.Length > 0 ? args_str[0] : String.Empty,
+ local_name,
use_cfo
},
{
12,
"function(e){return this[e]}",
(args_str) => args_str?.Length > 0 ? args_str[0] : String.Empty,
+ local_name,
use_cfo
}
};
[Theory]
- [MemberData(nameof(GettersTestData), parameters : false)]
- [MemberData(nameof(GettersTestData), parameters : true)]
- public async Task PropertyGettersOnObjectsTest(string eval_fn, string method_name, int line, int col, string cfo_fn, Func<string[], object> get_args_fn, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
+ [MemberData(nameof(GettersTestData), "ptd", false)]
+ [MemberData(nameof(GettersTestData), "ptd", true)]
+ [MemberData(nameof (GettersTestData), "swp", false)]
+ [MemberData(nameof (GettersTestData), "swp", true)]
+ public async Task PropertyGettersTest(string eval_fn, string method_name, int line, int col, string cfo_fn, Func<string[], object> get_args_fn, string local_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
"dotnet://debugger-test.dll/debugger-cfo-test.cs", line, col,
method_name,
$"window.setTimeout(function() {{ {eval_fn} }}, 1);",
wait_for_event_fn : async(pause_location) =>
{
var frame_locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
- var dt = new DateTime(10, 9, 8, 7, 6, 5);
await CheckProps(frame_locals, new
{
ptd = TObject("DebuggerTests.ClassWithProperties"),
- swp = TObject("DebuggerTests.StructWithProperties"),
+ swp = TObject("DebuggerTests.StructWithProperties")
}, "locals#0");
- var ptd = GetAndAssertObjectWithName(frame_locals, "ptd");
+ var obj = GetAndAssertObjectWithName(frame_locals, local_name);
- var ptd_props = await GetProperties(ptd?["value"] ? ["objectId"]?.Value<string>());
- await CheckProps(ptd_props, new
+ var dt = new DateTime(4, 5, 6, 7, 8, 9);
+ var obj_props = await GetProperties(obj?["value"] ? ["objectId"]?.Value<string>());
+ await CheckProps(obj_props, new
{
- Int = TGetter("Int"),
- String = TGetter("String"),
- DT = TGetter("DT"),
- IntArray = TGetter("IntArray"),
- DTArray = TGetter("DTArray")
- }, "ptd", num_fields : 7);
+ V = TNumber(0xDEADBEEF),
+ Int = TGetter("Int"),
+ String = TGetter("String"),
+ DT = TGetter("DT"),
+ IntArray = TGetter("IntArray"),
+ DTArray = TGetter("DTArray"),
+ StringField = TString(null),
+
+ // Auto properties show w/o getters, because they have
+ // a backing field
+ DTAutoProperty = TValueType("System.DateTime", dt.ToString())
+ }, local_name);
// Automatic properties don't have invokable getters, because we can get their
// value from the backing field directly
{
- dt = new DateTime(4, 5, 6, 7, 8, 9);
- var dt_auto_props = await GetObjectOnLocals(ptd_props, "DTAutoProperty");
- await CheckDateTime(ptd_props, "DTAutoProperty", dt);
+ var dt_auto_props = await GetObjectOnLocals(obj_props, "DTAutoProperty");
+ await CheckDateTime(obj_props, "DTAutoProperty", dt);
}
// Invoke getters, and check values
- var res = await InvokeGetter(ptd, cfo_fn, get_args_fn(new [] { "Int" }));
- Assert.True(res.IsOk, $"InvokeGetter failed with : {res}");
- await CheckValue(res.Value["result"], JObject.FromObject(new { type = "number", value = 5 }), "ptd.Int");
+ dt = new DateTime(3, 4, 5, 6, 7, 8);
+ var res = await InvokeGetter(obj, get_args_fn(new [] { "Int" }), cfo_fn);
+ await CheckValue(res.Value["result"], JObject.FromObject(new { type = "number", value = (0xDEADBEEF + (uint) dt.Month) }), $"{local_name}.Int");
- res = await InvokeGetter(ptd, cfo_fn, get_args_fn(new [] { "String" }));
- Assert.True(res.IsOk, $"InvokeGetter failed with : {res}");
- await CheckValue(res.Value["result"], JObject.FromObject(new { type = "string", value = "foobar" }), "ptd.String");
+ res = await InvokeGetter(obj, get_args_fn(new [] { "String" }), cfo_fn);
+ await CheckValue(res.Value["result"], JObject.FromObject(new { type = "string", value = $"String property, V: 0xDEADBEEF" }), $"{local_name}.String");
- dt = new DateTime(3, 4, 5, 6, 7, 8);
- res = await InvokeGetter(ptd, cfo_fn, get_args_fn(new [] { "DT" }));
- Assert.True(res.IsOk, $"InvokeGetter failed with : {res}");
- await CheckValue(res.Value["result"], TValueType("System.DateTime", dt.ToString()), "ptd.DT");
+ 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);
// Check arrays through getters
- res = await InvokeGetter(ptd, cfo_fn, get_args_fn(new [] { "IntArray" }));
- Assert.True(res.IsOk, $"InvokeGetter failed with : {res}");
- await CheckValue(res.Value["result"], TArray("int[]", 2), "ptd.IntArray");
+ res = await InvokeGetter(obj, get_args_fn(new [] { "IntArray" }), cfo_fn);
+ await CheckValue(res.Value["result"], TArray("int[]", 2), $"{local_name}.IntArray");
{
var arr_elems = await GetProperties(res.Value["result"] ? ["objectId"]?.Value<string>());
var exp_elems = new []
TNumber(20)
};
- await CheckProps(arr_elems, exp_elems, "ptd.IntArray");
+ await CheckProps(arr_elems, exp_elems, $"{local_name}.IntArray");
}
- res = await InvokeGetter(ptd, cfo_fn, get_args_fn(new [] { "DTArray" }));
- Assert.True(res.IsOk, $"InvokeGetter failed with : {res}");
- await CheckValue(res.Value["result"], TArray("System.DateTime[]", 2), "ptd.DTArray");
+ res = await InvokeGetter(obj, get_args_fn(new [] { "DTArray" }), cfo_fn);
+ await CheckValue(res.Value["result"], TArray("System.DateTime[]", 2), $"{local_name}.DTArray");
{
var dt0 = new DateTime(6, 7, 8, 9, 10, 11);
var dt1 = new DateTime(1, 2, 3, 4, 5, 6);
TValueType("System.DateTime", dt1.ToString()),
};
- await CheckProps(arr_elems, exp_elems, "ptd.DTArray");
- }
- });
+ await CheckProps(arr_elems, exp_elems, $"{local_name}.DTArray");
- [Theory]
- [InlineData("invoke_static_method_async ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTestAsync');", "MoveNext", 38, 12)]
- [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');", "PropertyGettersTest", 30, 12)]
- public async Task PropertyGettersOnStructsTest(string eval_fn, string method_name, int line, int col) => await CheckInspectLocalsAtBreakpointSite(
- "dotnet://debugger-test.dll/debugger-cfo-test.cs", line, col,
- method_name,
- $"window.setTimeout(function() {{ {eval_fn} }}, 1);",
- wait_for_event_fn : async(pause_location) =>
- {
- var frame_locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
- await CheckProps(frame_locals, new
- {
- ptd = TObject("DebuggerTests.ClassWithProperties"),
- swp = TObject("DebuggerTests.StructWithProperties"),
- }, "locals#0");
-
- var swp = GetAndAssertObjectWithName(frame_locals, "swp");
-
- var swp_props = await GetProperties(swp?["value"] ? ["objectId"]?.Value<string>());
- await CheckProps(swp_props, new
- {
- Int = TSymbol("int { get; }"),
- String = TSymbol("string { get; }"),
- DT = TSymbol("System.DateTime { get; }"),
- IntArray = TSymbol("int[] { get; }"),
- DTArray = TSymbol("System.DateTime[] { get; }")
- }, "swp");
+ res = await InvokeGetter(arr_elems[0], "Date");
+ await CheckDateTimeValue(res.Value["result"], dt0.Date);
+ }
});
[Theory]
- [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 30, 12, false)]
+ [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)]
[InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 30, 12, true)]
- [InlineData("invoke_getters_js_test ();", "/other.js", 29, 1, false)]
- [InlineData("invoke_getters_js_test ();", "/other.js", 29, 1, true)]
+ [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 30, 12, false)]
+ [InlineData("invoke_getters_js_test ();", "/other.js", 30, 1, false)]
+ [InlineData("invoke_getters_js_test ();", "/other.js", 30, 1, true)]
public async Task CheckAccessorsOnObjectsWithCFO(string eval_fn, string bp_loc, int line, int col, bool roundtrip)
{
await RunCallFunctionOn(
}
}
- async Task<Result> InvokeGetter(JToken obj, string fn, object arguments) => await ctx.cli.SendCommand(
- "Runtime.callFunctionOn",
- JObject.FromObject(new
+ public static TheoryData<string, string, int, int, bool> NegativeTestsData(bool use_cfo = false) => new TheoryData<string, string, int, int, bool>
+ { { "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:MethodForNegativeTests', null);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 45, 12, use_cfo },
+ { "negative_cfo_test ();", "/other.js", 62, 1, use_cfo }
+ };
+
+ [Theory]
+ [MemberData(nameof(NegativeTestsData), false)]
+ public async Task RunOnInvalidCfoId(string eval_fn, string bp_loc, int line, int col, bool use_cfo) => await RunCallFunctionOn(
+ eval_fn, "function() { return this; }", "ptd",
+ bp_loc, line, col,
+ test_fn : async(cfo_result) =>
+ {
+ var ptd_id = cfo_result.Value?["result"] ? ["objectId"]?.Value<string>();
+
+ var cfo_args = JObject.FromObject(new
+ {
+ functionDeclaration = "function () { return 0; }",
+ objectId = ptd_id + "_invalid"
+ });
+
+ var res = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token);
+ Assert.True(res.IsErr);
+ });
+
+ [Theory]
+ [MemberData(nameof(NegativeTestsData), false)]
+ public async Task RunOnInvalidThirdSegmentOfObjectId(string eval_fn, string bp_loc, int line, int col, bool use_cfo)
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async(cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
+ await SetBreakpoint(bp_loc, line, col);
+
+ // callFunctionOn
+ var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 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 frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value<string>());
+ var ptd = GetAndAssertObjectWithName(frame_locals, "ptd");
+ var ptd_id = ptd["value"]["objectId"].Value<string>();
+
+ var cfo_args = JObject.FromObject(new
+ {
+ functionDeclaration = "function () { return 0; }",
+ objectId = ptd_id + "_invalid"
+ });
+
+ var res = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token);
+ Assert.True(res.IsErr);
+ });
+ }
+
+ [Theory]
+ [MemberData(nameof(NegativeTestsData), false)]
+ [MemberData(nameof(NegativeTestsData), true)]
+ public async Task InvalidPropertyGetters(string eval_fn, string bp_loc, int line, int col, bool use_cfo)
+ {
+ 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);
+ await SetBreakpoint(bp_loc, line, col);
+ ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
+
+ // callFunctionOn
+ var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);";
+ await SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }));
+ var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE);
+
+ var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value<string>());
+ var ptd = GetAndAssertObjectWithName(frame_locals, "ptd");
+ var ptd_id = ptd["value"]["objectId"].Value<string>();
+
+ var invalid_args = new object[] { "NonExistant", String.Empty, null, 12310 };
+ foreach (var invalid_arg in invalid_args)
+ {
+ var getter_res = await InvokeGetter(JObject.FromObject(new { value = new { objectId = ptd_id } }), invalid_arg);
+ AssertEqual("undefined", getter_res.Value["result"] ? ["type"]?.ToString(), $"Expected to get undefined result for non-existant accessor - {invalid_arg}");
+ }
+ });
+ }
+
+ [Theory]
+ [MemberData(nameof(NegativeTestsData), false)]
+ public async Task ReturnNullFromCFO(string eval_fn, string bp_loc, int line, int col, bool use_cfo) => await RunCallFunctionOn(
+ eval_fn, "function() { return this; }", "ptd",
+ bp_loc, line, col,
+ test_fn : async(result) =>
{
- functionDeclaration = fn,
- objectId = obj["value"] ? ["objectId"]?.Value<string>(),
- arguments = new [] { new { value = arguments } }
- }), ctx.token);
+ var is_js = bp_loc.EndsWith(".js");
+ var ptd = JObject.FromObject(new { value = new { objectId = result.Value?["result"] ? ["objectId"]?.Value<string>() } });
+
+ var null_value_json = JObject.Parse("{ 'type': 'object', 'subtype': 'null', 'value': null }");
+ foreach (var returnByValue in new bool?[] { null, false, true })
+ {
+ var res = await InvokeGetter(ptd, "StringField", returnByValue : returnByValue);
+ if (is_js)
+ {
+ // In js case, it doesn't know the className, so the result looks slightly different
+ Assert.True(
+ JObject.DeepEquals(res.Value["result"], null_value_json),
+ $"[StringField#returnByValue = {returnByValue}] Json didn't match. Actual: {res.Value ["result"]} vs {null_value_json}");
+ }
+ else
+ {
+ await CheckValue(res.Value["result"], TString(null), "StringField");
+ }
+ }
+ });
/*
* 1. runs `Runtime.callFunctionOn` on the objectId,
var complex = GetAndAssertObjectWithName(locals, "complex");
// try to deref the non-pointer object, as a pointer
- var props = await GetProperties(complex["value"]["objectId"].Value<string>().Replace(":object:", ":pointer:"));
- Assert.Empty(props.Values());
+ await GetProperties(complex["value"]["objectId"].Value<string>().Replace(":object:", ":pointer:"), expect_ok: false);
// try to deref an invalid pointer id
- props = await GetProperties("dotnet:pointer:123897");
- Assert.Empty(props.Values());
+ await GetProperties("dotnet:pointer:123897", expect_ok: false);
});
async Task<JToken[]> CheckArrayElements(JToken array, JToken[] exp_elems)
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Microsoft.WebAssembly.Diagnostics;
using Xunit;
var val = l["value"];
Assert.Equal("number", val["type"]?.Value<string>());
Assert.Equal(value, val["value"].Value<T>());
+ Assert.Equal(value.ToString(), val["description"].Value<T>().ToString());
return;
}
Assert.True(false, $"Could not find variable '{name}'");
internal void CheckString(JToken locals, string name, string value)
{
- foreach (var l in locals)
- {
- if (name != l["name"]?.Value<string>())
- continue;
- var val = l["value"];
- if (value == null)
- {
- Assert.Equal("object", val["type"]?.Value<string>());
- Assert.Equal("null", val["subtype"]?.Value<string>());
- }
- else
- {
- Assert.Equal("string", val["type"]?.Value<string>());
- Assert.Equal(value, val["value"]?.Value<string>());
- }
- return;
- }
- Assert.True(false, $"Could not find variable '{name}'");
+ var l = GetAndAssertObjectWithName(locals, name);
+ CheckValue(l["value"], TString(value), name).Wait();
}
internal JToken CheckSymbol(JToken locals, string name, string value)
{
var l = GetAndAssertObjectWithName(locals, name);
- var val = l["value"];
- Assert.Equal("symbol", val["type"]?.Value<string>());
- Assert.Equal(value, val["value"]?.Value<string>());
+ CheckValue(l["value"], TSymbol(value), name).Wait();
return l;
}
{
var l = GetAndAssertObjectWithName(locals, name);
var val = l["value"];
- Assert.Equal("object", val["type"]?.Value<string>());
+ CheckValue(val, TObject(class_name, is_null: is_null), name).Wait();
Assert.True(val["isValueType"] == null || !val["isValueType"].Value<bool>());
- Assert.Equal(class_name, val["className"]?.Value<string>());
-
- var has_null_subtype = val["subtype"] != null && val["subtype"]?.Value<string>() == "null";
- Assert.Equal(is_null, has_null_subtype);
- if (subtype != null)
- Assert.Equal(subtype, val["subtype"]?.Value<string>());
return l;
}
internal async Task CheckDateTimeValue(JToken value, DateTime expected)
{
- AssertEqual("System.DateTime", value["className"]?.Value<string>(), "className");
- AssertEqual(expected.ToString(), value["description"]?.Value<string>(), "description");
-
- var members = await GetProperties(value["objectId"]?.Value<string>());
+ await CheckDateTimeMembers(value, expected);
- // not checking everything
- CheckNumber(members, "Year", expected.Year);
- CheckNumber(members, "Month", expected.Month);
- CheckNumber(members, "Day", expected.Day);
- CheckNumber(members, "Hour", expected.Hour);
- CheckNumber(members, "Minute", expected.Minute);
- CheckNumber(members, "Second", expected.Second);
+ var res = await InvokeGetter(JObject.FromObject(new { value = value }), "Date");
+ await CheckDateTimeMembers(res.Value["result"], expected.Date);
// FIXME: check some float properties too
+
+ async Task CheckDateTimeMembers(JToken v, DateTime exp_dt)
+ {
+ AssertEqual("System.DateTime", v["className"]?.Value<string>(), "className");
+ AssertEqual(exp_dt.ToString(), v["description"]?.Value<string>(), "description");
+
+ var members = await GetProperties(v["objectId"]?.Value<string>());
+
+ // not checking everything
+ CheckNumber(members, "Year", exp_dt.Year);
+ CheckNumber(members, "Month", exp_dt.Month);
+ CheckNumber(members, "Day", exp_dt.Day);
+ CheckNumber(members, "Hour", exp_dt.Hour);
+ CheckNumber(members, "Minute", exp_dt.Minute);
+ CheckNumber(members, "Second", exp_dt.Second);
+ }
}
internal JToken CheckBool(JToken locals, string name, bool expected)
{
var l = GetAndAssertObjectWithName(locals, name);
- var val = l["value"];
- Assert.Equal("boolean", val["type"]?.Value<string>());
- if (val["value"] == null)
- Assert.True(false, "expected bool value not found for variable named {name}");
- Assert.Equal(expected, val["value"]?.Value<bool>());
-
+ CheckValue(l["value"], TBool(expected), name).Wait();
return l;
}
internal JToken CheckValueType(JToken locals, string name, string class_name)
{
var l = GetAndAssertObjectWithName(locals, name);
- var val = l["value"];
- Assert.Equal("object", val["type"]?.Value<string>());
- Assert.True(val["isValueType"] != null && val["isValueType"].Value<bool>());
- Assert.Equal(class_name, val["className"]?.Value<string>());
+ CheckValue(l["value"], TValueType(class_name), name).Wait();
return l;
}
internal JToken CheckEnum(JToken locals, string name, string class_name, string descr)
{
var l = GetAndAssertObjectWithName(locals, name);
- var val = l["value"];
- Assert.Equal("object", val["type"]?.Value<string>());
- Assert.True(val["isEnum"] != null && val["isEnum"].Value<bool>());
- Assert.Equal(class_name, val["className"]?.Value<string>());
- Assert.Equal(descr, val["description"]?.Value<string>());
+ CheckValue(l["value"], TEnum(class_name, descr), name).Wait();
return l;
}
- internal void CheckArray(JToken locals, string name, string class_name)
- {
- foreach (var l in locals)
- {
- if (name != l["name"]?.Value<string>())
- continue;
-
- var val = l["value"];
- Assert.Equal("object", val["type"]?.Value<string>());
- Assert.Equal("array", val["subtype"]?.Value<string>());
- Assert.Equal(class_name, val["className"]?.Value<string>());
-
- //FIXME: elements?
- return;
- }
- Assert.True(false, $"Could not find variable '{name}'");
- }
+ internal void CheckArray(JToken locals, string name, string class_name, int length)
+ => CheckValue(
+ GetAndAssertObjectWithName(locals, name)["value"],
+ TArray(class_name, length), name).Wait();
internal JToken GetAndAssertObjectWithName(JToken obj, string name)
{
return wait_res;
}
+ internal async Task<Result> InvokeGetter(JToken obj, object arguments, string fn = "function(e){return this[e]}", bool expect_ok = true, bool? returnByValue = null)
+ {
+ var req = JObject.FromObject(new
+ {
+ functionDeclaration = fn,
+ objectId = obj["value"]?["objectId"]?.Value<string>(),
+ arguments = new[] { new { value = arguments } }
+ });
+ if (returnByValue != null)
+ req["returnByValue"] = returnByValue.Value;
+
+ var res = await ctx.cli.SendCommand("Runtime.callFunctionOn", req, ctx.token);
+ Assert.True(expect_ok == res.IsOk, $"InvokeGetter failed for {req} with {res}");
+
+ return res;
+ }
+
internal async Task<JObject> StepAndCheck(StepKind kind, string script_loc, int line, int column, string function_name,
Func<JObject, Task> wait_for_event_fn = null, Action<JToken> locals_fn = null, int times = 1)
{
}
/* @fn_args is for use with `Runtime.callFunctionOn` only */
- internal async Task<JToken> GetProperties(string id, JToken fn_args = null)
+ internal async Task<JToken> GetProperties(string id, JToken fn_args = null, bool expect_ok = true)
{
if (ctx.UseCallFunctionOnBeforeGetProperties && !id.StartsWith("dotnet:scope:"))
{
cfo_args["arguments"] = fn_args;
var result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token);
- AssertEqual(true, result.IsOk, $"Runtime.getProperties failed for {cfo_args.ToString ()}, with Result: {result}");
+ AssertEqual(expect_ok, result.IsOk, $"Runtime.getProperties returned {result.IsOk} instead of {expect_ok}, for {cfo_args.ToString ()}, with Result: {result}");
+ if (!result.IsOk)
+ return null;
id = result.Value["result"] ? ["objectId"]?.Value<string>();
}
});
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}");
if (!frame_props.IsOk)
- Assert.True(false, $"Runtime.getProperties failed for {get_prop_req.ToString ()}, with Result: {frame_props}");
+ return null;
var locals = frame_props.Value["result"];
// FIXME: Should be done when generating the list in library_mono.js, but not sure yet
internal static JObject TString(string value) =>
value == null ?
TObject("string", is_null : true) :
- JObject.FromObject(new { type = "string", value = @value, description = @value });
+ JObject.FromObject(new { type = "string", value = @value });
internal static JObject TNumber(int value) =>
JObject.FromObject(new { type = "number", value = @value.ToString(), description = value.ToString() });
+ internal static JObject TNumber(uint value) =>
+ JObject.FromObject(new { type = "number", value = @value.ToString(), description = value.ToString() });
+
internal static JObject TValueType(string className, string description = null, object members = null) =>
JObject.FromObject(new { type = "object", isValueType = true, className = className, description = description ?? className });
}
}
+ class DotnetObjectId
+ {
+ public string Scheme { get; }
+ public string Value { get; }
+
+ JObject value_json;
+ public JObject ValueAsJson
+ {
+ get
+ {
+ if (value_json == null)
+ {
+ try
+ {
+ value_json = JObject.Parse(Value);
+ }
+ catch (JsonReaderException) { }
+ }
+
+ return value_json;
+ }
+ }
+
+ public static bool TryParse(JToken jToken, out DotnetObjectId objectId) => TryParse(jToken?.Value<string>(), out objectId);
+
+ public static bool TryParse(string id, out DotnetObjectId objectId)
+ {
+ objectId = null;
+ if (id == null)
+ {
+ return false;
+ }
+
+ if (!id.StartsWith("dotnet:"))
+ {
+ return false;
+ }
+
+ var parts = id.Split(":", 3);
+
+ if (parts.Length < 3)
+ {
+ return false;
+ }
+
+ objectId = new DotnetObjectId(parts[1], parts[2]);
+
+ return true;
+ }
+
+ public DotnetObjectId(string scheme, string value)
+ {
+ Scheme = scheme;
+ Value = value;
+ }
+
+ public override string ToString() => $"dotnet:{Scheme}:{Value}";
+ }
+
enum StepKind
{
Into,
CheckObject(locals, "list", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>");
CheckObject(locals, "list_null", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>", is_null : true);
- CheckArray(locals, "list_arr", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>[]");
+ CheckArray(locals, "list_arr", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>[]", 1);
CheckObject(locals, "list_arr_null", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>[]", is_null : true);
// Unused locals
CheckObject(locals, "list_unused", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>");
CheckObject(locals, "list_null_unused", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>", is_null : true);
- CheckObject(locals, "list_arr_unused", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>[]");
+ CheckArray(locals, "list_arr_unused", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>[]", 1);
CheckObject(locals, "list_arr_null_unused", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>[]", is_null : true);
}
);
CheckValueType(this_props, "SimpleStructProperty", "Math.SimpleStruct");
var ss_props = await GetObjectOnLocals(this_props, "SimpleStructProperty");
- Assert.Equal(2, ss_props.Count());
- CheckValueType(ss_props, "dt", "System.DateTime");
- CheckValueType(ss_props, "gs", "Math.GenericStruct<System.DateTime>");
+ var dt = new DateTime(2020, 1, 2, 3, 4, 5);
+ await CheckProps(ss_props, new {
+ dt = TValueType("System.DateTime", dt.ToString()),
+ gs = TValueType("Math.GenericStruct<System.DateTime>")
+ }, "ss_props");
await CheckDateTime(ss_props, "dt", new DateTime(2020, 1, 2, 3, 4, 5));
CheckObject(locals, "this", "Math.NestedInMath");
//FIXME: check fields
CheckValueType(locals, "ss", "Math.SimpleStruct");
- CheckArray(locals, "ss_arr", "Math.SimpleStruct[]");
+ CheckArray(locals, "ss_arr", "Math.SimpleStruct[]", 0);
// TODO: struct fields
}
);
var pause_location = await EvaluateAndCheck(
"window.setTimeout(function() { invoke_method_with_structs(); }, 1);",
- debugger_test_loc, 22, 8, "MethodWithLocalStructs",
- locals_fn: (locals) =>
- {
- Assert.Equal(3, locals.Count());
+ debugger_test_loc, 22, 8, "MethodWithLocalStructs");
- CheckValueType(locals, "ss_local", "DebuggerTests.ValueTypesTest.SimpleStruct");
- CheckValueType(locals, "gs_local", "DebuggerTests.ValueTypesTest.GenericStruct<DebuggerTests.ValueTypesTest>");
- CheckObject(locals, "vt_local", "DebuggerTests.ValueTypesTest");
- }
- );
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ await CheckProps(locals, new
+ {
+ ss_local = TValueType("DebuggerTests.ValueTypesTest.SimpleStruct"),
+ gs_local = TValueType("DebuggerTests.ValueTypesTest.GenericStruct<DebuggerTests.ValueTypesTest>"),
+ vt_local = TObject("DebuggerTests.ValueTypesTest")
+ }, "locals");
var dt = new DateTime(2021, 2, 3, 4, 6, 7);
+ var vt_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "vt_local");
+ Assert.Equal(5, vt_local_props.Count());
+
+ CheckString(vt_local_props, "StringField", "string#0");
+ CheckValueType(vt_local_props, "SimpleStructField", "DebuggerTests.ValueTypesTest.SimpleStruct");
+ CheckValueType(vt_local_props, "SimpleStructProperty", "DebuggerTests.ValueTypesTest.SimpleStruct");
+ await CheckDateTime(vt_local_props, "DT", new DateTime(2020, 1, 2, 3, 4, 5));
+ CheckEnum(vt_local_props, "RGB", "DebuggerTests.RGB", "Blue");
+
// Check ss_local's properties
var ss_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_local");
await CheckProps(ss_local_props, new
{
+ V = TGetter("V"),
str_member = TString("set in MethodWithLocalStructs#SimpleStruct#str_member"),
- dt = TValueType("System.DateTime", dt.ToString()),
- gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"),
- Kind = TEnum("System.DateTimeKind", "Utc")
+ dt = TValueType("System.DateTime", dt.ToString()),
+ gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"),
+ Kind = TEnum("System.DateTimeKind", "Utc")
}, "ss_local");
{
+ 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);
}, "gs_local");
// Check vt_local's properties
- var vt_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "vt_local");
- Assert.Equal(5, vt_local_props.Count());
- CheckString(vt_local_props, "StringField", "string#0");
- CheckValueType(vt_local_props, "SimpleStructField", "DebuggerTests.ValueTypesTest.SimpleStruct");
- CheckValueType(vt_local_props, "SimpleStructProperty", "DebuggerTests.ValueTypesTest.SimpleStruct");
- await CheckDateTime(vt_local_props, "DT", new DateTime(2020, 1, 2, 3, 4, 5));
- CheckEnum(vt_local_props, "RGB", "DebuggerTests.RGB", "Blue");
+ var exp = new[]
+ {
+ ("SimpleStructProperty", 2, "Utc"),
+ ("SimpleStructField", 5, "Local")
+ };
+ foreach (var (name, bias, dt_kind) in exp)
{
- // SimpleStructProperty
- dt = new DateTime(2022, 3, 4, 5, 7, 8);
- var ssp_props = await CompareObjectPropertiesFor(vt_local_props, "SimpleStructProperty",
+ dt = new DateTime(2020 + bias, 1 + bias, 2 + bias, 3 + bias, 5 + bias, 6 + bias);
+ var ssp_props = await CompareObjectPropertiesFor(vt_local_props, name,
new
{
- str_member = TString("SimpleStructProperty#string#0#SimpleStruct#str_member"),
- dt = TValueType("System.DateTime", dt.ToString()),
- gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"),
- Kind = TEnum("System.DateTimeKind", "Utc")
+ V = TGetter("V"),
+ str_member = TString($"{name}#string#0#SimpleStruct#str_member"),
+ dt = TValueType("System.DateTime", dt.ToString()),
+ gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"),
+ Kind = TEnum("System.DateTimeKind", dt_kind)
},
- label: "vt_local_props.SimpleStructProperty");
+ label: $"vt_local_props.{name}");
await CheckDateTime(ssp_props, "dt", dt);
-
- // SimpleStructField
- dt = new DateTime(2025, 6, 7, 8, 10, 11);
- var ssf_props = await CompareObjectPropertiesFor(vt_local_props, "SimpleStructField",
- new
- {
- str_member = TString("SimpleStructField#string#0#SimpleStruct#str_member"),
- dt = TValueType("System.DateTime", dt.ToString()),
- gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"),
- Kind = TEnum("System.DateTimeKind", "Local")
- },
- label: "vt_local_props.SimpleStructField");
-
- await CheckDateTime(ssf_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");
}
// FIXME: check ss_local.gs.List's members
var pause_location = await EvaluateAndCheck(
"window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:TestStructsAsMethodArgs'); }, 1);",
- debugger_test_loc, 34, 12, "MethodWithStructArgs",
- locals_fn: (locals) =>
- {
- Assert.Equal(3, locals.Count());
-
- CheckString(locals, "label", "TestStructsAsMethodArgs#label");
- CheckValueType(locals, "ss_arg", "DebuggerTests.ValueTypesTest.SimpleStruct");
- CheckNumber(locals, "x", 3);
- }
- );
+ debugger_test_loc, 34, 12, "MethodWithStructArgs");
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ {
+ Assert.Equal(3, locals.Count());
+ CheckString(locals, "label", "TestStructsAsMethodArgs#label");
+ CheckValueType(locals, "ss_arg", "DebuggerTests.ValueTypesTest.SimpleStruct");
+ CheckNumber(locals, "x", 3);
+ }
var dt = new DateTime(2025, 6, 7, 8, 10, 11);
var ss_local_as_ss_arg = new
{
+ V = TGetter("V"),
str_member = TString("ss_local#SimpleStruct#string#0#SimpleStruct#str_member"),
dt = TValueType("System.DateTime", dt.ToString()),
gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"),
var ss_arg_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_arg");
await CheckProps(ss_arg_props, ss_local_as_ss_arg, "ss_arg");
+ var res = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_arg"), "V");
+ await CheckValue(res.Value["result"], TNumber(0xDEADBEEF + (uint) dt.Month), "ss_arg#V");
+
{
// Check ss_local.dt
await CheckDateTime(ss_arg_props, "dt", dt);
}
pause_location = await StepAndCheck(StepKind.Over, debugger_test_loc, 38, 8, "MethodWithStructArgs", times : 4,
- locals_fn: (locals) =>
- {
- Assert.Equal(3, locals.Count());
-
- CheckString(locals, "label", "TestStructsAsMethodArgs#label");
- CheckValueType(locals, "ss_arg", "DebuggerTests.ValueTypesTest.SimpleStruct");
- CheckNumber(locals, "x", 3);
+ locals_fn: (l) => { /* non-null to make sure that locals get fetched */ });
+ locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ {
+ Assert.Equal(3, locals.Count());
- }
- );
+ CheckString(locals, "label", "TestStructsAsMethodArgs#label");
+ CheckValueType(locals, "ss_arg", "DebuggerTests.ValueTypesTest.SimpleStruct");
+ CheckNumber(locals, "x", 3);
+ }
var ss_arg_updated = new
{
+ V = TGetter("V"),
str_member = TString("ValueTypesTest#MethodWithStructArgs#updated#ss_arg#str_member"),
dt = TValueType("System.DateTime", dt.ToString()),
gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"),
};
ss_arg_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_arg");
- await CheckProps(ss_arg_props, ss_arg_updated, "ss_ar");
+ await CheckProps(ss_arg_props, ss_arg_updated, "ss_arg");
+
+ res = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_arg"), "V");
+ await CheckValue(res.Value["result"], TNumber(0xDEADBEEF + (uint) dt.Month), "ss_arg#V");
{
// Check ss_local.gs
pause_location = await StepAndCheck(StepKind.Over, debugger_test_loc, 28, 12, "TestStructsAsMethodArgs",
times : 2, locals_fn: (l) => { /* non-null to make sure that locals get fetched */ });
- var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
await CheckProps(locals, new
{
ss_local = TValueType("DebuggerTests.ValueTypesTest.SimpleStruct"),
ctx = new DebugTestContext(cli, insp, token, scripts);
var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs";
- var lines = new [] { 202, 205 };
+ var lines = new [] { 203, 206 };
await SetBreakpoint(debugger_test_loc, lines[0], 12);
await SetBreakpoint(debugger_test_loc, lines[1], 12);
"window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:MethodUpdatingValueTypeMembers'); }, 1);",
debugger_test_loc, lines[0], 12, "MethodUpdatingValueTypeMembers");
- var dt = new DateTime(1, 2, 3, 4, 5, 6);
- await CheckLocals(pause_location, dt);
+ await CheckLocals(pause_location, new DateTime(1, 2, 3, 4, 5, 6), new DateTime(4, 5, 6, 7, 8, 9));
// Resume
- dt = new DateTime(9, 8, 7, 6, 5, 4);
pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", debugger_test_loc, lines[1], 12, "MethodUpdatingValueTypeMembers");
- await CheckLocals(pause_location, dt);
+ await CheckLocals(pause_location, new DateTime(9, 8, 7, 6, 5, 4), new DateTime(5, 1, 3, 7, 9, 10));
});
- async Task CheckLocals(JToken pause_location, DateTime dt)
+ async Task CheckLocals(JToken pause_location, DateTime obj_dt, DateTime vt_dt)
{
var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
await CheckProps(locals, new
{
await CheckProps(obj_props, new
{
- DT = TValueType("System.DateTime", dt.ToString())
+ DT = TValueType("System.DateTime", obj_dt.ToString())
}, "locals#obj.DT", num_fields : 5);
- await CheckDateTime(obj_props, "DT", dt);
+ await CheckDateTime(obj_props, "DT", obj_dt);
}
- var vt_props = await GetObjectOnLocals(locals, "obj");
+ var vt_props = await GetObjectOnLocals(locals, "vt");
{
await CheckProps(vt_props, new
{
- DT = TValueType("System.DateTime", dt.ToString())
+ DT = TValueType("System.DateTime", vt_dt.ToString())
}, "locals#obj.DT", num_fields : 5);
- await CheckDateTime(vt_props, "DT", dt);
+ await CheckDateTime(vt_props, "DT", vt_dt);
}
}
}
ctx = new DebugTestContext(cli, insp, token, scripts);
var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs";
- var lines = new [] { 211, 213 };
+ var lines = new [] { 212, 214 };
await SetBreakpoint(debugger_test_loc, lines[0], 12);
await SetBreakpoint(debugger_test_loc, lines[1], 12);
ctx = new DebugTestContext(cli, insp, token, scripts);
var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs";
- var lines = new [] { 222, 224 };
+ var lines = new [] { 223, 225 };
await SetBreakpoint(debugger_test_loc, lines[0], 12);
await SetBreakpoint(debugger_test_loc, lines[1], 12);
var ss_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_local");
await CheckProps(ss_local_props, new
{
+ V = TGetter("V"),
str_member = TString("set in MethodWithLocalStructsStaticAsync#SimpleStruct#str_member"),
- dt = TValueType("System.DateTime", dt.ToString()),
- gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"),
- Kind = TEnum("System.DateTimeKind", "Utc")
+ dt = TValueType("System.DateTime", dt.ToString()),
+ gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"),
+ Kind = TEnum("System.DateTimeKind", "Utc")
}, "ss_local");
{
+ 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);
}
[Theory]
- [InlineData(134, 12, "MethodWithLocalsForToStringTest", false, false)]
- [InlineData(144, 12, "MethodWithArgumentsForToStringTest", true, false)]
- [InlineData(189, 12, "MethodWithArgumentsForToStringTestAsync", true, true)]
- [InlineData(179, 12, "MethodWithArgumentsForToStringTestAsync", false, true)]
+ [InlineData(135, 12, "MethodWithLocalsForToStringTest", false, false)]
+ [InlineData(145, 12, "MethodWithArgumentsForToStringTest", true, false)]
+ [InlineData(190, 12, "MethodWithArgumentsForToStringTestAsync", true, true)]
+ [InlineData(180, 12, "MethodWithArgumentsForToStringTestAsync", false, true)]
public async Task InspectLocalsForToStringDescriptions(int line, int col, string method_name, bool call_other, bool invoke_async)
{
var insp = new Inspector();
});
}
+ [Fact]
+ public async Task InvalidValueTypeData()
+ {
+ await CheckInspectLocalsAtBreakpointSite(
+ "dotnet://debugger-test.dll/debugger-test.cs", 85, 8,
+ "OuterMethod",
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] Math:OuterMethod'); })",
+ wait_for_event_fn : async(pause_location) =>
+ {
+ var new_id = await CreateNewId(@"MONO._new_or_add_id_props ({ scheme: 'valuetype', idArgs: { containerId: 1 }, props: { klass: 3, value64: 4 }});");
+ await _invoke_getter(new_id, "NonExistant", expect_ok : false);
+
+ new_id = await CreateNewId(@"MONO._new_or_add_id_props ({ scheme: 'valuetype', idArgs: { containerId: 1 }, props: { klass: 3 }});");
+ await _invoke_getter(new_id, "NonExistant", expect_ok : false);
+
+ new_id = await CreateNewId(@"MONO._new_or_add_id_props ({ scheme: 'valuetype', idArgs: { containerId: 1 }, props: { klass: 3, value64: 'AA' }});");
+ await _invoke_getter(new_id, "NonExistant", expect_ok : false);
+ });
+
+ async Task<string> CreateNewId(string expr)
+ {
+ var res = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = expr }), ctx.token);
+ Assert.True(res.IsOk, "Expected Runtime.evaluate to succeed");
+ AssertEqual("string", res.Value["result"] ? ["type"]?.Value<string>(), "Expected Runtime.evaluate to return a string type result");
+ return res.Value["result"] ? ["value"]?.Value<string>();
+ }
+
+ async Task<Result> _invoke_getter(string obj_id, string property_name, bool expect_ok)
+ {
+ var expr = $"MONO._invoke_getter ('{obj_id}', '{property_name}')";
+ var res = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = expr }), ctx.token);
+ AssertEqual(expect_ok, res.IsOk, "Runtime.evaluate result not as expected for {expr}");
+
+ return res;
+ }
+ }
+
//TODO add tests covering basic stepping behavior as step in/out/over
}
-}
\ No newline at end of file
+}
public static void PropertyGettersTest()
{
- var ptd = new ClassWithProperties { DTAutoProperty = new DateTime(4, 5, 6, 7, 8, 9) };
- var swp = new StructWithProperties();
+ var ptd = new ClassWithProperties { DTAutoProperty = new DateTime(4, 5, 6, 7, 8, 9), V = 0xDEADBEEF };
+ var swp = new StructWithProperties { DTAutoProperty = new DateTime(4, 5, 6, 7, 8, 9), V = 0xDEADBEEF };
System.Console.WriteLine("break here");
}
public static async System.Threading.Tasks.Task PropertyGettersTestAsync()
{
- var ptd = new ClassWithProperties { DTAutoProperty = new DateTime(4, 5, 6, 7, 8, 9) };
- var swp = new StructWithProperties();
+ var ptd = new ClassWithProperties { DTAutoProperty = new DateTime (4, 5, 6, 7, 8, 9), V = 0xDEADBEEF };
+ var swp = new StructWithProperties { DTAutoProperty = new DateTime (4, 5, 6, 7, 8, 9), V = 0xDEADBEEF };
System.Console.WriteLine("break here");
await System.Threading.Tasks.Task.CompletedTask;
}
+
+ public static void MethodForNegativeTests (string value = null)
+ {
+ var ptd = new ClassWithProperties { StringField = value };
+ var swp = new StructWithProperties { StringField = value };
+ Console.WriteLine("break here");
+ }
}
class ClassWithProperties
{
- public int Int { get { return 5; } }
- public string String { get { return "foobar"; } }
+ public uint V;
+ public uint Int { get { return V + (uint)DT.Month; } }
+ public string String { get { return $"String property, V: 0x{V:X}"; } }
public DateTime DT { get { return new DateTime(3, 4, 5, 6, 7, 8); } }
public int[] IntArray { get { return new int[] { 10, 20 }; } }
struct StructWithProperties
{
- public int Int { get { return 5; } }
- public string String { get { return "foobar"; } }
+ public uint V;
+ public uint Int { get { return V + (uint)DT.Month; } }
+ public string String { get { return $"String property, V: 0x{V:X}"; } }
public DateTime DT { get { return new DateTime(3, 4, 5, 6, 7, 8); } }
public int[] IntArray { get { return new int[] { 10, 20 }; } }
public DateTime[] DTArray { get { return new DateTime[] { new DateTime(6, 7, 8, 9, 10, 11), new DateTime(1, 2, 3, 4, 5, 6) }; } }
+ public DateTime DTAutoProperty { get; set; }
+ public string StringField;
}
}
\ No newline at end of file
public struct SimpleStruct
{
+ public uint V { get { return 0xDEADBEEF + (uint)dt.Month; } set { } }
public string str_member;
public DateTime dt;
public GenericStruct<DateTime> gs;
exception_uncaught_test ();
}
+function negative_cfo_test (str_value = null) {
+ var ptd = {
+ get Int () { return 5; },
+ get String () { return "foobar"; },
+ get DT () { return "dt"; },
+ get IntArray () { return [1,2,3]; },
+ get DTArray () { return ["dt0", "dt1"]; },
+ DTAutoProperty: "dt",
+ StringField: str_value
+ };
+ console.log (`break here`);
+ return ptd;
+}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+/**
+ * @typedef WasmId
+ * @type {object}
+ * @property {string} idStr - full object id string
+ * @property {string} scheme - eg, object, valuetype, array ..
+ * @property {string} value - string part after `dotnet:scheme:` of the id string
+ * @property {object} o - value parsed as JSON
+ */
+
var MonoSupportLib = {
$MONO__postset: 'MONO.export_functions (Module);',
$MONO: {
_vt_stack: [],
mono_wasm_runtime_is_ready : false,
mono_wasm_ignore_pdb_load_errors: true,
+
+ /** @type {object.<string, object>} */
+ _id_table: {},
+
pump_message: function () {
if (!this.mono_background_exec)
this.mono_background_exec = Module.cwrap ("mono_background_exec", null);
},
_fixup_name_value_objects: function (var_list) {
- var out_list = [];
-
- var _fixup_value = function (value) {
- if (value != null && value != undefined) {
- var descr = value.description;
- if (descr == null || descr == undefined)
- value.description = '' + value.value;
- }
- return value;
- };
+ let out_list = [];
var i = 0;
while (i < var_list.length) {
- var o = var_list [i];
- var name = o.name;
+ let o = var_list [i];
+ const name = o.name;
if (name == null || name == undefined) {
i ++;
- o.value = _fixup_value(o.value);
out_list.push (o);
continue;
}
if (i + 1 < var_list.length) {
- _fixup_value(var_list[i + 1].value);
o = Object.assign (o, var_list [i + 1]);
}
},
_filter_automatic_properties: function (props) {
- var names_found = {};
- var final_var_list = [];
+ let names_found = {};
+ let final_var_list = [];
for (var i in props) {
var p = props [i];
return final_var_list;
},
- // Given `dotnet:object:foo:bar`,
- // returns [ 'dotnet', 'object', 'foo:bar']
- _split_object_id: function (id, delimiter = ':', count = 3) {
- if (id === undefined || id == "")
- return [];
+ /** Given `dotnet:object:foo:bar`,
+ * returns { scheme:'object', value: 'foo:bar' }
+ *
+ * Given `dotnet:pointer:{ b: 3 }`
+ * returns { scheme:'object', value: '{b:3}`, o: {b:3}
+ *
+ * @param {string} idStr
+ * @param {boolean} [throwOnError=false]
+ *
+ * @returns {WasmId}
+ */
+ _parse_object_id: function (idStr, throwOnError = false) {
+ if (idStr === undefined || idStr == "" || !idStr.startsWith ('dotnet:')) {
+ if (throwOnError)
+ throw new Error (`Invalid id: ${idStr}`);
+
+ return undefined;
+ }
+
+ const [, scheme, ...rest] = idStr.split(':');
+ let res = {
+ scheme,
+ value: rest.join (':'),
+ idStr,
+ o: {}
+ };
+
+ try {
+ res.o = JSON.parse(res.value);
+ // eslint-disable-next-line no-empty
+ } catch (e) {}
- if (delimiter === undefined) delimiter = ':';
- if (count === undefined) count = 3;
+ return res;
+ },
+
+ /**
+ * @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.`);
- var var_arr = id.split (delimiter);
- var result = var_arr.splice (0, count - 1);
+ entry = this._get_id_props (id.idStr);
+ if (entry !== undefined && entry.members !== undefined)
+ return entry.members;
- if (var_arr.length > 0)
- result.push (var_arr.join (delimiter));
- return result;
+ throw new Error (`Unknown valuetype id: ${id.idStr}`);
+ },
+
+ /**
+ *
+ * @callback GetIdArgsCallback
+ * @param {object} var
+ * @param {number} idx
+ * @returns {object}
+ */
+
+ /**
+ * @param {object[]} vars
+ * @param {GetIdArgsCallback} getIdArgs
+ * @returns {object}
+ */
+ _assign_vt_ids: function (vars, getIdArgs)
+ {
+ vars.forEach ((v, i) => {
+ // we might not have a `.value`, like in case of getters which have a `.get` instead
+ const value = v.value;
+ if (value === undefined || !value.isValueType)
+ return;
+
+ if (value.objectId !== undefined)
+ throw new Error (`Bug: Trying to assign valuetype id, but the var already has one: ${v}`);
+
+ value.objectId = this._new_or_add_id_props ({ scheme: 'valuetype', idArgs: getIdArgs (v, i), props: value._props });
+ delete value._props;
+ });
+
+ return vars;
},
//
// @var_list: [ { index: <var_id>, name: <var_name> }, .. ]
mono_wasm_get_variables: function(scope, var_list) {
- if (!this.mono_wasm_get_var_info)
- this.mono_wasm_get_var_info = Module.cwrap ("mono_wasm_get_var_info", null, [ 'number', 'number', 'number']);
-
- this.var_info = [];
- var numBytes = var_list.length * Int32Array.BYTES_PER_ELEMENT;
- var ptr = Module._malloc(numBytes);
- var heapBytes = new Int32Array(Module.HEAP32.buffer, ptr, numBytes);
+ const numBytes = var_list.length * Int32Array.BYTES_PER_ELEMENT;
+ const ptr = Module._malloc(numBytes);
+ let heapBytes = new Int32Array(Module.HEAP32.buffer, ptr, numBytes);
for (let i=0; i<var_list.length; i++) {
heapBytes[i] = var_list[i].index;
}
this._async_method_objectId = 0;
- this.mono_wasm_get_var_info (scope, heapBytes.byteOffset, var_list.length);
+ let { res_ok, res } = this.mono_wasm_get_local_vars_info (scope, heapBytes.byteOffset, var_list.length);
Module._free(heapBytes.byteOffset);
- var res = MONO._fixup_name_value_objects (this.var_info);
+ if (!res_ok)
+ throw new Error (`Failed to get locals for scope ${scope}`);
- for (let i in res) {
- var res_name = res [i].name;
+ if (this._async_method_objectId != 0)
+ this._assign_vt_ids (res, v => ({ containerId: this._async_method_objectId, fieldOffset: v.fieldOffset }));
- var value = res[i].value;
+ for (let i in res) {
+ const res_name = res [i].name;
if (this._async_method_objectId != 0) {
//Async methods are special in the way that local variables can be lifted to generated class fields
//value of "this" comes here either
// ALTHOUGH, the name wouldn't have `<>` for method args
res [i].name = res_name.substring (1, res_name.indexOf ('>'));
}
-
- if (value.isValueType)
- value.objectId = `dotnet:valuetype:${this._async_method_objectId}:${res [i].fieldOffset}`;
} else if (res_name === undefined && var_list [i] !== undefined) {
// For non-async methods, we just have the var id, but we have the name
// from the caller
}
this._post_process_details(res);
- this.var_info = []
-
return res;
},
-
- mono_wasm_get_object_properties: function(objId, expandValueTypes) {
- if (!this.mono_wasm_get_object_properties_info)
- this.mono_wasm_get_object_properties_info = Module.cwrap ("mono_wasm_get_object_properties", null, [ 'number', 'bool' ]);
-
- this.var_info = [];
- this.mono_wasm_get_object_properties_info (objId, expandValueTypes);
-
- var res = MONO._filter_automatic_properties (MONO._fixup_name_value_objects (this.var_info));
- for (var i = 0; i < res.length; i++) {
- var res_val = res [i].value;
- // we might not have a `.value`, like in case of getters which have a `.get` instead
- if (res_val !== undefined && res_val.isValueType != undefined && res_val.isValueType)
- res_val.objectId = `dotnet:valuetype:${objId}:${res [i].fieldOffset}`;
- }
-
- this.var_info = [];
+ /**
+ * @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);
+ if (!res_ok)
+ throw new Error (`Failed to get properties for ${idNum}`);
+
+ res = MONO._filter_automatic_properties (res);
+ res = this._assign_vt_ids (res, v => ({ containerId: idNum, fieldOffset: v.fieldOffset }));
+ res = this._post_process_details (res);
return res;
},
- mono_wasm_get_array_values: function(objId) {
- if (!this.mono_wasm_get_array_values_info)
- this.mono_wasm_get_array_values_info = Module.cwrap ("mono_wasm_get_array_values", null, [ 'number' ]);
-
- this.var_info = [];
- this.mono_wasm_get_array_values_info (objId);
-
- var res = MONO._fixup_name_value_objects (this.var_info);
- for (var i = 0; i < res.length; i++) {
- var prop_value = res [i].value;
- if (prop_value.isValueType) {
- res [i].value.objectId = `dotnet:array:${objId}:${i}`;
- } else if (prop_value.objectId !== undefined && prop_value.objectId.startsWith("dotnet:pointer")) {
- prop_value.objectId = this._get_updated_ptr_id (prop_value.objectId, {
- varName: `[${i}]`
- });
- }
+ /**
+ * @param {WasmId} id
+ * @param {number} [startIdx=0]
+ * @param {number} [count=-1]
+ * @param {boolean} [expandValueTypes=false]
+ * @returns {object[]}
+ */
+ _get_array_values: function (id, startIdx = 0, count = -1, expandValueTypes = false) {
+ 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);
+ if (!res_ok)
+ throw new Error (`Failed to get properties for array id ${id.idStr}`);
+
+ res = this._assign_vt_ids (res, (_, i) => ({ arrayId: id.o.arrayId, arrayIdx: Number (startIdx) + i}));
+
+ for (let i = 0; i < res.length; i ++) {
+ let value = res [i].value;
+ if (value.objectId !== undefined && value.objectId.startsWith("dotnet:pointer"))
+ this._new_or_add_id_props ({ objectId: value.objectId, props: { varName: `[${i}]` } });
}
-
- this.var_info = [];
-
- return res;
- },
-
- mono_wasm_get_array_value_expanded: function(objId, idx) {
- if (!this.mono_wasm_get_array_value_expanded_info)
- this.mono_wasm_get_array_value_expanded_info = Module.cwrap ("mono_wasm_get_array_value_expanded", null, [ 'number', 'number' ]);
-
- this.var_info = [];
- this.mono_wasm_get_array_value_expanded_info (objId, idx);
-
- var res = MONO._fixup_name_value_objects (this.var_info);
- // length should be exactly one!
- if (res [0].value.isValueType != undefined && res [0].value.isValueType)
- res [0].value.objectId = `dotnet:array:${objId}:${idx}`;
-
- this.var_info = [];
-
+ res = this._post_process_details (res);
return res;
},
return details;
},
- _next_value_type_id: function () {
- return ++this._next_value_type_id_var;
+ /**
+ * Gets the next id number to use for generating ids
+ *
+ * @returns {number}
+ */
+ _next_id: function () {
+ return ++this._next_id_var;
},
_extract_and_cache_value_types: function (var_list) {
return var_list;
for (let i in var_list) {
- var value = var_list [i].value;
+ let value = var_list [i].value;
if (value === undefined)
continue;
if (value.objectId !== undefined && value.objectId.startsWith ("dotnet:pointer:")) {
- var ptr_args = this._get_ptr_args (value.objectId);
- if (ptr_args.varName === undefined) {
- // It might have been already set in some cases, like arrays
- // where the name would be `0`, but we want `[0]` for pointers,
- // so the deref would look like `*[0]`
- value.objectId = this._get_updated_ptr_id (value.objectId, {
- varName: var_list [i].name
- });
- }
+ let ptr_args = this._get_id_props (value.objectId);
+ if (ptr_args === undefined)
+ throw new Error (`Bug: Expected to find an entry for pointer id: ${value.objectId}`);
+
+ // It might have been already set in some cases, like arrays
+ // where the name would be `0`, but we want `[0]` for pointers,
+ // so the deref would look like `*[0]`
+ ptr_args.varName = ptr_args.varName || var_list [i].name;
}
if (value.type != "object" || value.isValueType != true || value.expanded != true) // undefined would also give us false
continue;
// Generate objectId for expanded valuetypes
-
- var objectId = value.objectId;
- if (objectId == undefined)
- objectId = `dotnet:valuetype:${this._next_value_type_id ()}`;
- value.objectId = objectId;
+ value.objectId = value.objectId || this._new_or_add_id_props ({ scheme: 'valuetype' });
this._extract_and_cache_value_types (value.members);
- this._value_types_cache [objectId] = value.members;
+ const new_props = Object.assign ({ members: value.members }, value.__extra_vt_props);
+
+ this._new_or_add_id_props ({ objectId: value.objectId, props: new_props });
delete value.members;
+ delete value.__extra_vt_props;
}
return var_list;
},
- _get_details_for_value_type: function (objectId, fetchDetailsFn) {
- if (objectId in this._value_types_cache)
- return this._value_types_cache[objectId];
-
- this._post_process_details (fetchDetailsFn());
- if (objectId in this._value_types_cache)
- return this._value_types_cache[objectId];
-
- // return error
- throw new Error (`Could not get details for ${objectId}`);
- },
-
- _is_object_id_array: function (objectId) {
- // Keep this in sync with `_get_array_details`
- return (objectId.startsWith ('dotnet:array:') && objectId.split (':').length == 3);
- },
-
- _get_array_details: function (objectId, objectIdParts) {
- // Keep this in sync with `_is_object_id_array`
- switch (objectIdParts.length) {
- case 3:
- return this._post_process_details (this.mono_wasm_get_array_values(objectIdParts[2]));
-
- case 4:
- var arrayObjectId = objectIdParts[2];
- var arrayIdx = objectIdParts[3];
- return this._get_details_for_value_type(
- objectId, () => this.mono_wasm_get_array_value_expanded(arrayObjectId, arrayIdx));
-
- default:
- throw new Error (`object id format not supported : ${objectId}`);
- }
- },
-
_get_cfo_res_details: function (objectId, args) {
if (!(objectId in this._call_function_res_cache))
throw new Error(`Could not find any object with id ${objectId}`);
- var real_obj = this._call_function_res_cache [objectId];
+ const real_obj = this._call_function_res_cache [objectId];
- var descriptors = Object.getOwnPropertyDescriptors (real_obj);
+ const descriptors = Object.getOwnPropertyDescriptors (real_obj);
if (args.accessorPropertiesOnly) {
Object.keys (descriptors).forEach (k => {
if (descriptors [k].get === undefined)
});
}
- var res_details = [];
+ let res_details = [];
Object.keys (descriptors).forEach (k => {
- var new_obj;
- var prop_desc = descriptors [k];
+ let new_obj;
+ let prop_desc = descriptors [k];
if (typeof prop_desc.value == "object") {
// convert `{value: { type='object', ... }}`
// to `{ name: 'foo', value: { type='object', ... }}
return { __value_as_json_string__: JSON.stringify (res_details) };
},
- _get_ptr_args: function (objectId) {
- var parts = this._split_object_id (objectId);
- if (parts.length != 3)
- throw new Error (`Bug: Unexpected objectId format for a pointer, expected 3 parts: ${objectId}`);
- return JSON.parse (parts [2]);
- },
+ /**
+ * Generates a new id, and a corresponding entry for associated properties
+ * like `dotnet:pointer:{ a: 4 }`
+ * The third segment of that `{a:4}` is the idArgs parameter
+ *
+ * Only `scheme` or `objectId` can be set.
+ * if `scheme`, then a new id is generated, and it's properties set
+ * if `objectId`, then it's properties are updated
+ *
+ * @param {object} args
+ * @param {string} [args.scheme=undefined] scheme second part of `dotnet:pointer:..`
+ * @param {string} [args.objectId=undefined] objectId
+ * @param {object} [args.idArgs={}] The third segment of the objectId
+ * @param {object} [args.props={}] Properties for the generated id
+ *
+ * @returns {string} generated/updated id string
+ */
+ _new_or_add_id_props: function ({ scheme = undefined, objectId = undefined, idArgs = {}, props = {} }) {
+ if (scheme === undefined && objectId === undefined)
+ throw new Error (`Either scheme or objectId must be given`);
+
+ if (scheme !== undefined && objectId !== undefined)
+ throw new Error (`Both scheme, and objectId cannot be given`);
+
+ if (objectId !== undefined && Object.entries (idArgs).length > 0)
+ throw new Error (`Both objectId, and idArgs cannot be given`);
+
+ if (Object.entries (idArgs).length == 0) {
+ // We want to generate a new id, only if it doesn't have other
+ // attributes that it can use to uniquely identify.
+ // Eg, we don't do this for `dotnet:valuetype:{containerId:4, fieldOffset: 24}`
+ idArgs.num = this._next_id ();
+ }
- _get_updated_ptr_id: function (objectId, new_args) {
- var old_args = {};
- if (typeof (objectId) === 'string' && objectId.length)
- old_args = this._get_ptr_args (objectId);
+ let idStr;
+ if (objectId !== undefined) {
+ idStr = objectId;
+ const old_props = this._id_table [idStr];
+ if (old_props === undefined)
+ throw new Error (`ObjectId not found in the id table: ${idStr}`);
- return `dotnet:pointer:${JSON.stringify ( Object.assign (old_args, new_args) )}`;
+ this._id_table [idStr] = Object.assign (old_props, props);
+ } else {
+ idStr = `dotnet:${scheme}:${JSON.stringify (idArgs)}`;
+ this._id_table [idStr] = props;
+ }
+
+ return idStr;
+ },
+
+ /**
+ * @param {string} objectId
+ * @returns {object}
+ */
+ _get_id_props: function (objectId) {
+ return this._id_table [objectId];
},
_get_deref_ptr_value: function (objectId) {
- if (!this.mono_wasm_get_deref_ptr_value_info)
- this.mono_wasm_get_deref_ptr_value_info = Module.cwrap("mono_wasm_get_deref_ptr_value", null, ['number', 'number']);
+ const ptr_args = this._get_id_props (objectId);
+ if (ptr_args === undefined)
+ throw new Error (`Unknown pointer id: ${objectId}`);
- var ptr_args = this._get_ptr_args (objectId);
if (ptr_args.ptr_addr == 0 || ptr_args.klass_addr == 0)
throw new Error (`Both ptr_addr and klass_addr need to be non-zero, to dereference a pointer. objectId: ${objectId}`);
- this.var_info = [];
- var value_addr = new DataView (Module.HEAPU8.buffer).getUint32 (ptr_args.ptr_addr, /* littleEndian */ true);
- this.mono_wasm_get_deref_ptr_value_info (value_addr, ptr_args.klass_addr);
+ const value_addr = new DataView (Module.HEAPU8.buffer).getUint32 (ptr_args.ptr_addr, /* littleEndian */ true);
+ let { res_ok, res } = this.mono_wasm_get_deref_ptr_value_info (value_addr, ptr_args.klass_addr);
+ if (!res_ok)
+ throw new Error (`Failed to dereference pointer ${objectId}`);
- var res = MONO._fixup_name_value_objects(this.var_info);
if (res.length > 0) {
if (ptr_args.varName === undefined)
throw new Error (`Bug: no varName found for the pointer. objectId: ${objectId}`);
}
res = this._post_process_details (res);
- this.var_info = [];
return res;
},
mono_wasm_get_details: function (objectId, args) {
- var parts = objectId.split(":");
- if (parts[0] != "dotnet")
- throw new Error ("Can't handle non-dotnet object ids. ObjectId: " + objectId);
+ let id = this._parse_object_id (objectId, true);
- switch (parts[1]) {
- case "object":
- if (parts.length != 3)
- throw new Error(`exception this time: Invalid object id format: ${objectId}`);
+ switch (id.scheme) {
+ case "object": {
+ if (isNaN (id.value))
+ throw new Error (`Invalid objectId: ${objectId}. Expected a numeric id.`);
- return this._post_process_details(this.mono_wasm_get_object_properties(parts[2], false));
+ return this._get_object_properties(id.value, false);
+ }
case "array":
- return this._get_array_details(objectId, parts);
+ return this._get_array_values (id);
case "valuetype":
- if (parts.length != 3 && parts.length != 4) {
- // dotnet:valuetype:vtid
- // dotnet:valuetype:containerObjectId:vtId
- throw new Error(`Invalid object id format: ${objectId}`);
- }
-
- var containerObjectId = parts[2];
- return this._get_details_for_value_type(objectId, () => this.mono_wasm_get_object_properties(containerObjectId, true));
+ return this._get_vt_properties(id);
case "cfo_res":
return this._get_cfo_res_details (objectId, args);
},
_cache_call_function_res: function (obj) {
- var id = `dotnet:cfo_res:${this._next_call_function_res_id++}`;
+ const id = `dotnet:cfo_res:${this._next_call_function_res_id++}`;
this._call_function_res_cache[id] = obj;
return id;
},
delete this._cache_call_function_res[objectId];
},
- _invoke_getter_on_object: function (objectId, name) {
- if (!this.mono_wasm_invoke_getter_on_object)
- this.mono_wasm_invoke_getter_on_object = Module.cwrap ("mono_wasm_invoke_getter_on_object", 'void', [ 'number', 'string' ]);
-
- if (objectId < 0) {
- // invalid id
- return [];
+ /**
+ * @param {string} objectIdStr objectId
+ * @param {string} name property name
+ * @returns {object} return value
+ */
+ _invoke_getter: function (objectIdStr, name) {
+ const id = this._parse_object_id (objectIdStr);
+ if (id === undefined)
+ throw new Error (`Invalid object id: ${objectIdStr}`);
+
+ let getter_res;
+ if (id.scheme == 'object') {
+ if (isNaN (id.o) || id.o < 0)
+ throw new Error (`Invalid object id: ${objectIdStr}`);
+
+ let { res_ok, res } = this.mono_wasm_invoke_getter_on_object_info (id.o, name);
+ if (!res_ok)
+ throw new Error (`Invoking getter on ${objectIdStr} failed`);
+
+ getter_res = res;
+ } else if (id.scheme == 'valuetype') {
+ const id_props = this._get_id_props (objectIdStr);
+ if (id_props === undefined)
+ throw new Error (`Unknown valuetype id: ${objectIdStr}`);
+
+ if (typeof id_props.value64 !== 'string' || isNaN (id_props.klass))
+ throw new Error (`Bug: Cannot invoke getter on ${objectIdStr}, because of missing or invalid klass/value64 fields. idProps: ${JSON.stringify (id_props)}`);
+
+ const dataPtr = Module._malloc (id_props.value64.length);
+ const dataHeap = new Uint8Array (Module.HEAPU8.buffer, dataPtr, id_props.value64.length);
+ dataHeap.set (new Uint8Array (this._base64_to_uint8 (id_props.value64)));
+
+ let { res_ok, res } = this.mono_wasm_invoke_getter_on_value_info (dataHeap.byteOffset, id_props.klass, name);
+ Module._free (dataHeap.byteOffset);
+
+ if (!res_ok) {
+ console.debug (`Invoking getter on valuetype ${objectIdStr}, with props: ${JSON.stringify (id_props)} failed`);
+ throw new Error (`Invoking getter on valuetype ${objectIdStr} failed`);
+ }
+ getter_res = res;
+ } else {
+ throw new Error (`Only object, and valuetypes supported for getters, id: ${objectIdStr}`);
}
- this.mono_wasm_invoke_getter_on_object (objectId, name);
- var getter_res = MONO._post_process_details (MONO.var_info);
-
- MONO.var_info = [];
- return getter_res [0];
+ getter_res = MONO._post_process_details (getter_res);
+ return getter_res.length > 0 ? getter_res [0] : {};
},
_create_proxy_from_object_id: function (objectId) {
- var details = this.mono_wasm_get_details(objectId);
+ const details = this.mono_wasm_get_details(objectId);
- if (this._is_object_id_array (objectId))
+ if (objectId.startsWith ('dotnet:array:'))
return details.map (p => p.value);
- var objIdParts = objectId.split (':');
- var objIdNum = -1;
- if (objectId.startsWith ("dotnet:object:"))
- objIdNum = objIdParts [2];
-
- var proxy = {};
+ let proxy = {};
Object.keys (details).forEach (p => {
var prop = details [p];
if (prop.get !== undefined) {
// TODO: `set`
- // We don't add a `get` for non-object types right now,
- // so, we shouldn't get here with objIdNum==-1
Object.defineProperty (proxy,
prop.name,
- { get () { return MONO._invoke_getter_on_object (objIdNum, prop.name); } }
+ { get () { return MONO._invoke_getter (objectId, prop.name); } }
);
} else {
proxy [prop.name] = prop.value;
if (request.arguments != undefined && !Array.isArray (request.arguments))
throw new Error (`"arguments" should be an array, but was ${request.arguments}`);
- var objId = request.objectId;
- var proxy;
+ const objId = request.objectId;
+ let proxy;
- if (objId in this._call_function_res_cache) {
- proxy = this._call_function_res_cache [objId];
- } else if (!objId.startsWith ('dotnet:cfo_res:')) {
+ if (objId.startsWith ('dotnet:cfo_res:')) {
+ if (objId in this._call_function_res_cache)
+ proxy = this._call_function_res_cache [objId];
+ else
+ throw new Error (`Unknown object id ${objId}`);
+ } else {
proxy = this._create_proxy_from_object_id (objId);
}
- var fn_args = request.arguments != undefined ? request.arguments.map(a => JSON.stringify(a.value)) : [];
- var fn_eval_str = `var fn = ${request.functionDeclaration}; fn.call (proxy, ...[${fn_args}]);`;
+ const fn_args = request.arguments != undefined ? request.arguments.map(a => JSON.stringify(a.value)) : [];
+ const fn_eval_str = `var fn = ${request.functionDeclaration}; fn.call (proxy, ...[${fn_args}]);`;
- var fn_res = eval (fn_eval_str);
- if (fn_res == undefined) // should we just return undefined?
- throw Error ('Function returned undefined result');
+ const fn_res = eval (fn_eval_str);
+ if (fn_res === undefined)
+ return { type: "undefined" };
+
+ if (fn_res === null || (fn_res.subtype === 'null' && fn_res.value === undefined))
+ return fn_res;
// primitive type
if (Object (fn_res) !== fn_res)
if (request.returnByValue)
return {type: "object", value: fn_res};
- var fn_res_id = this._cache_call_function_res (fn_res);
+ const fn_res_id = this._cache_call_function_res (fn_res);
if (Object.getPrototypeOf (fn_res) == Array.prototype) {
return {
type: "object",
},
_clear_per_step_state: function () {
- this._next_value_type_id_var = 0;
- this._value_types_cache = {};
+ this._next_id_var = 0;
+ this._id_table = {};
},
mono_wasm_debugger_resume: function () {
this._clear_per_step_state ();
},
-
+
mono_wasm_start_single_stepping: function (kind) {
console.log (">> mono_wasm_start_single_stepping " + kind);
if (!this.mono_wasm_setup_single_step)
return this.mono_wasm_pause_on_exceptions (state_enum);
},
+ _register_c_fn: function (name, ...args) {
+ Object.defineProperty (this._c_fn_table, name + '_wrapper', { value: Module.cwrap (name, ...args) });
+ },
+
+ /**
+ * Calls `Module.cwrap` for the function name,
+ * and creates a wrapper around it that returns
+ * `{ bool result, object var_info }
+ *
+ * @param {string} name C function name
+ * @param {string} ret_type
+ * @param {string[]} params
+ *
+ * @returns {void}
+ */
+ _register_c_var_fn: function (name, ret_type, params) {
+ if (ret_type !== 'bool')
+ throw new Error (`Bug: Expected a C function signature that returns bool`);
+
+ this._register_c_fn (name, ret_type, params);
+ Object.defineProperty (this, name + '_info', {
+ value: function (...args) {
+ MONO.var_info = [];
+ const res_ok = MONO._c_fn_table [name + '_wrapper'] (...args);
+ let res = MONO.var_info;
+ MONO.var_info = [];
+ if (res_ok) {
+ res = this._fixup_name_value_objects (res);
+ return { res_ok, res };
+ }
+
+ return { res_ok, res: undefined };
+ }
+ });
+ },
+
mono_wasm_runtime_ready: function () {
this.mono_wasm_runtime_is_ready = true;
// DO NOT REMOVE - magic debugger init function
// FIXME: where should this go?
this._next_call_function_res_id = 0;
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_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']);
+ this._register_c_var_fn ('mono_wasm_get_deref_ptr_value', 'bool', [ 'number', 'number']);
},
mono_wasm_set_breakpoint: function (assembly, method_token, il_offset) {
},
// Set environment variable NAME to VALUE
- // Should be called before mono_load_runtime_and_bcl () in most cases
+ // Should be called before mono_load_runtime_and_bcl () in most cases
mono_wasm_setenv: function (name, value) {
if (!this.wasm_setenv)
this.wasm_setenv = Module.cwrap ('mono_wasm_setenv', null, ['string', 'string']);
this.wasm_parse_runtime_options = Module.cwrap ('mono_wasm_parse_runtime_options', null, ['number', 'number']);
var argv = Module._malloc (options.length * 4);
var wasm_strdup = Module.cwrap ('mono_wasm_strdup', 'number', ['string']);
- aindex = 0;
+ let aindex = 0;
for (var i = 0; i < options.length; ++i) {
Module.setValue (argv + (aindex * 4), wasm_strdup (options [i]), "i32");
aindex += 1;
mono_wasm_add_null_var: function(className)
{
- fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className));
+ let fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className));
if (!fixed_class_name) {
// Eg, when a @className is passed from js itself, like
// mono_wasm_add_null_var ("string")
value: {
type: "string",
value: var_value,
+ description: var_value
}
});
},
_mono_wasm_add_getter_var: function(className, invokable) {
- fixed_class_name = MONO._mono_csharp_fixup_class_name (className);
+ const fixed_class_name = MONO._mono_csharp_fixup_class_name (className);
if (invokable != 0) {
var name;
if (MONO.var_info.length > 0)
},
_mono_wasm_add_array_var: function(className, objectId, length) {
- fixed_class_name = MONO._mono_csharp_fixup_class_name(className);
+ const fixed_class_name = MONO._mono_csharp_fixup_class_name(className);
if (objectId == 0) {
MONO.mono_wasm_add_null_var (fixed_class_name);
return;
subtype: "array",
className: fixed_class_name,
description: `${fixed_class_name}(${length})`,
- objectId: "dotnet:array:"+ objectId,
+ objectId: this._new_or_add_id_props ({ scheme: 'array', idArgs: { arrayId: objectId } })
}
});
},
+ // FIXME: improve
+ _base64_to_uint8: function (base64String) {
+ const byteCharacters = atob (base64String);
+ const byteNumbers = new Array(byteCharacters.length);
+ for (let i = 0; i < byteCharacters.length; i++) {
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
+ }
+
+ return new Uint8Array (byteNumbers);
+ },
+
+ _begin_value_type_var: function(className, args) {
+ if (args === undefined || (typeof args !== 'object')) {
+ console.debug (`_begin_value_type_var: Expected an args object`);
+ return;
+ }
+
+ const fixed_class_name = MONO._mono_csharp_fixup_class_name(className);
+ const toString = args.toString;
+ const base64String = btoa (String.fromCharCode (...new Uint8Array (Module.HEAPU8.buffer, args.value_addr, args.value_size)));
+ const vt_obj = {
+ value: {
+ type : "object",
+ className : fixed_class_name,
+ description : (toString == 0 ? fixed_class_name: Module.UTF8ToString (toString)),
+ expanded : true,
+ isValueType : true,
+ __extra_vt_props: { klass: args.klass, value64: base64String },
+ members : []
+ }
+ };
+ if (MONO._vt_stack.length == 0)
+ MONO._old_var_info = MONO.var_info;
+
+ MONO.var_info = vt_obj.value.members;
+ MONO._vt_stack.push (vt_obj);
+ },
+
+ _end_value_type_var: function() {
+ let top_vt_obj_popped = MONO._vt_stack.pop ();
+ top_vt_obj_popped.value.members = MONO._filter_automatic_properties (
+ MONO._fixup_name_value_objects (top_vt_obj_popped.value.members));
+
+ if (MONO._vt_stack.length == 0) {
+ MONO.var_info = MONO._old_var_info;
+ MONO.var_info.push(top_vt_obj_popped);
+ } else {
+ var top_obj = MONO._vt_stack [MONO._vt_stack.length - 1];
+ top_obj.value.members.push (top_vt_obj_popped);
+ MONO.var_info = top_obj.value.members;
+ }
+ },
+
+ _add_valuetype_unexpanded_var: function(className, args) {
+ if (args === undefined || (typeof args !== 'object')) {
+ console.debug (`_add_valuetype_unexpanded_var: Expected an args object`);
+ return;
+ }
+
+ const fixed_class_name = MONO._mono_csharp_fixup_class_name (className);
+ const toString = args.toString;
+
+ MONO.var_info.push ({
+ value: {
+ type: "object",
+ className: fixed_class_name,
+ description: (toString == 0 ? fixed_class_name : Module.UTF8ToString (toString)),
+ isValueType: true
+ }
+ });
+ },
+
+
mono_wasm_add_typed_value: function (type, str_value, value) {
- var type_str = type;
+ let type_str = type;
if (typeof type != 'string')
type_str = Module.UTF8ToString (type);
- if (typeof str_value != 'string')
str_value = Module.UTF8ToString (str_value);
switch (type_str) {
- case "bool":
+ case "bool": {
+ const v = value != 0;
MONO.var_info.push ({
value: {
type: "boolean",
- value: value != 0
+ value: v,
+ description: v.toString ()
}
});
break;
+ }
- case "char":
+ case "char": {
+ const v = `${value} '${String.fromCharCode (value)}'`;
MONO.var_info.push ({
value: {
type: "symbol",
- value: `${value} '${String.fromCharCode (value)}'`
+ value: v,
+ description: v
}
});
break;
+ }
case "number":
MONO.var_info.push ({
value: {
type: "number",
- value: value
+ value: value,
+ description: '' + value
}
});
break;
MONO._mono_wasm_add_array_var (str_value, value.objectId, value.length);
break;
+ case "begin_vt":
+ MONO._begin_value_type_var (str_value, value);
+ break;
+
+ case "end_vt":
+ MONO._end_value_type_var ();
+ break;
+
+ case "unexpanded_vt":
+ MONO._add_valuetype_unexpanded_var (str_value, value);
+ break;
+
case "pointer": {
- var fixed_value_str = MONO._mono_csharp_fixup_class_name (str_value);
+ const fixed_value_str = MONO._mono_csharp_fixup_class_name (str_value);
if (value.klass_addr == 0 || value.ptr_addr == 0 || fixed_value_str.startsWith ('(void*')) {
// null or void*, which we can't deref
MONO.var_info.push({
type: "object",
className: fixed_value_str,
description: fixed_value_str,
- objectId: this._get_updated_ptr_id ('', value)
+ objectId: this._new_or_add_id_props ({ scheme: 'pointer', props: value })
}
});
}
break;
default: {
- var msg = `'${str_value}' ${value}`;
+ const msg = `'${str_value}' ${value}`;
MONO.var_info.push ({
value: {
MONO._async_method_objectId = objectId;
},
- mono_wasm_begin_value_type_var: function(className, toString) {
- fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className));
- var vt_obj = {
- value: {
- type: "object",
- className: fixed_class_name,
- description: (toString == 0 ? fixed_class_name : Module.UTF8ToString (toString)),
- // objectId will be generated by MonoProxy
- expanded: true,
- isValueType: true,
- members: []
- }
- };
- if (MONO._vt_stack.length == 0)
- MONO._old_var_info = MONO.var_info;
-
- MONO.var_info = vt_obj.value.members;
- MONO._vt_stack.push (vt_obj);
- },
-
- mono_wasm_end_value_type_var: function() {
- var top_vt_obj_popped = MONO._vt_stack.pop ();
- top_vt_obj_popped.value.members = MONO._filter_automatic_properties (
- MONO._fixup_name_value_objects (top_vt_obj_popped.value.members));
-
- if (MONO._vt_stack.length == 0) {
- MONO.var_info = MONO._old_var_info;
- MONO.var_info.push(top_vt_obj_popped);
- } else {
- var top_obj = MONO._vt_stack [MONO._vt_stack.length - 1];
- top_obj.value.members.push (top_vt_obj_popped);
- MONO.var_info = top_obj.value.members;
- }
- },
-
- mono_wasm_add_value_type_unexpanded_var: function (className, toString) {
- fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className));
- MONO.var_info.push({
- value: {
- type: "object",
- className: fixed_class_name,
- description: (toString == 0 ? fixed_class_name : Module.UTF8ToString (toString)),
- // objectId added when enumerating object's properties
- expanded: false,
- isValueType: true
- }
- });
- },
-
mono_wasm_add_enum_var: function(className, members, value) {
// FIXME: flags
//
// group0: Monday:0
// group1: Monday
// group2: 0
- var re = new RegExp (`[,]?([^,:]+):(${value}(?=,)|${value}$)`, 'g')
- var members_str = Module.UTF8ToString (members);
+ const re = new RegExp (`[,]?([^,:]+):(${value}(?=,)|${value}$)`, 'g')
+ const members_str = Module.UTF8ToString (members);
- var match = re.exec(members_str);
- var member_name = match == null ? ('' + value) : match [1];
+ const match = re.exec(members_str);
+ const member_name = match == null ? ('' + value) : match [1];
- fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className));
+ const fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className));
MONO.var_info.push({
value: {
type: "object",
return;
}
- fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className));
+ const fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className));
MONO.var_info.push({
value: {
type: "object",
return `${ret_sig} ${method_name} (${args_sig})`;
}
- var tgt_sig;
+ let tgt_sig;
if (targetName != 0)
tgt_sig = args_to_sig (Module.UTF8ToString (targetName));
- var type_name = MONO._mono_csharp_fixup_class_name (Module.UTF8ToString (className));
+ const type_name = MONO._mono_csharp_fixup_class_name (Module.UTF8ToString (className));
if (objectId == -1) {
// Target property
mono_wasm_fire_bp: function () {
console.log ("mono_wasm_fire_bp");
+ // eslint-disable-next-line no-debugger
debugger;
},