Skip to content

Commit d020d36

Browse files
committed
[wasm] MemberReferenceResolver: rework a bit to fix some cases
1 parent 5f5cdf6 commit d020d36

File tree

3 files changed

+155
-99
lines changed

3 files changed

+155
-99
lines changed

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

Lines changed: 133 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using System.Threading;
77
using System.Threading.Tasks;
88
using Microsoft.Extensions.Logging;
9-
using Newtonsoft.Json;
109
using Newtonsoft.Json.Linq;
1110
using System.IO;
1211
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -82,68 +81,86 @@ public async Task<JObject> GetValueFromObject(JToken objRet, CancellationToken t
8281
return null;
8382
}
8483

85-
public async Task<JObject> TryToRunOnLoadedClasses(string varName, CancellationToken token)
84+
public async Task<(JObject containerObject, string remaining)> ResolveStaticMembersInStaticTypes(string varName, CancellationToken token)
8685
{
8786
string classNameToFind = "";
8887
string[] parts = varName.Split(".");
89-
var typeId = -1;
90-
foreach (string part in parts)
88+
var store = await proxy.LoadStore(sessionId, token);
89+
var methodInfo = ctx.CallStack.FirstOrDefault(s => s.Id == scopeId).Method.Info;
90+
91+
int typeId = -1;
92+
for (int i = 0; i < parts.Length; i++)
9193
{
94+
string part = parts[i].Trim();
95+
9296
if (classNameToFind.Length > 0)
9397
classNameToFind += ".";
94-
classNameToFind += part.Trim();
98+
classNameToFind += part;
99+
95100
if (typeId != -1)
96101
{
97-
var fields = await sdbHelper.GetTypeFields(sessionId, typeId, token);
98-
foreach (var field in fields)
99-
{
100-
if (field.Name == part.Trim())
101-
{
102-
var isInitialized = await sdbHelper.TypeIsInitialized(sessionId, typeId, token);
103-
if (isInitialized == 0)
104-
{
105-
isInitialized = await sdbHelper.TypeInitialize(sessionId, typeId, token);
106-
}
107-
var valueRet = await sdbHelper.GetFieldValue(sessionId, typeId, field.Id, token);
108-
return await GetValueFromObject(valueRet, token);
109-
}
110-
}
111-
var methodId = await sdbHelper.GetPropertyMethodIdByName(sessionId, typeId, part.Trim(), token);
112-
if (methodId != -1)
102+
string remaining = null;
103+
JObject memberObject = await FindStaticMemberInType(part, typeId);
104+
if (memberObject != null && i < parts.Length - 1)
105+
remaining = string.Join('.', parts[(i+1)..]);
106+
107+
return (memberObject, remaining);
108+
}
109+
110+
if (!string.IsNullOrEmpty(methodInfo.TypeInfo.Namespace))
111+
typeId = await FindStaticTypeId(methodInfo.TypeInfo.Namespace + "." + classNameToFind);
112+
if (typeId == -1)
113+
typeId = await FindStaticTypeId(classNameToFind);
114+
}
115+
116+
return (null, null);
117+
118+
async Task<JObject> FindStaticMemberInType(string name, int typeId)
119+
{
120+
var fields = await sdbHelper.GetTypeFields(sessionId, typeId, token);
121+
foreach (var field in fields)
122+
{
123+
if (field.Name != name)
124+
continue;
125+
126+
var isInitialized = await sdbHelper.TypeIsInitialized(sessionId, typeId, token);
127+
if (isInitialized == 0)
113128
{
114-
var commandParamsObj = new MemoryStream();
115-
var commandParamsObjWriter = new MonoBinaryWriter(commandParamsObj);
116-
commandParamsObjWriter.Write(0); //param count
117-
var retMethod = await sdbHelper.InvokeMethod(sessionId, commandParamsObj.ToArray(), methodId, "methodRet", token);
118-
return await GetValueFromObject(retMethod, token);
129+
isInitialized = await sdbHelper.TypeInitialize(sessionId, typeId, token);
119130
}
131+
var valueRet = await sdbHelper.GetFieldValue(sessionId, typeId, field.Id, token);
132+
133+
return await GetValueFromObject(valueRet, token);
120134
}
121-
var store = await proxy.LoadStore(sessionId, token);
122-
var info = ctx.CallStack.FirstOrDefault(s => s.Id == scopeId).Method.Info;
123-
var classNameToFindWithNamespace =
124-
string.IsNullOrEmpty(info.TypeInfo.Namespace) ?
125-
classNameToFind :
126-
info.TypeInfo.Namespace + "." + classNameToFind;
127135

128-
foreach (var asm in store.assemblies)
136+
var methodId = await sdbHelper.GetPropertyMethodIdByName(sessionId, typeId, name, token);
137+
if (methodId != -1)
129138
{
130-
if (await TryGetTypeIdFromName(classNameToFindWithNamespace, asm))
131-
break;
132-
if (await TryGetTypeIdFromName(classNameToFind, asm))
133-
break;
139+
var commandParamsObj = new MemoryStream();
140+
var commandParamsObjWriter = new MonoBinaryWriter(commandParamsObj);
141+
commandParamsObjWriter.Write(0); //param count
142+
var retMethod = await sdbHelper.InvokeMethod(sessionId, commandParamsObj.ToArray(), methodId, "methodRet", token);
143+
return await GetValueFromObject(retMethod, token);
134144
}
135145

136-
async Task<bool> TryGetTypeIdFromName(string typeName, AssemblyInfo assembly)
146+
return null;
147+
}
148+
149+
async Task<int> FindStaticTypeId(string typeName)
150+
{
151+
foreach (var asm in store.assemblies)
137152
{
138-
var type = assembly.GetTypeByName(typeName);
153+
var type = asm.GetTypeByName(typeName);
139154
if (type == null)
140-
return false;
155+
continue;
141156

142-
typeId = await sdbHelper.GetTypeIdFromToken(sessionId, assembly.DebugId, type.Token, token);
143-
return true;
157+
int id = await sdbHelper.GetTypeIdFromToken(sessionId, asm.DebugId, type.Token, token);
158+
if (id != -1)
159+
return id;
144160
}
161+
162+
return -1;
145163
}
146-
return null;
147164
}
148165

