Skip to content

Commit b0766a9

Browse files
authored
[wasm][debugger] Added support for getting members of static structures. (#69542)
* Added info flow about being static + including static. * Added static testcases to Browsable, changed names to more suitable. * Fixed tests affected by adding static members to GetProperties reply. * Removed whitespaces I did not intend to correct. * Test require full name after merge with main. * Added @radical's sugestions. * Fixed tests: Private attr is detected even when it's not set. * Better way of checking attr flags.
1 parent b31a48a commit b0766a9

File tree

10 files changed

+649
-82
lines changed

10 files changed

+649
-82
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ public async Task<JObject> ReadAsVariableValue(
9191
CancellationToken token,
9292
bool isOwn = false,
9393
int typeIdForObject = -1,
94-
bool forDebuggerDisplayAttribute = false)
94+
bool forDebuggerDisplayAttribute = false,
95+
bool includeStatic = false)
9596
{
9697
long initialPos = /*retDebuggerCmdReader == null ? 0 : */retDebuggerCmdReader.BaseStream.Position;
9798
ElementType etype = (ElementType)retDebuggerCmdReader.ReadByte();
@@ -199,7 +200,7 @@ public async Task<JObject> ReadAsVariableValue(
199200
}
200201
case ElementType.ValueType:
201202
{
202-
ret = await ReadAsValueType(retDebuggerCmdReader, name, initialPos, forDebuggerDisplayAttribute, token);
203+
ret = await ReadAsValueType(retDebuggerCmdReader, name, initialPos, forDebuggerDisplayAttribute, includeStatic, token);
203204
break;
204205
}
205206
case (ElementType)ValueTypeId.Null:
@@ -300,6 +301,7 @@ public async Task<JObject> ReadAsValueType(
300301
string name,
301302
long initialPos,
302303
bool forDebuggerDisplayAttribute,
304+
bool includeStatic,
303305
CancellationToken token)
304306
{
305307
// FIXME: debugger proxy
@@ -337,6 +339,7 @@ public async Task<JObject> ReadAsValueType(
337339
typeId,
338340
numValues,
339341
isEnum,
342+
includeStatic,
340343
token);
341344
_valueTypes[valueType.Id.Value] = valueType;
342345
return await valueType.ToJObject(_sdbAgent, forDebuggerDisplayAttribute, token);

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

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ public static async Task<Dictionary<string, JObject>> ExpandPropertyValues(
302302
bool isOwn,
303303
CancellationToken token,
304304
Dictionary<string, JObject> allMembers,
305-
bool includeStatic = false)
305+
bool includeStatic)
306306
{
307307
using var retDebuggerCmdReader = await sdbHelper.GetTypePropertiesReader(typeId, token);
308308
if (retDebuggerCmdReader == null)
@@ -323,7 +323,8 @@ public static async Task<Dictionary<string, JObject>> ExpandPropertyValues(
323323
var attrs = (PropertyAttributes)retDebuggerCmdReader.ReadInt32(); //attrs
324324
if (getMethodId == 0 || await sdbHelper.GetParamCount(getMethodId, token) != 0)
325325
continue;
326-
if (!includeStatic && await sdbHelper.MethodIsStatic(getMethodId, token))
326+
bool isStatic = await sdbHelper.MethodIsStatic(getMethodId, token);
327+
if (!includeStatic && isStatic)
327328
continue;
328329

329330
MethodInfoWithDebugInformation getterInfo = await sdbHelper.GetMethodInfo(getMethodId, token);
@@ -336,7 +337,7 @@ public static async Task<Dictionary<string, JObject>> ExpandPropertyValues(
336337
if (!allMembers.TryGetValue(propName, out JObject existingMember))
337338
{
338339
// new member
339-
await AddProperty(getMethodId, state, propName, getterMemberAccessAttrs);
340+
await AddProperty(getMethodId, state, propName, getterMemberAccessAttrs, isStatic);
340341
continue;
341342
}
342343

@@ -387,7 +388,7 @@ public static async Task<Dictionary<string, JObject>> ExpandPropertyValues(
387388
{
388389
// hiding with a non-auto property, so nothing to adjust
389390
// add the new property
390-
await AddProperty(getMethodId, state, overriddenOrHiddenPropName, getterMemberAccessAttrs);
391+
await AddProperty(getMethodId, state, overriddenOrHiddenPropName, getterMemberAccessAttrs, isStatic);
391392
continue;
392393
}
393394

@@ -419,7 +420,7 @@ async Task UpdateBackingFieldWithPropertyAttributes(JObject backingField, string
419420
allMembers[evalue["name"].Value<string>()] = evalue;
420421
}
421422

422-
async Task AddProperty(int getMethodId, DebuggerBrowsableState? state, string propNameWithSufix, MethodAttributes getterAttrs)
423+
async Task AddProperty(int getMethodId, DebuggerBrowsableState? state, string propNameWithSufix, MethodAttributes getterAttrs, bool isPropertyStatic)
423424
{
424425
string returnTypeName = await sdbHelper.GetReturnType(getMethodId, token);
425426
JObject propRet = null;
@@ -431,12 +432,12 @@ async Task AddProperty(int getMethodId, DebuggerBrowsableState? state, string pr
431432
}
432433
catch (Exception)
433434
{
434-
propRet = GetNotAutoExpandableObject(getMethodId, propNameWithSufix);
435+
propRet = GetNotAutoExpandableObject(getMethodId, propNameWithSufix, isPropertyStatic);
435436
}
436437
}
437438
else
438439
{
439-
propRet = GetNotAutoExpandableObject(getMethodId, propNameWithSufix);
440+
propRet = GetNotAutoExpandableObject(getMethodId, propNameWithSufix, isPropertyStatic);
440441
}
441442

442443
propRet["isOwn"] = isOwn;
@@ -461,11 +462,12 @@ async Task AddProperty(int getMethodId, DebuggerBrowsableState? state, string pr
461462
}
462463
}
463464

464-
JObject GetNotAutoExpandableObject(int methodId, string propertyName)
465+
JObject GetNotAutoExpandableObject(int methodId, string propertyName, bool isStatic)
465466
{
466467
JObject methodIdArgs = JObject.FromObject(new
467468
{
468-
containerId = objectId.Value,
469+
isStatic = isStatic,
470+
containerId = isStatic ? typeId : objectId.Value,
469471
isValueType = isValueType,
470472
methodId = methodId
471473
});
@@ -563,7 +565,8 @@ public static async Task<GetMembersResult> GetObjectMemberValues(
563565
isValueType: false,
564566
isOwn,
565567
token,
566-
allMembers);
568+
allMembers,
569+
includeStatic);
567570

568571
// ownProperties
569572
// Note: ownProperties should mean that we return members of the klass itself,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -754,7 +754,7 @@ internal async Task<ValueOrError<GetMembersResult>> RuntimeGetObjectMembers(Sess
754754
: ValueOrError<GetMembersResult>.WithError(resScope);
755755
case "valuetype":
756756
var resValue = await MemberObjectsExplorer.GetValueTypeMemberValues(
757-
context.SdbAgent, objectId.Value, getObjectOptions, token, sortByAccessLevel, includeStatic: false);
757+
context.SdbAgent, objectId.Value, getObjectOptions, token, sortByAccessLevel, includeStatic: true);
758758
return resValue switch
759759
{
760760
null => ValueOrError<GetMembersResult>.WithError($"Could not get properties for {objectId}"),

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

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1797,20 +1797,18 @@ public async Task<JObject> InvokeMethod(ArraySegment<byte> argsBuffer, int metho
17971797
return await ValueCreator.ReadAsVariableValue(retDebuggerCmdReader, name, token);
17981798
}
17991799

1800-
public Task<JObject> InvokeMethod(int objectId, int methodId, bool isValueType, CancellationToken token)
1800+
public Task<JObject> InvokeMethod(int objectId, int methodId, bool isValueType, CancellationToken token, bool isMethodStatic = false)
18011801
{
1802-
if (isValueType)
1802+
if (isValueType && !isMethodStatic)
18031803
{
18041804
return ValueCreator.TryGetValueTypeById(objectId, out var valueType)
18051805
? InvokeMethod(valueType.Buffer, methodId, token)
18061806
: throw new ArgumentException($"Could not find valuetype with id {objectId}, for method id: {methodId}", nameof(objectId));
18071807
}
1808-
else
1809-
{
1810-
using var commandParamsObjWriter = new MonoBinaryWriter();
1808+
using var commandParamsObjWriter = new MonoBinaryWriter();
1809+
if (!isMethodStatic)
18111810
commandParamsObjWriter.Write(ElementType.Class, objectId);
1812-
return InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, token);
1813-
}
1811+
return InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, token);
18141812
}
18151813

18161814
public Task<JObject> InvokeMethod(DotnetObjectId dotnetObjectId, CancellationToken token, int methodId = -1)
@@ -1820,10 +1818,15 @@ public Task<JObject> InvokeMethod(DotnetObjectId dotnetObjectId, CancellationTok
18201818
JObject args = dotnetObjectId.ValueAsJson;
18211819
int? objectId = args["containerId"]?.Value<int>();
18221820
int? embeddedMethodId = args["methodId"]?.Value<int>();
1821+
bool isMethodStatic = args["isStatic"]?.Value<bool>() == true;
18231822

18241823
return objectId == null || embeddedMethodId == null
18251824
? throw new ArgumentException($"Invalid object id for a method, with missing container, or methodId", nameof(dotnetObjectId))
1826-
: InvokeMethod(objectId.Value, embeddedMethodId.Value, isValueType: args["isValueType"]?.Value<bool>() == true, token);
1825+
: InvokeMethod(objectId.Value,
1826+
embeddedMethodId.Value,
1827+
isValueType: args["isValueType"]?.Value<bool>() == true,
1828+
token,
1829+
isMethodStatic);
18271830
}
18281831

18291832
return dotnetObjectId.Scheme is "object" or "valuetype"
@@ -1966,7 +1969,7 @@ public async Task<JArray> StackFrameGetValues(MethodInfoWithDebugInformation met
19661969
using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdFrame.GetThis, commandParamsWriter, token);
19671970
retDebuggerCmdReader.ReadByte(); //ignore type
19681971
var objectId = retDebuggerCmdReader.ReadInt32();
1969-
GetMembersResult asyncProxyMembers = await MemberObjectsExplorer.GetObjectMemberValues(this, objectId, GetObjectCommandOptions.WithProperties, token);
1972+
GetMembersResult asyncProxyMembers = await MemberObjectsExplorer.GetObjectMemberValues(this, objectId, GetObjectCommandOptions.WithProperties, token, includeStatic: true);
19701973
var asyncLocals = await GetHoistedLocalVariables(objectId, asyncProxyMembers.Flatten(), token);
19711974
return asyncLocals;
19721975
}
@@ -1977,7 +1980,7 @@ public async Task<JArray> StackFrameGetValues(MethodInfoWithDebugInformation met
19771980
{
19781981
try
19791982
{
1980-
var var_json = await ValueCreator.ReadAsVariableValue(localsDebuggerCmdReader, var.Name, token);
1983+
var var_json = await ValueCreator.ReadAsVariableValue(localsDebuggerCmdReader, var.Name, token, includeStatic: true);
19811984
locals.Add(var_json);
19821985
}
19831986
catch (Exception ex)

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

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -53,39 +53,35 @@ public static async Task<ValueTypeClass> CreateFromReader(
5353
int typeId,
5454
int numValues,
5555
bool isEnum,
56+
bool includeStatic,
5657
CancellationToken token)
5758
{
5859
var typeInfo = await sdbAgent.GetTypeInfo(typeId, token);
5960
var typeFieldsBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableFields;
6061
var typePropertiesBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableProperties;
6162

6263
IReadOnlyList<FieldTypeClass> fieldTypes = await sdbAgent.GetTypeFields(typeId, token);
63-
// statics should not be in valueType fields: CallFunctionOnTests.PropertyGettersTest
64+
65+
JArray fields = new();
66+
if (includeStatic)
67+
{
68+
IEnumerable<FieldTypeClass> staticFields =
69+
fieldTypes.Where(f => f.Attributes.HasFlag(FieldAttributes.Static));
70+
foreach (var field in staticFields)
71+
{
72+
var fieldValue = await sdbAgent.GetFieldValue(typeId, field.Id, token);
73+
fields.Add(GetFieldWithMetadata(field, fieldValue, isStatic: true));
74+
}
75+
}
76+
6477
IEnumerable<FieldTypeClass> writableFields = fieldTypes
6578
.Where(f => !f.Attributes.HasFlag(FieldAttributes.Literal)
6679
&& !f.Attributes.HasFlag(FieldAttributes.Static));
6780

68-
JArray fields = new();
6981
foreach (var field in writableFields)
7082
{
7183
var fieldValue = await sdbAgent.ValueCreator.ReadAsVariableValue(cmdReader, field.Name, token, true, field.TypeId, false);
72-
73-
fieldValue["__section"] = field.Attributes switch
74-
{
75-
FieldAttributes.Private => "private",
76-
FieldAttributes.Public => "result",
77-
_ => "internal"
78-
};
79-
80-
if (field.IsBackingField)
81-
fieldValue["__isBackingField"] = true;
82-
else
83-
{
84-
typeFieldsBrowsableInfo.TryGetValue(field.Name, out DebuggerBrowsableState? state);
85-
fieldValue["__state"] = state?.ToString();
86-
}
87-
88-
fields.Add(fieldValue);
84+
fields.Add(GetFieldWithMetadata(field, fieldValue, isStatic: false));
8985
}
9086

9187
long endPos = cmdReader.BaseStream.Position;
@@ -95,6 +91,26 @@ public static async Task<ValueTypeClass> CreateFromReader(
9591
cmdReader.BaseStream.Position = endPos;
9692

9793
return new ValueTypeClass(valueTypeBuffer, className, fields, typeId, isEnum);
94+
95+
JObject GetFieldWithMetadata(FieldTypeClass field, JObject fieldValue, bool isStatic)
96+
{
97+
// GetFieldValue returns JObject without name and we need this information
98+
if (isStatic)
99+
fieldValue["name"] = field.Name;
100+
FieldAttributes attr = field.Attributes & FieldAttributes.FieldAccessMask;
101+
fieldValue["__section"] = attr == FieldAttributes.Public
102+
? "public" :
103+
attr == FieldAttributes.Private ? "private" : "internal";
104+
105+
if (field.IsBackingField)
106+
{
107+
fieldValue["__isBackingField"] = true;
108+
return fieldValue;
109+
}
110+
typeFieldsBrowsableInfo.TryGetValue(field.Name, out DebuggerBrowsableState? state);
111+
fieldValue["__state"] = state?.ToString();
112+
return fieldValue;
113+
}
98114
}
99115

100116
public async Task<JObject> ToJObject(MonoSDBHelper sdbAgent, bool forDebuggerDisplayAttribute, CancellationToken token)

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,12 @@ public async Task PropertyGettersTest(string eval_fn, string method_name, int li
625625

626626
// Auto properties show w/o getters, because they have
627627
// a backing field
628-
DTAutoProperty = TDateTime(dt)
628+
DTAutoProperty = TDateTime(dt),
629+
630+
// Static properties
631+
PublicStaticDTProp = TGetter("PublicStaticDTProp"),
632+
PrivateStaticDTProp = TGetter("PrivateStaticDTProp"),
633+
InternalStaticDTProp = TGetter("InternalStaticDTProp"),
629634
}, local_name);
630635

631636
// Invoke getters, and check values

0 commit comments

Comments
 (0)