Skip to content

Commit 16e4b9a

Browse files
authored
[wasm][debugger] Fix DebuggerTypeProxy for structs and for classes with multiple constructors (#77039)
* Adding any new constructor breaks UsingDebuggerTypeProxy test. * Fix: now objects won't get identified as valueTypes by a mistake. * Test logic fix: rely on comparison with fixed entities only. * Fix: choosing proxy's matching constructor works. * Support DebuggerProxy in valueTypes. * Fix test to not rely on `testPropertiesNone`. * Fix tests. * Fix tests. * Edit and disable rootHidden test.
1 parent 134948e commit 16e4b9a

File tree

7 files changed

+152
-118
lines changed

7 files changed

+152
-118
lines changed

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,10 @@ private static async Task<JArray> GetRootHiddenChildren(
150150
{
151151
// a collection - expose elements to be of array scheme
152152
var memberNamedItems = members
153-
.Where(m => m["name"]?.Value<string>() == "Items" || m["name"]?.Value<string>() == "_items")
153+
.Where(m => m["name"]?.Value<string>() == "Items")
154154
.FirstOrDefault();
155155
if (memberNamedItems is not null &&
156-
(DotnetObjectId.TryParse(memberNamedItems["value"]?["objectId"]?.Value<string>(), out DotnetObjectId itemsObjectId)) &&
156+
DotnetObjectId.TryParse(memberNamedItems["value"]?["objectId"]?.Value<string>(), out DotnetObjectId itemsObjectId) &&
157157
itemsObjectId.Scheme == "array")
158158
{
159159
rootObjectId = itemsObjectId;
@@ -550,7 +550,8 @@ public static async Task<GetMembersResult> GetObjectMemberValues(
550550
// 2
551551
if (!getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerDisplayAttribute))
552552
{
553-
GetMembersResult debuggerProxy = await sdbHelper.GetValuesFromDebuggerProxyAttribute(objectId, typeIdsIncludingParents[0], token);
553+
GetMembersResult debuggerProxy = await sdbHelper.GetValuesFromDebuggerProxyAttributeForObject(
554+
objectId, typeIdsIncludingParents[0], token);
554555
if (debuggerProxy != null)
555556
return debuggerProxy;
556557
}

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

+100-62
Original file line numberDiff line numberDiff line change
@@ -2121,8 +2121,7 @@ public async Task<int> GetTypeByName(string typeToSearch, CancellationToken toke
21212121
return retDebuggerCmdReader.ReadInt32();
21222122
}
21232123

2124-
// FIXME: support valuetypes
2125-
public async Task<GetMembersResult> GetValuesFromDebuggerProxyAttribute(int objectId, int typeId, CancellationToken token)
2124+
public async Task<GetMembersResult> GetValuesFromDebuggerProxyAttributeForObject(int objectId, int typeId, CancellationToken token)
21262125
{
21272126
try
21282127
{
@@ -2132,25 +2131,11 @@ public async Task<GetMembersResult> GetValuesFromDebuggerProxyAttribute(int obje
21322131

21332132
using var ctorArgsWriter = new MonoBinaryWriter();
21342133
ctorArgsWriter.Write((byte)ValueTypeId.Null);
2135-
2136-
// FIXME: move method invocation to valueTypeclass?
2137-
if (ValueCreator.TryGetValueTypeById(objectId, out var valueType))
2138-
{
2139-
//FIXME: Issue #68390
2140-
//ctorArgsWriter.Write((byte)0); //not used but needed
2141-
//ctorArgsWriter.Write(0); //not used but needed
2142-
//ctorArgsWriter.Write((int)1); // num args
2143-
//ctorArgsWriter.Write(valueType.Buffer);
2144-
return null;
2145-
}
2146-
else
2147-
{
2148-
ctorArgsWriter.Write((byte)0); //not used
2149-
ctorArgsWriter.Write(0); //not used
2150-
ctorArgsWriter.Write((int)1); // num args
2151-
ctorArgsWriter.Write((byte)ElementType.Object);
2152-
ctorArgsWriter.Write(objectId);
2153-
}
2134+
ctorArgsWriter.Write((byte)0); //not used
2135+
ctorArgsWriter.Write(0); //not used
2136+
ctorArgsWriter.Write((int)1); // num args
2137+
ctorArgsWriter.Write((byte)ElementType.Object);
2138+
ctorArgsWriter.Write(objectId);
21542139

21552140
var retMethod = await InvokeMethod(ctorArgsWriter.GetParameterBuffer(), methodId, token);
21562141
if (!DotnetObjectId.TryParse(retMethod?["value"]?["objectId"]?.Value<string>(), out DotnetObjectId dotnetObjectId))
@@ -2170,6 +2155,39 @@ public async Task<GetMembersResult> GetValuesFromDebuggerProxyAttribute(int obje
21702155
return null;
21712156
}
21722157

2158+
public async Task<GetMembersResult> GetValuesFromDebuggerProxyAttributeForValueTypes(int valueTypeId, int typeId, CancellationToken token)
2159+
{
2160+
try
2161+
{
2162+
var typeName = await GetTypeName(typeId, token);
2163+
int methodId = await FindDebuggerProxyConstructorIdFor(typeId, token);
2164+
if (methodId == -1)
2165+
return null;
2166+
2167+
using var ctorArgsWriter = new MonoBinaryWriter();
2168+
ctorArgsWriter.Write((byte)ValueTypeId.Null);
2169+
2170+
if (!ValueCreator.TryGetValueTypeById(valueTypeId, out var valueType))
2171+
return null;
2172+
ctorArgsWriter.Write((byte)0); //not used but needed
2173+
ctorArgsWriter.Write(0); //not used but needed
2174+
ctorArgsWriter.Write((int)1); // num args
2175+
ctorArgsWriter.Write(valueType.Buffer);
2176+
var retMethod = await InvokeMethod(ctorArgsWriter.GetParameterBuffer(), methodId, token);
2177+
if (!DotnetObjectId.TryParse(retMethod?["value"]?["objectId"]?.Value<string>(), out DotnetObjectId dotnetObjectId))
2178+
throw new Exception($"Invoking .ctor ({methodId}) for DebuggerTypeProxy on type {typeId} returned {retMethod}");
2179+
GetMembersResult members = await GetTypeMemberValues(dotnetObjectId,
2180+
GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.ForDebuggerProxyAttribute,
2181+
token);
2182+
return members;
2183+
}
2184+
catch (Exception e)
2185+
{
2186+
logger.LogDebug($"Could not evaluate DebuggerTypeProxyAttribute of type {await GetTypeName(typeId, token)} - {e}");
2187+
return null;
2188+
}
2189+
}
2190+
21732191
private async Task<int> FindDebuggerProxyConstructorIdFor(int typeId, CancellationToken token)
21742192
{
21752193
try
@@ -2178,59 +2196,79 @@ private async Task<int> FindDebuggerProxyConstructorIdFor(int typeId, Cancellati
21782196
if (getCAttrsRetReader == null)
21792197
return -1;
21802198

2181-
var methodId = -1;
21822199
var parmCount = getCAttrsRetReader.ReadInt32();
2183-
for (int j = 0; j < parmCount; j++)
2200+
if (parmCount != 1)
2201+
throw new InternalErrorException($"Expected to find custom attribute with only one argument, but it has {parmCount} parameters.");
2202+
2203+
byte monoParamTypeId = getCAttrsRetReader.ReadByte();
2204+
// FIXME: DebuggerTypeProxyAttribute(string) - not supported
2205+
if ((ValueTypeId)monoParamTypeId != ValueTypeId.Type)
21842206
{
2185-
var monoTypeId = getCAttrsRetReader.ReadByte();
2186-
// FIXME: DebuggerTypeProxyAttribute(string) - not supported
2187-
if ((ValueTypeId)monoTypeId != ValueTypeId.Type)
2188-
continue;
2207+
logger.LogDebug($"DebuggerTypeProxy attribute is only supported with a System.Type parameter type. Got {(ValueTypeId)monoParamTypeId}");
2208+
return -1;
2209+
}
2210+
2211+
var typeProxyTypeId = getCAttrsRetReader.ReadInt32();
2212+
2213+
using var commandParamsWriter = new MonoBinaryWriter();
2214+
commandParamsWriter.Write(typeProxyTypeId);
2215+
var originalClassName = await GetTypeNameOriginal(typeProxyTypeId, token);
21892216

2190-
var cAttrTypeId = getCAttrsRetReader.ReadInt32();
2191-
using var commandParamsWriter = new MonoBinaryWriter();
2192-
commandParamsWriter.Write(cAttrTypeId);
2193-
var className = await GetTypeNameOriginal(cAttrTypeId, token);
2194-
if (className.IndexOf('[') > 0)
2217+
if (originalClassName.IndexOf('[') > 0)
2218+
{
2219+
string className = originalClassName;
2220+
className = className.Remove(className.IndexOf('['));
2221+
var assemblyId = await GetAssemblyIdFromType(typeProxyTypeId, token);
2222+
var assemblyName = await GetFullAssemblyName(assemblyId, token);
2223+
2224+
StringBuilder typeToSearch = new(className);
2225+
typeToSearch.Append('[');
2226+
List<int> genericTypeArgs = await GetTypeParamsOrArgsForGenericType(typeId, token);
2227+
for (int k = 0; k < genericTypeArgs.Count; k++)
21952228
{
2196-
className = className.Remove(className.IndexOf('['));
2197-
var assemblyId = await GetAssemblyIdFromType(cAttrTypeId, token);
2198-
var assemblyName = await GetFullAssemblyName(assemblyId, token);
2199-
var typeToSearch = className;
2200-
typeToSearch += "[["; //System.Collections.Generic.List`1[[System.Int32,mscorlib,Version=4.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089]],mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
2201-
List<int> genericTypeArgs = await GetTypeParamsOrArgsForGenericType(typeId, token);
2202-
for (int k = 0; k < genericTypeArgs.Count; k++)
2203-
{
2204-
var assemblyIdArg = await GetAssemblyIdFromType(genericTypeArgs[k], token);
2205-
var assemblyNameArg = await GetFullAssemblyName(assemblyIdArg, token);
2206-
var classNameArg = await GetTypeNameOriginal(genericTypeArgs[k], token);
2207-
typeToSearch += classNameArg + ", " + assemblyNameArg;
2208-
if (k + 1 < genericTypeArgs.Count)
2209-
typeToSearch += "], [";
2210-
else
2211-
typeToSearch += "]";
2212-
}
2213-
typeToSearch += "]";
2214-
typeToSearch += ", " + assemblyName;
2215-
var genericTypeId = await GetTypeByName(typeToSearch, token);
2216-
if (genericTypeId < 0)
2217-
break;
2218-
cAttrTypeId = genericTypeId;
2229+
// typeToSearch += '[';
2230+
var assemblyIdArg = await GetAssemblyIdFromType(genericTypeArgs[k], token);
2231+
var assemblyNameArg = await GetFullAssemblyName(assemblyIdArg, token);
2232+
var classNameArg = await GetTypeNameOriginal(genericTypeArgs[k], token);
2233+
typeToSearch.Append($"{(k == 0 ? "" : ",")}[{classNameArg}, {assemblyNameArg}]");
22192234
}
2220-
int[] methodIds = await GetMethodIdsByName(cAttrTypeId, ".ctor", BindingFlags.Default, token);
2221-
if (methodIds != null)
2222-
methodId = methodIds[0];
2223-
break;
2235+
typeToSearch.Append($"], {assemblyName}");
2236+
var genericTypeId = await GetTypeByName(typeToSearch.ToString(), token);
2237+
if (genericTypeId < 0)
2238+
{
2239+
logger.LogDebug($"Could not find instantiated generic type id for {typeToSearch}.");
2240+
return -1;
2241+
}
2242+
typeProxyTypeId = genericTypeId;
22242243
}
2244+
int[] constructorIds = await GetMethodIdsByName(typeProxyTypeId, ".ctor", BindingFlags.DeclaredOnly, token);
2245+
if (constructorIds is null)
2246+
throw new InternalErrorException($"Could not find any constructor for DebuggerProxy type: {originalClassName}");
2247+
2248+
if (constructorIds.Length == 1)
2249+
return constructorIds[0];
22252250

2226-
return methodId;
2251+
string expectedConstructorParamType = await GetTypeName(typeId, token);
2252+
foreach (var methodId in constructorIds)
2253+
{
2254+
var methodInfoFromRuntime = await GetMethodInfo(methodId, token);
2255+
// avoid calling to runtime if possible
2256+
var ps = methodInfoFromRuntime.Info.GetParametersInfo();
2257+
if (ps.Length != 1)
2258+
continue;
2259+
string parameters = await GetParameters(methodId, token);
2260+
if (string.IsNullOrEmpty(parameters))
2261+
throw new InternalErrorException($"Could not get method's parameter types. MethodId = {methodId}.");
2262+
if (parameters == $"({expectedConstructorParamType})")
2263+
return methodId;
2264+
}
2265+
throw new InternalErrorException($"Could not find a matching constructor for DebuggerProxy type: {originalClassName}");
22272266
}
22282267
catch (Exception e)
22292268
{
22302269
logger.LogDebug($"Could not evaluate DebuggerTypeProxyAttribute of type {await GetTypeName(typeId, token)} - {e}");
2270+
return -1;
22312271
}
2232-
2233-
return -1;
22342272
}
22352273

22362274
public ValueTypeClass GetValueTypeClass(int valueTypeId)

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

+1-3
Original file line numberDiff line numberDiff line change
@@ -213,9 +213,7 @@ public async Task<GetMembersResult> GetMemberValues(
213213
if (!getObjectOptions.HasFlag(GetObjectCommandOptions.ForDebuggerDisplayAttribute))
214214
{
215215
// FIXME: cache?
216-
result = await sdbHelper.GetValuesFromDebuggerProxyAttribute(Id.Value, TypeId, token);
217-
if (result != null)
218-
Console.WriteLine($"Investigate GetValuesFromDebuggerProxyAttribute\n{result}. There was a change of logic from loop to one iteration");
216+
result = await sdbHelper.GetValuesFromDebuggerProxyAttributeForValueTypes(Id.Value, TypeId, token);
219217
}
220218

221219
if (result == null && getObjectOptions.HasFlag(GetObjectCommandOptions.AccessorPropertiesOnly))

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

+6-2
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public async Task UsingDebuggerDisplay()
4141
[ConditionalFact(nameof(RunningOnChrome))]
4242
public async Task UsingDebuggerTypeProxy()
4343
{
44-
var bp = await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.DebuggerCustomViewTest", "run", 15);
44+
var bp = await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.DebuggerCustomViewTest", "run", 16);
4545
var pause_location = await EvaluateAndCheck(
4646
"window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.DebuggerCustomViewTest:run'); }, 1);",
4747
"dotnet://debugger-test.dll/debugger-custom-view-test.cs",
@@ -61,6 +61,10 @@ public async Task UsingDebuggerTypeProxy()
6161
props = await GetObjectOnFrame(frame, "b");
6262
await CheckString(props, "Val2", "one");
6363

64+
await CheckValueType(locals, "bs", "DebuggerTests.WithProxyStruct", description:"DebuggerTests.WithProxyStruct");
65+
props = await GetObjectOnFrame(frame, "bs");
66+
await CheckString(props, "Val2", "one struct");
67+
6468
await CheckObject(locals, "openWith", "System.Collections.Generic.Dictionary<string, string>", description: "Count = 3");
6569
props = await GetObjectOnFrame(frame, "openWith");
6670
Assert.Equal(1, props.Count());
@@ -106,7 +110,7 @@ async Task<bool> CheckProperties(JObject pause_location)
106110
Assert.True(task.Result);
107111
}
108112
}
109-
113+
110114
[ConditionalFact(nameof(RunningOnChrome))]
111115
public async Task InspectObjectOfTypeWithToStringOverriden()
112116
{

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

+10-2
Original file line numberDiff line numberDiff line change
@@ -772,9 +772,17 @@ internal async Task CheckProps(JToken actual, object exp_o, string label, int nu
772772
var exp_i = exp_v_arr[i];
773773
var act_i = actual_arr[i];
774774

775-
AssertEqual(i.ToString(), act_i["name"]?.Value<string>(), $"{label}-[{i}].name");
775+
string exp_name = exp_i["name"]?.Value<string>();
776+
if (string.IsNullOrEmpty(exp_name))
777+
exp_name = i.ToString();
778+
779+
AssertEqual(exp_name, act_i["name"]?.Value<string>(), $"{label}-[{i}].name");
776780
if (exp_i != null)
777-
await CheckValue(act_i["value"], exp_i, $"{label}-{i}th value");
781+
{
782+
await CheckValue(act_i["value"],
783+
((JObject)exp_i).GetValue("value")?.HasValues == true ? exp_i["value"] : exp_i,
784+
$"{label}-{i}th value");
785+
}
778786
}
779787
return;
780788
}

0 commit comments

Comments
 (0)