149166
// Checks Locals, followed by `this`
@@ -153,9 +170,6 @@ public async Task<JObject> Resolve(string varName, CancellationToken token)
153170
if (varName.Contains('('))
154171
return null;
155172

156-
string[] parts = varName.Split(".");
157-
JObject rootObject = null;
158-
159173
if (scopeCache.MemberReferences.TryGetValue(varName, out JObject ret)) {
160174
return ret;
161175
}
@@ -164,66 +178,98 @@ public async Task<JObject> Resolve(string varName, CancellationToken token)
164178
return await GetValueFromObject(valueRet, token);
165179
}
166180

167-
foreach (string part in parts)
181+
string[] parts = varName.Split(".");
182+
if (parts.Length == 0)
183+
return null;
184+
185+
JObject retObject = await ResolveAsLocalOrThisMember(parts[0]);
186+
if (retObject != null && parts.Length > 1)
187+
retObject = await ResolveAsInstanceMember(string.Join('.', parts[1..]), retObject);
188+
189+
if (retObject == null)
168190
{
169-
string partTrimmed = part.Trim();
170-
if (partTrimmed == "")
171-
return null;
172-
if (rootObject != null)
191+
(retObject, string remaining) = await ResolveStaticMembersInStaticTypes(varName, token);
192+
if (!string.IsNullOrEmpty(remaining))
173193
{
174-
if (rootObject?["subtype"]?.Value<string>() == "null")
175-
return null;
176-
if (DotnetObjectId.TryParse(rootObject?["objectId"]?.Value<string>(), out DotnetObjectId objectId))
194+
if (retObject?["subtype"]?.Value<string>() == "null")
177195
{
178-
var rootResObj = await proxy.RuntimeGetPropertiesInternal(sessionId, objectId, null, token);
179-
var objRet = rootResObj.FirstOrDefault(objPropAttr => objPropAttr["name"].Value<string>() == partTrimmed);
180-
if (objRet == null)
181-
return null;
182-
183-
rootObject = await GetValueFromObject(objRet, token);
196+
// NRE on null.$remaining
197+
retObject = null;
198+
}
199+
else
200+
{
201+
retObject = await ResolveAsInstanceMember(remaining, retObject);
184202
}
185-
continue;
186203
}
204+
}
205+
206+
scopeCache.MemberReferences[varName] = retObject;
207+
return retObject;
208+
209+
async Task<JObject> ResolveAsLocalOrThisMember(string name)
210+
{
187211
if (scopeCache.Locals.Count == 0 && !localsFetched)
188212
{
189213
Result scope_res = await proxy.GetScopeProperties(sessionId, scopeId, token);
190214
if (scope_res.IsErr)
191215
throw new Exception($"BUG: Unable to get properties for scope: {scopeId}. {scope_res}");
192216
localsFetched = true;
193217
}
194-
if (scopeCache.Locals.TryGetValue(partTrimmed, out JObject obj))
195-
{
196-
rootObject = obj["value"]?.Value<JObject>();
197-
}
198-
else if (scopeCache.Locals.TryGetValue("this", out JObject objThis))
218+
219+
if (scopeCache.Locals.TryGetValue(name, out JObject obj))
220+
return obj["value"]?.Value<JObject>();
221+
222+
if (!scopeCache.Locals.TryGetValue("this", out JObject objThis))
223+
return null;
224+
225+
if (!DotnetObjectId.TryParse(objThis?["value"]?["objectId"]?.Value<string>(), out DotnetObjectId objectId))
226+
return null;
227+
228+
var rootResObj = await proxy.RuntimeGetPropertiesInternal(sessionId, objectId, null, token);
229+
var objRet = rootResObj.FirstOrDefault(objPropAttr => objPropAttr["name"].Value<string>() == name);
230+
if (objRet != null)
231+
return await GetValueFromObject(objRet, token);
232+
233+
return null;
234+
}
235+
236+
async Task<JObject> ResolveAsInstanceMember(string expr, JObject baseObject)
237+
{
238+
JObject resolvedObject = baseObject;
239+
string[] parts = expr.Split('.');
240+
for (int i = 0; i < parts.Length; i++)
199241
{
200-
if (partTrimmed == "this")
201-
{
202-
rootObject = objThis?["value"].Value<JObject>();
203-
}
204-
else if (DotnetObjectId.TryParse(objThis?["value"]?["objectId"]?.Value<string>(), out DotnetObjectId objectId))
242+
string partTrimmed = parts[i].Trim();
243+
if (partTrimmed.Length == 0)
244+
return null;
245+
246+
if (!DotnetObjectId.TryParse(resolvedObject?["objectId"]?.Value<string>(), out DotnetObjectId objectId))
247+
return null;
248+
249+
var resolvedResObj = await proxy.RuntimeGetPropertiesInternal(sessionId, objectId, null, token);
250+
var objRet = resolvedResObj.FirstOrDefault(objPropAttr => objPropAttr["name"].Value<string>() == partTrimmed);
251+
if (objRet == null)
252+
return null;
253+
254+
resolvedObject = await GetValueFromObject(objRet, token);
255+
if (resolvedObject == null)
256+
return null;
257+
258+
if (resolvedObject["subtype"]?.Value<string>() == "null")
205259
{
206-
var rootResObj = await proxy.RuntimeGetPropertiesInternal(sessionId, objectId, null, token);
207-
var objRet = rootResObj.FirstOrDefault(objPropAttr => objPropAttr["name"].Value<string>() == partTrimmed);
208-
if (objRet != null)
260+
if (i < parts.Length - 1)
209261
{
210-
rootObject = await GetValueFromObject(objRet, token);
211-
}
212-
else
213-
{
214-
rootObject = await TryToRunOnLoadedClasses(varName, token);
215-
return rootObject;
262+
// there is some parts remaining, and can't
263+
// do null.$remaining
264+
return null;
216265
}
266+
267+
return resolvedObject;
217268
}
218269
}
270+
271+
return resolvedObject;
219272
}
220-
if (rootObject == null)
221-
{
222-
rootObject = await TryToRunOnLoadedClasses(varName, token);
223-
return rootObject;
224-
}
225-
scopeCache.MemberReferences[varName] = rootObject;
226-
return rootObject;
227273
}
228274

