Skip to content

Commit 293e5ed

Browse files
authored
[wasm][debugger] View multidimensional array when debugging (#60983)
* It's working when debug from chrome but not when debug from VS, because it uses callFunctionOn * Remove unrelated change. * Working also on VS. * Working also on VS. * Addressing @lewing and @radical comments * Change ArrayDimensions to be a record and not a class as suggested by @radical. * Addressing @radical comments.
1 parent 7b74a7e commit 293e5ed

File tree

13 files changed

+246
-80
lines changed

13 files changed

+246
-80
lines changed

src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,7 @@ private async Task<bool> CallOnFunction(MessageId id, JObject args, Cancellation
555555
args["details"] = await SdbHelper.GetPointerContent(id, int.Parse(objectId.Value), token);
556556
break;
557557
case "array":
558-
args["details"] = await SdbHelper.GetArrayValues(id, int.Parse(objectId.Value), token);
558+
args["details"] = await SdbHelper.GetArrayValuesProxy(id, int.Parse(objectId.Value), token);
559559
break;
560560
case "cfo_res":
561561
{

src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,52 @@ internal enum StepSize
356356
Line
357357
}
358358

359+
internal record ArrayDimensions
360+
{
361+
internal int Rank { get; }
362+
internal int [] Bounds { get; }
363+
internal int TotalLength { get; }
364+
public ArrayDimensions(int [] rank)
365+
{
366+
Rank = rank.Length;
367+
Bounds = rank;
368+
TotalLength = 1;
369+
for (int i = 0 ; i < Rank ; i++)
370+
TotalLength *= Bounds[i];
371+
}
372+
373+
public override string ToString()
374+
{
375+
return $"{string.Join(", ", Bounds)}";
376+
}
377+
internal string GetArrayIndexString(int idx)
378+
{
379+
if (idx < 0 || idx >= TotalLength)
380+
return "Invalid Index";
381+
int boundLimit = 1;
382+
int lastBoundLimit = 1;
383+
int[] arrayStr = new int[Rank];
384+
int rankStart = 0;
385+
while (idx > 0)
386+
{
387+
boundLimit = 1;
388+
for (int i = Rank - 1; i >= rankStart; i--)
389+
{
390+
lastBoundLimit = boundLimit;
391+
boundLimit *= Bounds[i];
392+
if (idx < boundLimit)
393+
{
394+
arrayStr[i] = (int)(idx / lastBoundLimit);
395+
idx -= arrayStr[i] * lastBoundLimit;
396+
rankStart = i;
397+
break;
398+
}
399+
}
400+
}
401+
return $"{string.Join(", ", arrayStr)}";
402+
}
403+
}
404+
359405
internal record MethodInfoWithDebugInformation(MethodInfo Info, int DebugId, string Name);
360406

361407
internal class TypeInfoWithDebugInformation
@@ -1327,7 +1373,8 @@ public async Task<string> GetTypeName(SessionId sessionId, int typeId, Cancellat
13271373
string className = await GetTypeNameOriginal(sessionId, typeId, token);
13281374
className = className.Replace("+", ".");
13291375
className = Regex.Replace(className, @"`\d+", "");
1330-
className = className.Replace("[]", "__SQUARED_BRACKETS__");
1376+
className = Regex.Replace(className, @"[[, ]+]", "__SQUARED_BRACKETS__");
1377+
//className = className.Replace("[]", "__SQUARED_BRACKETS__");
13311378
className = className.Replace("[", "<");
13321379
className = className.Replace("]", ">");
13331380
className = className.Replace("__SQUARED_BRACKETS__", "[]");
@@ -1378,15 +1425,20 @@ public async Task<string> GetStringValue(SessionId sessionId, int string_id, Can
13781425
}
13791426
return null;
13801427
}
1381-
public async Task<int> GetArrayLength(SessionId sessionId, int object_id, CancellationToken token)
1428+
public async Task<ArrayDimensions> GetArrayDimensions(SessionId sessionId, int object_id, CancellationToken token)
13821429
{
13831430
var commandParams = new MemoryStream();
13841431
var commandParamsWriter = new MonoBinaryWriter(commandParams);
13851432
commandParamsWriter.Write(object_id);
13861433
var retDebuggerCmdReader = await SendDebuggerAgentCommand<CmdArray>(sessionId, CmdArray.GetLength, commandParams, token);
13871434
var length = retDebuggerCmdReader.ReadInt32();
1388-
length = retDebuggerCmdReader.ReadInt32();
1389-
return length;
1435+
var rank = new int[length];
1436+
for (int i = 0 ; i < length; i++)
1437+
{
1438+
rank[i] = retDebuggerCmdReader.ReadInt32();
1439+
retDebuggerCmdReader.ReadInt32(); //lower_bound
1440+
}
1441+
return new ArrayDimensions(rank);
13901442
}
13911443
public async Task<List<int>> GetTypeIdFromObject(SessionId sessionId, int object_id, bool withParents, CancellationToken token)
13921444
{
@@ -1705,9 +1757,14 @@ public async Task<JObject> CreateJObjectForString(SessionId sessionId, MonoBinar
17051757
public async Task<JObject> CreateJObjectForArray(SessionId sessionId, MonoBinaryReader retDebuggerCmdReader, CancellationToken token)
17061758
{
17071759
var objectId = retDebuggerCmdReader.ReadInt32();
1708-
var value = await GetClassNameFromObject(sessionId, objectId, token);
1709-
var length = await GetArrayLength(sessionId, objectId, token);
1710-
return CreateJObject<string>(null, "object", $"{value.ToString()}({length})", false, value.ToString(), "dotnet:array:" + objectId, null, "array");
1760+
var className = await GetClassNameFromObject(sessionId, objectId, token);
1761+
var arrayType = className.ToString();
1762+
var length = await GetArrayDimensions(sessionId, objectId, token);
1763+
if (arrayType.LastIndexOf('[') > 0)
1764+
arrayType = arrayType.Insert(arrayType.LastIndexOf('[')+1, length.ToString());
1765+
if (className.LastIndexOf('[') > 0)
1766+
className = className.Insert(arrayType.LastIndexOf('[')+1, new string(',', length.Rank-1));
1767+
return CreateJObject<string>(null, "object", description : arrayType, writable : false, className.ToString(), "dotnet:array:" + objectId, null, subtype : length.Rank == 1 ? "array" : null);
17111768
}
17121769

17131770
public async Task<JObject> CreateJObjectForObject(SessionId sessionId, MonoBinaryReader retDebuggerCmdReader, int typeIdFromAttribute, bool forDebuggerDisplayAttribute, CancellationToken token)
@@ -2131,21 +2188,33 @@ public async Task<JArray> GetValueTypeProxy(SessionId sessionId, int valueTypeId
21312188

21322189
public async Task<JArray> GetArrayValues(SessionId sessionId, int arrayId, CancellationToken token)
21332190
{
2134-
var length = await GetArrayLength(sessionId, arrayId, token);
2191+
var dimensions = await GetArrayDimensions(sessionId, arrayId, token);
21352192
var commandParams = new MemoryStream();
21362193
var commandParamsWriter = new MonoBinaryWriter(commandParams);
21372194
commandParamsWriter.Write(arrayId);
21382195
commandParamsWriter.Write(0);
2139-
commandParamsWriter.Write(length);
2196+
commandParamsWriter.Write(dimensions.TotalLength);
21402197
var retDebuggerCmdReader = await SendDebuggerAgentCommand<CmdArray>(sessionId, CmdArray.GetValues, commandParams, token);
21412198
JArray array = new JArray();
2142-
for (int i = 0 ; i < length ; i++)
2199+
for (int i = 0 ; i < dimensions.TotalLength; i++)
21432200
{
2144-
var var_json = await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, i.ToString(), false, -1, false, token);
2201+
var var_json = await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, dimensions.GetArrayIndexString(i), isOwn : false, -1, forDebuggerDisplayAttribute : false, token);
21452202
array.Add(var_json);
21462203
}
21472204
return array;
21482205
}
2206+
2207+
public async Task<JObject> GetArrayValuesProxy(SessionId sessionId, int arrayId, CancellationToken token)
2208+
{
2209+
var length = await GetArrayDimensions(sessionId, arrayId, token);
2210+
var arrayProxy = JObject.FromObject(new
2211+
{
2212+
items = await GetArrayValues(sessionId, arrayId, token),
2213+
dimensionsDetails = length.Bounds
2214+
});
2215+
return arrayProxy;
2216+
}
2217+
21492218
public async Task<bool> EnableExceptions(SessionId sessionId, PauseOnExceptionsKind state, CancellationToken token)
21502219
{
21512220
if (state == PauseOnExceptionsKind.Unset)

src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs

Lines changed: 80 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,24 @@ public async Task InspectGenericValueTypeArrayLocals2(int line, int col, string
198198
frame_idx: frame_idx,
199199
use_cfo: use_cfo);
200200

201+
async Task<JToken> GetObjectWithCFO(string objectId, JObject fn_args = null)
202+
{
203+
var fn_decl = "function () { return this; }";
204+
var cfo_args = JObject.FromObject(new
205+
{
206+
functionDeclaration = fn_decl,
207+
objectId = objectId
208+
});
209+
210+
if (fn_args != null)
211+
cfo_args["arguments"] = fn_args;
212+
213+
// callFunctionOn
214+
var result = await cli.SendCommand("Runtime.callFunctionOn", cfo_args, token);
215+
216+
return await GetProperties(result.Value["result"]["objectId"]?.Value<string>(), fn_args);
217+
}
218+
201219
async Task TestSimpleArrayLocals(int line, int col, string entry_method_name, string method_name, string etype_name,
202220
string local_var_name_prefix, object[] array, object[] array_elem_props,
203221
bool test_prev_frame = false, int frame_idx = 0, bool use_cfo = false)
@@ -215,8 +233,8 @@ async Task TestSimpleArrayLocals(int line, int col, string entry_method_name, st
215233

216234
var locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value<string>());
217235
Assert.Equal(4, locals.Count());
218-
CheckArray(locals, $"{local_var_name_prefix}_arr", $"{etype_name}[]", array?.Length ?? 0);
219-
CheckArray(locals, $"{local_var_name_prefix}_arr_empty", $"{etype_name}[]", 0);
236+
CheckArray(locals, $"{local_var_name_prefix}_arr", $"{etype_name}[]", $"{etype_name}[{array?.Length ?? 0}]");
237+
CheckArray(locals, $"{local_var_name_prefix}_arr_empty", $"{etype_name}[]", $"{etype_name}[0]");
220238
CheckObject(locals, $"{local_var_name_prefix}_arr_null", $"{etype_name}[]", is_null: true);
221239
CheckBool(locals, "call_other", test_prev_frame);
222240

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

265283
var props = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], $"{local_var_name_prefix}_arr_empty");
266284
await CheckProps(props, new object[0], "${local_var_name_prefix}_arr_empty");
267-
268-
async Task<JToken> GetObjectWithCFO(string objectId, JObject fn_args = null)
269-
{
270-
var fn_decl = "function () { return this; }";
271-
var cfo_args = JObject.FromObject(new
272-
{
273-
functionDeclaration = fn_decl,
274-
objectId = objectId
275-
});
276-
277-
if (fn_args != null)
278-
cfo_args["arguments"] = fn_args;
279-
280-
// callFunctionOn
281-
var result = await cli.SendCommand("Runtime.callFunctionOn", cfo_args, token);
282-
283-
return await GetProperties(result.Value["result"]["objectId"]?.Value<string>(), fn_args);
284-
}
285285
}
286286

287287
[Theory]
@@ -313,10 +313,10 @@ public async Task InspectObjectArrayMembers(bool use_cfo)
313313
await CheckProps(c_props, new
314314
{
315315
id = TString("c#id"),
316-
ClassArrayProperty = TArray("DebuggerTests.SimpleClass[]", 3),
317-
ClassArrayField = TArray("DebuggerTests.SimpleClass[]", 3),
318-
PointsProperty = TArray("DebuggerTests.Point[]", 2),
319-
PointsField = TArray("DebuggerTests.Point[]", 2)
316+
ClassArrayProperty = TArray("DebuggerTests.SimpleClass[]", "DebuggerTests.SimpleClass[3]"),
317+
ClassArrayField = TArray("DebuggerTests.SimpleClass[]", "DebuggerTests.SimpleClass[3]"),
318+
PointsProperty = TArray("DebuggerTests.Point[]", "DebuggerTests.Point[2]"),
319+
PointsField = TArray("DebuggerTests.Point[]", "DebuggerTests.Point[2]")
320320
},
321321
"c"
322322
);
@@ -382,8 +382,8 @@ public async Task InspectValueTypeArrayLocalsStaticAsync(bool use_cfo)
382382
await CheckProps(frame_locals, new
383383
{
384384
call_other = TBool(false),
385-
gvclass_arr = TArray("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[]", 2),
386-
gvclass_arr_empty = TArray("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[]"),
385+
gvclass_arr = TArray("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[]", "DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[2]"),
386+
gvclass_arr_empty = TArray("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[]", "DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[0]"),
387387
gvclass_arr_null = TObject("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[]", is_null: true),
388388
gvclass = TValueType("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>"),
389389
// BUG: this shouldn't be null!
@@ -448,7 +448,7 @@ public async Task InspectValueTypeArrayLocalsInstanceAsync(bool use_cfo)
448448
{
449449
t1 = TObject("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>"),
450450
@this = TObject("DebuggerTests.ArrayTestsClass"),
451-
point_arr = TArray("DebuggerTests.Point[]", 2),
451+
point_arr = TArray("DebuggerTests.Point[]", "DebuggerTests.Point[2]"),
452452
point = TValueType("DebuggerTests.Point")
453453
}, "InspectValueTypeArrayLocalsInstanceAsync#locals");
454454

@@ -642,6 +642,59 @@ public async Task InvalidAccessors() => await CheckInspectLocalsAtBreakpointSite
642642
AssertEqual("undefined", res.Value["result"]?["type"]?.ToString(), "Expected to get undefined result for non-existant accessor");
643643
}
644644
});
645+
646+
[Theory]
647+
[InlineData(false)]
648+
[InlineData(true)]
649+
public async Task InspectPrimitiveTypeMultiArrayLocals(bool use_cfo)
650+
{
651+
var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
652+
653+
var eval_expr = "window.setTimeout(function() { invoke_static_method (" +
654+
$"'[debugger-test] DebuggerTests.MultiDimensionalArray:run'" +
655+
"); }, 1);";
645656

657+
var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, 343, 12, "run");
658+
659+
var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
660+
Assert.Equal(3, locals.Count());
661+
var int_arr_1 = !use_cfo ?
662+
await GetProperties(locals[0]["value"]["objectId"].Value<string>()) :
663+
await GetObjectWithCFO((locals[0]["value"]["objectId"].Value<string>()));
664+
665+
CheckNumber(int_arr_1, "0", 0);
666+
CheckNumber(int_arr_1, "1", 1);
667+
var int_arr_2 = !use_cfo ?
668+
await GetProperties(locals[1]["value"]["objectId"].Value<string>()) :
669+
await GetObjectWithCFO((locals[1]["value"]["objectId"].Value<string>()));
670+
CheckNumber(int_arr_2, "0, 0", 0);
671+
CheckNumber(int_arr_2, "0, 1", 1);
672+
CheckNumber(int_arr_2, "0, 2", 2);
673+
CheckNumber(int_arr_2, "1, 0", 10);
674+
CheckNumber(int_arr_2, "1, 1", 11);
675+
CheckNumber(int_arr_2, "1, 2", 12);
676+
677+
var int_arr_3 = !use_cfo ?
678+
await GetProperties(locals[2]["value"]["objectId"].Value<string>()) :
679+
await GetObjectWithCFO((locals[2]["value"]["objectId"].Value<string>()));
680+
CheckNumber(int_arr_3, "0, 0, 0", 0);
681+
CheckNumber(int_arr_3, "0, 0, 1", 1);
682+
CheckNumber(int_arr_3, "0, 0, 2", 2);
683+
CheckNumber(int_arr_3, "0, 1, 0", 10);
684+
CheckNumber(int_arr_3, "0, 1, 1", 11);
685+
CheckNumber(int_arr_3, "0, 1, 2", 12);
686+
CheckNumber(int_arr_3, "0, 2, 0", 20);
687+
CheckNumber(int_arr_3, "0, 2, 1", 21);
688+
CheckNumber(int_arr_3, "0, 2, 2", 22);
689+
CheckNumber(int_arr_3, "1, 0, 0", 100);
690+
CheckNumber(int_arr_3, "1, 0, 1", 101);
691+
CheckNumber(int_arr_3, "1, 0, 2", 102);
692+
CheckNumber(int_arr_3, "1, 1, 0", 110);
693+
CheckNumber(int_arr_3, "1, 1, 1", 111);
694+
CheckNumber(int_arr_3, "1, 1, 2", 112);
695+
CheckNumber(int_arr_3, "1, 2, 0", 120);
696+
CheckNumber(int_arr_3, "1, 2, 1", 121);
697+
CheckNumber(int_arr_3, "1, 2, 2", 122);
698+
}
646699
}
647700
}

