Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
e489e94
Description of DebuggerBrowsable behavior.
ilonatommy Nov 25, 2021
2423830
Added test for browse attributes.
ilonatommy Nov 25, 2021
f5ede4a
Corrected typos in the doc.
ilonatommy Nov 25, 2021
326ac82
Added Browse Never feature. Corrected Collapse test. ToDo: RootHidden.
ilonatommy Nov 29, 2021
abb60a5
Draft of RootHidden solution.
ilonatommy Dec 1, 2021
609161b
Added Array to test cases as it behaves differently than Collection.
ilonatommy Dec 1, 2021
5c82b46
Added name concatenation to make array/list elemetns in debug window …
ilonatommy Dec 1, 2021
890acbb
Merge branch 'main' into debugger-browse-attribute
ilonatommy Dec 1, 2021
973bd3a
Update docs/design/mono/debugger.md
ilonatommy Dec 2, 2021
0d37c77
Applied PR review suggestions.
ilonatommy Dec 2, 2021
8353e33
Added a reference to regular Browsable attribute behavior in .net.
ilonatommy Dec 2, 2021
8fbdff9
Applied most of review suggestions.
ilonatommy Dec 2, 2021
1a06a96
Stopping GetFieldsValue early.
ilonatommy Dec 2, 2021
3c9a02e
Remove unintentional change to the original code.
ilonatommy Dec 2, 2021
01d7e84
Merge branch 'main' into debugger-browse-attribute
ilonatommy Dec 2, 2021
8a322ab
Merge branch 'main' into debugger-browse-attribute
ilonatommy Dec 3, 2021
9ee0fe9
Do not skip fields that don't have browsable attributes.
ilonatommy Dec 3, 2021
1ec30f7
Changing the expected behavior to match Console Application. EventHan…
ilonatommy Dec 3, 2021
98c82bb
Changed the place of checking if objetc is an array.
ilonatommy Dec 3, 2021
00e58cb
Update src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTe…
ilonatommy Dec 6, 2021
8f954f4
Removed unused variables.
ilonatommy Dec 6, 2021
ef135fb
Merge branch 'debugger-browse-attribute' of https://github.com/ilonat…
ilonatommy Dec 6, 2021
314dac7
Removing space and unused import.
ilonatommy Dec 6, 2021
e068d0e
Merge with main.
ilonatommy Dec 9, 2021
4f4f703
Partially addressed @radical comments.
ilonatommy Dec 10, 2021
ab93329
Addressed the comment about extension instead of Union.
ilonatommy Dec 10, 2021
88682ab
Removed string cultural vunerability.
ilonatommy Dec 10, 2021
8629a99
Added Properties dictionary, the same as for fields.
ilonatommy Dec 10, 2021
53b525a
Fixed the bug I made by using dynamc.
ilonatommy Dec 10, 2021
458a78b
Applying @radical comments about refactoring.
ilonatommy Dec 10, 2021
feafa66
Corrected typo.
ilonatommy Dec 14, 2021
0ad80a4
Added tests for properties.
ilonatommy Dec 14, 2021
cd7f9da
Draft of changes for properties handling - never and root hidden fail…
ilonatommy Dec 14, 2021
b6902bd
Fix for RootHidden properties.
ilonatommy Dec 14, 2021
8945dbe
Added tests for static fields decorated with Browsable.
ilonatommy Dec 14, 2021
2c59af7
Correct a typo.
ilonatommy Dec 14, 2021
f6ad020
Merge with main.
ilonatommy Dec 17, 2021
bdf671b
Undo merge unintentional changes.
ilonatommy Dec 17, 2021
85928ed
Changing expected behavior for MulticastDelegateTest - in Console App…
ilonatommy Dec 17, 2021
b1acf8b
Removing not relevant changes created after merge with main.
ilonatommy Dec 17, 2021
153f41a
Remove file added in merge with main.
ilonatommy Dec 17, 2021
1276724
Revert "Removing not relevant changes created after merge with main."
ilonatommy Dec 17, 2021
f943552
Revert.
ilonatommy Dec 17, 2021
7c7c620
Revert revert.
ilonatommy Dec 17, 2021
e148b2d
One broken test for custom getter.
ilonatommy Dec 20, 2021
28ef9b0
Ugly fix to make all the tests work.
ilonatommy Dec 20, 2021
8cf81dc
Refactored JArray aggregation to Dictionary.
ilonatommy Dec 21, 2021
ae8ebb4
Better naming.
ilonatommy Dec 21, 2021
f7ee76b
Merge with main.
ilonatommy Dec 21, 2021
098e3f1
Remove not connected to PR file.
ilonatommy Jan 3, 2022
bb4cb8d
Applied @thaystg suggestions.
ilonatommy Jan 3, 2022
44145e4
Removed comments.
ilonatommy Jan 3, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion docs/design/mono/debugger.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.<br><br>
- __System.Diagnostics.DebuggerDisplay__
- __System.Diagnostics.DebuggerTypeProxy__
- ...
- __System.Diagnostics.DebuggerBrowsable__
- Collapsed - displayed normally.
- RootHidden:
- Simple type - not displayed in the debugger window.
- Collection / Array - the values of a collection are appearing in a flat view, using naming convention: *rootName[idx]*.

