Skip to content

Commit

Permalink
[wasm][debugger] Small checks on inputs, and some negative tests
Browse files Browse the repository at this point in the history
- These tests don't actually depend on the error message, and we don't
have another to way to differentiate why a command might have failed
with an exception. So, right now, they will pass as long as the commands
fail as expected.

- Future TODO: return `error`, instead of exception details for issues
in `mono.js`, like incorrect input, invalid id etc, and update these
tests accordingly.
  • Loading branch information
radical committed Aug 8, 2020
1 parent acb69b7 commit af5b05a
Show file tree
Hide file tree
Showing 8 changed files with 300 additions and 14 deletions.
8 changes: 8 additions & 0 deletions src/mono/mono/mini/mini-wasm-debugger.c
Original file line number Diff line number Diff line change
Expand Up @@ -1392,6 +1392,14 @@ EMSCRIPTEN_KEEPALIVE gboolean
mono_wasm_invoke_getter_on_value (void *value, MonoClass *klass, const char *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);
}

Expand Down
63 changes: 63 additions & 0 deletions src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -628,5 +628,68 @@ public async Task InvalidArrayId() => await CheckInspectLocalsAtBreakpointSite(
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");
}
});

}
}
151 changes: 151 additions & 0 deletions src/mono/wasm/debugger/DebuggerTestSuite/CallFunctionOnTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,36 @@ await insp.Ready(async(cli, token) =>
// 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");
});
}

Expand Down Expand Up @@ -736,6 +766,127 @@ async Task<Result> GetPropertiesAndCheckAccessors(JObject get_prop_req, int num_
}
}

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) =>
{
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,
* if @roundtrip == false, then
Expand Down
4 changes: 3 additions & 1 deletion src/mono/wasm/debugger/DebuggerTestSuite/Support.cs
Original file line number Diff line number Diff line change
Expand Up @@ -443,14 +443,16 @@ internal async Task<JObject> RunUntil(string methodName)
return wait_res;
}

internal async Task<Result> InvokeGetter(JToken obj, object arguments, string fn = "function(e){return this[e]}", bool expect_ok = true)
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}");
Expand Down
39 changes: 38 additions & 1 deletion src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1485,6 +1485,43 @@ await insp.Ready(async(cli, token) =>
});
}

[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
}
}
}
7 changes: 7 additions & 0 deletions src/mono/wasm/debugger/tests/debugger-cfo-test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ public static async System.Threading.Tasks.Task PropertyGettersTestAsync()
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
Expand Down
13 changes: 13 additions & 0 deletions src/mono/wasm/debugger/tests/other.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,16 @@ function exceptions_test () {
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;
}
Loading

0 comments on commit af5b05a

Please sign in to comment.