Skip to content

Commit 1711db7

Browse files
authored
Fix for #60340 (#60863)
* Fix for #60340 * Fixes element access for a constant index. * Undo changes made based on #60873, that were not intended to be commited. * Removed whitespaces. * Fixed element access by local variables, added tests for element access by object member variables. * Element access by indexing with simple object members is fixed. * Nested element access is fixed, e.g. a[a[a[0]]]. * Added tests for nested element access. Reverted an unintentional change in EvaluateSimpleMethodCallsCheckChangedValue test. * Change test name to enable running test batch on calling ".EvaluateExpressionsWithElementAccess" prefix. * Fix ElementAccessByMemberVariables that was failing after previous changes. * Removed unused namespace import. Regexes are not needed in the new approach. * Added implementatio of evaluation for multidimentional indexing.
1 parent a5eacee commit 1711db7

File tree

4 files changed

+292
-4
lines changed

4 files changed

+292
-4
lines changed

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

+70-4
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@ private class FindVariableNMethodCall : CSharpSyntaxWalker
2626
public List<IdentifierNameSyntax> identifiers = new List<IdentifierNameSyntax>();
2727
public List<InvocationExpressionSyntax> methodCall = new List<InvocationExpressionSyntax>();
2828
public List<MemberAccessExpressionSyntax> memberAccesses = new List<MemberAccessExpressionSyntax>();
29+
public List<ElementAccessExpressionSyntax> elementAccess = new List<ElementAccessExpressionSyntax>();
2930
public List<object> argValues = new List<object>();
3031
public Dictionary<string, JObject> memberAccessValues = new Dictionary<string, JObject>();
3132
private int visitCount;
3233
public bool hasMethodCalls;
34+
public bool hasElementAccesses;
3335

3436
public void VisitInternal(SyntaxNode node)
3537
{
@@ -44,14 +46,16 @@ public override void Visit(SyntaxNode node)
4446
if (node is MemberAccessExpressionSyntax maes
4547
&& node.Kind() == SyntaxKind.SimpleMemberAccessExpression
4648
&& !(node.Parent is MemberAccessExpressionSyntax)
47-
&& !(node.Parent is InvocationExpressionSyntax))
49+
&& !(node.Parent is InvocationExpressionSyntax)
50+
&& !(node.Parent is ElementAccessExpressionSyntax))
4851
{
4952
memberAccesses.Add(maes);
5053
}
5154

5255
if (node is IdentifierNameSyntax identifier
5356
&& !(identifier.Parent is MemberAccessExpressionSyntax)
5457
&& !(identifier.Parent is InvocationExpressionSyntax)
58+
&& !(node.Parent is ElementAccessExpressionSyntax)
5559
&& !identifiers.Any(x => x.Identifier.Text == identifier.Identifier.Text))
5660
{
5761
identifiers.Add(identifier);
@@ -65,15 +69,23 @@ public override void Visit(SyntaxNode node)
6569
hasMethodCalls = true;
6670
}
6771

72+
if (node is ElementAccessExpressionSyntax)
73+
{
74+
if (visitCount == 1)
75+
elementAccess.Add(node as ElementAccessExpressionSyntax);
76+
hasElementAccesses = true;
77+
}
78+
6879
if (node is AssignmentExpressionSyntax)
6980
throw new Exception("Assignment is not implemented yet");
7081
base.Visit(node);
7182
}
7283

73-
public SyntaxTree ReplaceVars(SyntaxTree syntaxTree, IEnumerable<JObject> ma_values, IEnumerable<JObject> id_values, IEnumerable<JObject> method_values)
84+
public SyntaxTree ReplaceVars(SyntaxTree syntaxTree, IEnumerable<JObject> ma_values, IEnumerable<JObject> id_values, IEnumerable<JObject> method_values, IEnumerable<JObject> ea_values)
7485
{
7586
var memberAccessToParamName = new Dictionary<string, string>();
7687
var methodCallToParamName = new Dictionary<string, string>();
88+
var elementAccessToParamName = new Dictionary<string, string>();
7789

7890
CompilationUnitSyntax root = syntaxTree.GetCompilationUnitRoot();
7991

@@ -110,6 +122,22 @@ public SyntaxTree ReplaceVars(SyntaxTree syntaxTree, IEnumerable<JObject> ma_val
110122
return SyntaxFactory.IdentifierName(id_name);
111123
});
112124

