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