src/mono/wasm/debugger/DebuggerTestSuite/AssignmentTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class AssignmentTests : DebuggerTestBase
1919
{ "MONO_TYPE_CHAR", TSymbol("0 '\u0000'"), TSymbol("97 'a'") },
2020
{ "MONO_TYPE_STRING", TString(default), TString("hello") },
2121
{ "MONO_TYPE_ENUM", TEnum("DebuggerTests.RGB", "Red"), TEnum("DebuggerTests.RGB", "Blue") },
22-
{ "MONO_TYPE_ARRAY", TObject("byte[]", is_null: true), TArray("byte[]", 2) },
22+
{ "MONO_TYPE_ARRAY", TObject("byte[]", is_null: true), TArray("byte[]", "byte[2]") },
2323
{ "MONO_TYPE_VALUETYPE", TValueType("DebuggerTests.Point"), TValueType("DebuggerTests.Point") },
2424
{ "MONO_TYPE_VALUETYPE2", TValueType("System.Decimal","0"), TValueType("System.Decimal", "1.1") },
2525
{ "MONO_TYPE_GENERICINST", TObject("System.Func<int>", is_null: true), TDelegate("System.Func<int>", "int Prepare ()") },

src/mono/wasm/debugger/DebuggerTestSuite/CallFunctionOnTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ public async Task RunOnJSObject(bool roundtrip) => await RunCallFunctionOn(
361361
await CheckProps(obj_own_val, new
362362
{
363363
a_obj = TObject("Object"),
364-
b_arr = TArray("Array", 2)
364+
b_arr = TArray("Array", "Array(2)")
365365
}, "obj_own");
366366
});
367367

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

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

