diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index 11b314dce1c7c3..7211b0cb9af9a8 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -757,11 +757,11 @@ internal async Task> 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.WithError($"Could not get properties for {objectId}"), - _ => ValueOrError.WithValue(sortByAccessLevel ? JObject.FromObject(new { result = resValType }) : resValType) + _ => ValueOrError.WithValue(resValType) }; } case "array": diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index 6cd13add1e757b..5bd65b4dffed9a 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -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 @@ -1632,7 +1632,7 @@ public async Task GetPropertyMethodIdByName(int typeId, string propertyName return -1; } - public async Task CreateJArrayForProperties(int typeId, ElementType elementType, ArraySegment object_buffer, JArray attributes, bool isAutoExpandable, string objectIdStr, bool isOwn, CancellationToken token) + public async Task CreateJArrayForProperties(int typeId, ElementType elementType, ArraySegment object_buffer, bool isAutoExpandable, string objectIdStr, bool isOwn, CancellationToken token, params JToken[] attributesCollections) { JArray ret = new JArray(); using var retDebuggerCmdReader = await GetTypePropertiesReader(typeId, token); @@ -1651,32 +1651,36 @@ public async Task 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().Equals(propertyNameStr)).Any()) + if (attributesCollections + .Any(attributes => attributes + .Where(attribute => attribute["name"].Value().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; @@ -1698,7 +1702,7 @@ public async Task GetPointerContent(int pointerId, CancellationToken to public async Task 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); @@ -1706,17 +1710,26 @@ public async Task GetPropertiesValuesOfValueType(int valueTypeId, Cancel var parentsCount = retDebuggerCmdReader.ReadInt32(); List typesToGetProperties = new List(); 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) { @@ -1870,7 +1883,6 @@ public async Task 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 @@ -1881,10 +1893,28 @@ public async Task CreateJObjectForValueType(MonoBinaryReader retDebugge else return CreateJObject(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; @@ -1894,6 +1924,12 @@ public async Task 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); @@ -2202,33 +2238,53 @@ public async Task StackFrameGetValues(MethodInfoWithDebugInformation met } - public async Task GetValueTypeValues(int valueTypeId, bool accessorPropertiesOnly, CancellationToken token) + public async Task 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 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(); @@ -2451,11 +2507,11 @@ public async Task 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); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 6512d3ab9aaae8..2f7158b7142638 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -997,46 +997,6 @@ 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'); })", - wait_for_event_fn: async (pause_location) => - { - var id = pause_location["callFrames"][0]["callFrameId"].Value(); - var (obj, _) = await EvaluateOnCallFrame(id, "this"); - var (pub, internalAndProtected, priv) = await GetPropertiesSortedByProtectionLevels(obj["objectId"]?.Value()); - - 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 - { - base_num = TNumber(5) - }, "internalAndProtected"); - - 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( "DebuggerTests.StructureGetters", "Evaluate", 2, "Evaluate", diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs index f91ba3c012effb..0de5d2b438b778 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs @@ -24,6 +24,9 @@ public class GetPropertiesTests : DebuggerTestBase {"_dateTime", (TDateTime(new DateTime(2020, 7, 6, 5, 4, 3)), true)}, {"_DTProp", (TGetter("_DTProp"), true)}, + // internal + {"b", (TBool(true), true)}, + // own public {"a", (TNumber(4), true)}, {"DateTime", (TGetter("DateTime"), true)}, @@ -126,6 +129,9 @@ public class GetPropertiesTests : DebuggerTestBase {"_stringField", (TString("CloneableStruct#_stringField"), true)}, {"_dateTime", (TDateTime(new DateTime(2020, 7, 6, 5, 4, 3 + 3)), true)}, {"_DTProp", (TGetter("_DTProp"), true)}, + + // internal + {"b", (TBool(true), true)}, // own public {"a", (TNumber(4), true)}, @@ -401,5 +407,97 @@ private static void AssertHasOnlyExpectedProperties(string[] expected_names, IEn } } + public static TheoryData, Dictionary, Dictionary, string> ClassGetPropertiesByProtectionLevels() + { + var data = new TheoryData, Dictionary, Dictionary, string>(); + // object DerivedClass: + var public_props = new Dictionary() + { + {"_DTProp", TGetter("_DTProp")}, // private: should not be here, is a property + + // own + {"a", TNumber(4)}, + {"DateTime", TGetter("DateTime")}, + // {"AutoStringProperty", TString("DerivedClass#AutoStringProperty")}, // should be here + {"FirstName", TGetter("FirstName")}, + // {"DateTimeForOverride", TGetter("DateTimeForOverride")}, // should be here + + // {"StringPropertyForOverrideWithAutoProperty", TString("DerivedClass#StringPropertyForOverrideWithAutoProperty")}, // should be here + {"Base_AutoStringPropertyForOverrideWithField", TString("DerivedClass#Base_AutoStringPropertyForOverrideWithField")}, + {"Base_GetterForOverrideWithField", TString("DerivedClass#Base_GetterForOverrideWithField")}, + {"BaseBase_MemberForOverride", TString("DerivedClass#BaseBase_MemberForOverride")}, + + {"_base_dateTime", TGetter("_base_dateTime")}, // private: should not be here + + // inherited public - should be here! + // {"Base_AutoStringProperty", TString("base#Base_AutoStringProperty")}, // should be here + + {"LastName", TGetter("LastName")} + }; + + var internal_protected_props = new Dictionary(){ + // internal + {"b", TBool(true)}, + // inherited protected + {"base_num", TNumber(5)} + }; + + var private_props = new Dictionary(){ + {"_stringField", TString("DerivedClass#_stringField")}, + {"_dateTime", TDateTime(new DateTime(2020, 7, 6, 5, 4, 3))}, + {"AutoStringProperty", TString("DerivedClass#AutoStringProperty")}, // public: should not be here + {"StringPropertyForOverrideWithAutoProperty", TString("DerivedClass#StringPropertyForOverrideWithAutoProperty")}, // public: should not be here + {"_base_name", TString("private_name")}, + {"Base_AutoStringProperty", TString("base#Base_AutoStringProperty")}, // public: should not be here + {"DateTimeForOverride", TGetter("DateTimeForOverride")} // public: should not be here + }; + // data.Add(public_props, internal_protected_props, private_props, "DerivedClass"); + + // structure CloneableStruct: + public_props = new Dictionary() + { + {"_DTProp", TGetter("_DTProp")}, // private: should not be here + + // own + {"a", TNumber(4)}, + {"DateTime", TGetter("DateTime")}, + // {"AutoStringProperty", TString("CloneableStruct#AutoStringProperty")}, // should be here + {"FirstName", TGetter("FirstName")}, + {"LastName", TGetter("LastName")} + }; + internal_protected_props = new Dictionary() + { + // internal + {"b", TBool(true)} + }; + private_props = new Dictionary() + { + {"_stringField", TString("CloneableStruct#_stringField")}, + {"_dateTime", TDateTime(new DateTime(2020, 7, 6, 5, 4, 3 + 3))}, + // {"_DTProp", TGetter("_DTProp")}, // should be here + + {"AutoStringProperty", TString("CloneableStruct#AutoStringProperty")} // public: should not be here + }; + data.Add(public_props, internal_protected_props, private_props, "CloneableStruct"); + return data; + } + + [Theory] + [MemberData(nameof(ClassGetPropertiesByProtectionLevels))] + public async Task PropertiesSortedByProtectionLevel( + Dictionary expectedPublic, Dictionary expectedProtInter, Dictionary expectedPriv, string entryMethod) => + await CheckInspectLocalsAtBreakpointSite( + $"DebuggerTests.GetPropertiesTests.{entryMethod}", "InstanceMethod", 1, "InstanceMethod", + $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.GetPropertiesTests.{entryMethod}:run'); }})", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var (obj, _) = await EvaluateOnCallFrame(id, "this"); + var (pub, internalAndProtected, priv) = await GetPropertiesSortedByProtectionLevels(obj["objectId"]?.Value()); + + await CheckProps(pub, expectedPublic, "public"); + await CheckProps(internalAndProtected, expectedProtInter, "internalAndProtected"); + await CheckProps(priv, expectedPriv, "private"); + }); } } diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-get-properties-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-get-properties-test.cs index d591a34ce140ae..c14dc5779c209e 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-get-properties-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-get-properties-test.cs @@ -64,6 +64,8 @@ public class DerivedClass : BaseClass, ICloneable private DateTime _dateTime = new DateTime(2020, 7, 6, 5, 4, 3); private DateTime _DTProp => new DateTime(2200, 5, 6, 7, 8, 9); + internal bool b = true; + public int a; public DateTime DateTime => _DTProp.AddMinutes(10); public string AutoStringProperty { get; set; } @@ -117,6 +119,8 @@ public struct CloneableStruct : ICloneable, IName private DateTime _dateTime; private DateTime _DTProp => new DateTime(2200, 5, 6, 7, 8, 9); + internal bool b = true; + public int a; public DateTime DateTime => _DTProp.AddMinutes(10); public string AutoStringProperty { get; set; }