Skip to content

Commit 8632a75

Browse files
authored
Changes from #92630 (#92753)
1 parent f1e4e90 commit 8632a75

File tree

5 files changed

+106
-46
lines changed

5 files changed

+106
-46
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,12 +387,14 @@ private static async Task<IList<JObject>> ResolveElementAccess(ExpressionSyntaxR
387387
{
388388
var values = new List<JObject>();
389389
JObject index = null;
390+
List<JObject> nestedIndexers = new();
390391
IEnumerable<ElementAccessExpressionSyntax> elementAccesses = replacer.elementAccess;
391392
foreach (ElementAccessExpressionSyntax elementAccess in elementAccesses.Reverse())
392393
{
393-
index = await resolver.Resolve(elementAccess, replacer.memberAccessValues, index, replacer.variableDefinitions, token);
394+
index = await resolver.Resolve(elementAccess, replacer.memberAccessValues, nestedIndexers, replacer.variableDefinitions, token);
394395
if (index == null)
395396
throw new ReturnAsErrorException($"Failed to resolve element access for {elementAccess}", "ReferenceError");
397+
nestedIndexers.Add(index);
396398
}
397399
values.Add(index);
398400
return values;

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

Lines changed: 79 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,12 @@ async Task<JObject> ResolveAsInstanceMember(ArraySegment<string> parts, JObject
366366
}
367367
}
368368

369-
public async Task<JObject> Resolve(ElementAccessExpressionSyntax elementAccess, Dictionary<string, JObject> memberAccessValues, JObject indexObject, List<VariableDefinition> variableDefinitions, CancellationToken token)
369+
public async Task<JObject> Resolve(
370+
ElementAccessExpressionSyntax elementAccess,
371+
Dictionary<string, JObject> memberAccessValues,
372+
List<JObject> nestedIndexObject,
373+
List<VariableDefinition> variableDefinitions,
374+
CancellationToken token)
370375
{
371376
try
372377
{
@@ -376,12 +381,13 @@ public async Task<JObject> Resolve(ElementAccessExpressionSyntax elementAccess,
376381

377382
if (rootObject == null)
378383
{
379-
// it might be a jagged array where indexObject should be treated as a new rootObject
380-
rootObject = indexObject;
381-
indexObject = null;
384+
// it might be a jagged array where the previously added nestedIndexObject should be treated as a new rootObject
385+
rootObject = nestedIndexObject.LastOrDefault();
386+
if (rootObject != null)
387+
nestedIndexObject.RemoveAt(nestedIndexObject.Count - 1);
382388
}
383389

384-
ElementIndexInfo elementIdxInfo = await GetElementIndexInfo();
390+
ElementIndexInfo elementIdxInfo = await GetElementIndexInfo(nestedIndexObject);
385391
if (elementIdxInfo is null)
386392
return null;
387393

@@ -394,6 +400,7 @@ public async Task<JObject> Resolve(ElementAccessExpressionSyntax elementAccess,
394400
if (!DotnetObjectId.TryParse(rootObject?["objectId"]?.Value<string>(), out DotnetObjectId objectId))
395401
throw new InvalidOperationException($"Cannot apply indexing with [] to a primitive object of type '{type}'");
396402

403+
bool isMultidimensional = elementIdxInfo.DimensionsCount != 1;
397404
switch (objectId.Scheme)
398405
{
399406
case "valuetype": //can be an inlined array
@@ -407,7 +414,7 @@ public async Task<JObject> Resolve(ElementAccessExpressionSyntax elementAccess,
407414
}
408415
case "array":
409416
rootObject["value"] = await context.SdbAgent.GetArrayValues(objectId.Value, token);
410-
if (!elementIdxInfo.IsMultidimensional)
417+
if (!isMultidimensional)
411418
{
412419
int.TryParse(elementIdxInfo.ElementIdxStr, out elementIdx);
413420
return (JObject)rootObject["value"][elementIdx]["value"];
@@ -417,18 +424,16 @@ public async Task<JObject> Resolve(ElementAccessExpressionSyntax elementAccess,
417424
return (JObject)(((JArray)rootObject["value"]).FirstOrDefault(x => x["name"].Value<string>() == elementIdxInfo.ElementIdxStr)["value"]);
418425
}
419426
case "object":
420-
if (elementIdxInfo.IsMultidimensional)
421-
throw new InvalidOperationException($"Cannot apply indexing with [,] to an object of type '{type}'");
422427
// ToDo: try to use the get_Item for string as well
423-
if (type == "string")
428+
if (!isMultidimensional && type == "string")
424429
{
425430
var eaExpressionFormatted = elementAccessStrExpression.Replace('.', '_'); // instance_str
426431
variableDefinitions.Add(new (eaExpressionFormatted, rootObject, ExpressionEvaluator.ConvertJSToCSharpLocalVariableAssignment(eaExpressionFormatted, rootObject)));
427432
var eaFormatted = elementAccessStr.Replace('.', '_'); // instance_str[1]
428433
var variableDef = await ExpressionEvaluator.GetVariableDefinitions(this, variableDefinitions, invokeToStringInObject: false, token);
429434
return await ExpressionEvaluator.EvaluateSimpleExpression(this, eaFormatted, elementAccessStr, variableDef, logger, token);
430435
}
431-
if (indexObject is null && elementIdxInfo.IndexingExpression is null)
436+
if (elementIdxInfo.Indexers is null || elementIdxInfo.Indexers.Count == 0)
432437
throw new InternalErrorException($"Unable to write index parameter to invoke the method in the runtime.");
433438

434439
var typeIds = await context.SdbAgent.GetTypeIdsForObject(objectId.Value, true, token);
@@ -441,15 +446,13 @@ public async Task<JObject> Resolve(ElementAccessExpressionSyntax elementAccess,
441446
{
442447
MethodInfoWithDebugInformation methodInfo = await context.SdbAgent.GetMethodInfo(methodIds[i], token);
443448
ParameterInfo[] paramInfo = methodInfo.GetParametersInfo();
444-
if (paramInfo.Length == 1)
449+
if (paramInfo.Length == elementIdxInfo.DimensionsCount)
445450
{
446451
try
447452
{
448-
if (indexObject != null && !CheckParametersCompatibility(paramInfo[0].TypeCode, indexObject))
453+
if (!CheckParametersCompatibility(paramInfo, elementIdxInfo.Indexers))
449454
continue;
450-
ArraySegment<byte> buffer = indexObject is null ?
451-
await WriteLiteralExpressionAsIndex(objectId, elementIdxInfo.IndexingExpression, elementIdxInfo.ElementIdxStr) :
452-
await WriteJObjectAsIndex(objectId, indexObject, elementIdxInfo.ElementIdxStr, paramInfo[0].TypeCode);
455+
ArraySegment<byte> buffer = await WriteIndexObjectAsIndices(objectId, elementIdxInfo.Indexers, paramInfo);
453456
JObject getItemRetObj = await context.SdbAgent.InvokeMethod(buffer, methodIds[i], token);
454457
return (JObject)getItemRetObj["value"];
455458
}
@@ -470,31 +473,32 @@ await WriteLiteralExpressionAsIndex(objectId, elementIdxInfo.IndexingExpression,
470473
throw new ReturnAsErrorException($"Unable to evaluate element access '{elementAccess}': {ex.Message}", ex.GetType().Name);
471474
}
472475

473-
async Task<ElementIndexInfo> GetElementIndexInfo()
476+
async Task<ElementIndexInfo> GetElementIndexInfo(List<JObject> nestedIndexers)
474477
{
475-
// e.g. x[a[0]], x[a[b[1]]] etc.
476-
if (indexObject is not null)
477-
return new ElementIndexInfo(ElementIdxStr: indexObject["value"].ToString() );
478-
479478
if (elementAccess.ArgumentList is null)
480479
return null;
481480

482-
StringBuilder elementIdxStr = new StringBuilder();
483-
var multiDimensionalArray = false;
481+
int dimCnt = elementAccess.ArgumentList.Arguments.Count;
484482
LiteralExpressionSyntax indexingExpression = null;
485-
for (int i = 0; i < elementAccess.ArgumentList.Arguments.Count; i++)
483+
StringBuilder elementIdxStr = new StringBuilder();
484+
List<object> indexers = new();
485+
// nesting should be resolved in reverse order
486+
int nestedIndexersCnt = nestedIndexers.Count - 1;
487+
for (int i = 0; i < dimCnt; i++)
486488
{
489+
JObject indexObject;
487490
var arg = elementAccess.ArgumentList.Arguments[i];
488491
if (i != 0)
489492
{
490493
elementIdxStr.Append(", ");
491-
multiDimensionalArray = true;
492494
}
493495
// e.g. x[1]
494496
if (arg.Expression is LiteralExpressionSyntax)
495497
{
496498
indexingExpression = arg.Expression as LiteralExpressionSyntax;
497-
elementIdxStr.Append(indexingExpression.ToString());
499+
string expression = indexingExpression.ToString();
500+
elementIdxStr.Append(expression);
501+
indexers.Add(indexingExpression);
498502
}
499503

500504
// e.g. x[a] or x[a.b]
@@ -508,6 +512,18 @@ async Task<ElementIndexInfo> GetElementIndexInfo()
508512
// x[a]
509513
indexObject ??= await Resolve(argParm.Identifier.Text, token);
510514
elementIdxStr.Append(indexObject["value"].ToString());
515+
indexers.Add(indexObject);
516+
}
517+
// nested indexing, e.g. x[a[0]], x[a[b[1]]], x[a[0], b[1]]
518+
else if (arg.Expression is ElementAccessExpressionSyntax)
519+
{
520+
if (nestedIndexers == null || nestedIndexersCnt < 0)
521+
throw new InvalidOperationException($"Cannot resolve nested indexing");
522+
JObject nestedIndexObject = nestedIndexers[nestedIndexersCnt];
523+
nestedIndexers.RemoveAt(nestedIndexersCnt);
524+
elementIdxStr.Append(nestedIndexObject["value"].ToString());
525+
indexers.Add(nestedIndexObject);
526+
nestedIndexersCnt--;
511527
}
512528
// indexing with expressions, e.g. x[a + 1]
513529
else
@@ -519,36 +535,57 @@ async Task<ElementIndexInfo> GetElementIndexInfo()
519535
if (idxType != "number")
520536
throw new InvalidOperationException($"Cannot index with an object of type '{idxType}'");
521537
elementIdxStr.Append(indexObject["value"].ToString());
538+
indexers.Add(indexObject);
522539
}
523540
}
524541
return new ElementIndexInfo(
542+
DimensionsCount: dimCnt,
525543
ElementIdxStr: elementIdxStr.ToString(),
526-
IsMultidimensional: multiDimensionalArray,
527-
IndexingExpression: indexingExpression);
544+
Indexers: indexers);
528545
}
529546

530-
async Task<ArraySegment<byte>> WriteJObjectAsIndex(DotnetObjectId rootObjId, JObject indexObject, string elementIdxStr, ElementType? expectedType)
547+
async Task<ArraySegment<byte>> WriteIndexObjectAsIndices(DotnetObjectId rootObjId, List<object> indexObjects, ParameterInfo[] paramInfo)
531548
{
532549
using var writer = new MonoBinaryWriter();
533550
writer.WriteObj(rootObjId, context.SdbAgent);
534-
writer.Write(1); // number of method args
535-
if (!await writer.WriteJsonValue(indexObject, context.SdbAgent, expectedType, token))
536-
throw new InternalErrorException($"Parsing index of type {indexObject["type"].Value<string>()} to write it into the buffer failed.");
551+
writer.Write(indexObjects.Count); // number of method args
552+
foreach ((ParameterInfo pi, object indexObject) in paramInfo.Zip(indexObjects))
553+
{
554+
if (indexObject is JObject indexJObject)
555+
{
556+
// indexed by an identifier name syntax
557+
if (!await writer.WriteJsonValue(indexJObject, context.SdbAgent, pi.TypeCode, token))
558+
throw new InternalErrorException($"Parsing index of type {indexJObject["type"].Value<string>()} to write it into the buffer failed.");
559+
}
560+
else if (indexObject is LiteralExpressionSyntax expression)
561+
{
562+
// indexed by a literal expression syntax
563+
if (!await writer.WriteConst(expression, context.SdbAgent, token))
564+
throw new InternalErrorException($"Parsing literal expression index = {expression} to write it into the buffer failed.");
565+
}
566+
else
567+
{
568+
throw new InternalErrorException($"Unexpected index type.");
569+
}
570+
}
537571
return writer.GetParameterBuffer();
538572
}
573+
}
539574

540-
async Task<ArraySegment<byte>> WriteLiteralExpressionAsIndex(DotnetObjectId rootObjId, LiteralExpressionSyntax indexingExpression, string elementIdxStr)
575+
private static bool CheckParametersCompatibility(ParameterInfo[] paramInfos, List<object> indexObjects)
576+
{
577+
if (paramInfos.Length != indexObjects.Count)
578+
return false;
579+
foreach ((ParameterInfo paramInfo, object indexObj) in paramInfos.Zip(indexObjects))
541580
{
542-
using var writer = new MonoBinaryWriter();
543-
writer.WriteObj(rootObjId, context.SdbAgent);
544-
writer.Write(1); // number of method args
545-
if (!await writer.WriteConst(indexingExpression, context.SdbAgent, token))
546-
throw new InternalErrorException($"Parsing index of type {indexObject["type"].Value<string>()} to write it into the buffer failed.");
547-
return writer.GetParameterBuffer();
581+
// shouldn't we check LiteralExpressionSyntax for compatibility as well?
582+
if (indexObj is JObject indexJObj && !CheckParameterCompatibility(paramInfo.TypeCode, indexJObj))
583+
return false;
548584
}
585+
return true;
549586
}
550587

551-
private static bool CheckParametersCompatibility(ElementType? paramTypeCode, JObject value)
588+
private static bool CheckParameterCompatibility(ElementType? paramTypeCode, JObject value)
552589
{
553590
if (!paramTypeCode.HasValue)
554591
return true;
@@ -871,7 +908,8 @@ public JObject TryGetEvaluationResult(string id)
871908

872909
private sealed record ElementIndexInfo(
873910
string ElementIdxStr,
874-
bool IsMultidimensional = false,
875-
LiteralExpressionSyntax IndexingExpression = null);
911+
// keeps JObjects and LiteralExpressionSyntaxes:
912+
List<object> Indexers,
913+
int DimensionsCount = 1);
876914
}
877915
}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,5 +731,23 @@ await CheckEvaluateFail(id,
731731
("dt+1", "Cannot evaluate '(dt+1\n)': (2,9): error CS0019: Operator '+' cannot be applied to operands of type 'object' and 'int'")
732732
);
733733
});
734+
735+
[Fact]
736+
public async Task EvaluateObjectIndexingMultidimensional() => await CheckInspectLocalsAtBreakpointSite(
737+
"DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 12, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals",
738+
"window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })",
739+
wait_for_event_fn: async (pause_location) =>
740+
{
741+
var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
742+
await EvaluateOnCallFrameAndCheck(id,
743+
("f[j, aDouble]", TNumber("3.34")), //only IdentifierNameSyntaxes
744+
("f[1, aDouble]", TNumber("3.34")), //IdentifierNameSyntax with LiteralExpressionSyntax
745+
("f[aChar, \"&\", longString]", TString("9-&-longString")),
746+
("f[f.numArray[j], aDouble]", TNumber("4.34")), //ElementAccessExpressionSyntax
747+
("f[f.numArray[j], f.numArray[0]]", TNumber("3")), //multiple ElementAccessExpressionSyntaxes
748+
("f[f.numArray[f.numList[0]], f.numArray[i]]", TNumber("3")),
749+
("f[f.numArray[f.numList[0]], f.numArray[f.numArray[i]]]", TNumber("4"))
750+
);
751+
});
734752
}
735753
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ public async Task EvaluateIndexingNegative() => await CheckInspectLocalsAtBreakp
585585
Assert.Equal("Unable to evaluate element access 'f.idx0[2]': Cannot apply indexing with [] to a primitive object of type 'number'", res.Error["result"]?["description"]?.Value<string>());
586586
var exceptionDetailsStack = res.Error["exceptionDetails"]?["stackTrace"]?["callFrames"]?[0];
587587
Assert.Equal("DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", exceptionDetailsStack?["functionName"]?.Value<string>());
588-
Assert.Equal(556, exceptionDetailsStack?["lineNumber"]?.Value<int>());
588+
Assert.Equal(558, exceptionDetailsStack?["lineNumber"]?.Value<int>());
589589
Assert.Equal(12, exceptionDetailsStack?["columnNumber"]?.Value<int>());
590590
(_, res) = await EvaluateOnCallFrame(id, "f[1]", expect_ok: false );
591591
Assert.Equal( "Unable to evaluate element access 'f[1]': Cannot apply indexing with [] to an object of type 'DebuggerTests.EvaluateLocalsWithIndexingTests.TestEvaluate'", res.Error["result"]?["description"]?.Value<string>());
@@ -722,7 +722,7 @@ public async Task EvaluateIndexingByExpressionNegative() => await CheckInspectLo
722722
Assert.Equal("Unable to evaluate element access 'f.numList[\"a\" + 1]': Cannot index with an object of type 'string'", res.Error["result"]?["description"]?.Value<string>());
723723
var exceptionDetailsStack = res.Error["exceptionDetails"]?["stackTrace"]?["callFrames"]?[0];
724724
Assert.Equal("DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", exceptionDetailsStack?["functionName"]?.Value<string>());
725-
Assert.Equal(556, exceptionDetailsStack?["lineNumber"]?.Value<int>());
725+
Assert.Equal(558, exceptionDetailsStack?["lineNumber"]?.Value<int>());
726726
Assert.Equal(12, exceptionDetailsStack?["columnNumber"]?.Value<int>());
727727
});
728728

src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -522,19 +522,21 @@ public class TestEvaluate
522522
public int idx0;
523523
public int idx1;
524524

525-
// ToDo: add 2d indexing - https://github.com/dotnet/runtime/issues/76062
526525
public string this[char key] => "res_" + key;
527526
public string this[bool key] => key.ToString();
528527
public bool this[string key] => key.Length > 3;
529528
public int this[double key] => (int)key;
530529
public int this[float key] => (int)key;
531530
public int this[decimal key] => (int)key;
532531

532+
public double this[int key1, double key2] => key1 + key2;
533+
public string this[char key1, string key2, string key3] => $"{key1}-{key2}-{key3}";
534+
533535
public void run()
534536
{
535537
numList = new List<int> { 1, 2 };
536538
textList = new List<string> { "1", "2" };
537-
numArray = new int[] { 1, 2 };
539+
numArray = new int[] { 1, 2, 0 };
538540
textArray = new string[] { "1", "2" };
539541
numArrayOfArrays = new int[][] { numArray, numArray };
540542
numListOfLists = new List<List<int>> { numList, numList };

0 commit comments

Comments
 (0)