- Never - not displayed in the debugger window.

36 changes: 34 additions & 2 deletions src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using Microsoft.CodeAnalysis.Debugging;
using System.IO.Compression;
using System.Reflection;
using System.Diagnostics;

namespace Microsoft.WebAssembly.Diagnostics
{
Expand Down Expand Up @@ -331,6 +332,8 @@ internal class MethodInfo
public bool IsStatic() => (methodDef.Attributes & MethodAttributes.Static) != 0;
public int IsAsync { get; set; }
public bool IsHiddenFromDebugger { get; }
public bool IsBrowsable { get; }
public Enum BrowsableState { get; }
public TypeInfo TypeInfo { get; }

public MethodInfo(AssemblyInfo assembly, MethodDefinitionHandle methodDefHandle, int token, SourceFile source, TypeInfo type, MetadataReader asmMetadataReader, MetadataReader pdbMetadataReader)
Expand Down Expand Up @@ -480,6 +483,8 @@ internal class TypeInfo
internal int Token { get; }
internal string Namespace { get; }

public Dictionary<string, DebuggerBrowsableState?> DebuggerBrowsableFields = new Dictionary<string, DebuggerBrowsableState?>();

public TypeInfo(AssemblyInfo assembly, TypeDefinitionHandle typeHandle, TypeDefinition type)
{
this.assembly = assembly;
Expand All @@ -499,6 +504,35 @@ public TypeInfo(AssemblyInfo assembly, TypeDefinitionHandle typeHandle, TypeDefi
FullName = Namespace + "." + Name;
else
FullName = Name;

foreach (FieldDefinitionHandle field in type.GetFields())
{
var fieldDefinition = metadataReader.GetFieldDefinition(field);
var fieldName = metadataReader.GetString(fieldDefinition.Name);
var hasBrowsableAttribute = false;
foreach (var cattr in fieldDefinition.GetCustomAttributes())
{
if (hasBrowsableAttribute)
break;

var ctorHandle = metadataReader.GetCustomAttribute(cattr).Constructor;
if (ctorHandle.Kind == HandleKind.MemberReference)
{
var container = metadataReader.GetMemberReference((MemberReferenceHandle)ctorHandle).Parent;
var value = metadataReader.GetBlobBytes(metadataReader.GetCustomAttribute(cattr).Value);
var attributeName = metadataReader.GetString(metadataReader.GetTypeReference((TypeReferenceHandle)container).Name);
if (attributeName == "DebuggerBrowsableAttribute")
{
var state = (DebuggerBrowsableState)value[2];
if (Enum.IsDefined(typeof(DebuggerBrowsableState), state))
{
DebuggerBrowsableFields.Add(fieldName, state);
hasBrowsableAttribute = true;
}
}
}
}
}
}

public TypeInfo(AssemblyInfo assembly, string name)
Expand All @@ -514,7 +548,6 @@ public TypeInfo(AssemblyInfo assembly, string name)
public override string ToString() => "TypeInfo('" + FullName + "')";
}


internal class AssemblyInfo
{
private static int next_id;
Expand Down Expand Up @@ -648,7 +681,6 @@ SourceFile FindSource(DocumentHandle doc, int rowid, string documentName)
}
}
}

}