125+
// 1.2 Replace all this.a[x] occurrences with this_a_ABDE
126+
root = root.ReplaceNodes(elementAccess, (ea, _) =>
127+
{
128+
string eaStr = ea.ToString();
129+
if (!elementAccessToParamName.TryGetValue(eaStr, out string id_name))
130+
{
131+
// Generate a random suffix
132+
string suffix = Guid.NewGuid().ToString().Substring(0, 5);
133+
string prefix = eaStr.Trim().Replace(".", "_").Replace("[", "_").Replace("]", "_");
134+
id_name = $"{prefix}_{suffix}";
135+
elementAccessToParamName[eaStr] = id_name;
136+
}
137+
138+
return SyntaxFactory.IdentifierName(id_name);
139+
});
140+
113141
var paramsSet = new HashSet<string>();
114142

115143
// 2. For every unique member ref, add a corresponding method param
@@ -148,6 +176,18 @@ public SyntaxTree ReplaceVars(SyntaxTree syntaxTree, IEnumerable<JObject> ma_val
148176
}
149177
}
150178

179+
if (ea_values != null)
180+
{
181+
foreach ((ElementAccessExpressionSyntax eas, JObject value) in elementAccess.Zip(ea_values))
182+
{
183+
string node_str = eas.ToString();
184+
if (!elementAccessToParamName.TryGetValue(node_str, out string id_name))
185+
{
186+
throw new Exception($"BUG: Expected to find an id name for the element access string: {node_str}");
187+
}
188+
root = UpdateWithNewMethodParam(root, id_name, value);
189+
}
190+
}
151191

152192
return syntaxTree.WithRootAndOptions(root, syntaxTree.Options);
153193

@@ -281,6 +321,20 @@ private static async Task<IList<JObject>> ResolveMethodCalls(IEnumerable<Invocat
281321
return values;
282322
}
283323

324+
private static async Task<IList<JObject>> ResolveElementAccess(IEnumerable<ElementAccessExpressionSyntax> elementAccesses, Dictionary<string, JObject> memberAccessValues, MemberReferenceResolver resolver, CancellationToken token)
325+
{
326+
var values = new List<JObject>();
327+
JObject index = null;
328+
foreach (ElementAccessExpressionSyntax elementAccess in elementAccesses.Reverse())
329+
{
330+
index = await resolver.Resolve(elementAccess, memberAccessValues, index, token);
331+
if (index == null)
332+
throw new ReturnAsErrorException($"Failed to resolve element access for {elementAccess}", "ReferenceError");
333+
}
334+
values.Add(index);
335+
return values;
336+
}
337+
284338
[UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file",
285339
Justification = "Suppressing the warning until gets fixed, see https://github.com/dotnet/runtime/issues/51202")]
286340
internal static async Task<JObject> CompileAndRunTheExpression(string expression, MemberReferenceResolver resolver, CancellationToken token)
@@ -330,7 +384,7 @@ public static object Evaluate()
330384

331385
IList<JObject> identifierValues = await ResolveIdentifiers(findVarNMethodCall.identifiers, resolver, token);
332386

333-
syntaxTree = findVarNMethodCall.ReplaceVars(syntaxTree, memberAccessValues, identifierValues, null);
387+
syntaxTree = findVarNMethodCall.ReplaceVars(syntaxTree, memberAccessValues, identifierValues, null, null);
334388

335389
if (findVarNMethodCall.hasMethodCalls)
336390
{
@@ -340,7 +394,19 @@ public static object Evaluate()
340394

341395
IList<JObject> methodValues = await ResolveMethodCalls(findVarNMethodCall.methodCall, findVarNMethodCall.memberAccessValues, resolver, token);
342396

343-
syntaxTree = findVarNMethodCall.ReplaceVars(syntaxTree, null, null, methodValues);
397+
syntaxTree = findVarNMethodCall.ReplaceVars(syntaxTree, null, null, methodValues, null);
398+
}
399+
400+
// eg. "elements[0]"
401+
if (findVarNMethodCall.hasElementAccesses)
402+
{
403+
expressionTree = GetExpressionFromSyntaxTree(syntaxTree);
404+
405+
findVarNMethodCall.VisitInternal(expressionTree);
406+
407+
IList<JObject> elementAccessValues = await ResolveElementAccess(findVarNMethodCall.elementAccess, findVarNMethodCall.memberAccessValues, resolver, token);
408+
409+
syntaxTree = findVarNMethodCall.ReplaceVars(syntaxTree, null, null, null, elementAccessValues);
344410
}
345411

346412
expressionTree = GetExpressionFromSyntaxTree(syntaxTree);

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

+91
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,97 @@ public async Task<JObject> Resolve(string varName, CancellationToken token)
206206
return rootObject;
207207
}
208208

