Skip to content

Commit

Permalink
Changes from #92630 (#92753)
Browse files Browse the repository at this point in the history
  • Loading branch information
ilonatommy committed Oct 3, 2023
1 parent f1e4e90 commit 8632a75
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -387,12 +387,14 @@ private static async Task<IList<JObject>> ResolveElementAccess(ExpressionSyntaxR
{
var values = new List<JObject>();
JObject index = null;
List<JObject> nestedIndexers = new();
IEnumerable<ElementAccessExpressionSyntax> elementAccesses = replacer.elementAccess;
foreach (ElementAccessExpressionSyntax elementAccess in elementAccesses.Reverse())
{
index = await resolver.Resolve(elementAccess, replacer.memberAccessValues, index, replacer.variableDefinitions, token);
index = await resolver.Resolve(elementAccess, replacer.memberAccessValues, nestedIndexers, replacer.variableDefinitions, token);
if (index == null)
throw new ReturnAsErrorException($"Failed to resolve element access for {elementAccess}", "ReferenceError");
nestedIndexers.Add(index);
}
values.Add(index);
return values;
Expand Down
120 changes: 79 additions & 41 deletions src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,12 @@ async Task<JObject> ResolveAsInstanceMember(ArraySegment<string> parts, JObject
}
}

public async Task<JObject> Resolve(ElementAccessExpressionSyntax elementAccess, Dictionary<string, JObject> memberAccessValues, JObject indexObject, List<VariableDefinition> variableDefinitions, CancellationToken token)
public async Task<JObject> Resolve(
ElementAccessExpressionSyntax elementAccess,
Dictionary<string, JObject> memberAccessValues,
List<JObject> nestedIndexObject,
List<VariableDefinition> variableDefinitions,
CancellationToken token)
{
try
{
Expand All @@ -376,12 +381,13 @@ public async Task<JObject> Resolve(ElementAccessExpressionSyntax elementAccess,

if (rootObject == null)
{
// it might be a jagged array where indexObject should be treated as a new rootObject
rootObject = indexObject;
indexObject = null;
// it might be a jagged array where the previously added nestedIndexObject should be treated as a new rootObject
rootObject = nestedIndexObject.LastOrDefault();
if (rootObject != null)
nestedIndexObject.RemoveAt(nestedIndexObject.Count - 1);
}

ElementIndexInfo elementIdxInfo = await GetElementIndexInfo();
ElementIndexInfo elementIdxInfo = await GetElementIndexInfo(nestedIndexObject);
if (elementIdxInfo is null)
return null;

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

bool isMultidimensional = elementIdxInfo.DimensionsCount != 1;
switch (objectId.Scheme)
{
case "valuetype": //can be an inlined array
Expand All @@ -407,7 +414,7 @@ public async Task<JObject> Resolve(ElementAccessExpressionSyntax elementAccess,
}
case "array":
rootObject["value"] = await context.SdbAgent.GetArrayValues(objectId.Value, token);
if (!elementIdxInfo.IsMultidimensional)
if (!isMultidimensional)
{
int.TryParse(elementIdxInfo.ElementIdxStr, out elementIdx);
return (JObject)rootObject["value"][elementIdx]["value"];
Expand All @@ -417,18 +424,16 @@ public async Task<JObject> Resolve(ElementAccessExpressionSyntax elementAccess,
return (JObject)(((JArray)rootObject["value"]).FirstOrDefault(x => x["name"].Value<string>() == elementIdxInfo.ElementIdxStr)["value"]);
}
case "object":
if (elementIdxInfo.IsMultidimensional)
throw new InvalidOperationException($"Cannot apply indexing with [,] to an object of type '{type}'");
// ToDo: try to use the get_Item for string as well
if (type == "string")
if (!isMultidimensional && type == "string")
{
var eaExpressionFormatted = elementAccessStrExpression.Replace('.', '_'); // instance_str
variableDefinitions.Add(new (eaExpressionFormatted, rootObject, ExpressionEvaluator.ConvertJSToCSharpLocalVariableAssignment(eaExpressionFormatted, rootObject)));
var eaFormatted = elementAccessStr.Replace('.', '_'); // instance_str[1]
var variableDef = await ExpressionEvaluator.GetVariableDefinitions(this, variableDefinitions, invokeToStringInObject: false, token);
return await ExpressionEvaluator.EvaluateSimpleExpression(this, eaFormatted, elementAccessStr, variableDef, logger, token);
}
if (indexObject is null && elementIdxInfo.IndexingExpression is null)
if (elementIdxInfo.Indexers is null || elementIdxInfo.Indexers.Count == 0)
throw new InternalErrorException($"Unable to write index parameter to invoke the method in the runtime.");

var typeIds = await context.SdbAgent.GetTypeIdsForObject(objectId.Value, true, token);
Expand All @@ -441,15 +446,13 @@ public async Task<JObject> Resolve(ElementAccessExpressionSyntax elementAccess,
{
MethodInfoWithDebugInformation methodInfo = await context.SdbAgent.GetMethodInfo(methodIds[i], token);
ParameterInfo[] paramInfo = methodInfo.GetParametersInfo();
if (paramInfo.Length == 1)
if (paramInfo.Length == elementIdxInfo.DimensionsCount)
{
try
{
if (indexObject != null && !CheckParametersCompatibility(paramInfo[0].TypeCode, indexObject))
if (!CheckParametersCompatibility(paramInfo, elementIdxInfo.Indexers))
continue;
ArraySegment<byte> buffer = indexObject is null ?
await WriteLiteralExpressionAsIndex(objectId, elementIdxInfo.IndexingExpression, elementIdxInfo.ElementIdxStr) :
await WriteJObjectAsIndex(objectId, indexObject, elementIdxInfo.ElementIdxStr, paramInfo[0].TypeCode);
ArraySegment<byte> buffer = await WriteIndexObjectAsIndices(objectId, elementIdxInfo.Indexers, paramInfo);
JObject getItemRetObj = await context.SdbAgent.InvokeMethod(buffer, methodIds[i], token);
return (JObject)getItemRetObj["value"];
}
Expand All @@ -470,31 +473,32 @@ await WriteLiteralExpressionAsIndex(objectId, elementIdxInfo.IndexingExpression,
throw new ReturnAsErrorException($"Unable to evaluate element access '{elementAccess}': {ex.Message}", ex.GetType().Name);
}

async Task<ElementIndexInfo> GetElementIndexInfo()
async Task<ElementIndexInfo> GetElementIndexInfo(List<JObject> nestedIndexers)
{
// e.g. x[a[0]], x[a[b[1]]] etc.
if (indexObject is not null)
return new ElementIndexInfo(ElementIdxStr: indexObject["value"].ToString() );

if (elementAccess.ArgumentList is null)
return null;

StringBuilder elementIdxStr = new StringBuilder();
var multiDimensionalArray = false;
int dimCnt = elementAccess.ArgumentList.Arguments.Count;
LiteralExpressionSyntax indexingExpression = null;
for (int i = 0; i < elementAccess.ArgumentList.Arguments.Count; i++)
StringBuilder elementIdxStr = new StringBuilder();
List<object> indexers = new();
// nesting should be resolved in reverse order
int nestedIndexersCnt = nestedIndexers.Count - 1;
for (int i = 0; i < dimCnt; i++)
{
JObject indexObject;
var arg = elementAccess.ArgumentList.Arguments[i];
if (i != 0)
{
elementIdxStr.Append(", ");
multiDimensionalArray = true;
}
// e.g. x[1]
if (arg.Expression is LiteralExpressionSyntax)
{
indexingExpression = arg.Expression as LiteralExpressionSyntax;
elementIdxStr.Append(indexingExpression.ToString());
string expression = indexingExpression.ToString();
elementIdxStr.Append(expression);
indexers.Add(indexingExpression);
}

// e.g. x[a] or x[a.b]
Expand All @@ -508,6 +512,18 @@ async Task<ElementIndexInfo> GetElementIndexInfo()
// x[a]
indexObject ??= await Resolve(argParm.Identifier.Text, token);
elementIdxStr.Append(indexObject["value"].ToString());
indexers.Add(indexObject);
}
// nested indexing, e.g. x[a[0]], x[a[b[1]]], x[a[0], b[1]]
else if (arg.Expression is ElementAccessExpressionSyntax)
{
if (nestedIndexers == null || nestedIndexersCnt < 0)
throw new InvalidOperationException($"Cannot resolve nested indexing");
JObject nestedIndexObject = nestedIndexers[nestedIndexersCnt];
nestedIndexers.RemoveAt(nestedIndexersCnt);
elementIdxStr.Append(nestedIndexObject["value"].ToString());
indexers.Add(nestedIndexObject);
nestedIndexersCnt--;
}
// indexing with expressions, e.g. x[a + 1]
else
Expand All @@ -519,36 +535,57 @@ async Task<ElementIndexInfo> GetElementIndexInfo()
if (idxType != "number")
throw new InvalidOperationException($"Cannot index with an object of type '{idxType}'");
elementIdxStr.Append(indexObject["value"].ToString());
indexers.Add(indexObject);
}
}
return new ElementIndexInfo(
DimensionsCount: dimCnt,
ElementIdxStr: elementIdxStr.ToString(),
IsMultidimensional: multiDimensionalArray,
IndexingExpression: indexingExpression);
Indexers: indexers);
}

async Task<ArraySegment<byte>> WriteJObjectAsIndex(DotnetObjectId rootObjId, JObject indexObject, string elementIdxStr, ElementType? expectedType)
async Task<ArraySegment<byte>> WriteIndexObjectAsIndices(DotnetObjectId rootObjId, List<object> indexObjects, ParameterInfo[] paramInfo)
{
using var writer = new MonoBinaryWriter();
writer.WriteObj(rootObjId, context.SdbAgent);
writer.Write(1); // number of method args
if (!await writer.WriteJsonValue(indexObject, context.SdbAgent, expectedType, token))
throw new InternalErrorException($"Parsing index of type {indexObject["type"].Value<string>()} to write it into the buffer failed.");
writer.Write(indexObjects.Count); // number of method args
foreach ((ParameterInfo pi, object indexObject) in paramInfo.Zip(indexObjects))
{
if (indexObject is JObject indexJObject)
{
// indexed by an identifier name syntax
if (!await writer.WriteJsonValue(indexJObject, context.SdbAgent, pi.TypeCode, token))
throw new InternalErrorException($"Parsing index of type {indexJObject["type"].Value<string>()} to write it into the buffer failed.");
}
else if (indexObject is LiteralExpressionSyntax expression)
{
// indexed by a literal expression syntax
if (!await writer.WriteConst(expression, context.SdbAgent, token))
throw new InternalErrorException($"Parsing literal expression index = {expression} to write it into the buffer failed.");
}
else
{
throw new InternalErrorException($"Unexpected index type.");
}
}
return writer.GetParameterBuffer();
}
}

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

private static bool CheckParametersCompatibility(ElementType? paramTypeCode, JObject value)
private static bool CheckParameterCompatibility(ElementType? paramTypeCode, JObject value)
{
if (!paramTypeCode.HasValue)
return true;
Expand Down Expand Up @@ -871,7 +908,8 @@ public JObject TryGetEvaluationResult(string id)

private sealed record ElementIndexInfo(
string ElementIdxStr,
bool IsMultidimensional = false,
LiteralExpressionSyntax IndexingExpression = null);
// keeps JObjects and LiteralExpressionSyntaxes:
List<object> Indexers,
int DimensionsCount = 1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -731,5 +731,23 @@ await CheckEvaluateFail(id,
("dt+1", "Cannot evaluate '(dt+1\n)': (2,9): error CS0019: Operator '+' cannot be applied to operands of type 'object' and 'int'")
);
});

[Fact]
public async Task EvaluateObjectIndexingMultidimensional() => await CheckInspectLocalsAtBreakpointSite(
"DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 12, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals",
"window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })",
wait_for_event_fn: async (pause_location) =>
{
var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
await EvaluateOnCallFrameAndCheck(id,
("f[j, aDouble]", TNumber("3.34")), //only IdentifierNameSyntaxes
("f[1, aDouble]", TNumber("3.34")), //IdentifierNameSyntax with LiteralExpressionSyntax
("f[aChar, \"&\", longString]", TString("9-&-longString")),
("f[f.numArray[j], aDouble]", TNumber("4.34")), //ElementAccessExpressionSyntax
("f[f.numArray[j], f.numArray[0]]", TNumber("3")), //multiple ElementAccessExpressionSyntaxes
("f[f.numArray[f.numList[0]], f.numArray[i]]", TNumber("3")),
("f[f.numArray[f.numList[0]], f.numArray[f.numArray[i]]]", TNumber("4"))
);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ public async Task EvaluateIndexingNegative() => await CheckInspectLocalsAtBreakp
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>());
var exceptionDetailsStack = res.Error["exceptionDetails"]?["stackTrace"]?["callFrames"]?[0];
Assert.Equal("DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", exceptionDetailsStack?["functionName"]?.Value<string>());
Assert.Equal(556, exceptionDetailsStack?["lineNumber"]?.Value<int>());
Assert.Equal(558, exceptionDetailsStack?["lineNumber"]?.Value<int>());
Assert.Equal(12, exceptionDetailsStack?["columnNumber"]?.Value<int>());
(_, res) = await EvaluateOnCallFrame(id, "f[1]", expect_ok: false );
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>());
Expand Down Expand Up @@ -722,7 +722,7 @@ public async Task EvaluateIndexingByExpressionNegative() => await CheckInspectLo
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>());
var exceptionDetailsStack = res.Error["exceptionDetails"]?["stackTrace"]?["callFrames"]?[0];
Assert.Equal("DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", exceptionDetailsStack?["functionName"]?.Value<string>());
Assert.Equal(556, exceptionDetailsStack?["lineNumber"]?.Value<int>());
Assert.Equal(558, exceptionDetailsStack?["lineNumber"]?.Value<int>());
Assert.Equal(12, exceptionDetailsStack?["columnNumber"]?.Value<int>());
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -522,19 +522,21 @@ public class TestEvaluate
public int idx0;
public int idx1;

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

public double this[int key1, double key2] => key1 + key2;
public string this[char key1, string key2, string key3] => $"{key1}-{key2}-{key3}";

public void run()
{
numList = new List<int> { 1, 2 };
textList = new List<string> { "1", "2" };
numArray = new int[] { 1, 2 };
numArray = new int[] { 1, 2, 0 };
textArray = new string[] { "1", "2" };
numArrayOfArrays = new int[][] { numArray, numArray };
numListOfLists = new List<List<int>> { numList, numList };
Expand Down

0 comments on commit 8632a75

Please sign in to comment.