229275
public async Task<JObject> Resolve(ElementAccessExpressionSyntax elementAccess, Dictionary<string, JObject> memberAccessValues, JObject indexObject, CancellationToken token)

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

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,10 @@ await EvaluateOnCallFrameFail(id,
442442
//BUG: TODO:
443443
//("a)", "CompilationError"),
444444

445+
("this.c.e", "ReferenceError"), // this.<int-field>.<field>
446+
("this.e.a", "ReferenceError"), // this.<int-local>.<field>
447+
("this.dt.e", "ReferenceError"), // this.<field>.<local>
448+
445449
("this.a.", "ReferenceError"),
446450
("a.", "ReferenceError"),
447451

@@ -465,6 +469,9 @@ public async Task NegativeTestsInStaticMethod() => await CheckInspectLocalsAtBre
465469
var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
466470

467471
await EvaluateOnCallFrameFail(id,
472+
("EvaluateStaticClass.f_s.c", "ReferenceError"), // <static-type>.<local>.<field>
473+
("EvaluateStaticClass.NonExistant.f_s.c", "ReferenceError"), // <static-type>.<non-existant>.<local>.<field>
474+
("NonExistant.f_s.dateTime", "ReferenceError"), // <non-existant>.<local>...
468475
("me.foo", "ReferenceError"),
469476
("this", "ReferenceError"),
470477
("this.NullIfAIsNotZero.foo", "ReferenceError"));
@@ -693,10 +700,11 @@ public async Task EvaluateStaticClass() => await CheckInspectLocalsAtBreakpointS
693700
var frame = pause_location["callFrames"][0];
694701

695702
await EvaluateOnCallFrameAndCheck(id,
696-
("DebuggerTests.EvaluateStaticClass.StaticField1", TNumber(10)));
697-
await EvaluateOnCallFrameAndCheck(id,
698-
("DebuggerTests.EvaluateStaticClass.StaticProperty1", TString("StaticProperty1")));
699-
await EvaluateOnCallFrameAndCheck(id,
703+
("EvaluateStaticClass.StaticDTProperty1.Hour", TNumber(4)), // <static-type>.<static-prop>.<instance member>
704+
("EvaluateStaticClass.StaticDTField1.Hour", TNumber(3)), // <static-type>.<static-field>.<instance member>
705+
("EvaluateStaticClass.StaticDTField1.Date.Year", TNumber(2000)), // <static-type>.<static-field>.<instance-member>.<instance member>
706+
("DebuggerTests.EvaluateStaticClass.StaticField1", TNumber(10)),
707+
("DebuggerTests.EvaluateStaticClass.StaticProperty1", TString("StaticProperty1")),
700708
("DebuggerTests.EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented")));
701709
});
702710