209+
public async Task<JObject> Resolve(ElementAccessExpressionSyntax elementAccess, Dictionary<string, JObject> memberAccessValues, JObject indexObject, CancellationToken token)
210+
{
211+
try
212+
{
213+
JObject rootObject = null;
214+
string elementAccessStrExpression = elementAccess.Expression.ToString();
215+
rootObject = await Resolve(elementAccessStrExpression, token);
216+
if (rootObject == null)
217+
{
218+
rootObject = indexObject;
219+
indexObject = null;
220+
}
221+
if (rootObject != null)
222+
{
223+
string elementIdxStr;
224+
int elementIdx = 0;
225+
// x[1] or x[a] or x[a.b]
226+
if (indexObject == null)
227+
{
228+
if (elementAccess.ArgumentList != null)
229+
{
230+
var commandParamsObj = new MemoryStream();
231+
var commandParamsObjWriter = new MonoBinaryWriter(commandParamsObj);
232+
foreach (var arg in elementAccess.ArgumentList.Arguments)
233+
{
234+
// e.g. x[1]
235+
if (arg.Expression is LiteralExpressionSyntax)
236+
{
237+
var argParm = arg.Expression as LiteralExpressionSyntax;
238+
elementIdxStr = argParm.ToString();
239+
int.TryParse(elementIdxStr, out elementIdx);
240+
}
241+
242+
// e.g. x[a] or x[a.b]
243+
if (arg.Expression is IdentifierNameSyntax)
244+
{
245+
var argParm = arg.Expression as IdentifierNameSyntax;
246+
247+
// x[a.b]
248+
memberAccessValues.TryGetValue(argParm.Identifier.Text, out indexObject);
249+
250+
// x[a]
251+
if (indexObject == null)
252+
{
253+
indexObject = await Resolve(argParm.Identifier.Text, token);
254+
}
255+
elementIdxStr = indexObject["value"].ToString();
256+
int.TryParse(elementIdxStr, out elementIdx);
257+
}
258+
}
259+
}
260+
}
261+
// e.g. x[a[0]], x[a[b[1]]] etc.
262+
else
263+
{
264+
elementIdxStr = indexObject["value"].ToString();
265+
int.TryParse(elementIdxStr, out elementIdx);
266+
}
267+
if (elementIdx >= 0)
268+
{
269+
DotnetObjectId.TryParse(rootObject?["objectId"]?.Value<string>(), out DotnetObjectId objectId);
270+
switch (objectId.Scheme)
271+
{
272+
case "array":
273+
rootObject["value"] = await sdbHelper.GetArrayValues(sessionId, int.Parse(objectId.Value), token);
274+
return (JObject)rootObject["value"][elementIdx]["value"];
275+
case "object":
276+
var typeIds = await sdbHelper.GetTypeIdFromObject(sessionId, int.Parse(objectId.Value), true, token);
277+
int methodId = await sdbHelper.GetMethodIdByName(sessionId, typeIds[0], "ToArray", token);
278+
var commandParamsObj = new MemoryStream();
279+
var commandParamsObjWriter = new MonoBinaryWriter(commandParamsObj);
280+
commandParamsObjWriter.WriteObj(objectId, sdbHelper);
281+
var toArrayRetMethod = await sdbHelper.InvokeMethod(sessionId, commandParamsObj.ToArray(), methodId, elementAccess.Expression.ToString(), token);
282+
rootObject = await GetValueFromObject(toArrayRetMethod, token);
283+
DotnetObjectId.TryParse(rootObject?["objectId"]?.Value<string>(), out DotnetObjectId arrayObjectId);
284+
rootObject["value"] = await sdbHelper.GetArrayValues(sessionId, int.Parse(arrayObjectId.Value), token);
285+
return (JObject)rootObject["value"][elementIdx]["value"];
286+
default:
287+
throw new InvalidOperationException($"Cannot apply indexing with [] to an expression of type '{objectId.Scheme}'");
288+
}
289+
}
290+
}
291+
return null;
292+
}
293+
catch (Exception ex)
294+
{
295+
var e = ex;
296+
throw new Exception($"Unable to evaluate method '{elementAccess}'");
297+
}
298+
}
299+
209300
public async Task<JObject> Resolve(InvocationExpressionSyntax method, Dictionary<string, JObject> memberAccessValues, CancellationToken token)
210301
{
211302
var methodName = "";

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

+91
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,97 @@ await EvaluateOnCallFrameAndCheck(id,
565565
("this.CallMethodWithObj(this.objToTest)", TNumber(10)));
566566
});
567567