656656
res = await InvokeGetter(obj, get_args_fn(new[] { "DTArray" }), cfo_fn);
657-
await CheckValue(res.Value["result"], TArray("System.DateTime[]", 2), $"{local_name}.DTArray");
657+
await CheckValue(res.Value["result"], TArray("System.DateTime[]", "System.DateTime[2]"), $"{local_name}.DTArray");
658658
{
659659
var dt0 = new DateTime(6, 7, 8, 9, 10, 11);
660660
var dt1 = new DateTime(1, 2, 3, 4, 5, 6);
@@ -944,7 +944,7 @@ async Task CheckCFOResult(Result result)
944944
if (res_array_len < 0)
945945
await CheckValue(result.Value["result"], TObject("Object"), $"cfo-res");
946946
else
947-
await CheckValue(result.Value["result"], TArray("Array", res_array_len), $"cfo-res");
947+
await CheckValue(result.Value["result"], TArray("Array", $"Array({res_array_len})"), $"cfo-res");
948948
}
949949
}
950950
}

src/mono/wasm/debugger/DebuggerTestSuite/CustomViewTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public async Task UsingDebuggerTypeProxy()
5151
var props = await GetObjectOnFrame(frame, "myList");
5252
Assert.Equal(1, props.Count());
5353

54-
CheckArray(props, "Items", "int[]", 4);
54+
CheckArray(props, "Items", "int[]", "int[4]");
5555

5656
CheckObject(locals, "b", "DebuggerTests.WithProxy", description:"DebuggerTests.WithProxy");
5757
props = await GetObjectOnFrame(frame, "b");

0 commit comments

Comments
 (0)