private void ProcessSourceLink()
Expand Down
168 changes: 125 additions & 43 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using System.Reflection;
using System.Text;
using System.Runtime.CompilerServices;
using System.Diagnostics;

namespace Microsoft.WebAssembly.Diagnostics
{
Expand Down Expand Up @@ -2339,48 +2340,15 @@ public async Task<JArray> GetObjectValues(int objectId, GetObjectCommandOptions
{
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();
var (regularFields, rootHiddenCollections, rootHiddenArrays) = await FilterFieldsByDebuggerBrowsable(fields, typeId[i], token);

using var commandParamsWriter = new MonoBinaryWriter();
commandParamsWriter.Write(objectId);
commandParamsWriter.Write(fields.Count);
foreach (var field in fields)
{
commandParamsWriter.Write(field.Id);
}

using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdObject.RefGetValues, commandParamsWriter, token);
var regularObjects = await GetFieldsValue(regularFields, i == 0);
var rootHiddenCollectionObjects = await GetFieldsValue(rootHiddenCollections, i == 0, true, false);
var rootHiddenArrayObjects = await GetFieldsValue(rootHiddenArrays, i == 0, true, 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<string>().Equals(fieldValue["name"].Value<string>())).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));
ret = new JArray(ret.Union(regularObjects));
ret = new JArray(ret.Union(rootHiddenCollectionObjects));
ret = new JArray(ret.Union(rootHiddenArrayObjects));
}
if (!getCommandType.HasFlag(GetObjectCommandOptions.WithProperties))
return ret;
Expand All @@ -2405,16 +2373,18 @@ public async Task<JArray> GetObjectValues(int objectId, GetObjectCommandOptions
for (int i = 0; i < typeId.Count; i++)
{
var fields = await GetTypeFields(typeId[i], token);
allFields.Add(fields);
var (regularFields, _, _) = await FilterFieldsByDebuggerBrowsable(fields, typeId[i], token);
allFields.Add(regularFields);
}
foreach (var item in ret)
{
bool foundField = false;
for (int j = 0 ; j < allFields.Count; j++)
for (int j = 0; j < allFields.Count; j++)
{
foreach (var field in allFields[j])
{
if (field.Name.Equals(item["name"].Value<string>())) {
if (field.Name.Equals(item["name"].Value<string>()))
{
if (item["isOwn"] == null || (item["isOwn"].Value<bool>() && j == 0) || !item["isOwn"].Value<bool>())
foundField = true;
break;
Expand All @@ -2430,6 +2400,118 @@ public async Task<JArray> GetObjectValues(int objectId, GetObjectCommandOptions
ret = retAfterRemove;
}
return ret;

async Task<JArray> GetFieldsValue(List<FieldTypeClass> fields, bool isFirstElement, bool isRootHidden = false, bool isArray = false)
{
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);

JArray objFields = new JArray();
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, isFirstElement, field.TypeId, getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerDisplayAttribute), token);
if (ret.Where(attribute => attribute["name"].Value<string>().Equals(fieldValue["name"].Value<string>())).Any())
{
return null;
}
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)
{
DotnetObjectId.TryParse(fieldValue?["value"]?["objectId"]?.Value<string>(), out DotnetObjectId objectId);
if (int.TryParse(objectId.Value, out int objectIdToGetInfo))
{
var resultValue = new JArray();
if (!isArray)
{
resultValue = await GetObjectValues(objectIdToGetInfo, getCommandType, token);
DotnetObjectId.TryParse(resultValue[0]?["value"]?["objectId"]?.Value<string>(), out DotnetObjectId objectId2);
int.TryParse(objectId2.Value, out objectIdToGetInfo);
}
resultValue = await GetArrayValues(objectIdToGetInfo, token);
foreach (var item in resultValue)
{
item["name"] = string.Concat(fieldValue["name"], "[", item["name"], "]");
objFields.Add(item);
}
}
}
else
{
objFields.Add(fieldValue);
}
}
return objFields;
}

async Task<(List<FieldTypeClass>, List<FieldTypeClass>, List<FieldTypeClass>)> FilterFieldsByDebuggerBrowsable(List<FieldTypeClass> fields, int typeId, CancellationToken token)
{
var typeInfo = await GetTypeInfo(typeId, token);
var typeFieldsBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableFields;
if (typeFieldsBrowsableInfo == null)
return (fields, new List<FieldTypeClass>(), new List<FieldTypeClass>());

var regularFields = new List<FieldTypeClass>();
var rootHiddenCollections = new List<FieldTypeClass>();
var rootHiddenArrays = new List<FieldTypeClass>();
foreach (var field in fields)
{
typeFieldsBrowsableInfo.TryGetValue(field.Name, out DebuggerBrowsableState? state);
switch (state)
{
case DebuggerBrowsableState.Never:
break;
case DebuggerBrowsableState.RootHidden:
var typeName = await GetTypeName(field.TypeId, token);
if (typeName.StartsWith("System.Collections.Generic"))
{
rootHiddenCollections.Add(field);
break;
}
if (typeName.EndsWith("[]"))
{
rootHiddenArrays.Add(field);
break;
}
break;
//null and DebuggerBrowsableState.Collapsed have the same result
default:
regularFields.Add(field);
break;
}
}
return (regularFields, rootHiddenCollections, rootHiddenArrays);
}
}