@@ -769,16 +777,16 @@ public async Task EvaluateStaticClassesFromDifferentNamespaceInDifferentFrames()
769777
var frame = pause_location["callFrames"][0];
770778

771779
await EvaluateOnCallFrameAndCheck(id_top,
772-
("EvaluateStaticClass.StaticField1", TNumber(20)),
773-
("EvaluateStaticClass.StaticProperty1", TString("StaticProperty2")),
774-
("EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented")));
780+
("EvaluateStaticClass.StaticField1", TNumber(20)),
781+
("EvaluateStaticClass.StaticProperty1", TString("StaticProperty2")),
782+
("EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented")));
775783

776784
var id_second = pause_location["callFrames"][1]["callFrameId"].Value<string>();
777785

778786
await EvaluateOnCallFrameAndCheck(id_second,
779-
("EvaluateStaticClass.StaticField1", TNumber(10)),
780-
("EvaluateStaticClass.StaticProperty1", TString("StaticProperty1")),
781-
("EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented")));
787+
("EvaluateStaticClass.StaticField1", TNumber(10)),
788+
("EvaluateStaticClass.StaticProperty1", TString("StaticProperty1")),
789+
("EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented")));
782790
});
783791

784792
[Fact]

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
@@ -418,7 +418,9 @@ public static void EvaluateAsyncMethods()
418418
public static class EvaluateStaticClass
419419
{
420420
public static int StaticField1 = 10;
421+
public static DateTime StaticDTField1 = new DateTime(2000, 5, 4, 3, 2, 1);
421422
public static string StaticProperty1 => "StaticProperty1";
423+
public static DateTime StaticDTProperty1 => new DateTime(2000, 8, 1, 4, 7, 1);
422424
public static string StaticPropertyWithError => throw new Exception("not implemented");
423425
}
424426

@@ -466,7 +468,7 @@ public void run()
466468
textListOfLists = new List<List<string>> { textList, textList };
467469
idx0 = 0;
468470
idx1 = 1;
469-
}
471+
}
470472
}
471473

472474
public static void EvaluateLocals()
@@ -493,4 +495,4 @@ public static void Run()
493495
var a = 0;
494496
}
495497
}
496-
}
498+
}

0 commit comments

Comments
 (0)