Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[release/6.0] [wasm][debugger] View multidimensional array when debugging #62283

Merged
merged 1 commit into from
Dec 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ private async Task<bool> CallOnFunction(MessageId id, JObject args, Cancellation
args["details"] = await SdbHelper.GetPointerContent(id, int.Parse(objectId.Value), token);
break;
case "array":
args["details"] = await SdbHelper.GetArrayValues(id, int.Parse(objectId.Value), token);
args["details"] = await SdbHelper.GetArrayValuesProxy(id, int.Parse(objectId.Value), token);
break;
case "cfo_res":
{
Expand Down
91 changes: 80 additions & 11 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,52 @@ internal enum StepSize
Line
}

internal record ArrayDimensions
{
internal int Rank { get; }
internal int [] Bounds { get; }
internal int TotalLength { get; }
public ArrayDimensions(int [] rank)
{
Rank = rank.Length;
Bounds = rank;
TotalLength = 1;
for (int i = 0 ; i < Rank ; i++)
TotalLength *= Bounds[i];
}

public override string ToString()
{
return $"{string.Join(", ", Bounds)}";
}
internal string GetArrayIndexString(int idx)
{
if (idx < 0 || idx >= TotalLength)
return "Invalid Index";
int boundLimit = 1;
int lastBoundLimit = 1;
int[] arrayStr = new int[Rank];
int rankStart = 0;
while (idx > 0)
{
boundLimit = 1;
for (int i = Rank - 1; i >= rankStart; i--)
{
lastBoundLimit = boundLimit;
boundLimit *= Bounds[i];
if (idx < boundLimit)
{
arrayStr[i] = (int)(idx / lastBoundLimit);
idx -= arrayStr[i] * lastBoundLimit;
rankStart = i;
break;
}
}
}
return $"{string.Join(", ", arrayStr)}";
}
}

internal record MethodInfoWithDebugInformation(MethodInfo Info, int DebugId, string Name);

internal class TypeInfoWithDebugInformation
Expand Down Expand Up @@ -1400,7 +1446,8 @@ public async Task<string> GetTypeName(SessionId sessionId, int typeId, Cancellat
string className = await GetTypeNameOriginal(sessionId, typeId, token);
className = className.Replace("+", ".");
className = Regex.Replace(className, @"`\d+", "");
className = className.Replace("[]", "__SQUARED_BRACKETS__");
className = Regex.Replace(className, @"[[, ]+]", "__SQUARED_BRACKETS__");
//className = className.Replace("[]", "__SQUARED_BRACKETS__");
className = className.Replace("[", "<");
className = className.Replace("]", ">");
className = className.Replace("__SQUARED_BRACKETS__", "[]");
Expand Down Expand Up @@ -1451,15 +1498,20 @@ public async Task<string> GetStringValue(SessionId sessionId, int string_id, Can
}
return null;
}
public async Task<int> GetArrayLength(SessionId sessionId, int object_id, CancellationToken token)
public async Task<ArrayDimensions> GetArrayDimensions(SessionId sessionId, int object_id, CancellationToken token)
{
var commandParams = new MemoryStream();
var commandParamsWriter = new MonoBinaryWriter(commandParams);
commandParamsWriter.Write(object_id);
var retDebuggerCmdReader = await SendDebuggerAgentCommand<CmdArray>(sessionId, CmdArray.GetLength, commandParams, token);
var length = retDebuggerCmdReader.ReadInt32();
length = retDebuggerCmdReader.ReadInt32();
return length;
var rank = new int[length];
for (int i = 0 ; i < length; i++)
{
rank[i] = retDebuggerCmdReader.ReadInt32();
retDebuggerCmdReader.ReadInt32(); //lower_bound
}
return new ArrayDimensions(rank);
}
public async Task<List<int>> GetTypeIdFromObject(SessionId sessionId, int object_id, bool withParents, CancellationToken token)
{
Expand Down Expand Up @@ -1778,9 +1830,14 @@ public async Task<JObject> CreateJObjectForString(SessionId sessionId, MonoBinar
public async Task<JObject> CreateJObjectForArray(SessionId sessionId, MonoBinaryReader retDebuggerCmdReader, CancellationToken token)
{
var objectId = retDebuggerCmdReader.ReadInt32();
var value = await GetClassNameFromObject(sessionId, objectId, token);
var length = await GetArrayLength(sessionId, objectId, token);
return CreateJObject<string>(null, "object", $"{value.ToString()}({length})", false, value.ToString(), "dotnet:array:" + objectId, null, "array");
var className = await GetClassNameFromObject(sessionId, objectId, token);
var arrayType = className.ToString();
var length = await GetArrayDimensions(sessionId, objectId, token);
if (arrayType.LastIndexOf('[') > 0)
arrayType = arrayType.Insert(arrayType.LastIndexOf('[')+1, length.ToString());
if (className.LastIndexOf('[') > 0)
className = className.Insert(arrayType.LastIndexOf('[')+1, new string(',', length.Rank-1));
return CreateJObject<string>(null, "object", description : arrayType, writable : false, className.ToString(), "dotnet:array:" + objectId, null, subtype : length.Rank == 1 ? "array" : null);
}

public async Task<JObject> CreateJObjectForObject(SessionId sessionId, MonoBinaryReader retDebuggerCmdReader, int typeIdFromAttribute, bool forDebuggerDisplayAttribute, CancellationToken token)
Expand Down Expand Up @@ -2206,21 +2263,33 @@ public async Task<JArray> GetValueTypeProxy(SessionId sessionId, int valueTypeId

public async Task<JArray> GetArrayValues(SessionId sessionId, int arrayId, CancellationToken token)
{
var length = await GetArrayLength(sessionId, arrayId, token);
var dimensions = await GetArrayDimensions(sessionId, arrayId, token);
var commandParams = new MemoryStream();
var commandParamsWriter = new MonoBinaryWriter(commandParams);
commandParamsWriter.Write(arrayId);
commandParamsWriter.Write(0);
commandParamsWriter.Write(length);
commandParamsWriter.Write(dimensions.TotalLength);
var retDebuggerCmdReader = await SendDebuggerAgentCommand<CmdArray>(sessionId, CmdArray.GetValues, commandParams, token);
JArray array = new JArray();
for (int i = 0 ; i < length ; i++)
for (int i = 0 ; i < dimensions.TotalLength; i++)
{
var var_json = await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, i.ToString(), false, -1, false, token);
var var_json = await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, dimensions.GetArrayIndexString(i), isOwn : false, -1, forDebuggerDisplayAttribute : false, token);
array.Add(var_json);
}
return array;
}

public async Task<JObject> GetArrayValuesProxy(SessionId sessionId, int arrayId, CancellationToken token)
{
var length = await GetArrayDimensions(sessionId, arrayId, token);
var arrayProxy = JObject.FromObject(new
{
items = await GetArrayValues(sessionId, arrayId, token),
dimensionsDetails = length.Bounds
});
return arrayProxy;
}

public async Task<bool> EnableExceptions(SessionId sessionId, PauseOnExceptionsKind state, CancellationToken token)
{
if (state == PauseOnExceptionsKind.Unset)
Expand Down
107 changes: 80 additions & 27 deletions src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,24 @@ public async Task InspectGenericValueTypeArrayLocals2(int line, int col, string
frame_idx: frame_idx,
use_cfo: use_cfo);

async Task<JToken> GetObjectWithCFO(string objectId, JObject fn_args = null)
{
var fn_decl = "function () { return this; }";
var cfo_args = JObject.FromObject(new
{
functionDeclaration = fn_decl,
objectId = objectId
});

if (fn_args != null)
cfo_args["arguments"] = fn_args;

// callFunctionOn
var result = await cli.SendCommand("Runtime.callFunctionOn", cfo_args, token);

return await GetProperties(result.Value["result"]["objectId"]?.Value<string>(), fn_args);
}

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_elem_props,
bool test_prev_frame = false, int frame_idx = 0, bool use_cfo = false)
Expand All @@ -215,8 +233,8 @@ async Task TestSimpleArrayLocals(int line, int col, string entry_method_name, st

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}[]", array?.Length ?? 0);
CheckArray(locals, $"{local_var_name_prefix}_arr_empty", $"{etype_name}[]", 0);
CheckArray(locals, $"{local_var_name_prefix}_arr", $"{etype_name}[]", $"{etype_name}[{array?.Length ?? 0}]");
CheckArray(locals, $"{local_var_name_prefix}_arr_empty", $"{etype_name}[]", $"{etype_name}[0]");
CheckObject(locals, $"{local_var_name_prefix}_arr_null", $"{etype_name}[]", is_null: true);
CheckBool(locals, "call_other", test_prev_frame);

Expand Down Expand Up @@ -264,24 +282,6 @@ async Task TestSimpleArrayLocals(int line, int col, string entry_method_name, st

var props = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], $"{local_var_name_prefix}_arr_empty");
await CheckProps(props, new object[0], "${local_var_name_prefix}_arr_empty");

async Task<JToken> GetObjectWithCFO(string objectId, JObject fn_args = null)
{
var fn_decl = "function () { return this; }";
var cfo_args = JObject.FromObject(new
{
functionDeclaration = fn_decl,
objectId = objectId
});

if (fn_args != null)
cfo_args["arguments"] = fn_args;

// callFunctionOn
var result = await cli.SendCommand("Runtime.callFunctionOn", cfo_args, token);

return await GetProperties(result.Value["result"]["objectId"]?.Value<string>(), fn_args);
}
}