568+
[Fact]
569+
public async Task EvaluateExpressionsWithElementAccessByConstant() => await CheckInspectLocalsAtBreakpointSite(
570+
"DebuggerTests.EvaluateLocalsWithElementAccessTests", "EvaluateLocals", 5, "EvaluateLocals",
571+
"window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithElementAccessTests:EvaluateLocals'); })",
572+
wait_for_event_fn: async (pause_location) =>
573+
{
574+
var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
575+
576+
await EvaluateOnCallFrameAndCheck(id,
577+
("f.numList[0]", TNumber(1)),
578+
("f.textList[1]", TString("2")),
579+
("f.numArray[1]", TNumber(2)),
580+
("f.textArray[0]", TString("1")));
581+
});
582+
583+
[Fact]
584+
public async Task EvaluateExpressionsWithElementAccessByLocalVariable() => await CheckInspectLocalsAtBreakpointSite(
585+
"DebuggerTests.EvaluateLocalsWithElementAccessTests", "EvaluateLocals", 5, "EvaluateLocals",
586+
"window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithElementAccessTests:EvaluateLocals'); })",
587+
wait_for_event_fn: async (pause_location) =>
588+
{
589+
var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
590+
591+
await EvaluateOnCallFrameAndCheck(id,
592+
("f.numList[i]", TNumber(1)),
593+
("f.textList[j]", TString("2")),
594+
("f.numArray[j]", TNumber(2)),
595+
("f.textArray[i]", TString("1")));
596+
597+
});
598+
599+
[Fact]
600+
public async Task EvaluateExpressionsWithElementAccessByMemberVariables() => await CheckInspectLocalsAtBreakpointSite(
601+
"DebuggerTests.EvaluateLocalsWithElementAccessTests", "EvaluateLocals", 5, "EvaluateLocals",
602+
"window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithElementAccessTests:EvaluateLocals'); })",
603+
wait_for_event_fn: async (pause_location) =>
604+
{
605+
var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
606+
607+
await EvaluateOnCallFrameAndCheck(id,
608+
("f.idx0", TNumber(0)),
609+
("f.idx1", TNumber(1)),
610+
("f.numList[f.idx0]", TNumber(1)),
611+
("f.textList[f.idx1]", TString("2")),
612+
("f.numArray[f.idx1]", TNumber(2)),
613+
("f.textArray[f.idx0]", TString("1")));
614+
615+
});
616+
617+
[Fact]
618+
public async Task EvaluateExpressionsWithElementAccessNested() => await CheckInspectLocalsAtBreakpointSite(
619+
"DebuggerTests.EvaluateLocalsWithElementAccessTests", "EvaluateLocals", 5, "EvaluateLocals",
620+
"window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithElementAccessTests:EvaluateLocals'); })",
621+
wait_for_event_fn: async (pause_location) =>
622+
{
623+
var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
624+
625+
await EvaluateOnCallFrameAndCheck(id,
626+
("f.idx0", TNumber(0)),
627+
("f.numList[f.numList[f.idx0]]", TNumber(2)),
628+
("f.textList[f.numList[f.idx0]]", TString("2")),
629+
("f.numArray[f.numArray[f.idx0]]", TNumber(2)),
630+
("f.textArray[f.numArray[f.idx0]]", TString("2")));
631+
632+
});
633+
634+
[Fact]
635+
public async Task EvaluateExpressionsWithElementAccessMultidimentional() => await CheckInspectLocalsAtBreakpointSite(
636+
"DebuggerTests.EvaluateLocalsWithElementAccessTests", "EvaluateLocals", 5, "EvaluateLocals",
637+
"window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithElementAccessTests:EvaluateLocals'); })",
638+
wait_for_event_fn: async (pause_location) =>
639+
{
640+
var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
641+
642+
await EvaluateOnCallFrameAndCheck(id,
643+
("j", TNumber(1)),
644+
("f.idx1", TNumber(1)),
645+
("f.numArrayOfArrays[1][1]", TNumber(2)),
646+
("f.numArrayOfArrays[j][j]", TNumber(2)),
647+
("f.numArrayOfArrays[f.idx1][f.idx1]", TNumber(2)),
648+
("f.numListOfLists[1][1]", TNumber(2)),
649+
("f.numListOfLists[j][j]", TNumber(2)),
650+
("f.numListOfLists[f.idx1][f.idx1]", TNumber(2)),
651+
("f.textArrayOfArrays[1][1]", TString("2")),
652+
("f.textArrayOfArrays[j][j]", TString("2")),
653+
("f.textArrayOfArrays[f.idx1][f.idx1]", TString("2")),
654+
("f.textListOfLists[1][1]", TString("2")),
655+
("f.textListOfLists[j][j]", TString("2")),
656+
("f.textListOfLists[f.idx1][f.idx1]", TString("2")));
657+
658+
});
568659

569660
[Fact]
570661
public async Task EvaluateSimpleMethodCallsCheckChangedValue() => await CheckInspectLocalsAtBreakpointSite(

0 commit comments

Comments
 (0)