Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -757,11 +757,11 @@ internal async Task<ValueOrError<JToken>> RuntimeGetPropertiesInternal(SessionId
}
case "valuetype":
{
var resValType = await context.SdbAgent.GetValueTypeValues(objectId.Value, accessorPropertiesOnly, token);
var resValType = await context.SdbAgent.GetValueTypeValues(objectId.Value, accessorPropertiesOnly, token, sortByAccessLevel);
return resValType switch
{
null => ValueOrError<JToken>.WithError($"Could not get properties for {objectId}"),
_ => ValueOrError<JToken>.WithValue(sortByAccessLevel ? JObject.FromObject(new { result = resValType }) : resValType)
_ => ValueOrError<JToken>.WithValue(resValType)
};
}
case "array":
Expand Down
154 changes: 105 additions & 49 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -710,23 +710,23 @@ public FieldTypeClass(int id, string name, int typeId, bool isNotPrivate, FieldA
internal class ValueTypeClass
{
public byte[] valueTypeBuffer;
public JArray valueTypeJson;
public JArray valueTypeJsonProps;
public JObject json;
public JArray jsonProps;
public int typeId;
public JArray valueTypeProxy;
public string valueTypeVarName;
public bool valueTypeAutoExpand;
public int Id;
public ValueTypeClass(string varName, byte[] buffer, JArray json, int id, bool expand_properties, int valueTypeId)
public string Id;
public ValueTypeClass(string varName, byte[] buffer, JObject json, int id, bool expand_properties, int valueTypeId)
{
valueTypeBuffer = buffer;
valueTypeJson = json;
this.json = json;
typeId = id;
valueTypeJsonProps = null;
jsonProps = null;
valueTypeProxy = null;
valueTypeVarName = varName;
valueTypeAutoExpand = expand_properties;
Id = valueTypeId;
Id = $"dotnet:valuetype:{valueTypeId}";
}
}
internal class PointerValue
Expand Down Expand Up @@ -1632,7 +1632,7 @@ public async Task<int> GetPropertyMethodIdByName(int typeId, string propertyName
return -1;
}

public async Task<JArray> CreateJArrayForProperties(int typeId, ElementType elementType, ArraySegment<byte> object_buffer, JArray attributes, bool isAutoExpandable, string objectIdStr, bool isOwn, CancellationToken token)
public async Task<JArray> CreateJArrayForProperties(int typeId, ElementType elementType, ArraySegment<byte> object_buffer, bool isAutoExpandable, string objectIdStr, bool isOwn, CancellationToken token, params JToken[] attributesCollections)
{
JArray ret = new JArray();
using var retDebuggerCmdReader = await GetTypePropertiesReader(typeId, token);
Expand All @@ -1651,32 +1651,36 @@ public async Task<JArray> CreateJArrayForProperties(int typeId, ElementType elem
if (getMethodId == 0 || await GetParamCount(getMethodId, token) != 0 || await MethodIsStatic(getMethodId, token))
continue;
JObject propRet = null;
if (attributes.Where(attribute => attribute["name"].Value<string>().Equals(propertyNameStr)).Any())
if (attributesCollections
.Any(attributes => attributes
.Where(attribute => attribute["name"].Value<string>().Equals(propertyNameStr)).Any()))
continue;
if (isAutoExpandable)
{
try {
try
{
propRet = await InvokeMethod(object_buffer, getMethodId, propertyNameStr, token);
}
catch (Exception)
catch (Exception ex)
{
continue;
logger.LogDebug($"Method {getMethodId} of object {objectId.Value} could not be ivoked as AutoExpandable, error - {ex.Message}.");
}
}
else
if (propRet == null)
{
propRet = JObject.FromObject(new {
get = new
{
type = "function",
objectId = $"dotnet:methodId:{objectId.Value}:{getMethodId}:{elementType}",
className = "Function",
description = "get " + propertyNameStr + " ()",
methodId = getMethodId,
objectIdValue = objectIdStr
},
name = propertyNameStr
});
propRet = JObject.FromObject(new
{
get = new
{
type = "function",
objectId = $"dotnet:methodId:{objectId.Value}:{getMethodId}:{elementType}",
className = "Function",
description = "get " + propertyNameStr + " ()",
methodId = getMethodId,
objectIdValue = objectIdStr
},
name = propertyNameStr
});
}
if (isOwn)
propRet["isOwn"] = true;
Expand All @@ -1698,25 +1702,34 @@ public async Task<JObject> GetPointerContent(int pointerId, CancellationToken to

public async Task<JArray> GetPropertiesValuesOfValueType(int valueTypeId, CancellationToken token)
{
JArray ret = new JArray();
JArray properties = new JArray();
var valueType = valueTypes[valueTypeId];
using var commandParamsWriter = new MonoBinaryWriter();
commandParamsWriter.Write(valueType.typeId);
using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdType.GetParents, commandParamsWriter, token);
var parentsCount = retDebuggerCmdReader.ReadInt32();
List<int> typesToGetProperties = new List<int>();
typesToGetProperties.Add(valueType.typeId);
for (int i = 0 ; i < parentsCount; i++)
for (int i = 0; i < parentsCount; i++)
{
typesToGetProperties.Add(retDebuggerCmdReader.ReadInt32());
}
for (int i = 0 ; i < typesToGetProperties.Count; i++)
for (int i = 0; i < typesToGetProperties.Count; i++)
{
var properties = await CreateJArrayForProperties(typesToGetProperties[i], ElementType.ValueType, valueType.valueTypeBuffer, valueType.valueTypeJson, valueType.valueTypeAutoExpand, $"dotnet:valuetype:{valueType.Id}", i == 0, token);
ret = new JArray(ret.Union(properties));
var props = await CreateJArrayForProperties(
typesToGetProperties[i],
ElementType.ValueType,
valueType.valueTypeBuffer,
valueType.valueTypeAutoExpand,
valueType.Id,
isOwn: i == 0,
token,
valueType.json["result"],
valueType.json["internalProperties"],
valueType.json["privateProperties"]);
properties.AddRange(props);
}

return ret;
return properties;
}

private static bool AutoExpandable(string className) {
Expand Down Expand Up @@ -1870,7 +1883,6 @@ public async Task<JObject> CreateJObjectForValueType(MonoBinaryReader retDebugge
var description = className;
var numFields = retDebuggerCmdReader.ReadInt32();
var fields = await GetTypeFields(typeId, token);
JArray valueTypeFields = new JArray();
if (className.IndexOf("System.Nullable<") == 0) //should we call something on debugger-agent to check???
{
retDebuggerCmdReader.ReadByte(); //ignoring the boolean type
Expand All @@ -1881,10 +1893,28 @@ public async Task<JObject> CreateJObjectForValueType(MonoBinaryReader retDebugge
else
return CreateJObject<string>(null, "object", className, false, className, null, null, "null", true);
}
for (int i = 0; i < numFields ; i++)

JArray fieldsPublic = new JArray();
JArray fieldsInternal = new JArray();
JArray fieldsPrivate = new JArray();
for (int i = 0; i < numFields; i++)
{
fieldValueType = await CreateJObjectForVariableValue(retDebuggerCmdReader, fields.ElementAt(i).Name, true, fields.ElementAt(i).TypeId, false, token);
valueTypeFields.Add(fieldValueType);
fieldValueType = await CreateJObjectForVariableValue(retDebuggerCmdReader, fields[i].Name, true, fields[i].TypeId, false, token);
switch (fields[i].ProtectionLevel)
{
case FieldAttributes.Public:
fieldsPublic.Add(fieldValueType);
break;
case FieldAttributes.FamANDAssem:
case FieldAttributes.Private:
fieldsPrivate.Add(fieldValueType);
break;
case FieldAttributes.Family:
case FieldAttributes.Assembly:
case FieldAttributes.FamORAssem:
fieldsInternal.Add(fieldValueType);
break;
}
}

long endPos = retDebuggerCmdReader.BaseStream.Position;
Expand All @@ -1894,6 +1924,12 @@ public async Task<JObject> CreateJObjectForValueType(MonoBinaryReader retDebugge
byte[] valueTypeBuffer = new byte[endPos - initialPos];
retDebuggerCmdReader.Read(valueTypeBuffer, 0, (int)(endPos - initialPos));
retDebuggerCmdReader.BaseStream.Position = endPos;
var valueTypeFields = JObject.FromObject(new
{
result = fieldsPublic,
internalProperties = fieldsInternal,
privateProperties = fieldsPrivate
});
valueTypes[valueTypeId] = new ValueTypeClass(name, valueTypeBuffer, valueTypeFields, typeId, AutoExpandable(className), valueTypeId);
if (AutoInvokeToString(className) || isEnum == 1) {
int methodId = await GetMethodIdByName(typeId, "ToString", token);
Expand Down Expand Up @@ -2202,33 +2238,53 @@ public async Task<JArray> StackFrameGetValues(MethodInfoWithDebugInformation met

}

public async Task<JArray> GetValueTypeValues(int valueTypeId, bool accessorPropertiesOnly, CancellationToken token)
public async Task<JToken> GetValueTypeValues(int valueTypeId, bool accessorPropertiesOnly, CancellationToken token, bool sortByAccessLevel)
{
if (!valueTypes.TryGetValue(valueTypeId, out ValueTypeClass valueType))
return null;
if (valueType.valueTypeJsonProps == null)
{
valueType.valueTypeJsonProps = await GetPropertiesValuesOfValueType(valueTypeId, token);
}
if (valueType.jsonProps == null)
valueType.jsonProps = await GetPropertiesValuesOfValueType(valueTypeId, token);

if (accessorPropertiesOnly)
return valueType.valueTypeJsonProps;
var ret = new JArray(valueType.valueTypeJson.Union(valueType.valueTypeJsonProps));
return ret;
return sortByAccessLevel ?
JObject.FromObject(new
{
result = valueTypes[valueTypeId].jsonProps,
internalProperties = new JArray(),
privateProperties = new JArray()
}) :
valueTypes[valueTypeId].jsonProps;
var publicValues = valueTypes[valueTypeId].json["result"].Union(valueTypes[valueTypeId].jsonProps);
var internalValues = valueTypes[valueTypeId].json["internalProperties"];
var privateValues = valueTypes[valueTypeId].json["privateProperties"];

return sortByAccessLevel ?
JObject.FromObject(new
{
result = publicValues,
internalProperties = internalValues,
privateProperties = privateValues
}) :
new JArray(publicValues.Union(internalValues).Union(privateValues));
}

public async Task<JArray> GetValueTypeProxy(int valueTypeId, CancellationToken token)
{
if (valueTypes[valueTypeId].valueTypeProxy != null)
return valueTypes[valueTypeId].valueTypeProxy;
valueTypes[valueTypeId].valueTypeProxy = new JArray(valueTypes[valueTypeId].valueTypeJson);

var retDebuggerCmdReader = await GetTypePropertiesReader(valueTypes[valueTypeId].typeId, token);
valueTypes[valueTypeId].valueTypeProxy = new JArray(
valueTypes[valueTypeId].json["result"],
valueTypes[valueTypeId].json["internalProperties"],
valueTypes[valueTypeId].json["privateProperties"]);

var retDebuggerCmdReader = await GetTypePropertiesReader(valueTypes[valueTypeId].typeId, token);
if (retDebuggerCmdReader == null)
return null;

var nProperties = retDebuggerCmdReader.ReadInt32();

for (int i = 0 ; i < nProperties; i++)
for (int i = 0; i < nProperties; i++)
{
retDebuggerCmdReader.ReadInt32(); //propertyId
string propertyNameStr = retDebuggerCmdReader.ReadString();
Expand Down Expand Up @@ -2451,11 +2507,11 @@ public async Task<JArray> GetObjectValues(int objectId, GetObjectCommandOptions
typeId,
ElementType.Class,
commandParamsObjWriter.GetParameterBuffer(),
new JArray(objects.Values),
getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute),
$"dotnet:object:{objectId}",
i == 0,
token);
token,
new JArray(objects.Values));
var properties = await GetProperties(props, allFields, typeId, token);
objects.TryAddRange(properties);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -997,45 +997,32 @@ await RuntimeEvaluateAndCheck(
("a.valueToCheck", TNumber(20)));
});

[Fact]
public async Task EvaluateProtectionLevels() => await CheckInspectLocalsAtBreakpointSite(
"DebuggerTests.GetPropertiesTests.DerivedClass", "InstanceMethod", 1, "InstanceMethod",
"window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.GetPropertiesTests.DerivedClass:run'); })",
[Theory]
[InlineData("EvaluateClass", true)]
[InlineData("EvaluateStruct", false)]
public async Task EvaluateProtectionLevels(string entryMethod, bool hasProtectedLevel) => await CheckInspectLocalsAtBreakpointSite(
"DebuggerTests.EvaluateProtectionLevels", entryMethod, 2, entryMethod,
$"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.EvaluateProtectionLevels:{entryMethod}'); }})",
wait_for_event_fn: async (pause_location) =>
{
var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
var (obj, _) = await EvaluateOnCallFrame(id, "this");
var (obj, _) = await EvaluateOnCallFrame(id, "test");
var (pub, internalAndProtected, priv) = await GetPropertiesSortedByProtectionLevels(obj["objectId"]?.Value<string>());

Assert.True(pub[0] != null);
Assert.Equal(pub[0]["value"]["value"], "public");

await CheckProps(pub, new
{
a = TNumber(4),
Base_AutoStringPropertyForOverrideWithField = TString("DerivedClass#Base_AutoStringPropertyForOverrideWithField"),
Base_GetterForOverrideWithField = TString("DerivedClass#Base_GetterForOverrideWithField"),
BaseBase_MemberForOverride = TString("DerivedClass#BaseBase_MemberForOverride"),
DateTime = TGetter("DateTime", TDateTime(new DateTime(2200, 5, 6, 7, 18, 9))),
_DTProp = TGetter("_DTProp", TDateTime(new DateTime(2200, 5, 6, 7, 8, 9))),
FirstName = TGetter("FirstName", TString("DerivedClass#FirstName")),
_base_dateTime = TGetter("_base_dateTime", TDateTime(new DateTime(2134, 5, 7, 1, 9, 2))),
LastName = TGetter("LastName", TString("BaseClass#LastName"))
}, "public");

await CheckProps(internalAndProtected, new
Assert.True(internalAndProtected[0] != null);
Assert.Equal(internalAndProtected[0]["value"]["value"], "internal");
if (hasProtectedLevel)
{
base_num = TNumber(5)
}, "internalAndProtected");
Assert.True(internalAndProtected[1] != null);
Assert.Equal(internalAndProtected[1]["value"]["value"], "protected");
}
Assert.True(priv[0] != null);
Assert.Equal(priv[0]["value"]["value"], "private");

await CheckProps(priv, new
{
_stringField = TString("DerivedClass#_stringField"),
_dateTime = TDateTime(new DateTime(2020, 7, 6, 5, 4, 3)),
AutoStringProperty = TString("DerivedClass#AutoStringProperty"),
StringPropertyForOverrideWithAutoProperty = TString("DerivedClass#StringPropertyForOverrideWithAutoProperty"),
_base_name = TString("private_name"),
Base_AutoStringProperty = TString("base#Base_AutoStringProperty"),
DateTimeForOverride = TGetter("DateTimeForOverride", TDateTime(new DateTime(2190, 9, 7, 5, 3, 2)))
}, "private");
});
});

[Fact]
public async Task StructureGetters() => await CheckInspectLocalsAtBreakpointSite(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,44 @@ public static void Evaluate()
}
}

public static class EvaluateProtectionLevels
{
public class TestClass
{
public string fieldPublic = "public";
private string fieldPrivate = "private";
internal string fieldInternal = "internal";
protected string fieldProtected = "protected";

public TestClass()
{
var a = fieldPrivate;
}
}

public struct TestStruct
{
public string fieldPublic = "public";
private string fieldPrivate = "private";
internal string fieldInternal = "internal";

public TestStruct()
{
var a = fieldPrivate;
}
}

public static void EvaluateClass()
{
var test = new TestClass();
}

public static void EvaluateStruct()
{
var test = new TestStruct();
}
}

public static class StructureGetters
{
public struct Point
Expand Down