[Theory]
Expand Down Expand Up @@ -313,10 +313,10 @@ public async Task InspectObjectArrayMembers(bool use_cfo)
await CheckProps(c_props, new
{
id = TString("c#id"),
ClassArrayProperty = TArray("DebuggerTests.SimpleClass[]", 3),
ClassArrayField = TArray("DebuggerTests.SimpleClass[]", 3),
PointsProperty = TArray("DebuggerTests.Point[]", 2),
PointsField = TArray("DebuggerTests.Point[]", 2)
ClassArrayProperty = TArray("DebuggerTests.SimpleClass[]", "DebuggerTests.SimpleClass[3]"),
ClassArrayField = TArray("DebuggerTests.SimpleClass[]", "DebuggerTests.SimpleClass[3]"),
PointsProperty = TArray("DebuggerTests.Point[]", "DebuggerTests.Point[2]"),
PointsField = TArray("DebuggerTests.Point[]", "DebuggerTests.Point[2]")
},
"c"
);
Expand Down Expand Up @@ -382,8 +382,8 @@ public async Task InspectValueTypeArrayLocalsStaticAsync(bool use_cfo)
await CheckProps(frame_locals, new
{
call_other = TBool(false),
gvclass_arr = TArray("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[]", 2),
gvclass_arr_empty = TArray("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[]"),
gvclass_arr = TArray("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[]", "DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[2]"),
gvclass_arr_empty = TArray("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[]", "DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[0]"),
gvclass_arr_null = TObject("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[]", is_null: true),
gvclass = TValueType("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>"),
// BUG: this shouldn't be null!
Expand Down Expand Up @@ -448,7 +448,7 @@ public async Task InspectValueTypeArrayLocalsInstanceAsync(bool use_cfo)
{
t1 = TObject("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>"),
@this = TObject("DebuggerTests.ArrayTestsClass"),
point_arr = TArray("DebuggerTests.Point[]", 2),
point_arr = TArray("DebuggerTests.Point[]", "DebuggerTests.Point[2]"),
point = TValueType("DebuggerTests.Point")
}, "InspectValueTypeArrayLocalsInstanceAsync#locals");

Expand Down Expand Up @@ -642,6 +642,59 @@ public async Task InvalidAccessors() => await CheckInspectLocalsAtBreakpointSite
AssertEqual("undefined", res.Value["result"]?["type"]?.ToString(), "Expected to get undefined result for non-existant accessor");
}
});