public async Task<JArray> GetObjectProxy(int objectId, CancellationToken token)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,58 @@ await RuntimeEvaluateAndCheck(
("\"15\"", TString("15")));
});

[Fact]
public async Task EvaluateBrowsableProperties() => await CheckInspectLocalsAtBreakpointSite(
"DebuggerTests.EvaluateBrowsableProperties", "Evaluate", 4, "Evaluate",
"window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateBrowsableProperties:Evaluate'); 1 })",
wait_for_event_fn: async (pause_location) =>
{
var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();

var (testNever, _) = await EvaluateOnCallFrame(id, "testNever");
await CheckValue(testNever, TObject("DebuggerTests.EvaluateBrowsableProperties.TestEvaluateNever"), nameof(testNever));
var testNeverProps = await GetProperties(testNever["objectId"]?.Value<string>());
await CheckProps(testNeverProps, new
{
list = TObject("System.Collections.Generic.List<int>", description: "Count = 2"),
array = TObject("int[]", description: "int[2]"),
text = TString("text")
}, "testNeverProps#1");

var (testCollapsed, _) = await EvaluateOnCallFrame(id, "testCollapsed");
await CheckValue(testCollapsed, TObject("DebuggerTests.EvaluateBrowsableProperties.TestEvaluateCollapsed"), nameof(testCollapsed));
var testCollapsedProps = await GetProperties(testCollapsed["objectId"]?.Value<string>());
await CheckProps(testCollapsedProps, new
{
list = TObject("System.Collections.Generic.List<int>", description: "Count = 2"),
array = TObject("int[]", description: "int[2]"),
text = TString("text"),
listCollapsed = TObject("System.Collections.Generic.List<int>", description: "Count = 2"),
arrayCollapsed = TObject("int[]", description: "int[2]"),
textCollapsed = TString("textCollapsed")
}, "testCollapsedProps#1");

var (testRootHidden, _) = await EvaluateOnCallFrame(id, "testRootHidden");
await CheckValue(testRootHidden, TObject("DebuggerTests.EvaluateBrowsableProperties.TestEvaluateRootHidden"), nameof(testRootHidden));
var testRootHiddenProps = await GetProperties(testRootHidden["objectId"]?.Value<string>());
var (refList, _) = await EvaluateOnCallFrame(id, "testNever.list");
var refListProp = await GetProperties(refList["objectId"]?.Value<string>());
var refListElementsProp = await GetProperties(refListProp[0]["value"]["objectId"]?.Value<string>());
var (refArray, _) = await EvaluateOnCallFrame(id, "testNever.array");
var refArrayProp = await GetProperties(refArray["objectId"]?.Value<string>());
//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"], "]");
}
var mergedRefItems = new JArray(refListElementsProp.Union(refArrayProp));
Assert.Equal(mergedRefItems, testRootHiddenProps);
});
}

}
Loading