diff --git a/docs/design/mono/debugger.md b/docs/design/mono/debugger.md index 3a3a2936b79eb4..2268d81bf1047b 100644 --- a/docs/design/mono/debugger.md +++ b/docs/design/mono/debugger.md @@ -18,4 +18,11 @@ Web Assembly Debugger supports usage of following attributes: - Stepping In/Over: results in an additional stepping need to proceed to the next line.

- __System.Diagnostics.DebuggerDisplay__ - __System.Diagnostics.DebuggerTypeProxy__ -- ... \ No newline at end of file +- __System.Diagnostics.DebuggerBrowsable__ ([doc](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.debuggerbrowsableattribute?view=net-6.0)) + - Collapsed - displayed normally. + - RootHidden: + - Simple type - not displayed in the debugger window. + - Collection / Array - the values of a collection are displayed in a flat view, using the naming convention: *rootName[idx]*. + + - Never - not displayed in the debugger window. + diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs index 6fb9b309d88e28..a50f486e052f6c 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs @@ -21,6 +21,8 @@ using System.IO.Compression; using System.Reflection; using System.Collections.Immutable; +using System.Diagnostics; +using Microsoft.VisualBasic; namespace Microsoft.WebAssembly.Diagnostics { @@ -475,14 +477,19 @@ public VarInfo[] GetLiveVarsAt(int offset) internal class TypeInfo { + private readonly ILogger logger; internal AssemblyInfo assembly; private TypeDefinition type; private List methods; internal int Token { get; } internal string Namespace { get; } - public TypeInfo(AssemblyInfo assembly, TypeDefinitionHandle typeHandle, TypeDefinition type) + public Dictionary DebuggerBrowsableFields = new(); + public Dictionary DebuggerBrowsableProperties = new(); + + public TypeInfo(AssemblyInfo assembly, TypeDefinitionHandle typeHandle, TypeDefinition type, ILogger logger) { + this.logger = logger; this.assembly = assembly; var metadataReader = assembly.asmMetadataReader; Token = MetadataTokens.GetToken(metadataReader, typeHandle); @@ -500,6 +507,63 @@ public TypeInfo(AssemblyInfo assembly, TypeDefinitionHandle typeHandle, TypeDefi FullName = Namespace + "." + Name; else FullName = Name; + + foreach (var field in type.GetFields()) + { + try + { + var fieldDefinition = metadataReader.GetFieldDefinition(field); + var fieldName = metadataReader.GetString(fieldDefinition.Name); + AppendToBrowsable(DebuggerBrowsableFields, fieldDefinition.GetCustomAttributes(), fieldName); + } + catch (Exception ex) + { + logger.LogDebug($"Failed to read browsable attributes of a field. ({ex.Message})"); + continue; + } + } + + foreach (var prop in type.GetProperties()) + { + try + { + var propDefinition = metadataReader.GetPropertyDefinition(prop); + var propName = metadataReader.GetString(propDefinition.Name); + AppendToBrowsable(DebuggerBrowsableProperties, propDefinition.GetCustomAttributes(), propName); + } + catch (Exception ex) + { + logger.LogDebug($"Failed to read browsable attributes of a property. ({ex.Message})"); + continue; + } + } + + void AppendToBrowsable(Dictionary dict, CustomAttributeHandleCollection customAttrs, string fieldName) + { + foreach (var cattr in customAttrs) + { + try + { + var ctorHandle = metadataReader.GetCustomAttribute(cattr).Constructor; + if (ctorHandle.Kind != HandleKind.MemberReference) + continue; + var container = metadataReader.GetMemberReference((MemberReferenceHandle)ctorHandle).Parent; + var valueBytes = metadataReader.GetBlobBytes(metadataReader.GetCustomAttribute(cattr).Value); + var attributeName = metadataReader.GetString(metadataReader.GetTypeReference((TypeReferenceHandle)container).Name); + if (attributeName != "DebuggerBrowsableAttribute") + continue; + var state = (DebuggerBrowsableState)valueBytes[2]; + if (!Enum.IsDefined(typeof(DebuggerBrowsableState), state)) + continue; + dict.Add(fieldName, state); + break; + } + catch + { + continue; + } + } + } } public TypeInfo(AssemblyInfo assembly, string name) @@ -515,7 +579,6 @@ public TypeInfo(AssemblyInfo assembly, string name) public override string ToString() => "TypeInfo('" + FullName + "')"; } - internal class AssemblyInfo { private static int next_id; @@ -652,7 +715,7 @@ SourceFile FindSource(DocumentHandle doc, int rowid, string documentName) { var typeDefinition = asmMetadataReader.GetTypeDefinition(type); - var typeInfo = new TypeInfo(this, type, typeDefinition); + var typeInfo = new TypeInfo(this, type, typeDefinition, logger); TypesByName[typeInfo.FullName] = typeInfo; TypesByToken[typeInfo.Token] = typeInfo; if (pdbMetadataReader != null) @@ -680,7 +743,6 @@ SourceFile FindSource(DocumentHandle doc, int rowid, string documentName) } } } - } private void ProcessSourceLink() diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index 35d1ef4fceba12..80eaed9daf6948 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -16,6 +16,7 @@ using System.Reflection; using System.Text; using System.Runtime.CompilerServices; +using System.Diagnostics; namespace Microsoft.WebAssembly.Diagnostics { @@ -2316,128 +2317,247 @@ public async Task GetValuesFromDebuggerProxyAttribute(int objectId, int public async Task GetObjectValues(int objectId, GetObjectCommandOptions getCommandType, CancellationToken token) { - var typeId = await GetTypeIdFromObject(objectId, true, token); + var typeIdsIncludingParents = await GetTypeIdFromObject(objectId, true, token); if (!getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerDisplayAttribute)) { - var debuggerProxy = await GetValuesFromDebuggerProxyAttribute(objectId, typeId[0], token); + var debuggerProxy = await GetValuesFromDebuggerProxyAttribute(objectId, typeIdsIncludingParents[0], token); if (debuggerProxy != null) return debuggerProxy; } - var className = await GetTypeName(typeId[0], token); - JArray ret = new JArray(); if (await IsDelegate(objectId, token)) { var description = await GetDelegateMethodDescription(objectId, token); - - var obj = JObject.FromObject(new { - value = new - { - type = "symbol", - value = description, - description - }, - name = "Target" - }); - ret.Add(obj); - return ret; + return new JArray( + JObject.FromObject(new + { + value = new + { + type = "symbol", + value = description, + description + }, + name = "Target" + })); } - for (int i = 0; i < typeId.Count; i++) + + 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; + if (!getCommandType.HasFlag(GetObjectCommandOptions.AccessorPropertiesOnly)) { - className = await GetTypeName(typeId[i], token); - var fields = await GetTypeFields(typeId[i], token); - if (getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute)) - fields = fields.Where(field => field.IsPublic).ToList(); - JArray objectFields = new JArray(); - - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(objectId); - commandParamsWriter.Write(fields.Count); - foreach (var field in fields) - { - commandParamsWriter.Write(field.Id); - } + var fields = await GetTypeFields(typeId, token); + var (collapsedFields, rootHiddenFields) = await FilterFieldsByDebuggerBrowsable(fields, typeId, token); - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdObject.RefGetValues, commandParamsWriter, token); + var collapsedFieldsValues = await GetFieldsValues(collapsedFields, isOwn); + var hiddenFieldsValues = await GetFieldsValues(rootHiddenFields, isOwn, isRootHidden: true); - foreach (var field in fields) - { - long initialPos = retDebuggerCmdReader.BaseStream.Position; - int valtype = retDebuggerCmdReader.ReadByte(); - retDebuggerCmdReader.BaseStream.Position = initialPos; - var fieldValue = await CreateJObjectForVariableValue(retDebuggerCmdReader, field.Name, i == 0, field.TypeId, getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerDisplayAttribute), token); - if (ret.Where(attribute => attribute["name"].Value().Equals(fieldValue["name"].Value())).Any()) { - continue; - } - if (getCommandType.HasFlag(GetObjectCommandOptions.WithSetter)) - { - var command_params_writer_to_set = new MonoBinaryWriter(); - command_params_writer_to_set.Write(objectId); - command_params_writer_to_set.Write(1); - command_params_writer_to_set.Write(field.Id); - var (data, length) = command_params_writer_to_set.ToBase64(); - - fieldValue.Add("set", JObject.FromObject(new { - commandSet = CommandSet.ObjectRef, - command = CmdObject.RefSetValues, - buffer = data, - valtype, - length = length - })); - } - objectFields.Add(fieldValue); - } - ret = new JArray(ret.Union(objectFields)); + objects.TryAddRange(collapsedFieldsValues); + objects.TryAddRange(hiddenFieldsValues); } if (!getCommandType.HasFlag(GetObjectCommandOptions.WithProperties)) - return ret; + return new JArray(objects.Values); using var commandParamsObjWriter = new MonoBinaryWriter(); commandParamsObjWriter.WriteObj(new DotnetObjectId("object", objectId), this); - var props = await CreateJArrayForProperties(typeId[i], commandParamsObjWriter.GetParameterBuffer(), ret, getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute), $"dotnet:object:{objectId}", i == 0, token); - ret = new JArray(ret.Union(props)); + var props = await CreateJArrayForProperties( + typeId, + commandParamsObjWriter.GetParameterBuffer(), + new JArray(objects.Values), + getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute), + $"dotnet:object:{objectId}", + i == 0, + token); + var properties = await GetProperties(props, typeId, token); + objects.TryAddRange(properties); // ownProperties // Note: ownProperties should mean that we return members of the klass itself, // 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 retAfterRemove = new JArray(); - List> allFields = new List>(); - for (int i = 0; i < typeId.Count; i++) + 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))); + + 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); + } + return new JArray(objects.Values); + + async Task AppendRootHiddenChildren(JObject root, JArray expandedCollection) + { + if (!DotnetObjectId.TryParse(root?["value"]?["objectId"]?.Value(), out DotnetObjectId rootHiddenObjectId)) + return; + + var resultValue = new JArray(); + // collections require extracting items to get inner values; items are of array type + // arrays have "subtype": "array" field, collections don't + var subtype = root?["value"]?["subtype"]; + var rootHiddenObjectIdInt = rootHiddenObjectId.Value; + if (subtype == null || subtype?.Value() != "array") { - var fields = await GetTypeFields(typeId[i], token); - allFields.Add(fields); + resultValue = await GetObjectValues(rootHiddenObjectIdInt, getCommandType, token); + DotnetObjectId.TryParse(resultValue[0]?["value"]?["objectId"]?.Value(), out DotnetObjectId objectId2); + rootHiddenObjectIdInt = objectId2.Value; + } + resultValue = await GetArrayValues(rootHiddenObjectIdInt, token); + + // root hidden item name has to be unique, so we concatenate the root's name to it + foreach (var item in resultValue) + { + item["name"] = string.Concat(root["name"], "[", item["name"], "]"); + expandedCollection.Add(item); + } + } + + async Task GetFieldsValues(List fields, bool isOwn, bool isRootHidden = false) + { + JArray objFields = new JArray(); + if (fields.Count == 0) + return objFields; + + if (getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute)) + fields = fields.Where(field => field.IsPublic).ToList(); + + using var commandParamsWriter = new MonoBinaryWriter(); + commandParamsWriter.Write(objectId); + commandParamsWriter.Write(fields.Count); + foreach (var field in fields) + commandParamsWriter.Write(field.Id); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdObject.RefGetValues, commandParamsWriter, token); + + foreach (var field in fields) + { + long initialPos = retDebuggerCmdReader.BaseStream.Position; + int valtype = retDebuggerCmdReader.ReadByte(); + retDebuggerCmdReader.BaseStream.Position = initialPos; + var fieldValue = await CreateJObjectForVariableValue(retDebuggerCmdReader, field.Name, isOwn: isOwn, field.TypeId, getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerDisplayAttribute), token); + if (objects.Where((k, v) => k.Equals(fieldValue["name"].Value())).Any()) + continue; + if (getCommandType.HasFlag(GetObjectCommandOptions.WithSetter)) + { + var command_params_writer_to_set = new MonoBinaryWriter(); + command_params_writer_to_set.Write(objectId); + command_params_writer_to_set.Write(1); + command_params_writer_to_set.Write(field.Id); + var (data, length) = command_params_writer_to_set.ToBase64(); + + fieldValue.Add("set", JObject.FromObject(new + { + commandSet = CommandSet.ObjectRef, + command = CmdObject.RefSetValues, + buffer = data, + valtype, + length = length + })); + } + if (!isRootHidden) + { + objFields.Add(fieldValue); + continue; + } + await AppendRootHiddenChildren(fieldValue, objFields); } - foreach (var item in ret) + return objFields; + } + + async Task<(List, List)> FilterFieldsByDebuggerBrowsable(List fields, int typeId, CancellationToken token) + { + if (fields.Count == 0) + return (fields, new List()); + + var typeInfo = await GetTypeInfo(typeId, token); + var typeFieldsBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableFields; + var typeProperitesBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableProperties; + if (typeFieldsBrowsableInfo == null || typeFieldsBrowsableInfo.Count == 0) + return (fields, new List()); + + var collapsedFields = new List(); + var rootHiddenFields = new List(); + foreach (var field in fields) { - bool foundField = false; - for (int j = 0 ; j < allFields.Count; j++) + if (!typeFieldsBrowsableInfo.TryGetValue(field.Name, out DebuggerBrowsableState? state)) { - foreach (var field in allFields[j]) + if (!typeProperitesBrowsableInfo.TryGetValue(field.Name, out DebuggerBrowsableState? propState)) { - if (field.Name.Equals(item["name"].Value())) { - if (item["isOwn"] == null || (item["isOwn"].Value() && j == 0) || !item["isOwn"].Value()) - foundField = true; - break; - } + collapsedFields.Add(field); + continue; } - if (foundField) + state = propState; + } + switch (state) + { + case DebuggerBrowsableState.Never: + break; + case DebuggerBrowsableState.RootHidden: + var typeName = await GetTypeName(field.TypeId, token); + if (typeName.StartsWith("System.Collections.Generic", StringComparison.Ordinal) || + typeName.EndsWith("[]", StringComparison.Ordinal)) + rootHiddenFields.Add(field); + break; + case DebuggerBrowsableState.Collapsed: + collapsedFields.Add(field); break; + default: + throw new NotImplementedException($"DebuggerBrowsableState: {state}"); } - if (!foundField) { - retAfterRemove.Add(item); + } + return (collapsedFields, rootHiddenFields); + } + + async Task GetProperties(JArray props, 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)) + { + regularProps.Add(p); + continue; + } + switch (state) + { + case DebuggerBrowsableState.Never: + break; + 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); + await AppendRootHiddenChildren(rootObject, regularProps); + break; + case DebuggerBrowsableState.Collapsed: + regularProps.Add(p); + break; + default: + throw new NotImplementedException($"DebuggerBrowsableState: {state}"); } } - ret = retAfterRemove; + return regularProps; } - return ret; } public async Task GetObjectProxy(int objectId, CancellationToken token) @@ -2524,4 +2644,24 @@ public async Task SetVariableValue(int thread_id, int frame_id, int varId, return true; } } + + internal static class HelperExtensions + { + public static void AddRange(this JArray arr, JArray addedArr) + { + foreach (var item in addedArr) + arr.Add(item); + } + + public static void TryAddRange(this Dictionary dict, JArray addedArr) + { + foreach (var item in addedArr) + { + var key = item["name"]?.Value(); + if (key == null) + continue; + dict.TryAdd(key, item); + } + } + } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs index f22b4ae3196bef..f78f013e8a68e6 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs @@ -670,10 +670,8 @@ internal async Task CheckProps(JToken actual, object exp_o, string label, int nu if (exp_i != null) await CheckValue(act_i["value"], exp_i, $"{label}-{i}th value"); } - return; } - // Not an array var exp = exp_o as JObject; if (exp == null) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 80bef3eb092578..14a786c040b4e3 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -854,6 +854,127 @@ await RuntimeEvaluateAndCheck( ("\"15\"\n//comment as vs does\n", TString("15")), ("\"15\"", TString("15"))); }); + + [Theory] + [InlineData("EvaluateBrowsableProperties", "TestEvaluateFieldsNone", "testFieldsNone", 10)] + [InlineData("EvaluateBrowsableProperties", "TestEvaluatePropertiesNone", "testPropertiesNone", 10)] + [InlineData("EvaluateBrowsableCustomProperties", "TestEvaluatePropertiesNone", "testPropertiesNone", 5, true)] + public async Task EvaluateBrowsableNone(string outerClassName, string className, string localVarName, int breakLine, bool isCustomGetter = false) => await CheckInspectLocalsAtBreakpointSite( + $"DebuggerTests.{outerClassName}", "Evaluate", breakLine, "Evaluate", + $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.{outerClassName}:Evaluate'); 1 }})", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + + var (testNone, _) = await EvaluateOnCallFrame(id, localVarName); + await CheckValue(testNone, TObject($"DebuggerTests.{outerClassName}.{className}"), nameof(testNone)); + var testNoneProps = await GetProperties(testNone["objectId"]?.Value()); + + if (isCustomGetter) + await CheckProps(testNoneProps, new + { + list = TGetter("list", TObject("System.Collections.Generic.List", description: "Count = 2")), + array = TGetter("array", TObject("int[]", description: "int[2]")), + text = TGetter("text", TString("text")) + }, "testNoneProps#1"); + else + await CheckProps(testNoneProps, new + { + list = TObject("System.Collections.Generic.List", description: "Count = 2"), + array = TObject("int[]", description: "int[2]"), + text = TString("text") + }, "testNoneProps#1"); + }); + + [Theory] + [InlineData("EvaluateBrowsableProperties", "TestEvaluateFieldsNever", "testFieldsNever", 10)] + [InlineData("EvaluateBrowsableProperties", "TestEvaluatePropertiesNever", "testPropertiesNever", 10)] + [InlineData("EvaluateBrowsableStaticProperties", "TestEvaluateFieldsNever", "testFieldsNever", 10)] + [InlineData("EvaluateBrowsableStaticProperties", "TestEvaluatePropertiesNever", "testPropertiesNever", 10)] + [InlineData("EvaluateBrowsableCustomProperties", "TestEvaluatePropertiesNever", "testPropertiesNever", 5)] + public async Task EvaluateBrowsableNever(string outerClassName, string className, string localVarName, int breakLine) => await CheckInspectLocalsAtBreakpointSite( + $"DebuggerTests.{outerClassName}", "Evaluate", breakLine, "Evaluate", + $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.{outerClassName}:Evaluate'); 1 }})", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + + var (testNever, _) = await EvaluateOnCallFrame(id, localVarName); + await CheckValue(testNever, TObject($"DebuggerTests.{outerClassName}.{className}"), nameof(testNever)); + var testNeverProps = await GetProperties(testNever["objectId"]?.Value()); + await CheckProps(testNeverProps, new + { + }, "testNeverProps#1"); + }); + + [Theory] + [InlineData("EvaluateBrowsableProperties", "TestEvaluateFieldsCollapsed", "testFieldsCollapsed", 10)] + [InlineData("EvaluateBrowsableProperties", "TestEvaluatePropertiesCollapsed", "testPropertiesCollapsed", 10)] + [InlineData("EvaluateBrowsableStaticProperties", "TestEvaluateFieldsCollapsed", "testFieldsCollapsed", 10)] + [InlineData("EvaluateBrowsableStaticProperties", "TestEvaluatePropertiesCollapsed", "testPropertiesCollapsed", 10)] + [InlineData("EvaluateBrowsableCustomProperties", "TestEvaluatePropertiesCollapsed", "testPropertiesCollapsed", 5, true)] + public async Task EvaluateBrowsableCollapsed(string outerClassName, string className, string localVarName, int breakLine, bool isCustomGetter = false) => await CheckInspectLocalsAtBreakpointSite( + $"DebuggerTests.{outerClassName}", "Evaluate", breakLine, "Evaluate", + $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.{outerClassName}:Evaluate'); 1 }})", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + + var (testCollapsed, _) = await EvaluateOnCallFrame(id, localVarName); + await CheckValue(testCollapsed, TObject($"DebuggerTests.{outerClassName}.{className}"), nameof(testCollapsed)); + var testCollapsedProps = await GetProperties(testCollapsed["objectId"]?.Value()); + if (isCustomGetter) + await CheckProps(testCollapsedProps, new + { + listCollapsed = TGetter("listCollapsed", TObject("System.Collections.Generic.List", description: "Count = 2")), + arrayCollapsed = TGetter("arrayCollapsed", TObject("int[]", description: "int[2]")), + textCollapsed = TGetter("textCollapsed", TString("textCollapsed")) + }, "testCollapsedProps#1"); + else + await CheckProps(testCollapsedProps, new + { + listCollapsed = TObject("System.Collections.Generic.List", description: "Count = 2"), + arrayCollapsed = TObject("int[]", description: "int[2]"), + textCollapsed = TString("textCollapsed") + }, "testCollapsedProps#1"); + }); + + [Theory] + [InlineData("EvaluateBrowsableProperties", "TestEvaluateFieldsRootHidden", "testFieldsRootHidden", 10)] + [InlineData("EvaluateBrowsableProperties", "TestEvaluatePropertiesRootHidden", "testPropertiesRootHidden", 10)] + [InlineData("EvaluateBrowsableStaticProperties", "TestEvaluateFieldsRootHidden", "testFieldsRootHidden", 10)] + [InlineData("EvaluateBrowsableStaticProperties", "TestEvaluatePropertiesRootHidden", "testPropertiesRootHidden", 10)] + [InlineData("EvaluateBrowsableCustomProperties", "TestEvaluatePropertiesRootHidden", "testPropertiesRootHidden", 5, true)] + public async Task EvaluateBrowsableRootHidden(string outerClassName, string className, string localVarName, int breakLine, bool isCustomGetter = false) => await CheckInspectLocalsAtBreakpointSite( + $"DebuggerTests.{outerClassName}", "Evaluate", breakLine, "Evaluate", + $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.{outerClassName}:Evaluate'); 1 }})", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + + var (testRootHidden, _) = await EvaluateOnCallFrame(id, localVarName); + await CheckValue(testRootHidden, TObject($"DebuggerTests.{outerClassName}.{className}"), nameof(testRootHidden)); + var testRootHiddenProps = await GetProperties(testRootHidden["objectId"]?.Value()); + var (refList, _) = await EvaluateOnCallFrame(id, "testPropertiesNone.list"); + var refListProp = await GetProperties(refList["objectId"]?.Value()); + var refListElementsProp = await GetProperties(refListProp[0]["value"]["objectId"]?.Value()); + var (refArray, _) = await EvaluateOnCallFrame(id, "testPropertiesNone.array"); + var refArrayProp = await GetProperties(refArray["objectId"]?.Value()); + + //in Console App names are in [] + //adding variable name to make elements unique + foreach (var item in refArrayProp) + { + item["name"] = string.Concat("arrayRootHidden[", item["name"], "]"); + } + foreach (var item in refListElementsProp) + { + item["name"] = string.Concat("listRootHidden[", item["name"], "]"); + } + // Console.WriteLine(testRootHiddenProps); + var mergedRefItems = new JArray(refListElementsProp.Union(refArrayProp)); + Assert.Equal(mergedRefItems, testRootHiddenProps); + }); [Fact] public async Task EvaluateStaticAttributeInAssemblyNotRelatedButLoaded() => await CheckInspectLocalsAtBreakpointSite( diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs index 398d640c0a01c8..22b8b165159885 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs @@ -654,7 +654,6 @@ public async Task MulticastDelegateTest() => await CheckInspectLocalsAtBreakpoin var this_props = await GetObjectOnLocals(frame_locals, "this"); await CheckProps(this_props, new { - TestEvent = TSymbol("System.EventHandler"), Delegate = TSymbol("System.MulticastDelegate") }, "this_props"); }); 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 f27888c17200cc..b0ff3f95ba3de3 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 @@ -504,6 +504,319 @@ public static void EvaluateLocals() } } + public static class EvaluateBrowsableProperties + { + public class TestEvaluateFieldsNone + { + public List list = new List() { 1, 2 }; + public int[] array = new int[] { 11, 22 }; + public string text = "text"; + } + + public class TestEvaluatePropertiesNone + { + public List list { get; set; } + public int[] array { get; set; } + public string text { get; set; } + + public TestEvaluatePropertiesNone() + { + list = new List() { 1, 2 }; + array = new int[] { 11, 22 }; + text = "text"; + } + } + + public class TestEvaluateFieldsNever + { + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public List listNever = new List() { 1, 2 }; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public int[] arrayNever = new int[] { 11, 22 }; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public string textNever = "textNever"; + } + + public class TestEvaluatePropertiesNever + { + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public List listNever { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public int[] arrayNever { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public string textNever { get; set; } + + public TestEvaluatePropertiesNever() + { + listNever = new List() { 1, 2 }; + arrayNever = new int[] { 11, 22 }; + textNever = "textNever"; + } + } + + public class TestEvaluateFieldsCollapsed + { + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public List listCollapsed = new List() { 1, 2 }; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public int[] arrayCollapsed = new int[] { 11, 22 }; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public string textCollapsed = "textCollapsed"; + } + + public class TestEvaluatePropertiesCollapsed + { + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public List listCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public int[] arrayCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public string textCollapsed { get; set; } + + public TestEvaluatePropertiesCollapsed() + { + listCollapsed = new List() { 1, 2 }; + arrayCollapsed = new int[] { 11, 22 }; + textCollapsed = "textCollapsed"; + } + } + + public class TestEvaluateFieldsRootHidden + { + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public List listRootHidden = new List() { 1, 2 }; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public int[] arrayRootHidden = new int[] { 11, 22 }; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public string textRootHidden = "textRootHidden"; + } + + public class TestEvaluatePropertiesRootHidden + { + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public List listRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public int[] arrayRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public string textRootHidden { get; set; } + + public TestEvaluatePropertiesRootHidden() + { + listRootHidden = new List() { 1, 2 }; + arrayRootHidden = new int[] { 11, 22 }; + textRootHidden = "textRootHidden"; + } + } + + public static void Evaluate() + { + var testFieldsNone = new TestEvaluateFieldsNone(); + var testFieldsNever = new TestEvaluateFieldsNever(); + var testFieldsCollapsed = new TestEvaluateFieldsCollapsed(); + var testFieldsRootHidden = new TestEvaluateFieldsRootHidden(); + + var testPropertiesNone = new TestEvaluatePropertiesNone(); + var testPropertiesNever = new TestEvaluatePropertiesNever(); + var testPropertiesCollapsed = new TestEvaluatePropertiesCollapsed(); + var testPropertiesRootHidden = new TestEvaluatePropertiesRootHidden(); + } + } + + public static class EvaluateBrowsableStaticProperties + { + public class TestEvaluateFieldsNone + { + public static List list = new List() { 1, 2 }; + public static int[] array = new int[] { 11, 22 }; + public static string text = "text"; + } + + public class TestEvaluatePropertiesNone + { + public static List list { get; set; } + public static int[] array { get; set; } + public static string text { get; set; } + + public TestEvaluatePropertiesNone() + { + list = new List() { 1, 2 }; + array = new int[] { 11, 22 }; + text = "text"; + } + } + + public class TestEvaluateFieldsNever + { + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public static List listNever = new List() { 1, 2 }; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public static int[] arrayNever = new int[] { 11, 22 }; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public static string textNever = "textNever"; + } + + public class TestEvaluatePropertiesNever + { + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public static List listNever { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public static int[] arrayNever { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public static string textNever { get; set; } + + public TestEvaluatePropertiesNever() + { + listNever = new List() { 1, 2 }; + arrayNever = new int[] { 11, 22 }; + textNever = "textNever"; + } + } + + public class TestEvaluateFieldsCollapsed + { + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public static List listCollapsed = new List() { 1, 2 }; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public static int[] arrayCollapsed = new int[] { 11, 22 }; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public static string textCollapsed = "textCollapsed"; + } + + public class TestEvaluatePropertiesCollapsed + { + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public static List listCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public static int[] arrayCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public static string textCollapsed { get; set; } + + public TestEvaluatePropertiesCollapsed() + { + listCollapsed = new List() { 1, 2 }; + arrayCollapsed = new int[] { 11, 22 }; + textCollapsed = "textCollapsed"; + } + } + + public class TestEvaluateFieldsRootHidden + { + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public static List listRootHidden = new List() { 1, 2 }; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public static int[] arrayRootHidden = new int[] { 11, 22 }; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public static string textRootHidden = "textRootHidden"; + } + + public class TestEvaluatePropertiesRootHidden + { + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public static List listRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public static int[] arrayRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public static string textRootHidden { get; set; } + + public TestEvaluatePropertiesRootHidden() + { + listRootHidden = new List() { 1, 2 }; + arrayRootHidden = new int[] { 11, 22 }; + textRootHidden = "textRootHidden"; + } + } + + public static void Evaluate() + { + var testFieldsNone = new TestEvaluateFieldsNone(); + var testFieldsNever = new TestEvaluateFieldsNever(); + var testFieldsCollapsed = new TestEvaluateFieldsCollapsed(); + var testFieldsRootHidden = new TestEvaluateFieldsRootHidden(); + + var testPropertiesNone = new TestEvaluatePropertiesNone(); + var testPropertiesNever = new TestEvaluatePropertiesNever(); + var testPropertiesCollapsed = new TestEvaluatePropertiesCollapsed(); + var testPropertiesRootHidden = new TestEvaluatePropertiesRootHidden(); + } + } + + public static class EvaluateBrowsableCustomProperties + { + public class TestEvaluatePropertiesNone + { + public List list { get { return new List() { 1, 2 }; } } + public int[] array { get { return new int[] { 11, 22 }; } } + public string text { get { return "text"; } } + } + + public class TestEvaluatePropertiesNever + { + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public List listNever { get { return new List() { 1, 2 }; } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public int[] arrayNever { get { return new int[] { 11, 22 }; } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public string textNever { get { return "textNever"; } } + } + + public class TestEvaluatePropertiesCollapsed + { + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public List listCollapsed { get { return new List() { 1, 2 }; } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public int[] arrayCollapsed { get { return new int[] { 11, 22 }; } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public string textCollapsed { get { return "textCollapsed"; } } + } + + public class TestEvaluatePropertiesRootHidden + { + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public List listRootHidden { get { return new List() { 1, 2 }; } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public int[] arrayRootHidden { get { return new int[] { 11, 22 }; } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public string textRootHidden { get { return "textRootHidden"; } } + } + + public static void Evaluate() + { + var testPropertiesNone = new TestEvaluatePropertiesNone(); + var testPropertiesNever = new TestEvaluatePropertiesNever(); + var testPropertiesCollapsed = new TestEvaluatePropertiesCollapsed(); + var testPropertiesRootHidden = new TestEvaluatePropertiesRootHidden(); + } + } } namespace DebuggerTestsV2