From ae2e360936eb0ee737fb81c872c87dd7794782d3 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Tue, 4 Jan 2022 16:12:26 +0100 Subject: [PATCH] Divide properties into: public, private, protected. (#62627) * Description of DebuggerBrowsable behavior. * Added test for browse attributes. * Corrected typos in the doc. * Added Browse Never feature. Corrected Collapse test. ToDo: RootHidden. * Draft of RootHidden solution. * Added Array to test cases as it behaves differently than Collection. * Added name concatenation to make array/list elemetns in debug window unique. * Update docs/design/mono/debugger.md Co-authored-by: Ankit Jain * Applied PR review suggestions. * Added a reference to regular Browsable attribute behavior in .net. * Applied most of review suggestions. * Stopping GetFieldsValue early. * Remove unintentional change to the original code. * Do not skip fields that don't have browsable attributes. * Changing the expected behavior to match Console Application. EventHandlers are Browsable.Never by default. * Changed the place of checking if objetc is an array. * Update src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs Co-authored-by: Ankit Jain * Removed unused variables. * Removing space and unused import. * Draft - parameter for choosing sorted and unsorted returned value version. * Working version. * Added "protected internal" and "private protected" cases. Changed field name to be consistant with the value. * Changed approach: placed sorting at the end of the function, not in the middle. * Fix for tests that use GetProperties. * Fixed most tests (31 failing). * Removed whitespaces. * Fixed CheckAccessorsOnObjectsWithCFO tests. * Fix for InspectTypeInheritedMembers. * Revert unnecessary indent change. * Partially addressed @radical comments. * Addressed the comment about extension instead of Union. * Removed string cultural vunerability. * Added Properties dictionary, the same as for fields. * Fixed the bug I made by using dynamc. * Applying @radical comments about refactoring. * Corrected typo. * Added tests for properties. * Draft of changes for properties handling - never and root hidden failing. * Fix for RootHidden properties. * Added tests for static fields decorated with Browsable. * Correct a typo. * Undo merge unintentional changes. * Changing expected behavior for MulticastDelegateTest - in Console Application EventHandler is Browsable.Never by default so we should not expect it to be visible in the debug window. * Removing not relevant changes created after merge with main. * Remove file added in merge with main. * Revert "Removing not relevant changes created after merge with main." This reverts commit b1acf8b546d1b95bea101290c25ec7f15cb78799. * Revert. * Revert revert. * One broken test for custom getter. * Ugly fix to make all the tests work. * Refactored JArray aggregation to Dictionary. * Better naming. * Add skipped change from merge. * Fix getters testing. * Removed unnecessary doubled execution. * Decreased complexity by replacing a loop with data aggregation. * Replaced O(nm) Linq with O(n) loop through dictionary. * Remove comment, flip conditions. * Removed not intended comments and changes. * Missing removal for previous commit. * Added tests for protection levels. Co-authored-by: Ankit Jain --- .../debugger/BrowserDebugProxy/DebugStore.cs | 1 - .../debugger/BrowserDebugProxy/MonoProxy.cs | 41 +++-- .../BrowserDebugProxy/MonoSDBHelper.cs | 142 ++++++++++++------ .../DebuggerTestSuite/DebuggerTestBase.cs | 68 ++++++++- .../EvaluateOnCallFrameTests.cs | 22 ++- .../debugger-test/debugger-evaluate-test.cs | 21 +++ 6 files changed, 231 insertions(+), 64 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs index a50f486e052f6c..669f37c62c3e74 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs @@ -22,7 +22,6 @@ using System.Reflection; using System.Collections.Immutable; using System.Diagnostics; -using Microsoft.VisualBasic; namespace Microsoft.WebAssembly.Diagnostics { diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index b12de2655f70fd..42c30e89f5e6d3 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -461,12 +461,12 @@ protected override async Task AcceptCommand(MessageId id, string method, J if (!DotnetObjectId.TryParse(args?["objectId"], out DotnetObjectId objectId)) break; - var ret = await RuntimeGetPropertiesInternal(id, objectId, args, token); + var ret = await RuntimeGetPropertiesInternal(id, objectId, args, token, true); if (ret == null) { SendResponse(id, Result.Err($"Unable to RuntimeGetProperties '{objectId}'"), token); } else - SendResponse(id, Result.OkFromObject(new { result = ret }), token); + SendResponse(id, Result.OkFromObject(ret), token); return true; } @@ -658,7 +658,7 @@ private async Task OnSetVariableValue(MessageId id, int scopeId, string va return true; } - internal async Task RuntimeGetPropertiesInternal(SessionId id, DotnetObjectId objectId, JToken args, CancellationToken token) + internal async Task RuntimeGetPropertiesInternal(SessionId id, DotnetObjectId objectId, JToken args, CancellationToken token, bool sortByAccessLevel = false) { var context = GetContext(id); var accessorPropertiesOnly = false; @@ -675,33 +675,48 @@ internal async Task RuntimeGetPropertiesInternal(SessionId id, DotnetObj objectValuesOpt |= GetObjectCommandOptions.OwnProperties; } } - //Console.WriteLine($"RuntimeGetProperties - {args}"); try { switch (objectId.Scheme) { case "scope": { - var res = await GetScopeProperties(id, objectId.Value, token); - return res.Value?["result"]; + var resScope = await GetScopeProperties(id, objectId.Value, token); + if (sortByAccessLevel) + return resScope.Value; + return resScope.Value?["result"]; } case "valuetype": - return await context.SdbAgent.GetValueTypeValues(objectId.Value, accessorPropertiesOnly, token); + { + var resValType = await context.SdbAgent.GetValueTypeValues(objectId.Value, accessorPropertiesOnly, token); + return sortByAccessLevel ? JObject.FromObject(new { result = resValType }) : resValType; + } case "array": - return await context.SdbAgent.GetArrayValues(objectId.Value, token); + { + var resArr = await context.SdbAgent.GetArrayValues(objectId.Value, token); + return sortByAccessLevel ? JObject.FromObject(new { result = resArr }) : resArr; + } case "methodId": { - var objRet = await context.SdbAgent.InvokeMethodInObject(objectId.Value, objectId.SubValue, "", token); - return new JArray(objRet); + var resMethod = await context.SdbAgent.InvokeMethodInObject(objectId.Value, objectId.SubValue, "", token); + return sortByAccessLevel ? JObject.FromObject(new { result = resMethod }) : new JArray(resMethod); } case "object": - return await context.SdbAgent.GetObjectValues(objectId.Value, objectValuesOpt, token); + { + var resObj = (await context.SdbAgent.GetObjectValues(objectId.Value, objectValuesOpt, token, sortByAccessLevel)); + return sortByAccessLevel ? resObj[0] : resObj; + } case "pointer": - return new JArray{await context.SdbAgent.GetPointerContent(objectId.Value, token)}; + { + var resPointer = new JArray { await context.SdbAgent.GetPointerContent(objectId.Value, token) }; + return sortByAccessLevel ? JObject.FromObject(new { result = resPointer }) : resPointer; + } case "cfo_res": { Result res = await SendMonoCommand(id, MonoCommands.GetDetails(RuntimeId, objectId.Value, args), token); string value_json_str = res.Value["result"]?["value"]?["__value_as_json_string__"]?.Value(); - return value_json_str != null ? JArray.Parse(value_json_str) : null; + return value_json_str != null ? + (sortByAccessLevel ? JObject.FromObject(new { result = JArray.Parse(value_json_str) }) : JArray.Parse(value_json_str)) : + null; } default: return null; diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index 80eaed9daf6948..f613922e6caec2 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -646,13 +646,15 @@ internal class FieldTypeClass public int Id { get; } public string Name { get; } public int TypeId { get; } - public bool IsPublic { get; } - public FieldTypeClass(int id, string name, int typeId, bool isPublic) + public bool IsNotPrivate { get; } + public FieldAttributes ProtectionLevel { get; } + public FieldTypeClass(int id, string name, int typeId, bool isNotPrivate, FieldAttributes protectionLevel) { Id = id; Name = name; TypeId = typeId; - IsPublic = isPublic; + IsNotPrivate = isNotPrivate; + ProtectionLevel = protectionLevel; } } internal class ValueTypeClass @@ -1235,14 +1237,14 @@ public async Task> GetTypeFields(int typeId, CancellationTo for (int i = 0 ; i < nFields; i++) { - bool isPublic = false; + bool isNotPrivate = false; int fieldId = retDebuggerCmdReader.ReadInt32(); //fieldId string fieldNameStr = retDebuggerCmdReader.ReadString(); int fieldTypeId = retDebuggerCmdReader.ReadInt32(); //typeId int attrs = retDebuggerCmdReader.ReadInt32(); //attrs int isSpecialStatic = retDebuggerCmdReader.ReadInt32(); //is_special_static if (((attrs & (int)MethodAttributes.Public) != 0)) - isPublic = true; + isNotPrivate = true; if (isSpecialStatic == 1) continue; if (fieldNameStr.Contains("k__BackingField")) @@ -1251,7 +1253,7 @@ public async Task> GetTypeFields(int typeId, CancellationTo fieldNameStr = fieldNameStr.Replace("<", ""); fieldNameStr = fieldNameStr.Replace(">", ""); } - ret.Add(new FieldTypeClass(fieldId, fieldNameStr, fieldTypeId, isPublic)); + ret.Add(new FieldTypeClass(fieldId, fieldNameStr, fieldTypeId, isNotPrivate, (FieldAttributes)((attrs) & (int)FieldAttributes.FieldAccessMask))); } typeInfo.FieldsList = ret; return ret; @@ -2315,41 +2317,46 @@ public async Task GetValuesFromDebuggerProxyAttribute(int objectId, int return null; } - public async Task GetObjectValues(int objectId, GetObjectCommandOptions getCommandType, CancellationToken token) + public async Task GetObjectValues(int objectId, GetObjectCommandOptions getCommandType, CancellationToken token, bool sortByAccessLevel = false) { var typeIdsIncludingParents = await GetTypeIdFromObject(objectId, true, token); if (!getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerDisplayAttribute)) { var debuggerProxy = await GetValuesFromDebuggerProxyAttribute(objectId, typeIdsIncludingParents[0], token); if (debuggerProxy != null) - return debuggerProxy; + return sortByAccessLevel ? + new JArray(JObject.FromObject( new { result = debuggerProxy, internalProperties = new JArray(), privateProperties = new JArray() } )) : + debuggerProxy; } if (await IsDelegate(objectId, token)) { var description = await GetDelegateMethodDescription(objectId, token); - return new JArray( - JObject.FromObject(new + var objValues = new JArray(JObject.FromObject(new + { + value = new { - value = new - { - type = "symbol", - value = description, - description - }, - name = "Target" - })); + type = "symbol", + value = description, + description + }, + name = "Target" + })); + return sortByAccessLevel ? + new JArray(JObject.FromObject(new { result = objValues, internalProperties = new JArray(), privateProperties = new JArray() })) : + objValues; } - + var allFields = new List(); var objects = new Dictionary(); for (int i = 0; i < typeIdsIncludingParents.Count; i++) { int typeId = typeIdsIncludingParents[i]; // 0th id is for the object itself, and then its parents bool isOwn = i == 0; + var fields = await GetTypeFields(typeId, token); + allFields.AddRange(fields); if (!getCommandType.HasFlag(GetObjectCommandOptions.AccessorPropertiesOnly)) { - var fields = await GetTypeFields(typeId, token); var (collapsedFields, rootHiddenFields) = await FilterFieldsByDebuggerBrowsable(fields, typeId, token); var collapsedFieldsValues = await GetFieldsValues(collapsedFields, isOwn); @@ -2358,8 +2365,12 @@ public async Task GetObjectValues(int objectId, GetObjectCommandOptions objects.TryAddRange(collapsedFieldsValues); objects.TryAddRange(hiddenFieldsValues); } + if (!getCommandType.HasFlag(GetObjectCommandOptions.WithProperties)) - return new JArray(objects.Values); + return sortByAccessLevel ? + SegregatePropertiesByAccessLevel(allFields, objects, token) : + new JArray(objects.Values); + using var commandParamsObjWriter = new MonoBinaryWriter(); commandParamsObjWriter.WriteObj(new DotnetObjectId("object", objectId), this); var props = await CreateJArrayForProperties( @@ -2370,7 +2381,7 @@ public async Task GetObjectValues(int objectId, GetObjectCommandOptions $"dotnet:object:{objectId}", i == 0, token); - var properties = await GetProperties(props, typeId, token); + var properties = await GetProperties(props, allFields, typeId, token); objects.TryAddRange(properties); // ownProperties @@ -2378,33 +2389,52 @@ public async Task GetObjectValues(int objectId, GetObjectCommandOptions // but we are going to ignore that here, because otherwise vscode/chrome don't // seem to ask for inherited fields at all. //if (ownProperties) - //break; + //break; /*if (accessorPropertiesOnly) break;*/ } - if (getCommandType.HasFlag(GetObjectCommandOptions.AccessorPropertiesOnly)) - { - var ownId = typeIdsIncludingParents[0]; - var ownFields = await GetTypeFields(ownId, token); - - var parentFields = new List(); - for (int i = 1; i < typeIdsIncludingParents.Count; i++) - parentFields.AddRange(await GetTypeFields(typeIdsIncludingParents[i], token)); - - var ownDuplicatedFields = ownFields.Where(field => objects.Any(obj => - field.Name.Equals(obj.Key))); + return sortByAccessLevel ? + SegregatePropertiesByAccessLevel(allFields, objects, token) : + new JArray(objects.Values); - var parentDuplicatedFields = parentFields.Where(field => objects.Any(obj => - field.Name.Equals(obj.Key) && - (obj.Value["isOwn"] == null || - !obj.Value["isOwn"].Value()))); - - foreach (var duplicate in ownDuplicatedFields) - objects.Remove(duplicate.Name); - foreach (var duplicate in parentDuplicatedFields) - objects.Remove(duplicate.Name); + JArray SegregatePropertiesByAccessLevel(List fields, Dictionary objectsDict, CancellationToken token) + { + if (fields.Count == 0) + return new JArray(JObject.FromObject(new { result = new JArray(objectsDict.Values) })); + + var pubNames = fields.Where(field => field.ProtectionLevel == FieldAttributes.Public) + .Select(field => field.Name).ToList(); + var privNames = fields.Where(field => + (field.ProtectionLevel == FieldAttributes.Private || + field.ProtectionLevel == FieldAttributes.FamANDAssem) && + // when field is inherited it is listed both in Private and Public, to avoid duplicates: + pubNames.All(pubFieldName => pubFieldName != field.Name)) + .Select(field => field.Name).ToList(); + //protected == family, internal == assembly + var protectedAndInternalNames = fields.Where(field => + field.ProtectionLevel == FieldAttributes.Family || + field.ProtectionLevel == FieldAttributes.Assembly || + field.ProtectionLevel == FieldAttributes.FamORAssem) + .Select(field => field.Name).ToList(); + var accessorProperties = objectsDict + .Where(obj => ( + pubNames.All(name => name != obj.Key) && + privNames.All(name => name != obj.Key) && + protectedAndInternalNames.All(name => name != obj.Key))) + .Select((k, v) => k.Value); + + var pubProperties = objectsDict.GetValuesByKeys(pubNames); + var privProperties = objectsDict.GetValuesByKeys(privNames); + var protAndIntProperties = objectsDict.GetValuesByKeys(protectedAndInternalNames); + pubProperties.AddRange(new JArray(accessorProperties)); + + return new JArray(JObject.FromObject(new + { + result = pubProperties, + internalProperties = protAndIntProperties, + privateProperties = privProperties + })); } - return new JArray(objects.Values); async Task AppendRootHiddenChildren(JObject root, JArray expandedCollection) { @@ -2439,7 +2469,7 @@ async Task GetFieldsValues(List fields, bool isOwn, bool return objFields; if (getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute)) - fields = fields.Where(field => field.IsPublic).ToList(); + fields = fields.Where(field => field.IsNotPrivate).ToList(); using var commandParamsWriter = new MonoBinaryWriter(); commandParamsWriter.Write(objectId); @@ -2527,14 +2557,18 @@ async Task GetFieldsValues(List fields, bool isOwn, bool return (collapsedFields, rootHiddenFields); } - async Task GetProperties(JArray props, int typeId, CancellationToken token) + async Task GetProperties(JArray props, List fields, int typeId, CancellationToken token) { var typeInfo = await GetTypeInfo(typeId, token); var typeProperitesBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableProperties; var regularProps = new JArray(); foreach (var p in props) { - if (!typeProperitesBrowsableInfo.TryGetValue(p["name"].Value(), out DebuggerBrowsableState? state)) + var propName = p["name"].Value(); + // if property has a backing field - avoid adding a duplicate + if (fields.Any(field => field.Name == propName)) + continue; + if (!typeProperitesBrowsableInfo.TryGetValue(propName, out DebuggerBrowsableState? state)) { regularProps.Add(p); continue; @@ -2546,7 +2580,7 @@ async Task GetProperties(JArray props, int typeId, CancellationToken tok case DebuggerBrowsableState.RootHidden: DotnetObjectId rootObjId; DotnetObjectId.TryParse(p["get"]["objectId"].Value(), out rootObjId); - var rootObject = await InvokeMethodInObject(rootObjId.Value, rootObjId.SubValue, p["name"].Value(), token); + var rootObject = await InvokeMethodInObject(rootObjId.Value, rootObjId.SubValue, propName, token); await AppendRootHiddenChildren(rootObject, regularProps); break; case DebuggerBrowsableState.Collapsed: @@ -2663,5 +2697,17 @@ public static void TryAddRange(this Dictionary dict, JArray adde dict.TryAdd(key, item); } } + + public static JArray GetValuesByKeys(this Dictionary dict, List keys) + { + var result = new JArray(); + foreach (var name in keys) + { + if (!dict.TryGetValue(name, out var obj)) + continue; + result.Add(obj); + } + return result; + } } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs index f78f013e8a68e6..f5a9033a21662c 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs @@ -627,7 +627,7 @@ internal async Task CheckCustomType(JToken actual_val, JToken exp_val, string la if (expectedValue.Type != JTokenType.Null) { var valueAfterRunGet = await GetProperties(get["objectId"]?.Value()); - await CheckValue(valueAfterRunGet[0]?["value"], expectedValue, exp_val["type_name"]?.Value()); + await CheckValue(valueAfterRunGet["value"], expectedValue, exp_val["type_name"]?.Value()); } break; } @@ -837,6 +837,13 @@ internal async Task GetProperties(string id, JToken fn_args = null, bool return null; var locals = frame_props.Value["result"]; + var locals_internal = frame_props.Value["internalProperties"]; + var locals_private = frame_props.Value["privateProperties"]; + + if (locals_internal != null) + locals = new JArray(locals.Union(locals_internal)); + if (locals_private != null) + locals = new JArray(locals.Union(locals_private)); // FIXME: Should be done when generating the list in dotnet.cjs.lib.js, but not sure yet // whether to remove it, and how to do it correctly. if (locals is JArray) @@ -854,6 +861,65 @@ internal async Task GetProperties(string id, JToken fn_args = null, bool return locals; } + internal async Task<(JToken, JToken, JToken)> GetPropertiesSortedByProtectionLevels(string id, JToken fn_args = null, bool? own_properties = null, bool? accessors_only = null, bool expect_ok = true) + { + if (UseCallFunctionOnBeforeGetProperties && !id.StartsWith("dotnet:scope:")) + { + var fn_decl = "function () { return this; }"; + var cfo_args = JObject.FromObject(new + { + functionDeclaration = fn_decl, + objectId = id + }); + if (fn_args != null) + cfo_args["arguments"] = fn_args; + + var result = await cli.SendCommand("Runtime.callFunctionOn", cfo_args, token); + AssertEqual(expect_ok, result.IsOk, $"Runtime.getProperties returned {result.IsOk} instead of {expect_ok}, for {cfo_args.ToString()}, with Result: {result}"); + if (!result.IsOk) + return (null, null, null); + id = result.Value["result"]?["objectId"]?.Value(); + } + + var get_prop_req = JObject.FromObject(new + { + objectId = id + }); + if (own_properties.HasValue) + { + get_prop_req["ownProperties"] = own_properties.Value; + } + if (accessors_only.HasValue) + { + get_prop_req["accessorPropertiesOnly"] = accessors_only.Value; + } + + var frame_props = await cli.SendCommand("Runtime.getProperties", get_prop_req, token); + AssertEqual(expect_ok, frame_props.IsOk, $"Runtime.getProperties returned {frame_props.IsOk} instead of {expect_ok}, for {get_prop_req}, with Result: {frame_props}"); + if (!frame_props.IsOk) + return (null, null, null);; + + var locals = frame_props.Value["result"]; + var locals_internal = frame_props.Value["internalProperties"]; + var locals_private = frame_props.Value["privateProperties"]; + + // FIXME: Should be done when generating the list in dotnet.cjs.lib.js, but not sure yet + // whether to remove it, and how to do it correctly. + if (locals is JArray) + { + foreach (var p in locals) + { + if (p["name"]?.Value() == "length" && p["enumerable"]?.Value() != true) + { + p.Remove(); + break; + } + } + } + + return (locals, locals_internal, locals_private); + } + internal async Task<(JToken, Result)> EvaluateOnCallFrame(string id, string expression, bool expect_ok = true) { var evaluate_req = JObject.FromObject(new diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 14a786c040b4e3..0b199cfc6183e5 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -971,7 +971,6 @@ public async Task EvaluateBrowsableRootHidden(string outerClassName, string clas { item["name"] = string.Concat("listRootHidden[", item["name"], "]"); } - // Console.WriteLine(testRootHiddenProps); var mergedRefItems = new JArray(refListElementsProp.Union(refArrayProp)); Assert.Equal(mergedRefItems, testRootHiddenProps); }); @@ -996,6 +995,27 @@ public async Task EvaluateLocalObjectFromAssemblyNotRelatedButLoaded() await RuntimeEvaluateAndCheck( ("a.valueToCheck", TNumber(20))); }); + + [Fact] + public async Task EvaluateProtectionLevels() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.EvaluateProtectionLevels", "Evaluate", 2, "Evaluate", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateProtectionLevels:Evaluate'); })", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var (obj, _) = await EvaluateOnCallFrame(id, "testClass"); + var (pub, internalAndProtected, priv) = await GetPropertiesSortedByProtectionLevels(obj["objectId"]?.Value()); + + Assert.True(pub[0] != null); + Assert.True(internalAndProtected[0] != null); + Assert.True(internalAndProtected[1] != null); + Assert.True(priv[0] != null); + + Assert.Equal(pub[0]["value"]["value"], "public"); + Assert.Equal(internalAndProtected[0]["value"]["value"], "internal"); + Assert.Equal(internalAndProtected[1]["value"]["value"], "protected"); + Assert.Equal(priv[0]["value"]["value"], "private"); + }); } } diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs index b0ff3f95ba3de3..1eac05cf41c1c8 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs @@ -817,6 +817,27 @@ public static void Evaluate() var testPropertiesRootHidden = new TestEvaluatePropertiesRootHidden(); } } + + 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 static void Evaluate() + { + var testClass = new TestClass(); + } + } } namespace DebuggerTestsV2