[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task InspectPrimitiveTypeMultiArrayLocals(bool use_cfo)
{
var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";

var eval_expr = "window.setTimeout(function() { invoke_static_method (" +
$"'[debugger-test] DebuggerTests.MultiDimensionalArray:run'" +
"); }, 1);";

var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, 343, 12, "run");

var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
Assert.Equal(3, locals.Count());
var int_arr_1 = !use_cfo ?
await GetProperties(locals[0]["value"]["objectId"].Value<string>()) :
await GetObjectWithCFO((locals[0]["value"]["objectId"].Value<string>()));

CheckNumber(int_arr_1, "0", 0);
CheckNumber(int_arr_1, "1", 1);
var int_arr_2 = !use_cfo ?
await GetProperties(locals[1]["value"]["objectId"].Value<string>()) :
await GetObjectWithCFO((locals[1]["value"]["objectId"].Value<string>()));
CheckNumber(int_arr_2, "0, 0", 0);
CheckNumber(int_arr_2, "0, 1", 1);
CheckNumber(int_arr_2, "0, 2", 2);
CheckNumber(int_arr_2, "1, 0", 10);
CheckNumber(int_arr_2, "1, 1", 11);
CheckNumber(int_arr_2, "1, 2", 12);

var int_arr_3 = !use_cfo ?
await GetProperties(locals[2]["value"]["objectId"].Value<string>()) :
await GetObjectWithCFO((locals[2]["value"]["objectId"].Value<string>()));
CheckNumber(int_arr_3, "0, 0, 0", 0);
CheckNumber(int_arr_3, "0, 0, 1", 1);
CheckNumber(int_arr_3, "0, 0, 2", 2);
CheckNumber(int_arr_3, "0, 1, 0", 10);
CheckNumber(int_arr_3, "0, 1, 1", 11);
CheckNumber(int_arr_3, "0, 1, 2", 12);
CheckNumber(int_arr_3, "0, 2, 0", 20);
CheckNumber(int_arr_3, "0, 2, 1", 21);
CheckNumber(int_arr_3, "0, 2, 2", 22);
CheckNumber(int_arr_3, "1, 0, 0", 100);
CheckNumber(int_arr_3, "1, 0, 1", 101);
CheckNumber(int_arr_3, "1, 0, 2", 102);
CheckNumber(int_arr_3, "1, 1, 0", 110);
CheckNumber(int_arr_3, "1, 1, 1", 111);
CheckNumber(int_arr_3, "1, 1, 2", 112);
CheckNumber(int_arr_3, "1, 2, 0", 120);
CheckNumber(int_arr_3, "1, 2, 1", 121);
CheckNumber(int_arr_3, "1, 2, 2", 122);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class AssignmentTests : DebuggerTestBase
{ "MONO_TYPE_CHAR", TSymbol("0 '\u0000'"), TSymbol("97 'a'") },
{ "MONO_TYPE_STRING", TString(default), TString("hello") },
{ "MONO_TYPE_ENUM", TEnum("DebuggerTests.RGB", "Red"), TEnum("DebuggerTests.RGB", "Blue") },
{ "MONO_TYPE_ARRAY", TObject("byte[]", is_null: true), TArray("byte[]", 2) },
{ "MONO_TYPE_ARRAY", TObject("byte[]", is_null: true), TArray("byte[]", "byte[2]") },
{ "MONO_TYPE_VALUETYPE", TValueType("DebuggerTests.Point"), TValueType("DebuggerTests.Point") },
{ "MONO_TYPE_VALUETYPE2", TValueType("System.Decimal","0"), TValueType("System.Decimal", "1.1") },
{ "MONO_TYPE_GENERICINST", TObject("System.Func<int>", is_null: true), TDelegate("System.Func<int>", "int Prepare ()") },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ public async Task RunOnJSObject(bool roundtrip) => await RunCallFunctionOn(
await CheckProps(obj_own_val, new
{
a_obj = TObject("Object"),
b_arr = TArray("Array", 2)
b_arr = TArray("Array", "Array(2)")
}, "obj_own");
});

Expand Down Expand Up @@ -641,7 +641,7 @@ public async Task PropertyGettersTest(string eval_fn, string method_name, int li
// Check arrays through getters

res = await InvokeGetter(obj, get_args_fn(new[] { "IntArray" }), cfo_fn);
await CheckValue(res.Value["result"], TArray("int[]", 2), $"{local_name}.IntArray");
await CheckValue(res.Value["result"], TArray("int[]", "int[2]"), $"{local_name}.IntArray");
{
var arr_elems = await GetProperties(res.Value["result"]?["objectId"]?.Value<string>());
var exp_elems = new[]
Expand All @@ -654,7 +654,7 @@ public async Task PropertyGettersTest(string eval_fn, string method_name, int li
}

res = await InvokeGetter(obj, get_args_fn(new[] { "DTArray" }), cfo_fn);
await CheckValue(res.Value["result"], TArray("System.DateTime[]", 2), $"{local_name}.DTArray");
await CheckValue(res.Value["result"], TArray("System.DateTime[]", "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);
Expand Down Expand Up @@ -944,7 +944,7 @@ async Task CheckCFOResult(Result result)
if (res_array_len < 0)
await CheckValue(result.Value["result"], TObject("Object"), $"cfo-res");
else
await CheckValue(result.Value["result"], TArray("Array", res_array_len), $"cfo-res");
await CheckValue(result.Value["result"], TArray("Array", $"Array({res_array_len})"), $"cfo-res");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public async Task UsingDebuggerTypeProxy()
var props = await GetObjectOnFrame(frame, "myList");
Assert.Equal(1, props.Count());

CheckArray(props, "Items", "int[]", 4);
CheckArray(props, "Items", "int[]", "int[4]");

CheckObject(locals, "b", "DebuggerTests.WithProxy", description:"DebuggerTests.WithProxy");
props = await GetObjectOnFrame(frame, "b");
Expand Down
Loading