diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs index 039f74b016d0ed..47bc311b319057 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs @@ -348,6 +348,7 @@ internal sealed class MethodInfo internal bool IsEnCMethod; internal LocalScopeHandleCollection localScopes; public bool IsStatic() => (methodDef.Attributes & MethodAttributes.Static) != 0; + public MethodAttributes Attributes => methodDef.Attributes; public int IsAsync { get; set; } public DebuggerAttributesInfo DebuggerAttrInfo { get; set; } public TypeInfo TypeInfo { get; } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs index e9a4950dd34774..df8e33460e8260 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs @@ -63,64 +63,67 @@ public MessageId(string sessionId, int id) internal sealed class DotnetObjectId { + private int? _intValue; + public string Scheme { get; } - public int Value { get; } + public int Value + { + get + { + if (_intValue == null) + throw new ArgumentException($"DotnetObjectId (scheme: {Scheme}, ValueAsJson: {ValueAsJson}) does not have an int value"); + return _intValue.Value; + } + } public int SubValue { get; set; } - public bool IsValueType { get; set; } + public bool IsValueType => Scheme == "valuetype"; + + public JObject ValueAsJson { get; init; } public static bool TryParse(JToken jToken, out DotnetObjectId objectId) => TryParse(jToken?.Value(), out objectId); public static bool TryParse(string id, out DotnetObjectId objectId) { objectId = null; - try - { - if (id == null) - return false; - - if (!id.StartsWith("dotnet:")) - return false; + if (id == null) + return false; - string[] parts = id.Split(":"); + if (!id.StartsWith("dotnet:")) + return false; - if (parts.Length < 3) - return false; + string[] parts = id.Split(":", 3); - objectId = new DotnetObjectId(parts[1], int.Parse(parts[2])); - switch (objectId.Scheme) - { - case "methodId": - if (parts.Length > 4) - { - objectId.SubValue = int.Parse(parts[3]); - objectId.IsValueType = parts[4] == "ValueType"; - return true; - } - return false; - } - return true; - } - catch (Exception) - { + if (parts.Length < 3) return false; - } + + objectId = new DotnetObjectId(parts[1], parts[2]); + return true; } public DotnetObjectId(string scheme, int value) - { - Scheme = scheme; - Value = value; - } + : this(scheme, value.ToString()) { } - public override string ToString() + public DotnetObjectId(string scheme, string value) { - switch (Scheme) + Scheme = scheme; + if (int.TryParse(value, out int ival)) { - case "methodId": - return $"dotnet:{Scheme}:{Value}:{SubValue}"; + _intValue = ival; + } + else + { + try + { + ValueAsJson = JObject.Parse(value); + } + catch (JsonReaderException) { } } - return $"dotnet:{Scheme}:{Value}"; } + + public override string ToString() + => _intValue != null + ? $"dotnet:{Scheme}:{_intValue}" + : $"dotnet:{Scheme}:{ValueAsJson}"; } public struct Result diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs index c6caa8acc9c2de..b4e3de9d17beae 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs @@ -8,6 +8,7 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; +using BrowserDebugProxy; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; @@ -472,7 +473,7 @@ protected override async Task AcceptCommand(MessageId sessionId, JObject a var to = args?["to"].Value().Replace("propertyIterator", ""); if (!DotnetObjectId.TryParse(to, out DotnetObjectId objectId)) return false; - var res = await RuntimeGetPropertiesInternal(sessionId, objectId, args, token); + var res = await RuntimeGetObjectMembers(sessionId, objectId, args, token); var variables = ConvertToFirefoxContent(res); var o = JObject.FromObject(new { @@ -521,7 +522,7 @@ protected override async Task AcceptCommand(MessageId sessionId, JObject a //{"iterator":{"type":"propertyIterator","actor":"server1.conn19.child63/propertyIterator73","count":3},"from":"server1.conn19.child63/obj71"} if (!DotnetObjectId.TryParse(args?["to"], out DotnetObjectId objectId)) return false; - var res = await RuntimeGetPropertiesInternal(sessionId, objectId, args, token); + var res = await RuntimeGetObjectMembers(sessionId, objectId, args, token); var variables = ConvertToFirefoxContent(res); var o = JObject.FromObject(new { @@ -547,7 +548,7 @@ protected override async Task AcceptCommand(MessageId sessionId, JObject a if (ctx.CallStack == null) return false; Frame scope = ctx.CallStack.FirstOrDefault(s => s.Id == objectId.Value); - var res = await RuntimeGetPropertiesInternal(sessionId, objectId, args, token); + var res = await RuntimeGetObjectMembers(sessionId, objectId, args, token); var variables = ConvertToFirefoxContent(res); var o = JObject.FromObject(new { @@ -701,11 +702,12 @@ private static JObject GetPrototype(DotnetObjectId objectId, JObject args) return o; } - private static JObject ConvertToFirefoxContent(ValueOrError res) + private static JObject ConvertToFirefoxContent(ValueOrError res) { JObject variables = new JObject(); //TODO check if res.Error and do something - foreach (var variable in res.Value) + var resVars = res.Value.Flatten(); + foreach (var variable in resVars) { JObject variableDesc; if (variable["get"] != null) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs new file mode 100644 index 00000000000000..24a2eaebbf2e86 --- /dev/null +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs @@ -0,0 +1,655 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.WebAssembly.Diagnostics; +using Newtonsoft.Json.Linq; + +namespace BrowserDebugProxy +{ + internal static class MemberObjectsExplorer + { + private static bool IsACollectionType(string typeName) + => typeName is not null && + (typeName.StartsWith("System.Collections.Generic", StringComparison.Ordinal) || + typeName.EndsWith("[]", StringComparison.Ordinal)); + + private static string GetNamePrefixForValues(string memberName, string typeName, bool isOwn, DebuggerBrowsableState? state) + { + if (isOwn || state != DebuggerBrowsableState.RootHidden) + return memberName; + + string justClassName = Path.GetExtension(typeName); + if (justClassName[0] == '.') + justClassName = justClassName[1..]; + return $"{memberName} ({justClassName})"; + } + + private static async Task ReadFieldValue(MonoSDBHelper sdbHelper, MonoBinaryReader reader, FieldTypeClass field, int objectId, TypeInfoWithDebugInformation typeInfo, int fieldValueType, bool isOwn, GetObjectCommandOptions getObjectOptions, CancellationToken token) + { + var fieldValue = await sdbHelper.CreateJObjectForVariableValue( + reader, + field.Name, + token, + isOwn: isOwn, + field.TypeId, + getObjectOptions.HasFlag(GetObjectCommandOptions.ForDebuggerDisplayAttribute)); + + var typeFieldsBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableFields; + var typePropertiesBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableProperties; + + if (!typeFieldsBrowsableInfo.TryGetValue(field.Name, out DebuggerBrowsableState? state)) + { + // for backing fields, we are getting it from the properties + typePropertiesBrowsableInfo.TryGetValue(field.Name, out state); + } + fieldValue["__state"] = state?.ToString(); + + fieldValue["__section"] = field.Attributes switch + { + FieldAttributes.Private => "private", + FieldAttributes.Public => "result", + _ => "internal" + }; + if (field.IsBackingField) + fieldValue["__isBackingField"] = true; + if (field.Attributes.HasFlag(FieldAttributes.Static)) + fieldValue["__isStatic"] = true; + + if (getObjectOptions.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 = fieldValueType, + length = length, + id = MonoSDBHelper.GetNewId() + })); + } + + return fieldValue; + } + + private static async Task GetRootHiddenChildren( + MonoSDBHelper sdbHelper, + JObject root, + string rootNamePrefix, + string rootTypeName, + GetObjectCommandOptions getCommandOptions, + bool includeStatic, + CancellationToken token) + { + var rootValue = root?["value"] ?? root["get"]; + + if (rootValue?["subtype"]?.Value() == "null") + return new JArray(); + + var type = rootValue?["type"]?.Value(); + if (type != "object" && type != "function") + return new JArray(); + + if (!DotnetObjectId.TryParse(rootValue?["objectId"]?.Value(), out DotnetObjectId rootObjectId)) + throw new Exception($"Cannot parse object id from {root} for {rootNamePrefix}"); + + // if it's an accessor + if (root["get"] != null) + return await GetRootHiddenChildrenForProperty(); + + if (rootValue?["type"]?.Value() != "object") + return new JArray(); + + // unpack object/valuetype + if (rootObjectId.Scheme is "object" or "valuetype") + { + GetMembersResult members; + if (rootObjectId.Scheme is "valuetype") + { + var valType = sdbHelper.GetValueTypeClass(rootObjectId.Value); + if (valType == null || valType.IsEnum) + return new JArray(); + members = await valType.GetMemberValues(sdbHelper, getCommandOptions, false, includeStatic, token); + } + else members = await GetObjectMemberValues(sdbHelper, rootObjectId.Value, getCommandOptions, token, false, includeStatic); + + if (!IsACollectionType(rootTypeName)) + { + // is a class/valuetype with members + var resultValue = members.Flatten(); + foreach (var item in resultValue) + item["name"] = $"{rootNamePrefix}.{item["name"]}"; + return resultValue; + } + else + { + // a collection - expose elements to be of array scheme + var memberNamedItems = members + .Where(m => m["name"]?.Value() == "Items" || m["name"]?.Value() == "_items") + .FirstOrDefault(); + if (memberNamedItems is not null && + (DotnetObjectId.TryParse(memberNamedItems["value"]?["objectId"]?.Value(), out DotnetObjectId itemsObjectId)) && + itemsObjectId.Scheme == "array") + { + rootObjectId = itemsObjectId; + } + } + } + + if (rootObjectId.Scheme == "array") + { + JArray resultValue = await sdbHelper.GetArrayValues(rootObjectId.Value, 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"] = $"{rootNamePrefix}[{item["name"]}]"; + + return resultValue; + } + else + { + return new JArray(); + } + + async Task GetRootHiddenChildrenForProperty() + { + var resMethod = await sdbHelper.InvokeMethod(rootObjectId, token); + return await GetRootHiddenChildren(sdbHelper, resMethod, rootNamePrefix, rootTypeName, getCommandOptions, includeStatic, token); + } + } + + public static Task GetTypeMemberValues( + MonoSDBHelper sdbHelper, + DotnetObjectId dotnetObjectId, + GetObjectCommandOptions getObjectOptions, + CancellationToken token, + bool sortByAccessLevel = false, + bool includeStatic = false) + => dotnetObjectId.IsValueType + ? GetValueTypeMemberValues(sdbHelper, dotnetObjectId.Value, getObjectOptions, token, sortByAccessLevel, includeStatic) + : GetObjectMemberValues(sdbHelper, dotnetObjectId.Value, getObjectOptions, token, sortByAccessLevel, includeStatic); + + public static async Task ExpandFieldValues( + MonoSDBHelper sdbHelper, + DotnetObjectId id, + int containerTypeId, + IReadOnlyList fields, + GetObjectCommandOptions getCommandOptions, + bool isOwn, + bool includeStatic, + CancellationToken token) + { + JArray fieldValues = new JArray(); + if (fields.Count == 0) + return fieldValues; + + if (getCommandOptions.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute)) + fields = fields.Where(field => field.IsNotPrivate).ToList(); + + using var commandParamsWriter = new MonoBinaryWriter(); + commandParamsWriter.Write(id.Value); + commandParamsWriter.Write(fields.Count); + foreach (var field in fields) + commandParamsWriter.Write(field.Id); + MonoBinaryReader retDebuggerCmdReader = id.IsValueType + ? await sdbHelper.SendDebuggerAgentCommand(CmdType.GetValues, commandParamsWriter, token) : + await sdbHelper.SendDebuggerAgentCommand(CmdObject.RefGetValues, commandParamsWriter, token); + + var typeInfo = await sdbHelper.GetTypeInfo(containerTypeId, token); + + int numFieldsRead = 0; + foreach (FieldTypeClass field in fields) + { + long initialPos = retDebuggerCmdReader.BaseStream.Position; + int valtype = retDebuggerCmdReader.ReadByte(); + retDebuggerCmdReader.BaseStream.Position = initialPos; + + JObject fieldValue = await ReadFieldValue(sdbHelper, retDebuggerCmdReader, field, id.Value, typeInfo, valtype, isOwn, getCommandOptions, token); + numFieldsRead++; + + if (!Enum.TryParse(fieldValue["__state"].Value(), out DebuggerBrowsableState fieldState) + || fieldState == DebuggerBrowsableState.Collapsed) + { + fieldValues.Add(fieldValue); + continue; + } + + if (fieldState == DebuggerBrowsableState.Never) + continue; + + string namePrefix = field.Name; + string containerTypeName = await sdbHelper.GetTypeName(containerTypeId, token); + namePrefix = GetNamePrefixForValues(field.Name, containerTypeName, isOwn, fieldState); + string typeName = await sdbHelper.GetTypeName(field.TypeId, token); + + var enumeratedValues = await GetRootHiddenChildren( + sdbHelper, fieldValue, namePrefix, typeName, getCommandOptions, includeStatic, token); + if (enumeratedValues != null) + fieldValues.AddRange(enumeratedValues); + } + + if (numFieldsRead != fields.Count) + throw new Exception($"Bug: Got {numFieldsRead} instead of expected {fields.Count} field values"); + + return fieldValues; + } + + public static Task GetValueTypeMemberValues( + MonoSDBHelper sdbHelper, int valueTypeId, GetObjectCommandOptions getCommandOptions, CancellationToken token, bool sortByAccessLevel = false, bool includeStatic = false) + { + return sdbHelper.valueTypes.TryGetValue(valueTypeId, out ValueTypeClass valueType) + ? valueType.GetMemberValues(sdbHelper, getCommandOptions, sortByAccessLevel, includeStatic, token) + : throw new ArgumentException($"Could not find any valuetype with id: {valueTypeId}", nameof(valueTypeId)); + } + + public static async Task GetExpandedMemberValues( + MonoSDBHelper sdbHelper, + string typeName, + string namePrefix, + JObject value, + DebuggerBrowsableState? state, + bool includeStatic, + CancellationToken token) + { + if (state is DebuggerBrowsableState.RootHidden) + { + if (MonoSDBHelper.IsPrimitiveType(typeName)) + return GetHiddenElement(); + + return await GetRootHiddenChildren(sdbHelper, value, namePrefix, typeName, GetObjectCommandOptions.None, includeStatic, token); + + } + else if (state is DebuggerBrowsableState.Never) + { + return GetHiddenElement(); + } + return new JArray(value); + + JArray GetHiddenElement() + { + return new JArray(JObject.FromObject(new + { + name = namePrefix, + __hidden = true + })); + } + } + + public static async Task> GetNonAutomaticPropertyValues( + MonoSDBHelper sdbHelper, + int typeId, + string containerTypeName, + ArraySegment getterParamsBuffer, + bool isAutoExpandable, + DotnetObjectId objectId, + bool isValueType, + bool isOwn, + CancellationToken token, + Dictionary allMembers, + bool includeStatic = false) + { + using var retDebuggerCmdReader = await sdbHelper.GetTypePropertiesReader(typeId, token); + if (retDebuggerCmdReader == null) + return null; + + var nProperties = retDebuggerCmdReader.ReadInt32(); + var typeInfo = await sdbHelper.GetTypeInfo(typeId, token); + var typePropertiesBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableProperties; + + GetMembersResult ret = new(); + for (int i = 0; i < nProperties; i++) + { + retDebuggerCmdReader.ReadInt32(); //propertyId + string propName = retDebuggerCmdReader.ReadString(); + var getMethodId = retDebuggerCmdReader.ReadInt32(); + retDebuggerCmdReader.ReadInt32(); //setmethod + var attrs = (PropertyAttributes)retDebuggerCmdReader.ReadInt32(); //attrs + if (getMethodId == 0 || await sdbHelper.GetParamCount(getMethodId, token) != 0) + continue; + if (!includeStatic && await sdbHelper.MethodIsStatic(getMethodId, token)) + continue; + + MethodInfoWithDebugInformation getterInfo = await sdbHelper.GetMethodInfo(getMethodId, token); + MethodAttributes getterAttrs = getterInfo?.Info.Attributes ?? MethodAttributes.Public; + getterAttrs &= MethodAttributes.MemberAccessMask; + + typePropertiesBrowsableInfo.TryGetValue(propName, out DebuggerBrowsableState? state); + + if (allMembers.TryGetValue(propName, out JObject backingField)) + { + if (backingField["__isBackingField"]?.Value() == true) + { + // Update backingField's access with the one from the property getter + backingField["__section"] = getterAttrs switch + { + MethodAttributes.Private => "private", + MethodAttributes.Public => "result", + _ => "internal" + }; + backingField["__state"] = state?.ToString(); + + if (state is not null) + { + string namePrefix = GetNamePrefixForValues(propName, containerTypeName, isOwn, state); + + string backingFieldTypeName = backingField["value"]?["className"]?.Value(); + var expanded = await GetExpandedMemberValues( + sdbHelper, backingFieldTypeName, namePrefix, backingField, state, includeStatic, token); + backingField.Remove(); + allMembers.Remove(propName); + foreach (JObject evalue in expanded) + allMembers[evalue["name"].Value()] = evalue; + } + } + + // derived type already had a member of this name + continue; + } + else + { + string returnTypeName = await sdbHelper.GetReturnType(getMethodId, token); + JObject propRet = null; + if (isAutoExpandable || (state is DebuggerBrowsableState.RootHidden && IsACollectionType(returnTypeName))) + { + try + { + propRet = await sdbHelper.InvokeMethod(getterParamsBuffer, getMethodId, token, name: propName); + } + catch (Exception) + { + continue; + } + } + else + propRet = GetNotAutoExpandableObject(getMethodId, propName); + + propRet["isOwn"] = isOwn; + propRet["__section"] = getterAttrs switch + { + MethodAttributes.Private => "private", + MethodAttributes.Public => "result", + _ => "internal" + }; + propRet["__state"] = state?.ToString(); + + string namePrefix = GetNamePrefixForValues(propName, containerTypeName, isOwn, state); + var expandedMembers = await GetExpandedMemberValues( + sdbHelper, returnTypeName, namePrefix, propRet, state, includeStatic, token); + foreach (var member in expandedMembers) + { + var key = member["name"]?.Value(); + if (key != null) + { + allMembers.TryAdd(key, member as JObject); + } + } + } + } + return allMembers; + + JObject GetNotAutoExpandableObject(int methodId, string propertyName) + { + JObject methodIdArgs = JObject.FromObject(new + { + containerId = objectId.Value, + isValueType = isValueType, + methodId = methodId + }); + + return JObject.FromObject(new + { + get = new + { + type = "function", + objectId = $"dotnet:method:{methodIdArgs.ToString(Newtonsoft.Json.Formatting.None)}", + className = "Function", + description = "get " + propertyName + " ()" + }, + name = propertyName + }); + } + } + + public static async Task GetObjectMemberValues( + MonoSDBHelper sdbHelper, + int objectId, + GetObjectCommandOptions getCommandType, + CancellationToken token, + bool sortByAccessLevel = false, + bool includeStatic = false) + { + if (await sdbHelper.IsDelegate(objectId, token)) + { + var description = await sdbHelper.GetDelegateMethodDescription(objectId, token); + var objValues = JObject.FromObject(new + { + value = new + { + type = "symbol", + value = description, + description + }, + name = "Target" + }); + + return GetMembersResult.FromValues(new List() { objValues }); + } + + // 1 + var typeIdsIncludingParents = await sdbHelper.GetTypeIdsForObject(objectId, true, token); + + // 2 + if (!getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerDisplayAttribute)) + { + GetMembersResult debuggerProxy = await sdbHelper.GetValuesFromDebuggerProxyAttribute(objectId, typeIdsIncludingParents[0], token); + if (debuggerProxy != null) + return debuggerProxy; + } + + // 3. GetProperties + DotnetObjectId id = new DotnetObjectId("object", objectId); + using var commandParamsObjWriter = new MonoBinaryWriter(); + commandParamsObjWriter.WriteObj(id, sdbHelper); + ArraySegment getPropertiesParamBuffer = commandParamsObjWriter.GetParameterBuffer(); + + var allMembers = new Dictionary(); + for (int i = 0; i < typeIdsIncludingParents.Count; i++) + { + int typeId = typeIdsIncludingParents[i]; + string typeName = await sdbHelper.GetTypeName(typeId, token); + // 0th id is for the object itself, and then its ancestors + bool isOwn = i == 0; + IReadOnlyList thisTypeFields = await sdbHelper.GetTypeFields(typeId, token); + if (!includeStatic) + thisTypeFields = thisTypeFields.Where(f => !f.Attributes.HasFlag(FieldAttributes.Static)).ToList(); + if (thisTypeFields.Count > 0) + { + var allFields = await ExpandFieldValues( + sdbHelper, id, typeId, thisTypeFields, getCommandType, isOwn, includeStatic, token); + + if (getCommandType.HasFlag(GetObjectCommandOptions.AccessorPropertiesOnly)) + { + foreach (var f in allFields) + f["__hidden"] = true; + } + AddOnlyNewValuesByNameTo(allFields, allMembers, isOwn); + } + + // skip loading properties if not necessary + if (!getCommandType.HasFlag(GetObjectCommandOptions.WithProperties)) + return GetMembersResult.FromValues(allMembers.Values, sortByAccessLevel); + + allMembers = await GetNonAutomaticPropertyValues( + sdbHelper, + typeId, + typeName, + getPropertiesParamBuffer, + getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute), + id, + isValueType: false, + isOwn, + token, + allMembers); + + // 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; + /*if (accessorPropertiesOnly) + break;*/ + } + return GetMembersResult.FromValues(allMembers.Values, sortByAccessLevel); + + static void AddOnlyNewValuesByNameTo(JArray namedValues, IDictionary valuesDict, bool isOwn) + { + foreach (var item in namedValues) + { + var key = item["name"]?.Value(); + if (key != null) + { + valuesDict.TryAdd(key, item as JObject); + } + } + } + } + + } + + internal sealed class GetMembersResult + { + // public: + public JArray Result { get; set; } + // private: + public JArray PrivateMembers { get; set; } + // protected / internal: + public JArray OtherMembers { get; set; } + + public JObject JObject => JObject.FromObject(new + { + result = Result, + privateProperties = PrivateMembers, + internalProperties = OtherMembers + }); + + public GetMembersResult() + { + Result = new JArray(); + PrivateMembers = new JArray(); + OtherMembers = new JArray(); + } + + public GetMembersResult(JArray value, bool sortByAccessLevel) + { + var t = FromValues(value, sortByAccessLevel); + Result = t.Result; + PrivateMembers = t.PrivateMembers; + OtherMembers = t.OtherMembers; + } + + public static GetMembersResult FromValues(IEnumerable values, bool splitMembersByAccessLevel = false) => + FromValues(new JArray(values), splitMembersByAccessLevel); + + public static GetMembersResult FromValues(JArray values, bool splitMembersByAccessLevel = false) + { + GetMembersResult result = new(); + if (splitMembersByAccessLevel) + { + foreach (var member in values) + result.Split(member); + return result; + } + result.Result.AddRange(values); + return result; + } + + private void Split(JToken member) + { + if (member["__hidden"]?.Value() == true) + return; + + if (member["__section"]?.Value() is not string section) + { + Result.Add(member); + return; + } + + switch (section) + { + case "private": + PrivateMembers.Add(member); + return; + case "internal": + OtherMembers.Add(member); + return; + default: + Result.Add(member); + return; + } + } + + public GetMembersResult Clone() => new GetMembersResult() + { + Result = (JArray)Result.DeepClone(), + PrivateMembers = (JArray)PrivateMembers.DeepClone(), + OtherMembers = (JArray)OtherMembers.DeepClone() + }; + + public IEnumerable Where(Func predicate) + { + foreach (var item in Result) + { + if (predicate(item)) + { + yield return item; + } + } + foreach (var item in PrivateMembers) + { + if (predicate(item)) + { + yield return item; + } + } + foreach (var item in OtherMembers) + { + if (predicate(item)) + { + yield return item; + } + } + } + + internal JToken FirstOrDefault(Func p) + => Result.FirstOrDefault(p) + ?? PrivateMembers.FirstOrDefault(p) + ?? OtherMembers.FirstOrDefault(p); + + internal JArray Flatten() + { + var result = new JArray(); + result.AddRange(Result); + result.AddRange(PrivateMembers); + result.AddRange(OtherMembers); + return result; + } + public override string ToString() => $"{JObject}\n"; + } +} diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs index 63ceaa72428832..17690f88c9e0f8 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Generic; using System.Net.WebSockets; +using BrowserDebugProxy; namespace Microsoft.WebAssembly.Diagnostics { @@ -54,7 +55,7 @@ public async Task GetValueFromObject(JToken objRet, CancellationToken t { if (DotnetObjectId.TryParse(objRet?["value"]?["objectId"]?.Value(), out DotnetObjectId objectId)) { - var exceptionObject = await context.SdbAgent.GetObjectValues(objectId.Value, GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.OwnProperties, token); + GetMembersResult exceptionObject = await MemberObjectsExplorer.GetTypeMemberValues(context.SdbAgent, objectId, GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.OwnProperties, token); var exceptionObjectMessage = exceptionObject.FirstOrDefault(attr => attr["name"].Value().Equals("_message")); exceptionObjectMessage["value"]["value"] = objRet["value"]?["className"]?.Value() + ": " + exceptionObjectMessage["value"]?["value"]?.Value(); return exceptionObjectMessage["value"]?.Value(); @@ -64,16 +65,12 @@ public async Task GetValueFromObject(JToken objRet, CancellationToken t if (objRet["value"]?.Value() != null) return objRet["value"]?.Value(); - if (objRet["get"]?.Value() != null) - { - if (DotnetObjectId.TryParse(objRet?["get"]?["objectIdValue"]?.Value(), out DotnetObjectId objectId)) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.WriteObj(objectId, context.SdbAgent); - var ret = await context.SdbAgent.InvokeMethod(commandParamsWriter.GetParameterBuffer(), objRet["get"]["methodId"].Value(), objRet["name"].Value(), token); - return await GetValueFromObject(ret, token); - } + if (objRet["get"]?.Value() != null && + DotnetObjectId.TryParse(objRet?["get"]?["objectId"]?.Value(), out DotnetObjectId getterObjectId)) + { + var ret = await context.SdbAgent.InvokeMethod(getterObjectId, token); + return await GetValueFromObject(ret, token); } return null; } @@ -150,7 +147,7 @@ async Task FindStaticMemberInType(string name, int typeId) { using var commandParamsObjWriter = new MonoBinaryWriter(); commandParamsObjWriter.Write(0); //param count - var retMethod = await context.SdbAgent.InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, "methodRet", token); + var retMethod = await context.SdbAgent.InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, token); return await GetValueFromObject(retMethod, token); } return null; @@ -234,7 +231,7 @@ async Task ResolveAsLocalOrThisMember(string name) if (!DotnetObjectId.TryParse(objThis?["value"]?["objectId"]?.Value(), out DotnetObjectId objectId)) return null; - ValueOrError valueOrError = await proxy.RuntimeGetPropertiesInternal(sessionId, objectId, null, token); + ValueOrError valueOrError = await proxy.RuntimeGetObjectMembers(sessionId, objectId, null, token); if (valueOrError.IsError) { logger.LogDebug($"ResolveAsLocalOrThisMember failed with : {valueOrError.Error}"); @@ -261,7 +258,7 @@ async Task ResolveAsInstanceMember(string expr, JObject baseObject) if (!DotnetObjectId.TryParse(resolvedObject?["objectId"]?.Value(), out DotnetObjectId objectId)) return null; - ValueOrError valueOrError = await proxy.RuntimeGetPropertiesInternal(sessionId, objectId, null, token); + ValueOrError valueOrError = await proxy.RuntimeGetObjectMembers(sessionId, objectId, null, token); if (valueOrError.IsError) { logger.LogDebug($"ResolveAsInstanceMember failed with : {valueOrError.Error}"); @@ -358,9 +355,9 @@ public async Task Resolve(ElementAccessExpressionSyntax elementAccess, rootObject["value"] = await context.SdbAgent.GetArrayValues(objectId.Value, token); return (JObject)rootObject["value"][elementIdx]["value"]; case "object": - var typeIds = await context.SdbAgent.GetTypeIdFromObject(objectId.Value, true, token); + var typeIds = await context.SdbAgent.GetTypeIdsForObject(objectId.Value, true, token); int methodId = await context.SdbAgent.GetMethodIdByName(typeIds[0], "ToArray", token); - var toArrayRetMethod = await context.SdbAgent.InvokeMethodInObject(objectId, methodId, elementAccess.Expression.ToString(), token); + var toArrayRetMethod = await context.SdbAgent.InvokeMethod(objectId.Value, methodId, isValueType: false, token); rootObject = await GetValueFromObject(toArrayRetMethod, token); DotnetObjectId.TryParse(rootObject?["objectId"]?.Value(), out DotnetObjectId arrayObjectId); rootObject["value"] = await context.SdbAgent.GetArrayValues(arrayObjectId.Value, token); @@ -409,7 +406,18 @@ public async Task Resolve(InvocationExpressionSyntax method, Dictionary if (!DotnetObjectId.TryParse(rootObject?["objectId"]?.Value(), out DotnetObjectId objectId)) throw new ExpressionEvaluationFailedException($"Cannot invoke method '{methodName}' on invalid object id: {rootObject}"); - var typeIds = await context.SdbAgent.GetTypeIdFromObject(objectId.Value, true, token); + List typeIds; + if (objectId.IsValueType) + { + if (!context.SdbAgent.valueTypes.TryGetValue(objectId.Value, out ValueTypeClass valueType)) + throw new Exception($"Could not find valuetype {objectId}"); + + typeIds = new List(1) { valueType.TypeId }; + } + else + { + typeIds = await context.SdbAgent.GetTypeIdsForObject(objectId.Value, true, token); + } int methodId = await context.SdbAgent.GetMethodIdByName(typeIds[0], methodName, token); var className = await context.SdbAgent.GetTypeNameOriginal(typeIds[0], token); if (methodId == 0) //try to search on System.Linq.Enumerable @@ -481,7 +489,7 @@ public async Task Resolve(InvocationExpressionSyntax method, Dictionary if (!await commandParamsObjWriter.WriteConst(methodParamsInfo[argIndex].TypeCode, methodParamsInfo[argIndex].Value, context.SdbAgent, token)) throw new InternalErrorException($"Unable to write optional parameter {methodParamsInfo[argIndex].Name} value in method '{methodName}' to the mono buffer."); } - var retMethod = await context.SdbAgent.InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, "methodRet", token); + var retMethod = await context.SdbAgent.InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, token); return await GetValueFromObject(retMethod, token); } } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index 83bb6140f5b7dd..48af87882f7a5a 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -12,6 +12,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.Net.Http; +using BrowserDebugProxy; namespace Microsoft.WebAssembly.Diagnostics { @@ -475,16 +476,19 @@ protected override async Task AcceptCommand(MessageId id, JObject parms, C if (!DotnetObjectId.TryParse(args?["objectId"], out DotnetObjectId objectId)) break; - var valueOrError = await RuntimeGetPropertiesInternal(id, objectId, args, token, true); + var valueOrError = await RuntimeGetObjectMembers(id, objectId, args, token, true); if (valueOrError.IsError) { logger.LogDebug($"Runtime.getProperties: {valueOrError.Error}"); SendResponse(id, valueOrError.Error.Value, token); + return true; } - else + if (valueOrError.Value.JObject == null) { - SendResponse(id, Result.OkFromObject(valueOrError.Value), token); + SendResponse(id, Result.Err($"Failed to get properties for '{objectId}'"), token); + return true; } + SendResponse(id, Result.OkFromObject(valueOrError.Value.JObject), token); return true; } @@ -662,8 +666,10 @@ private async Task CallOnFunction(MessageId id, JObject args, Cancellation } switch (objectId.Scheme) { + case "method": + args["details"] = await context.SdbAgent.GetMethodProxy(objectId.ValueAsJson, token); + break; case "object": - case "methodId": args["details"] = await context.SdbAgent.GetObjectProxy(objectId.Value, token); break; case "valuetype": @@ -679,12 +685,10 @@ private async Task CallOnFunction(MessageId id, JObject args, Cancellation args["details"] = await context.SdbAgent.GetArrayValuesProxy(objectId.Value, token); break; case "cfo_res": - { Result cfo_res = await SendMonoCommand(id, MonoCommands.CallFunctionOn(RuntimeId, args), token); cfo_res = Result.OkFromObject(new { result = cfo_res.Value?["result"]?["value"]}); SendResponse(id, cfo_res, token); return true; - } case "scope": { SendResponse(id, @@ -707,7 +711,7 @@ private async Task CallOnFunction(MessageId id, JObject args, Cancellation byte[] newBytes = Convert.FromBase64String(res.Value?["result"]?["value"]?["value"]?.Value()); var retDebuggerCmdReader = new MonoBinaryReader(newBytes); retDebuggerCmdReader.ReadByte(); //number of objects returned. - var obj = await context.SdbAgent.CreateJObjectForVariableValue(retDebuggerCmdReader, "ret", false, -1, false, token); + var obj = await context.SdbAgent.CreateJObjectForVariableValue(retDebuggerCmdReader, "ret", token); /*JTokenType? res_value_type = res.Value?["result"]?["value"]?.Type;*/ res = Result.OkFromObject(new { result = obj["value"]}); SendResponse(id, res, token); @@ -738,83 +742,69 @@ private async Task OnSetVariableValue(MessageId id, int scopeId, string va return true; } - internal async Task> RuntimeGetPropertiesInternal(SessionId id, DotnetObjectId objectId, JToken args, CancellationToken token, bool sortByAccessLevel = false) + internal async Task> RuntimeGetObjectMembers(SessionId id, DotnetObjectId objectId, JToken args, CancellationToken token, bool sortByAccessLevel = false) { var context = GetContext(id); - var accessorPropertiesOnly = false; - GetObjectCommandOptions objectValuesOpt = GetObjectCommandOptions.WithProperties; + GetObjectCommandOptions getObjectOptions = GetObjectCommandOptions.WithProperties; if (args != null) { if (args["accessorPropertiesOnly"] != null && args["accessorPropertiesOnly"].Value()) { - objectValuesOpt |= GetObjectCommandOptions.AccessorPropertiesOnly; - accessorPropertiesOnly = true; + getObjectOptions |= GetObjectCommandOptions.AccessorPropertiesOnly; } if (args["ownProperties"] != null && args["ownProperties"].Value()) { - objectValuesOpt |= GetObjectCommandOptions.OwnProperties; + getObjectOptions |= GetObjectCommandOptions.OwnProperties; } } - try { + try + { switch (objectId.Scheme) { case "scope": - { Result resScope = await GetScopeProperties(id, objectId.Value, token); return resScope.IsOk - ? ValueOrError.WithValue(sortByAccessLevel ? resScope.Value : resScope.Value?["result"]) - : ValueOrError.WithError(resScope); - } + ? ValueOrError.WithValue( + new GetMembersResult((JArray)resScope.Value?["result"], sortByAccessLevel: false)) + : ValueOrError.WithError(resScope); case "valuetype": - { - var valType = context.SdbAgent.GetValueTypeClass(objectId.Value); - if (valType == null) - return ValueOrError.WithError($"Internal Error: No valuetype found for {objectId}."); - var resValue = await valType.GetValues(context.SdbAgent, accessorPropertiesOnly, token); + var resValue = await MemberObjectsExplorer.GetValueTypeMemberValues( + context.SdbAgent, objectId.Value, getObjectOptions, token, sortByAccessLevel, includeStatic: false); return resValue switch { - null => ValueOrError.WithError($"Could not get properties for {objectId}"), - _ => ValueOrError.WithValue(sortByAccessLevel ? JObject.FromObject(new { result = resValue }) : resValue) + null => ValueOrError.WithError($"Could not get properties for {objectId}"), + _ => ValueOrError.WithValue(resValue) }; - } case "array": - { var resArr = await context.SdbAgent.GetArrayValues(objectId.Value, token); - return ValueOrError.WithValue(sortByAccessLevel ? JObject.FromObject(new { result = resArr }) : resArr); - } - case "methodId": - { - var resMethod = await context.SdbAgent.InvokeMethodInObject(objectId, objectId.SubValue, null, token); - return ValueOrError.WithValue(sortByAccessLevel ? JObject.FromObject(new { result = new JArray(resMethod) }) : new JArray(resMethod)); - } + return ValueOrError.WithValue(GetMembersResult.FromValues(resArr)); + case "method": + var resMethod = await context.SdbAgent.InvokeMethod(objectId, token); + return ValueOrError.WithValue(GetMembersResult.FromValues(new JArray(resMethod))); case "object": - { - var resObj = await context.SdbAgent.GetObjectValues(objectId.Value, objectValuesOpt, token, sortByAccessLevel); - return ValueOrError.WithValue(sortByAccessLevel ? resObj[0] : resObj); - } + var resObj = await MemberObjectsExplorer.GetObjectMemberValues( + context.SdbAgent, objectId.Value, getObjectOptions, token, sortByAccessLevel, includeStatic: true); + return ValueOrError.WithValue(resObj); case "pointer": - { var resPointer = new JArray { await context.SdbAgent.GetPointerContent(objectId.Value, token) }; - return ValueOrError.WithValue(sortByAccessLevel ? JObject.FromObject(new { result = resPointer }) : resPointer); - } + return ValueOrError.WithValue(GetMembersResult.FromValues(resPointer)); case "cfo_res": - { Result res = await SendMonoCommand(id, MonoCommands.GetDetails(RuntimeId, objectId.Value, args), token); string value_json_str = res.Value["result"]?["value"]?["__value_as_json_string__"]?.Value(); if (res.IsOk && value_json_str == null) - return ValueOrError.WithError($"Internal error: Could not find expected __value_as_json_string__ field in the result: {res}"); + return ValueOrError.WithError( + $"Internal error: Could not find expected __value_as_json_string__ field in the result: {res}"); return value_json_str != null - ? ValueOrError.WithValue(sortByAccessLevel ? JObject.FromObject(new { result = JArray.Parse(value_json_str) }) : JArray.Parse(value_json_str)) - : ValueOrError.WithError(res); - } + ? ValueOrError.WithValue(GetMembersResult.FromValues(JArray.Parse(value_json_str))) + : ValueOrError.WithError(res); default: - return ValueOrError.WithError($"RuntimeGetProperties: unknown object id scheme: {objectId.Scheme}"); + return ValueOrError.WithError($"RuntimeGetProperties: unknown object id scheme: {objectId.Scheme}"); } } catch (Exception ex) { - return ValueOrError.WithError($"RuntimeGetProperties: Failed to get properties for {objectId}: {ex}"); + return ValueOrError.WithError($"RuntimeGetProperties: Failed to get properties for {objectId}: {ex}"); } } @@ -1105,7 +1095,8 @@ internal async Task OnReceiveDebuggerAgentEvent(SessionId sessionId, JObje string reason = "exception"; int object_id = retDebuggerCmdReader.ReadInt32(); var caught = retDebuggerCmdReader.ReadByte(); - var exceptionObject = await context.SdbAgent.GetObjectValues(object_id, GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.OwnProperties, token); + var exceptionObject = await MemberObjectsExplorer.GetObjectMemberValues( + context.SdbAgent, object_id, GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.OwnProperties, token); var exceptionObjectMessage = exceptionObject.FirstOrDefault(attr => attr["name"].Value().Equals("_message")); var data = JObject.FromObject(new { diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index a5a6000692b553..a3e0fb51fafbab 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -16,7 +16,7 @@ using System.Reflection; using System.Text; using System.Runtime.CompilerServices; -using System.Diagnostics; +using BrowserDebugProxy; namespace Microsoft.WebAssembly.Diagnostics { @@ -696,110 +696,16 @@ internal sealed class FieldTypeClass public string Name { get; } public int TypeId { get; } public bool IsNotPrivate { get; } - public FieldAttributes ProtectionLevel { get; } - public FieldTypeClass(int id, string name, int typeId, bool isNotPrivate, FieldAttributes protectionLevel) + public bool IsBackingField { get; } + public FieldAttributes Attributes { get; } + public FieldTypeClass(int id, string name, int typeId, bool isBackingField, FieldAttributes attributes) { Id = id; Name = name; TypeId = typeId; - IsNotPrivate = isNotPrivate; - ProtectionLevel = protectionLevel; - } - } - internal sealed class ValueTypeClass - { - private readonly JArray json; - private readonly int typeId; - private readonly bool autoExpand; - private readonly int id; - private JArray proxy; - private JArray jsonProps; - - public byte[] Buffer { get; } - - public ValueTypeClass(byte[] buffer, JArray json, int typeId, bool expand_properties, int valueTypeId) - { - Buffer = buffer; - this.json = json; - this.typeId = typeId; - jsonProps = null; - proxy = null; - autoExpand = expand_properties; - id = valueTypeId; - } - - public async Task GetProxy(MonoSDBHelper sdbAgent, CancellationToken token) - { - if (proxy != null) - return proxy; - proxy = new JArray(json); - - var retDebuggerCmdReader = await sdbAgent.GetTypePropertiesReader(typeId, token); - if (retDebuggerCmdReader == null) - return null; - - var nProperties = retDebuggerCmdReader.ReadInt32(); - - for (int i = 0; i < nProperties; i++) - { - retDebuggerCmdReader.ReadInt32(); //propertyId - string propertyNameStr = retDebuggerCmdReader.ReadString(); - - var getMethodId = retDebuggerCmdReader.ReadInt32(); - retDebuggerCmdReader.ReadInt32(); //setmethod - retDebuggerCmdReader.ReadInt32(); //attrs - if (await sdbAgent.MethodIsStatic(getMethodId, token)) - continue; - using var command_params_writer_to_proxy = new MonoBinaryWriter(); - command_params_writer_to_proxy.Write(getMethodId); - command_params_writer_to_proxy.Write(Buffer); - command_params_writer_to_proxy.Write(0); - var (data, length) = command_params_writer_to_proxy.ToBase64(); - proxy.Add(JObject.FromObject(new - { - get = JObject.FromObject(new - { - commandSet = CommandSet.Vm, - command = CmdVM.InvokeMethod, - buffer = data, - length = length, - id = MonoSDBHelper.GetNewId() - }), - name = propertyNameStr - })); - } - return proxy; - } - - public async Task GetProperties(MonoSDBHelper sdbAgent, CancellationToken token) - { - JArray ret = new JArray(); - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(typeId); - using var retDebuggerCmdReader = await sdbAgent.SendDebuggerAgentCommand(CmdType.GetParents, commandParamsWriter, token); - var parentsCount = retDebuggerCmdReader.ReadInt32(); - List typesToGetProperties = new List(); - typesToGetProperties.Add(typeId); - for (int i = 0; i < parentsCount; i++) - { - typesToGetProperties.Add(retDebuggerCmdReader.ReadInt32()); - } - for (int i = 0; i < typesToGetProperties.Count; i++) - { - var properties = await sdbAgent.CreateJArrayForProperties(typesToGetProperties[i], ElementType.ValueType, Buffer, json, autoExpand, $"dotnet:valuetype:{id}", i == 0, token); - ret = new JArray(ret.Union(properties)); - } - return ret; - } - - public async Task GetValues(MonoSDBHelper sdbAgent, bool accessorPropertiesOnly, CancellationToken token) - { - if (jsonProps == null) - jsonProps = await GetProperties(sdbAgent, token); - if (accessorPropertiesOnly) - return jsonProps; - var ret = new JArray(json.Union(jsonProps)); - return ret; + IsNotPrivate = (Attributes & FieldAttributes.FieldAccessMask & FieldAttributes.Public) != 0; + Attributes = attributes; + IsBackingField = isBackingField; } } @@ -808,6 +714,8 @@ internal sealed class PointerValue public long address; public int typeId; public string varName; + private JObject _value; + public PointerValue(long address, int typeId, string varName) { this.address = address; @@ -815,6 +723,22 @@ public PointerValue(long address, int typeId, string varName) this.varName = varName; } + public async Task GetValue(MonoSDBHelper sdbHelper, CancellationToken token) + { + if (_value == null) + { + using var commandParamsWriter = new MonoBinaryWriter(); + commandParamsWriter.Write(address); + commandParamsWriter.Write(typeId); + using var retDebuggerCmdReader = await sdbHelper.SendDebuggerAgentCommand(CmdPointer.GetValue, commandParamsWriter, token); + string displayVarName = varName; + if (int.TryParse(varName, out _)) + displayVarName = $"[{varName}]"; + _value = await sdbHelper.CreateJObjectForVariableValue(retDebuggerCmdReader, "*" + displayVarName, token); + } + + return _value; + } } internal sealed class MonoSDBHelper { @@ -838,6 +762,7 @@ internal sealed class MonoSDBHelper private Regex regexForAsyncLocals = new Regex(@"\<([^)]*)\>", RegexOptions.Singleline); public static int GetNewId() { return cmdId++; } + public static int GetNewObjectId() => Interlocked.Increment(ref debuggerObjectId); public MonoSDBHelper(MonoProxy proxy, ILogger logger, SessionId sessionId) { @@ -1318,7 +1243,7 @@ public async Task GetFieldValue(int typeId, int fieldId, CancellationTo commandParamsWriter.Write(fieldId); using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdType.GetValues, commandParamsWriter, token); - return await CreateJObjectForVariableValue(retDebuggerCmdReader, "", false, -1, false, token); + return await CreateJObjectForVariableValue(retDebuggerCmdReader, "", token); } public async Task TypeIsInitialized(int typeId, CancellationToken token) @@ -1374,23 +1299,24 @@ public async Task> GetTypeFields(int typeId, CancellationTo for (int i = 0 ; i < nFields; i++) { - bool isNotPrivate = false; int fieldId = retDebuggerCmdReader.ReadInt32(); //fieldId string fieldNameStr = retDebuggerCmdReader.ReadString(); int fieldTypeId = retDebuggerCmdReader.ReadInt32(); //typeId int attrs = retDebuggerCmdReader.ReadInt32(); //attrs + FieldAttributes fieldAttrs = (FieldAttributes)attrs; int isSpecialStatic = retDebuggerCmdReader.ReadInt32(); //is_special_static - if (((attrs & (int)MethodAttributes.Public) != 0)) - isNotPrivate = true; if (isSpecialStatic == 1) continue; + + bool isBackingField = false; if (fieldNameStr.Contains("k__BackingField")) { + isBackingField = true; fieldNameStr = fieldNameStr.Replace("k__BackingField", ""); fieldNameStr = fieldNameStr.Replace("<", ""); fieldNameStr = fieldNameStr.Replace(">", ""); } - ret.Add(new FieldTypeClass(fieldId, fieldNameStr, fieldTypeId, isNotPrivate, (FieldAttributes)((attrs) & (int)FieldAttributes.FieldAccessMask))); + ret.Add(new FieldTypeClass(fieldId, fieldNameStr, fieldTypeId, isBackingField, fieldAttrs)); } typeInfo.FieldsList = ret; return ret; @@ -1415,7 +1341,7 @@ private static string ReplaceCommonClassNames(string className) => //.Replace("System.Decimal", "decimal") .ToString(); - internal async Task GetCAttrsFromType(int objectId, int typeId, string attrName, CancellationToken token) + internal async Task GetCAttrsFromType(int typeId, string attrName, CancellationToken token) { using var commandParamsWriter = new MonoBinaryWriter(); commandParamsWriter.Write(typeId); @@ -1442,7 +1368,7 @@ internal async Task GetCAttrsFromType(int objectId, int typeId for (int j = 0; j < parmCount; j++) { //to typed_args - await CreateJObjectForVariableValue(retDebuggerCmdReader, "varName", false, -1, false, token); + await CreateJObjectForVariableValue(retDebuggerCmdReader, "varName", token); } } } @@ -1463,25 +1389,29 @@ public async Task GetAssemblyFromType(int type_id, CancellationToken token) return retDebuggerCmdReader.ReadInt32(); } - public async Task GetValueFromDebuggerDisplayAttribute(int objectId, int typeId, CancellationToken token) + public async Task GetValueFromDebuggerDisplayAttribute(DotnetObjectId dotnetObjectId, int typeId, CancellationToken token) { string expr = ""; try { - var getCAttrsRetReader = await GetCAttrsFromType(objectId, typeId, "System.Diagnostics.DebuggerDisplayAttribute", token); + var getCAttrsRetReader = await GetCAttrsFromType(typeId, "System.Diagnostics.DebuggerDisplayAttribute", token); if (getCAttrsRetReader == null) return null; var parmCount = getCAttrsRetReader.ReadInt32(); - var monoType = (ElementType) getCAttrsRetReader.ReadByte(); //MonoTypeEnum -> MONO_TYPE_STRING + var monoType = (ElementType)getCAttrsRetReader.ReadByte(); //MonoTypeEnum -> MONO_TYPE_STRING if (monoType != ElementType.String) return null; var stringId = getCAttrsRetReader.ReadInt32(); var dispAttrStr = await GetStringValue(stringId, token); ExecutionContext context = proxy.GetContext(sessionId); - JArray objectValues = await GetObjectValues(objectId, GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.ForDebuggerDisplayAttribute, token); + GetMembersResult members = await GetTypeMemberValues( + dotnetObjectId, + GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.ForDebuggerDisplayAttribute, + token); + JArray objectValues = new JArray(members.Flatten()); - var thisObj = CreateJObject(value: "", type: "object", description: "", writable: false, objectId: $"dotnet:object:{objectId}"); + var thisObj = CreateJObject(value: "", type: "object", description: "", writable: false, objectId: dotnetObjectId.ToString()); thisObj["name"] = "this"; objectValues.Add(thisObj); @@ -1579,7 +1509,8 @@ public async Task GetArrayDimensions(int object_id, Cancellatio } return new ArrayDimensions(rank); } - public async Task> GetTypeIdFromObject(int object_id, bool withParents, CancellationToken token) + + public async Task> GetTypeIdsForObject(int object_id, bool withParents, CancellationToken token) { List ret = new List(); using var commandParamsWriter = new MonoBinaryWriter(); @@ -1604,7 +1535,7 @@ public async Task> GetTypeIdFromObject(int object_id, bool withParents public async Task GetClassNameFromObject(int object_id, CancellationToken token) { - var type_id = await GetTypeIdFromObject(object_id, false, token); + var type_id = await GetTypeIdsForObject(object_id, false, token); return await GetTypeName(type_id[0], token); } @@ -1665,24 +1596,50 @@ public async Task GetDelegateMethodDescription(int objectId, Cancellatio return $"{returnType} {methodName} {parameters}"; } - public async Task InvokeMethod(ArraySegment valueTypeBuffer, int methodId, string varName, CancellationToken token) + + public async Task InvokeMethod(ArraySegment argsBuffer, int methodId, CancellationToken token, string name = null) { using var commandParamsWriter = new MonoBinaryWriter(); commandParamsWriter.Write(methodId); - commandParamsWriter.Write(valueTypeBuffer); + commandParamsWriter.Write(argsBuffer); commandParamsWriter.Write(0); using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdVM.InvokeMethod, commandParamsWriter, token); retDebuggerCmdReader.ReadByte(); //number of objects returned. - return await CreateJObjectForVariableValue(retDebuggerCmdReader, varName, false, -1, false, token); + return await CreateJObjectForVariableValue(retDebuggerCmdReader, name, token); + } + + public Task InvokeMethod(int objectId, int methodId, bool isValueType, CancellationToken token) + { + if (isValueType) + { + return valueTypes.TryGetValue(objectId, out var valueType) + ? InvokeMethod(valueType.Buffer, methodId, token) + : throw new ArgumentException($"Could not find valuetype with id {objectId}, for method id: {methodId}", nameof(objectId)); + } + else + { + using var commandParamsObjWriter = new MonoBinaryWriter(); + commandParamsObjWriter.Write(ElementType.Class, objectId); + return InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, token); + } } - public async Task InvokeMethodInObject(DotnetObjectId objectId, int methodId, string varName, CancellationToken token) + public Task InvokeMethod(DotnetObjectId dotnetObjectId, CancellationToken token, int methodId = -1) { - if (objectId.IsValueType && valueTypes.TryGetValue(objectId.Value, out var valueType)) - return await InvokeMethod(valueType.Buffer, methodId, varName, token); - using var commandParamsObjWriter = new MonoBinaryWriter(); - commandParamsObjWriter.Write(ElementType.Class, objectId.Value); - return await InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, varName, token); + if (dotnetObjectId.Scheme == "method") + { + JObject args = dotnetObjectId.ValueAsJson; + int? objectId = args["containerId"]?.Value(); + int? embeddedMethodId = args["methodId"]?.Value(); + + return objectId == null || embeddedMethodId == null + ? throw new ArgumentException($"Invalid object id for a method, with missing container, or methodId", nameof(dotnetObjectId)) + : InvokeMethod(objectId.Value, embeddedMethodId.Value, isValueType: args["isValueType"]?.Value() == true, token); + } + + return dotnetObjectId.Scheme is "object" or "valuetype" + ? InvokeMethod(dotnetObjectId.Value, methodId, isValueType: dotnetObjectId.IsValueType, token) + : throw new ArgumentException($"Cannot invoke method with id {methodId} on {dotnetObjectId}", nameof(dotnetObjectId)); } public async Task GetPropertyMethodIdByName(int typeId, string propertyName, CancellationToken token) @@ -1707,68 +1664,12 @@ public async Task GetPropertyMethodIdByName(int typeId, string propertyName return -1; } - public async Task CreateJArrayForProperties(int typeId, ElementType elementType, ArraySegment object_buffer, JArray attributes, bool isAutoExpandable, string objectIdStr, bool isOwn, CancellationToken token) - { - JArray ret = new JArray(); - using var retDebuggerCmdReader = await GetTypePropertiesReader(typeId, token); - if (retDebuggerCmdReader == null) - return null; - if (!DotnetObjectId.TryParse(objectIdStr, out DotnetObjectId objectId)) - return null; - var nProperties = retDebuggerCmdReader.ReadInt32(); - for (int i = 0 ; i < nProperties; i++) - { - retDebuggerCmdReader.ReadInt32(); //propertyId - string propertyNameStr = retDebuggerCmdReader.ReadString(); - var getMethodId = retDebuggerCmdReader.ReadInt32(); - retDebuggerCmdReader.ReadInt32(); //setmethod - var attrs = retDebuggerCmdReader.ReadInt32(); //attrs - if (getMethodId == 0 || await GetParamCount(getMethodId, token) != 0 || await MethodIsStatic(getMethodId, token)) - continue; - JObject propRet = null; - if (attributes.Where(attribute => attribute["name"].Value().Equals(propertyNameStr)).Any()) - continue; - if (isAutoExpandable) - { - try { - propRet = await InvokeMethod(object_buffer, getMethodId, propertyNameStr, token); - } - catch (Exception) - { - continue; - } - } - else - { - propRet = JObject.FromObject(new { - get = new - { - type = "function", - objectId = $"dotnet:methodId:{objectId.Value}:{getMethodId}:{elementType}", - className = "Function", - description = "get " + propertyNameStr + " ()", - methodId = getMethodId, - objectIdValue = objectIdStr - }, - name = propertyNameStr - }); - } - if (isOwn) - propRet["isOwn"] = true; - ret.Add(propRet); - } - return ret; - } public async Task GetPointerContent(int pointerId, CancellationToken token) { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(pointerValues[pointerId].address); - commandParamsWriter.Write(pointerValues[pointerId].typeId); - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdPointer.GetValue, commandParamsWriter, token); - var varName = pointerValues[pointerId].varName; - if (int.TryParse(varName, out _)) - varName = $"[{varName}]"; - return await CreateJObjectForVariableValue(retDebuggerCmdReader, "*" + varName, false, -1, false, token); + if (!pointerValues.TryGetValue(pointerId, out PointerValue pointerValue)) + throw new ArgumentException($"Could not find any pointer with id: {pointerId}", nameof(pointerId)); + + return await pointerValue.GetValue(this, token); } public static bool AutoExpandable(string className) { @@ -1789,7 +1690,7 @@ private static bool AutoInvokeToString(string className) { return false; } - private static JObject CreateJObject(T value, string type, string description, bool writable, string className = null, string objectId = null, string __custom_type = null, string subtype = null, bool isValueType = false, bool expanded = false, bool isEnum = false) + public static JObject CreateJObject(T value, string type, string description, bool writable, string className = null, string objectId = null, string __custom_type = null, string subtype = null, bool isValueType = false, bool expanded = false, bool isEnum = false) { var ret = JObject.FromObject(new { value = new @@ -1850,7 +1751,7 @@ public async Task CreateJObjectForPtr(ElementType etype, MonoBinaryRead int pointerId = -1; if (valueAddress != 0 && className != "(void*)") { - pointerId = Interlocked.Increment(ref debuggerObjectId); + pointerId = GetNewObjectId(); type = "object"; value = className; pointerValues[pointerId] = new PointerValue(valueAddress, typeId, name); @@ -1886,11 +1787,11 @@ public async Task CreateJObjectForArray(MonoBinaryReader retDebuggerCmd public async Task CreateJObjectForObject(MonoBinaryReader retDebuggerCmdReader, int typeIdFromAttribute, bool forDebuggerDisplayAttribute, CancellationToken token) { var objectId = retDebuggerCmdReader.ReadInt32(); - var type_id = await GetTypeIdFromObject(objectId, false, token); + var type_id = await GetTypeIdsForObject(objectId, false, token); string className = await GetTypeName(type_id[0], token); string debuggerDisplayAttribute = null; if (!forDebuggerDisplayAttribute) - debuggerDisplayAttribute = await GetValueFromDebuggerDisplayAttribute(objectId, type_id[0], token); + debuggerDisplayAttribute = await GetValueFromDebuggerDisplayAttribute(new DotnetObjectId("object", objectId), type_id[0], token); var description = className.ToString(); if (debuggerDisplayAttribute != null) @@ -1899,65 +1800,77 @@ public async Task CreateJObjectForObject(MonoBinaryReader retDebuggerCm if (await IsDelegate(objectId, token)) { if (typeIdFromAttribute != -1) - { className = await GetTypeName(typeIdFromAttribute, token); - } description = await GetDelegateMethodDescription(objectId, token); if (description == "") - { return CreateJObject(className.ToString(), "symbol", className.ToString(), false); - } } return CreateJObject(null, "object", description, false, className, $"dotnet:object:{objectId}"); } - public async Task CreateJObjectForValueType(MonoBinaryReader retDebuggerCmdReader, string name, long initialPos, CancellationToken token) + private static readonly string[] s_primitiveTypeNames = new[] { - JObject fieldValueType = null; - var isEnum = retDebuggerCmdReader.ReadByte(); + "bool", + "char", + "string", + "byte", + "sbyte", + "int", + "uint", + "long", + "ulong", + "short", + "ushort", + "float", + "double", + }; + + public static bool IsPrimitiveType(string simplifiedClassName) + => s_primitiveTypeNames.Contains(simplifiedClassName); + + public async Task CreateJObjectForValueType( + MonoBinaryReader retDebuggerCmdReader, string name, long initialPos, bool forDebuggerDisplayAttribute, CancellationToken token) + { + // FIXME: debugger proxy + var isEnum = retDebuggerCmdReader.ReadByte() == 1; var isBoxed = retDebuggerCmdReader.ReadByte() == 1; var typeId = retDebuggerCmdReader.ReadInt32(); var className = await GetTypeName(typeId, token); - var description = className; - var numFields = retDebuggerCmdReader.ReadInt32(); - var fields = await GetTypeFields(typeId, token); - JArray valueTypeFields = new JArray(); - if (className.IndexOf("System.Nullable<") == 0) //should we call something on debugger-agent to check??? + var numValues = retDebuggerCmdReader.ReadInt32(); + + if (className.IndexOf("System.Nullable<", StringComparison.Ordinal) == 0) //should we call something on debugger-agent to check??? { retDebuggerCmdReader.ReadByte(); //ignoring the boolean type var isNull = retDebuggerCmdReader.ReadInt32(); - var value = await CreateJObjectForVariableValue(retDebuggerCmdReader, name, false, -1, false, token); + + // Read the value, even if isNull==true, to correctly advance the reader + var value = await CreateJObjectForVariableValue(retDebuggerCmdReader, name, token); if (isNull != 0) return value; else return CreateJObject(null, "object", className, false, className, null, null, "null", true); } - for (int i = 0; i < numFields ; i++) + if (isBoxed && numValues == 1) { - fieldValueType = await CreateJObjectForVariableValue(retDebuggerCmdReader, fields.ElementAt(i).Name, true, fields.ElementAt(i).TypeId, false, token); - valueTypeFields.Add(fieldValueType); + if (IsPrimitiveType(className)) + { + var value = await CreateJObjectForVariableValue(retDebuggerCmdReader, name: null, token); + return value; + } } - long endPos = retDebuggerCmdReader.BaseStream.Position; - var valueTypeId = Interlocked.Increment(ref debuggerObjectId); - - retDebuggerCmdReader.BaseStream.Position = initialPos; - byte[] valueTypeBuffer = new byte[endPos - initialPos]; - retDebuggerCmdReader.Read(valueTypeBuffer, 0, (int)(endPos - initialPos)); - retDebuggerCmdReader.BaseStream.Position = endPos; - valueTypes[valueTypeId] = new ValueTypeClass(valueTypeBuffer, valueTypeFields, typeId, AutoExpandable(className), valueTypeId); - if (AutoInvokeToString(className) || isEnum == 1) { - int methodId = await GetMethodIdByName(typeId, "ToString", token); - var retMethod = await InvokeMethod(valueTypeBuffer, methodId, "methodRet", token); - description = retMethod["value"]?["value"].Value(); - if (className.Equals("System.Guid")) - description = description.ToUpperInvariant(); //to keep the old behavior - } - else if (isBoxed && numFields == 1) { - return fieldValueType; - } - return CreateJObject(null, "object", description, false, className, $"dotnet:valuetype:{valueTypeId}", null, null, true, true, isEnum == 1); + ValueTypeClass valueType = await ValueTypeClass.CreateFromReader( + this, + retDebuggerCmdReader, + initialPos, + className, + typeId, + numValues, + isEnum, + token); + valueTypes[valueType.Id.Value] = valueType; + return await valueType.ToJObject(this, forDebuggerDisplayAttribute, token); } public async Task CreateJObjectForNull(MonoBinaryReader retDebuggerCmdReader, CancellationToken token) @@ -1996,9 +1909,14 @@ public async Task CreateJObjectForNull(MonoBinaryReader retDebuggerCmdR return CreateJObject(null, "object", className, false, className, null, null, "null"); } - public async Task CreateJObjectForVariableValue(MonoBinaryReader retDebuggerCmdReader, string name, bool isOwn, int typeIdFromAttribute, bool forDebuggerDisplayAttribute, CancellationToken token) + public async Task CreateJObjectForVariableValue(MonoBinaryReader retDebuggerCmdReader, + string name, + CancellationToken token, + bool isOwn = false, + int typeIdForObject = -1, + bool forDebuggerDisplayAttribute = false) { - long initialPos = retDebuggerCmdReader == null ? 0 : retDebuggerCmdReader.BaseStream.Position; + long initialPos = /*retDebuggerCmdReader == null ? 0 : */retDebuggerCmdReader.BaseStream.Position; ElementType etype = (ElementType)retDebuggerCmdReader.ReadByte(); JObject ret = null; switch (etype) { @@ -2102,12 +2020,12 @@ public async Task CreateJObjectForVariableValue(MonoBinaryReader retDeb case ElementType.Class: case ElementType.Object: { - ret = await CreateJObjectForObject(retDebuggerCmdReader, typeIdFromAttribute, forDebuggerDisplayAttribute, token); + ret = await CreateJObjectForObject(retDebuggerCmdReader, typeIdForObject, forDebuggerDisplayAttribute, token); break; } case ElementType.ValueType: { - ret = await CreateJObjectForValueType(retDebuggerCmdReader, name, initialPos, token); + ret = await CreateJObjectForValueType(retDebuggerCmdReader, name, initialPos, forDebuggerDisplayAttribute, token); break; } case (ElementType)ValueTypeId.Null: @@ -2130,7 +2048,8 @@ public async Task CreateJObjectForVariableValue(MonoBinaryReader retDeb { if (isOwn) ret["isOwn"] = true; - ret["name"] = name; + if (!string.IsNullOrEmpty(name)) + ret["name"] = name; } return ret; } @@ -2163,7 +2082,7 @@ private static bool IsClosureReferenceField (string fieldName) fieldName.StartsWith ("<>8__", StringComparison.Ordinal); } - public async Task GetHoistedLocalVariables(int objectId, JArray asyncLocals, CancellationToken token) + public async Task GetHoistedLocalVariables(int objectId, IEnumerable asyncLocals, CancellationToken token) { JArray asyncLocalsFull = new JArray(); List objectsAlreadyRead = new(); @@ -2182,8 +2101,9 @@ public async Task GetHoistedLocalVariables(int objectId, JArray asyncLoc { if (!objectsAlreadyRead.Contains(dotnetObjectId.Value)) { - var asyncLocalsFromObject = await GetObjectValues(dotnetObjectId.Value, GetObjectCommandOptions.WithProperties, token); - var hoistedLocalVariable = await GetHoistedLocalVariables(dotnetObjectId.Value, asyncLocalsFromObject, token); + var asyncProxyMembersFromObject = await MemberObjectsExplorer.GetObjectMemberValues( + this, dotnetObjectId.Value, GetObjectCommandOptions.WithProperties, token); + var hoistedLocalVariable = await GetHoistedLocalVariables(dotnetObjectId.Value, asyncProxyMembersFromObject.Flatten(), token); asyncLocalsFull = new JArray(asyncLocalsFull.Union(hoistedLocalVariable)); } } @@ -2223,8 +2143,8 @@ public async Task StackFrameGetValues(MethodInfoWithDebugInformation met using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdFrame.GetThis, commandParamsWriter, token); retDebuggerCmdReader.ReadByte(); //ignore type var objectId = retDebuggerCmdReader.ReadInt32(); - var asyncLocals = await GetObjectValues(objectId, GetObjectCommandOptions.WithProperties, token); - asyncLocals = await GetHoistedLocalVariables(objectId, asyncLocals, token); + GetMembersResult asyncProxyMembers = await MemberObjectsExplorer.GetObjectMemberValues(this, objectId, GetObjectCommandOptions.WithProperties, token); + var asyncLocals = await GetHoistedLocalVariables(objectId, asyncProxyMembers.Flatten(), token); return asyncLocals; } @@ -2234,7 +2154,7 @@ public async Task StackFrameGetValues(MethodInfoWithDebugInformation met { try { - var var_json = await CreateJObjectForVariableValue(localsDebuggerCmdReader, var.Name, false, -1, false, token); + var var_json = await CreateJObjectForVariableValue(localsDebuggerCmdReader, var.Name, token); locals.Add(var_json); } catch (Exception ex) @@ -2246,7 +2166,7 @@ public async Task StackFrameGetValues(MethodInfoWithDebugInformation met if (!method.Info.IsStatic()) { using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdFrame.GetThis, commandParamsWriter, token); - var var_json = await CreateJObjectForVariableValue(retDebuggerCmdReader, "this", false, -1, false, token); + var var_json = await CreateJObjectForVariableValue(retDebuggerCmdReader, "this", token); var_json.Add("fieldOffset", -1); locals.Add(var_json); } @@ -2272,7 +2192,7 @@ public async Task GetArrayValues(int arrayId, CancellationToken token) JArray array = new JArray(); for (int i = 0 ; i < dimensions.TotalLength; i++) { - var var_json = await CreateJObjectForVariableValue(retDebuggerCmdReader, dimensions.GetArrayIndexString(i), isOwn : false, -1, forDebuggerDisplayAttribute : false, token); + var var_json = await CreateJObjectForVariableValue(retDebuggerCmdReader, dimensions.GetArrayIndexString(i), token); array.Add(var_json); } return array; @@ -2329,24 +2249,76 @@ public async Task GetTypeByName(string typeToSearch, CancellationToken toke return retDebuggerCmdReader.ReadInt32(); } - public async Task GetValuesFromDebuggerProxyAttribute(int objectId, int typeId, CancellationToken token) + // FIXME: support valuetypes + public async Task GetValuesFromDebuggerProxyAttribute(int objectId, int typeId, CancellationToken token) { - try { - var getCAttrsRetReader = await GetCAttrsFromType(objectId, typeId, "System.Diagnostics.DebuggerTypeProxyAttribute", token); - var methodId = -1; - if (getCAttrsRetReader == null) + try + { + int methodId = await FindDebuggerProxyConstructorIdFor(typeId, token); + if (methodId == -1) + { + logger.LogInformation($"GetValuesFromDebuggerProxyAttribute: could not find proxy constructor id for objectId {objectId}"); return null; - using var invokeParamsWriter = new MonoBinaryWriter(); - invokeParamsWriter.Write((byte)ValueTypeId.Null); - invokeParamsWriter.Write((byte)0); //not used - invokeParamsWriter.Write(0); //not used + } + + using var ctorArgsWriter = new MonoBinaryWriter(); + ctorArgsWriter.Write((byte)ValueTypeId.Null); + + // FIXME: move method invocation to valueTypeclass? + if (valueTypes.TryGetValue(objectId, out var valueType)) + { + //FIXME: Issue #68390 + //ctorArgsWriter.Write((byte)0); //not used but needed + //ctorArgsWriter.Write(0); //not used but needed + //ctorArgsWriter.Write((int)1); // num args + //ctorArgsWriter.Write(valueType.Buffer); + return null; + } + else + { + ctorArgsWriter.Write((byte)0); //not used + ctorArgsWriter.Write(0); //not used + ctorArgsWriter.Write((int)1); // num args + ctorArgsWriter.Write((byte)ElementType.Object); + ctorArgsWriter.Write(objectId); + } + + var retMethod = await InvokeMethod(ctorArgsWriter.GetParameterBuffer(), methodId, token); + logger.LogInformation($"* GetValuesFromDebuggerProxyAttribute got from InvokeMethod: {retMethod}"); + if (!DotnetObjectId.TryParse(retMethod?["value"]?["objectId"]?.Value(), out DotnetObjectId dotnetObjectId)) + throw new Exception($"Invoking .ctor ({methodId}) for DebuggerTypeProxy on type {typeId} returned {retMethod}"); + + GetMembersResult members = await GetTypeMemberValues(dotnetObjectId, + GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.ForDebuggerProxyAttribute, + token); + + return members; + } + catch (Exception e) + { + logger.LogInformation($"Could not evaluate DebuggerTypeProxyAttribute of type {await GetTypeName(typeId, token)} - {e}"); + } + + return null; + } + + private async Task FindDebuggerProxyConstructorIdFor(int typeId, CancellationToken token) + { + try + { + var getCAttrsRetReader = await GetCAttrsFromType(typeId, "System.Diagnostics.DebuggerTypeProxyAttribute", token); + if (getCAttrsRetReader == null) + return -1; + + var methodId = -1; var parmCount = getCAttrsRetReader.ReadInt32(); - invokeParamsWriter.Write((int)1); for (int j = 0; j < parmCount; j++) { var monoTypeId = getCAttrsRetReader.ReadByte(); + // FIXME: DebuggerTypeProxyAttribute(string) - not supported if ((ValueTypeId)monoTypeId != ValueTypeId.Type) continue; + var cAttrTypeId = getCAttrsRetReader.ReadInt32(); using var commandParamsWriter = new MonoBinaryWriter(); commandParamsWriter.Write(cAttrTypeId); @@ -2364,320 +2336,52 @@ public async Task GetValuesFromDebuggerProxyAttribute(int objectId, int var assemblyIdArg = await GetAssemblyIdFromType(genericTypeArgs[k], token); var assemblyNameArg = await GetFullAssemblyName(assemblyIdArg, token); var classNameArg = await GetTypeNameOriginal(genericTypeArgs[k], token); - typeToSearch += classNameArg +", " + assemblyNameArg; + typeToSearch += classNameArg + ", " + assemblyNameArg; if (k + 1 < genericTypeArgs.Count) typeToSearch += "], ["; else typeToSearch += "]"; } typeToSearch += "]"; - typeToSearch += ", " + assemblyName; + typeToSearch += ", " + assemblyName; var genericTypeId = await GetTypeByName(typeToSearch, token); if (genericTypeId < 0) - return null; - methodId = await GetMethodIdByName(genericTypeId, ".ctor", token); + break; + cAttrTypeId = genericTypeId; } - else - methodId = await GetMethodIdByName(cAttrTypeId, ".ctor", token); - invokeParamsWriter.Write((byte)ElementType.Object); - invokeParamsWriter.Write(objectId); - - var retMethod = await InvokeMethod(invokeParamsWriter.GetParameterBuffer(), methodId, "methodRet", token); - DotnetObjectId.TryParse(retMethod?["value"]?["objectId"]?.Value(), out DotnetObjectId dotnetObjectId); - var displayAttrs = await GetObjectValues(dotnetObjectId.Value, GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.ForDebuggerProxyAttribute, token); - return displayAttrs; + methodId = await GetMethodIdByName(cAttrTypeId, ".ctor", token); + break; } + + return methodId; } catch (Exception e) { logger.LogDebug($"Could not evaluate DebuggerTypeProxyAttribute of type {await GetTypeName(typeId, token)} - {e}"); } - return null; - } - - public async Task GetObjectValues(int objectId, GetObjectCommandOptions getCommandType, CancellationToken token, bool sortByAccessLevel = false) - { - var typeIdsIncludingParents = await GetTypeIdFromObject(objectId, true, token); - if (!getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerDisplayAttribute)) - { - var debuggerProxy = await GetValuesFromDebuggerProxyAttribute(objectId, typeIdsIncludingParents[0], token); - if (debuggerProxy != null) - return sortByAccessLevel ? - new JArray(JObject.FromObject( new { result = debuggerProxy, internalProperties = new JArray(), privateProperties = new JArray() } )) : - debuggerProxy; - } - if (await IsDelegate(objectId, token)) - { - var description = await GetDelegateMethodDescription(objectId, token); - var objValues = new JArray(JObject.FromObject(new - { - value = new - { - type = "symbol", - value = description, - description - }, - name = "Target" - })); - return sortByAccessLevel ? - new JArray(JObject.FromObject(new { result = objValues, internalProperties = new JArray(), privateProperties = new JArray() })) : - objValues; - } - var allFields = new List(); - var objects = new Dictionary(); - for (int i = 0; i < typeIdsIncludingParents.Count; i++) - { - int typeId = typeIdsIncludingParents[i]; - // 0th id is for the object itself, and then its parents - bool isOwn = i == 0; - var fields = await GetTypeFields(typeId, token); - allFields.AddRange(fields); - - if (!getCommandType.HasFlag(GetObjectCommandOptions.AccessorPropertiesOnly)) - { - var (collapsedFields, rootHiddenFields) = await FilterFieldsByDebuggerBrowsable(fields, typeId, token); - - var collapsedFieldsValues = await GetFieldsValues(collapsedFields, isOwn); - var hiddenFieldsValues = await GetFieldsValues(rootHiddenFields, isOwn, isRootHidden: true); - - objects.TryAddRange(collapsedFieldsValues); - objects.TryAddRange(hiddenFieldsValues); - } - - if (!getCommandType.HasFlag(GetObjectCommandOptions.WithProperties)) - return sortByAccessLevel ? - SegregatePropertiesByAccessLevel(allFields, objects, token) : - new JArray(objects.Values); - - using var commandParamsObjWriter = new MonoBinaryWriter(); - commandParamsObjWriter.WriteObj(new DotnetObjectId("object", objectId), this); - var props = await CreateJArrayForProperties( - typeId, - ElementType.Class, - commandParamsObjWriter.GetParameterBuffer(), - new JArray(objects.Values), - getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute), - $"dotnet:object:{objectId}", - i == 0, - token); - var properties = await GetProperties(props, allFields, 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; - /*if (accessorPropertiesOnly) - break;*/ - } - return sortByAccessLevel ? - SegregatePropertiesByAccessLevel(allFields, objects, token) : - new JArray(objects.Values); - - JArray SegregatePropertiesByAccessLevel(List fields, Dictionary objectsDict, CancellationToken token) - { - if (fields.Count == 0) - return new JArray(JObject.FromObject(new { result = new JArray(objectsDict.Values) })); - - var pubNames = fields.Where(field => field.ProtectionLevel == FieldAttributes.Public) - .Select(field => field.Name).ToList(); - var privNames = fields.Where(field => - (field.ProtectionLevel == FieldAttributes.Private || - field.ProtectionLevel == FieldAttributes.FamANDAssem) && - // when field is inherited it is listed both in Private and Public, to avoid duplicates: - pubNames.All(pubFieldName => pubFieldName != field.Name)) - .Select(field => field.Name).ToList(); - //protected == family, internal == assembly - var protectedAndInternalNames = fields.Where(field => - field.ProtectionLevel == FieldAttributes.Family || - field.ProtectionLevel == FieldAttributes.Assembly || - field.ProtectionLevel == FieldAttributes.FamORAssem) - .Select(field => field.Name).ToList(); - var accessorProperties = objectsDict - .Where(obj => ( - pubNames.All(name => name != obj.Key) && - privNames.All(name => name != obj.Key) && - protectedAndInternalNames.All(name => name != obj.Key))) - .Select((k, v) => k.Value); - - var pubProperties = objectsDict.GetValuesByKeys(pubNames); - var privProperties = objectsDict.GetValuesByKeys(privNames); - var protAndIntProperties = objectsDict.GetValuesByKeys(protectedAndInternalNames); - pubProperties.AddRange(new JArray(accessorProperties)); - - return new JArray(JObject.FromObject(new - { - result = pubProperties, - internalProperties = protAndIntProperties, - privateProperties = privProperties - })); - } - - 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") - { - 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.IsNotPrivate).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(); + return -1; + } - fieldValue.Add("set", JObject.FromObject(new - { - commandSet = CommandSet.ObjectRef, - command = CmdObject.RefSetValues, - buffer = data, - valtype, - length, - id = GetNewId() - })); - } - if (!isRootHidden) - { - objFields.Add(fieldValue); - continue; - } - await AppendRootHiddenChildren(fieldValue, objFields); - } - return objFields; - } + public Task GetTypeMemberValues(DotnetObjectId dotnetObjectId, GetObjectCommandOptions getObjectOptions, CancellationToken token, bool sortByAccessLevel = false) + => dotnetObjectId.IsValueType + ? MemberObjectsExplorer.GetValueTypeMemberValues(this, dotnetObjectId.Value, getObjectOptions, token) + : MemberObjectsExplorer.GetObjectMemberValues(this, dotnetObjectId.Value, getObjectOptions, token); - 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) - { - if (!typeFieldsBrowsableInfo.TryGetValue(field.Name, out DebuggerBrowsableState? state)) - { - if (!typeProperitesBrowsableInfo.TryGetValue(field.Name, out DebuggerBrowsableState? propState)) - { - collapsedFields.Add(field); - continue; - } - 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}"); - } - } - return (collapsedFields, rootHiddenFields); - } - async Task GetProperties(JArray props, List fields, int typeId, CancellationToken token) - { - var typeInfo = await GetTypeInfo(typeId, token); - var typeProperitesBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableProperties; - var regularProps = new JArray(); - foreach (var p in props) - { - var propName = p["name"].Value(); - // if property has a backing field - avoid adding a duplicate - if (fields.Any(field => field.Name == propName)) - continue; - if (!typeProperitesBrowsableInfo.TryGetValue(propName, out DebuggerBrowsableState? state)) - { - regularProps.Add(p); - continue; - } - switch (state) - { - case DebuggerBrowsableState.Never: - break; - case DebuggerBrowsableState.RootHidden: - DotnetObjectId rootObjId; - DotnetObjectId.TryParse(p["get"]["objectId"].Value(), out rootObjId); - var rootObject = await InvokeMethodInObject(rootObjId, rootObjId.SubValue, propName, token); - await AppendRootHiddenChildren(rootObject, regularProps); - break; - case DebuggerBrowsableState.Collapsed: - regularProps.Add(p); - break; - default: - throw new NotImplementedException($"DebuggerBrowsableState: {state}"); - } - } - return regularProps; - } + public async Task GetMethodProxy(JObject objectId, CancellationToken token) + { + var containerId = objectId["containerId"].Value(); + var methodId = objectId["methodId"].Value(); + var isValueType = objectId["isValueType"].Value(); + return await InvokeMethod(containerId, methodId, isValueType, token); } public async Task GetObjectProxy(int objectId, CancellationToken token) { - var ret = await GetObjectValues(objectId, GetObjectCommandOptions.WithSetter, token); - var typeIds = await GetTypeIdFromObject(objectId, true, token); + GetMembersResult members = await MemberObjectsExplorer.GetObjectMemberValues(this, objectId, GetObjectCommandOptions.WithSetter, token); + JArray ret = members.Flatten(); + var typeIds = await GetTypeIdsForObject(objectId, true, token); foreach (var typeId in typeIds) { var retDebuggerCmdReader = await GetTypePropertiesReader(typeId, token); @@ -2810,29 +2514,6 @@ public static void AddRange(this JArray arr, JArray 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); - } - } - - public static JArray GetValuesByKeys(this Dictionary dict, List keys) - { - var result = new JArray(); - foreach (var name in keys) - { - if (!dict.TryGetValue(name, out var obj)) - continue; - result.Add(obj); - } - return result; - } - public static bool IsNullValuedObject(this JObject obj) => obj != null && obj["type"]?.Value() == "object" && obj["subtype"]?.Value() == "null"; diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs new file mode 100644 index 00000000000000..a9732a5224bcc6 --- /dev/null +++ b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs @@ -0,0 +1,296 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.WebAssembly.Diagnostics; +using Newtonsoft.Json.Linq; + +namespace BrowserDebugProxy +{ + internal sealed class ValueTypeClass + { + private readonly bool autoExpand; + private JArray proxy; + private GetMembersResult _combinedResult; + private bool propertiesExpanded; + private bool fieldsExpanded; + private string className; + private JArray fields; + + public DotnetObjectId Id { get; init; } + public byte[] Buffer { get; init; } + public int TypeId { get; init; } + public bool IsEnum { get; init; } + + public ValueTypeClass(byte[] buffer, string className, JArray fields, int typeId, bool isEnum) + { + var valueTypeId = MonoSDBHelper.GetNewObjectId(); + var objectId = new DotnetObjectId("valuetype", valueTypeId); + + Buffer = buffer; + this.fields = fields; + this.className = className; + TypeId = typeId; + autoExpand = ShouldAutoExpand(className); + Id = objectId; + IsEnum = isEnum; + } + + public override string ToString() => $"{{ ValueTypeClass: typeId: {TypeId}, Id: {Id}, Id: {Id}, fields: {fields} }}"; + + public static async Task CreateFromReader( + MonoSDBHelper sdbAgent, + MonoBinaryReader cmdReader, + long initialPos, + string className, + int typeId, + int numValues, + bool isEnum, + CancellationToken token) + { + var typeInfo = await sdbAgent.GetTypeInfo(typeId, token); + var typeFieldsBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableFields; + var typePropertiesBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableProperties; + + IReadOnlyList fieldTypes = await sdbAgent.GetTypeFields(typeId, token); + // statics should not be in valueType fields: CallFunctionOnTests.PropertyGettersTest + IEnumerable writableFields = fieldTypes + .Where(f => !f.Attributes.HasFlag(FieldAttributes.Literal) + && !f.Attributes.HasFlag(FieldAttributes.Static)); + + JArray fields = new(); + foreach (var field in writableFields) + { + var fieldValue = await sdbAgent.CreateJObjectForVariableValue(cmdReader, field.Name, token, true, field.TypeId, false); + + fieldValue["__section"] = field.Attributes switch + { + FieldAttributes.Private => "private", + FieldAttributes.Public => "result", + _ => "internal" + }; + + if (field.IsBackingField) + fieldValue["__isBackingField"] = true; + else + { + typeFieldsBrowsableInfo.TryGetValue(field.Name, out DebuggerBrowsableState? state); + fieldValue["__state"] = state?.ToString(); + } + + fields.Add(fieldValue); + } + + long endPos = cmdReader.BaseStream.Position; + cmdReader.BaseStream.Position = initialPos; + byte[] valueTypeBuffer = new byte[endPos - initialPos]; + cmdReader.Read(valueTypeBuffer, 0, (int)(endPos - initialPos)); + cmdReader.BaseStream.Position = endPos; + + return new ValueTypeClass(valueTypeBuffer, className, fields, typeId, isEnum); + } + + public async Task ToJObject(MonoSDBHelper sdbAgent, bool forDebuggerDisplayAttribute, CancellationToken token) + { + string description = className; + if (ShouldAutoInvokeToString(className) || IsEnum) + { + int methodId = await sdbAgent.GetMethodIdByName(TypeId, "ToString", token); + var retMethod = await sdbAgent.InvokeMethod(Buffer, methodId, token, "methodRet"); + description = retMethod["value"]?["value"].Value(); + if (className.Equals("System.Guid")) + description = description.ToUpperInvariant(); //to keep the old behavior + } + else if (!forDebuggerDisplayAttribute) + { + string displayString = await sdbAgent.GetValueFromDebuggerDisplayAttribute(Id, TypeId, token); + if (displayString != null) + description = displayString; + } + + var obj = MonoSDBHelper.CreateJObject(null, "object", description, false, className, Id.ToString(), null, null, true, true, IsEnum); + return obj; + } + + public async Task GetProxy(MonoSDBHelper sdbHelper, CancellationToken token) + { + if (proxy != null) + return proxy; + + var retDebuggerCmdReader = await sdbHelper.GetTypePropertiesReader(TypeId, token); + if (retDebuggerCmdReader == null) + return null; + + if (!fieldsExpanded) + { + await ExpandedFieldValues(sdbHelper, includeStatic: false, token); + fieldsExpanded = true; + } + proxy = new JArray(fields); + + var nProperties = retDebuggerCmdReader.ReadInt32(); + + for (int i = 0; i < nProperties; i++) + { + retDebuggerCmdReader.ReadInt32(); //propertyId + string propertyNameStr = retDebuggerCmdReader.ReadString(); + + var getMethodId = retDebuggerCmdReader.ReadInt32(); + retDebuggerCmdReader.ReadInt32(); //setmethod + retDebuggerCmdReader.ReadInt32(); //attrs + if (await sdbHelper.MethodIsStatic(getMethodId, token)) + continue; + using var command_params_writer_to_proxy = new MonoBinaryWriter(); + command_params_writer_to_proxy.Write(getMethodId); + command_params_writer_to_proxy.Write(Buffer); + command_params_writer_to_proxy.Write(0); + + var (data, length) = command_params_writer_to_proxy.ToBase64(); + proxy.Add(JObject.FromObject(new + { + get = JObject.FromObject(new + { + commandSet = CommandSet.Vm, + command = CmdVM.InvokeMethod, + buffer = data, + length = length, + id = MonoSDBHelper.GetNewId() + }), + name = propertyNameStr + })); + } + return proxy; + } + + public async Task GetMemberValues( + MonoSDBHelper sdbHelper, GetObjectCommandOptions getObjectOptions, bool sortByAccessLevel, bool includeStatic, CancellationToken token) + { + // 1 + if (!propertiesExpanded) + { + await ExpandPropertyValues(sdbHelper, sortByAccessLevel, includeStatic, token); + propertiesExpanded = true; + } + + // 2 + GetMembersResult result = null; + if (!getObjectOptions.HasFlag(GetObjectCommandOptions.ForDebuggerDisplayAttribute)) + { + // FIXME: cache? + result = await sdbHelper.GetValuesFromDebuggerProxyAttribute(Id.Value, TypeId, token); + if (result != null) + Console.WriteLine($"Investigate GetValuesFromDebuggerProxyAttribute\n{result}. There was a change of logic from loop to one iteration"); + } + + if (result == null && getObjectOptions.HasFlag(GetObjectCommandOptions.AccessorPropertiesOnly)) + { + // 3 - just properties, skip fields + result = _combinedResult.Clone(); + RemovePropertiesFrom(result.Result); + RemovePropertiesFrom(result.PrivateMembers); + RemovePropertiesFrom(result.OtherMembers); + } + + if (result == null) + { + // 4 - fields + properties + result = _combinedResult.Clone(); + } + + return result; + + static void RemovePropertiesFrom(JArray collection) + { + List toRemove = new(); + foreach (JToken jt in collection) + { + if (jt is not JObject obj || obj["get"] != null) + continue; + toRemove.Add(jt); + } + foreach (var jt in toRemove) + { + collection.Remove(jt); + } + } + } + + public async Task ExpandedFieldValues(MonoSDBHelper sdbHelper, bool includeStatic, CancellationToken token) + { + JArray visibleFields = new(); + foreach (JObject field in fields) + { + if (!Enum.TryParse(field["__state"]?.Value(), out DebuggerBrowsableState state)) + { + visibleFields.Add(field); + continue; + } + var fieldValue = field["value"] ?? field["get"]; + string typeName = fieldValue?["className"]?.Value(); + JArray fieldMembers = await MemberObjectsExplorer.GetExpandedMemberValues( + sdbHelper, typeName, field["name"]?.Value(), field, state, includeStatic, token); + visibleFields.AddRange(fieldMembers); + } + fields = visibleFields; + } + + public async Task ExpandPropertyValues(MonoSDBHelper sdbHelper, bool splitMembersByAccessLevel, bool includeStatic, CancellationToken token) + { + using var commandParamsWriter = new MonoBinaryWriter(); + commandParamsWriter.Write(TypeId); + using MonoBinaryReader getParentsReader = await sdbHelper.SendDebuggerAgentCommand(CmdType.GetParents, commandParamsWriter, token); + int numParents = getParentsReader.ReadInt32(); + + if (!fieldsExpanded) + { + await ExpandedFieldValues(sdbHelper, includeStatic, token); + fieldsExpanded = true; + } + + var allMembers = new Dictionary(); + foreach (var f in fields) + allMembers[f["name"].Value()] = f as JObject; + + int typeId = TypeId; + var parentsCntPlusSelf = numParents + 1; + for (int i = 0; i < parentsCntPlusSelf; i++) + { + // isParent: + if (i != 0) typeId = getParentsReader.ReadInt32(); + + allMembers = await MemberObjectsExplorer.GetNonAutomaticPropertyValues( + sdbHelper, + typeId, + className, + Buffer, + autoExpand, + Id, + isValueType: true, + isOwn: i == 0, + token, + allMembers, + includeStatic); + } + _combinedResult = GetMembersResult.FromValues(allMembers.Values, splitMembersByAccessLevel); + } + + private static bool ShouldAutoExpand(string className) + => className is "System.DateTime" or + "System.DateTimeOffset" or + "System.TimeSpan"; + + private static bool ShouldAutoInvokeToString(string className) + => className is "System.DateTime" or + "System.DateTimeOffset" or + "System.TimeSpan" or + "System.Decimal" or + "System.Guid"; + } +} diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs index 447b06a7170c58..8bf434755105bf 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs @@ -386,6 +386,16 @@ internal void CheckContentValue(JToken token, string value) Assert.Equal(value, val); } + internal void CheckContainsJObject(JToken locals, JToken comparedTo, string name) + { + var val = GetAndAssertObjectWithName(locals, name); + JObject refValue = (JObject)val["value"]; + refValue?.Property("objectId")?.Remove(); + JObject comparedToValue = (JObject)comparedTo["value"]; + comparedToValue?.Property("objectId")?.Remove(); + Assert.Equal(val, comparedTo); + } + internal async Task CheckValueType(JToken locals, string name, string class_name, string description=null) { var l = GetAndAssertObjectWithName(locals, name); @@ -738,20 +748,21 @@ internal async Task CheckProps(JToken actual, object exp_o, string label, int nu Assert.True(actual_obj != null, $"[{label}] not value found for property named '{exp_name}'"); - var actual_val = actual_obj["value"]; if (exp_val.Type == JTokenType.Array) { - var actual_props = await GetProperties(actual_val["objectId"]?.Value()); + var actual_props = await GetProperties(actual_obj["value"]["objectId"]?.Value()); await CheckProps(actual_props, exp_val, $"{label}-{exp_name}"); } else if (exp_val["__custom_type"] != null && exp_val["__custom_type"]?.Value() == "getter") { // hack: for getters, actual won't have a .value + // are we doing it on purpose? Why? CHECK if properties are displayed in Browser/VS, if not revert the value field here + // we should be leaving properties, not their backing fields await CheckCustomType(actual_obj, exp_val, $"{label}#{exp_name}"); } else { - await CheckValue(actual_val, exp_val, $"{label}#{exp_name}"); + await CheckValue(actual_obj["value"], exp_val, $"{label}#{exp_name}"); } } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index ce77119109ebbc..1b693c113443b8 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -503,22 +503,22 @@ public async Task EvaluateSimpleMethodCallsError() => await CheckInspectLocalsAt "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateMethodTestsClass:EvaluateMethods'); })", wait_for_event_fn: async (pause_location) => { - var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); - var (_, res) = await EvaluateOnCallFrame(id, "this.objToTest.MyMethodWrong()", expect_ok: false ); - Assert.Contains($"Method 'MyMethodWrong' not found", res.Error["message"]?.Value()); + var (_, res) = await EvaluateOnCallFrame(id, "this.objToTest.MyMethodWrong()", expect_ok: false ); + Assert.Contains($"Method 'MyMethodWrong' not found", res.Error["message"]?.Value()); - (_, res) = await EvaluateOnCallFrame(id, "this.objToTest.MyMethod(1)", expect_ok: false); - Assert.Contains("Cannot invoke method 'this.objToTest.MyMethod(1)' - too many arguments passed", res.Error["message"]?.Value()); + (_, res) = await EvaluateOnCallFrame(id, "this.objToTest.MyMethod(1)", expect_ok: false); + Assert.Contains("Cannot invoke method 'this.objToTest.MyMethod(1)' - too many arguments passed", res.Error["message"]?.Value()); - (_, res) = await EvaluateOnCallFrame(id, "this.CallMethodWithParm(\"1\")", expect_ok: false ); - Assert.Contains("Unable to evaluate method 'this.CallMethodWithParm(\"1\")'", res.Error["message"]?.Value()); + (_, res) = await EvaluateOnCallFrame(id, "this.CallMethodWithParm(\"1\")", expect_ok: false ); + Assert.Contains("Unable to evaluate method 'this.CallMethodWithParm(\"1\")'", res.Error["message"]?.Value()); - (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjNull.MyMethod()", expect_ok: false ); - Assert.Contains("Expression 'this.ParmToTestObjNull.MyMethod' evaluated to null", res.Error["message"]?.Value()); + (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjNull.MyMethod()", expect_ok: false ); + Assert.Contains("Expression 'this.ParmToTestObjNull.MyMethod' evaluated to null", res.Error["message"]?.Value()); - (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjException.MyMethod()", expect_ok: false ); - Assert.Contains("Cannot invoke method 'MyMethod'", res.Error["message"]?.Value()); + (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjException.MyMethod()", expect_ok: false ); + Assert.Contains("Cannot invoke method 'MyMethod'", res.Error["message"]?.Value()); }); [Fact] @@ -679,18 +679,18 @@ public async Task EvaluateSimpleMethodCallsCheckChangedValue() => await CheckIns "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateMethodTestsClass:EvaluateMethods'); })", wait_for_event_fn: async (pause_location) => { - var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); - var frame = pause_location["callFrames"][0]; - var props = await GetObjectOnFrame(frame, "this"); - CheckNumber(props, "a", 1); + var frame = pause_location["callFrames"][0]; + var props = await GetObjectOnFrame(frame, "this"); + CheckNumber(props, "a", 1); - await EvaluateOnCallFrameAndCheck(id, - ("this.CallMethodChangeValue()", TObject("object", is_null : true))); + await EvaluateOnCallFrameAndCheck(id, + ("this.CallMethodChangeValue()", TObject("object", is_null : true))); - frame = pause_location["callFrames"][0]; - props = await GetObjectOnFrame(frame, "this"); - CheckNumber(props, "a", 11); + frame = pause_location["callFrames"][0]; + props = await GetObjectOnFrame(frame, "this"); + CheckNumber(props, "a", 11); }); [Fact] @@ -699,16 +699,16 @@ public async Task EvaluateStaticClass() => await CheckInspectLocalsAtBreakpointS "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateMethodTestsClass:EvaluateMethods'); })", wait_for_event_fn: async (pause_location) => { - var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); - var frame = pause_location["callFrames"][0]; + var frame = pause_location["callFrames"][0]; - await EvaluateOnCallFrameAndCheck(id, - ("DebuggerTests.EvaluateStaticClass.StaticField1", TNumber(10))); - await EvaluateOnCallFrameAndCheck(id, - ("DebuggerTests.EvaluateStaticClass.StaticProperty1", TString("StaticProperty1"))); - await EvaluateOnCallFrameAndCheck(id, - ("DebuggerTests.EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented"))); + await EvaluateOnCallFrameAndCheck(id, + ("DebuggerTests.EvaluateStaticClass.StaticField1", TNumber(10))); + await EvaluateOnCallFrameAndCheck(id, + ("DebuggerTests.EvaluateStaticClass.StaticProperty1", TString("StaticProperty1"))); + await EvaluateOnCallFrameAndCheck(id, + ("DebuggerTests.EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented"))); }); [Theory] @@ -720,17 +720,17 @@ public async Task EvaluateStaticClassFromStaticMethod(string type, string method $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] {type}:{method}'); }})", wait_for_event_fn: async (pause_location) => { - var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); - var frame = pause_location["callFrames"][0]; + var frame = pause_location["callFrames"][0]; - await EvaluateOnCallFrameAndCheck(id, - ("EvaluateStaticClass.StaticField1", TNumber(10)), - ("EvaluateStaticClass.StaticProperty1", TString("StaticProperty1")), - ("EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented")), - ("DebuggerTests.EvaluateStaticClass.StaticField1", TNumber(10)), - ("DebuggerTests.EvaluateStaticClass.StaticProperty1", TString("StaticProperty1")), - ("DebuggerTests.EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented"))); + await EvaluateOnCallFrameAndCheck(id, + ("EvaluateStaticClass.StaticField1", TNumber(10)), + ("EvaluateStaticClass.StaticProperty1", TString("StaticProperty1")), + ("EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented")), + ("DebuggerTests.EvaluateStaticClass.StaticField1", TNumber(10)), + ("DebuggerTests.EvaluateStaticClass.StaticProperty1", TString("StaticProperty1")), + ("DebuggerTests.EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented"))); }); [Fact] @@ -739,36 +739,36 @@ public async Task EvaluateNonStaticClassWithStaticFields() => await CheckInspect "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateMethodTestsClass:EvaluateAsyncMethods'); })", wait_for_event_fn: async (pause_location) => { - var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); - var frame = pause_location["callFrames"][0]; + var frame = pause_location["callFrames"][0]; - await EvaluateOnCallFrameAndCheck(id, - ("DebuggerTests.EvaluateNonStaticClassWithStaticFields.StaticField1", TNumber(10)), - ("DebuggerTests.EvaluateNonStaticClassWithStaticFields.StaticProperty1", TString("StaticProperty1")), - ("DebuggerTests.EvaluateNonStaticClassWithStaticFields.StaticPropertyWithError", TString("System.Exception: not implemented")), - ("EvaluateNonStaticClassWithStaticFields.StaticField1", TNumber(10)), - ("EvaluateNonStaticClassWithStaticFields.StaticProperty1", TString("StaticProperty1")), - ("EvaluateNonStaticClassWithStaticFields.StaticPropertyWithError", TString("System.Exception: not implemented"))); + await EvaluateOnCallFrameAndCheck(id, + ("DebuggerTests.EvaluateNonStaticClassWithStaticFields.StaticField1", TNumber(10)), + ("DebuggerTests.EvaluateNonStaticClassWithStaticFields.StaticProperty1", TString("StaticProperty1")), + ("DebuggerTests.EvaluateNonStaticClassWithStaticFields.StaticPropertyWithError", TString("System.Exception: not implemented")), + ("EvaluateNonStaticClassWithStaticFields.StaticField1", TNumber(10)), + ("EvaluateNonStaticClassWithStaticFields.StaticProperty1", TString("StaticProperty1")), + ("EvaluateNonStaticClassWithStaticFields.StaticPropertyWithError", TString("System.Exception: not implemented"))); }); - [Fact] + [ConditionalFact(nameof(RunningOnChrome))] public async Task EvaluateStaticClassesNested() => await CheckInspectLocalsAtBreakpointSite( "DebuggerTests.EvaluateMethodTestsClass", "EvaluateMethods", 3, "EvaluateMethods", "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateMethodTestsClass:EvaluateMethods'); })", wait_for_event_fn: async (pause_location) => { - var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); - var frame = pause_location["callFrames"][0]; + var frame = pause_location["callFrames"][0]; - await EvaluateOnCallFrameAndCheck(id, - ("DebuggerTests.EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticField1", TNumber(3)), - ("DebuggerTests.EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticProperty1", TString("StaticProperty3")), - ("DebuggerTests.EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticPropertyWithError", TString("System.Exception: not implemented 3")), - ("EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticField1", TNumber(3)), - ("EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticProperty1", TString("StaticProperty3")), - ("EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticPropertyWithError", TString("System.Exception: not implemented 3"))); + await EvaluateOnCallFrameAndCheck(id, + ("DebuggerTests.EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticField1", TNumber(3)), + ("DebuggerTests.EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticProperty1", TString("StaticProperty3")), + ("DebuggerTests.EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticPropertyWithError", TString("System.Exception: not implemented 3")), + ("EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticField1", TNumber(3)), + ("EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticProperty1", TString("StaticProperty3")), + ("EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticPropertyWithError", TString("System.Exception: not implemented 3"))); }); [Fact] @@ -777,14 +777,14 @@ public async Task EvaluateStaticClassesNestedWithNoNamespace() => await CheckIns "window.setTimeout(function() { invoke_static_method ('[debugger-test] NoNamespaceClass:EvaluateMethods'); })", wait_for_event_fn: async (pause_location) => { - var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); - var frame = pause_location["callFrames"][0]; + var frame = pause_location["callFrames"][0]; - await EvaluateOnCallFrameAndCheck(id, - ("NoNamespaceClass.NestedClass1.NestedClass2.NestedClass3.StaticField1", TNumber(30)), - ("NoNamespaceClass.NestedClass1.NestedClass2.NestedClass3.StaticProperty1", TString("StaticProperty30")), - ("NoNamespaceClass.NestedClass1.NestedClass2.NestedClass3.StaticPropertyWithError", TString("System.Exception: not implemented 30"))); + await EvaluateOnCallFrameAndCheck(id, + ("NoNamespaceClass.NestedClass1.NestedClass2.NestedClass3.StaticField1", TNumber(30)), + ("NoNamespaceClass.NestedClass1.NestedClass2.NestedClass3.StaticProperty1", TString("StaticProperty30")), + ("NoNamespaceClass.NestedClass1.NestedClass2.NestedClass3.StaticPropertyWithError", TString("System.Exception: not implemented 30"))); }); [ConditionalFact(nameof(RunningOnChrome))] @@ -807,7 +807,7 @@ await EvaluateOnCallFrameAndCheck(id_second, ("EvaluateStaticClass.StaticField1", TNumber(10)), ("EvaluateStaticClass.StaticProperty1", TString("StaticProperty1")), ("EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented"))); - }); + }); [ConditionalFact(nameof(RunningOnChrome))] public async Task EvaluateStaticClassInvalidField() => await CheckInspectLocalsAtBreakpointSite( @@ -815,36 +815,36 @@ public async Task EvaluateStaticClassInvalidField() => await CheckInspectLocalsA "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateMethodTestsClass:EvaluateMethods'); })", wait_for_event_fn: async (pause_location) => { - var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); - var frame = pause_location["callFrames"][0]; + var frame = pause_location["callFrames"][0]; - var (_, res) = await EvaluateOnCallFrame(id, "DebuggerTests.EvaluateStaticClass.StaticProperty2", expect_ok: false); - AssertEqual("Failed to resolve member access for DebuggerTests.EvaluateStaticClass.StaticProperty2", res.Error["result"]?["description"]?.Value(), "wrong error message"); + var (_, res) = await EvaluateOnCallFrame(id, "DebuggerTests.EvaluateStaticClass.StaticProperty2", expect_ok: false); + AssertEqual("Failed to resolve member access for DebuggerTests.EvaluateStaticClass.StaticProperty2", res.Error["result"]?["description"]?.Value(), "wrong error message"); - (_, res) = await EvaluateOnCallFrame(id, "DebuggerTests.InvalidEvaluateStaticClass.StaticProperty2", expect_ok: false); - AssertEqual("Failed to resolve member access for DebuggerTests.InvalidEvaluateStaticClass.StaticProperty2", res.Error["result"]?["description"]?.Value(), "wrong error message"); + (_, res) = await EvaluateOnCallFrame(id, "DebuggerTests.InvalidEvaluateStaticClass.StaticProperty2", expect_ok: false); + AssertEqual("Failed to resolve member access for DebuggerTests.InvalidEvaluateStaticClass.StaticProperty2", res.Error["result"]?["description"]?.Value(), "wrong error message"); }); - [ConditionalFact(nameof(RunningOnChrome))] - public async Task AsyncLocalsInContinueWithBlock() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.AsyncTests.ContinueWithTests", "ContinueWithStaticAsync", 4, "b__3_0", - "window.setTimeout(function() { invoke_static_method('[debugger-test] DebuggerTests.AsyncTests.ContinueWithTests:RunAsync'); })", - wait_for_event_fn: async (pause_location) => - { - var frame_locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); - var id = pause_location["callFrames"][0]["callFrameId"].Value(); + [ConditionalFact(nameof(RunningOnChrome))] + public async Task AsyncLocalsInContinueWithBlock() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.AsyncTests.ContinueWithTests", "ContinueWithStaticAsync", 4, "b__3_0", + "window.setTimeout(function() { invoke_static_method('[debugger-test] DebuggerTests.AsyncTests.ContinueWithTests:RunAsync'); })", + wait_for_event_fn: async (pause_location) => + { + var frame_locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); - await EvaluateOnCallFrameAndCheck(id, - ($"t.Status", TEnum("System.Threading.Tasks.TaskStatus", "RanToCompletion")), - ($" t.Status", TEnum("System.Threading.Tasks.TaskStatus", "RanToCompletion")) - ); - - await EvaluateOnCallFrameFail(id, - ("str", "ReferenceError"), - (" str", "ReferenceError") - ); - }); + await EvaluateOnCallFrameAndCheck(id, + ($"t.Status", TEnum("System.Threading.Tasks.TaskStatus", "RanToCompletion")), + ($" t.Status", TEnum("System.Threading.Tasks.TaskStatus", "RanToCompletion")) + ); + + await EvaluateOnCallFrameFail(id, + ("str", "ReferenceError"), + (" str", "ReferenceError") + ); + }); [ConditionalFact(nameof(RunningOnChrome))] public async Task EvaluateConstantValueUsingRuntimeEvaluate() => await CheckInspectLocalsAtBreakpointSite( @@ -861,10 +861,15 @@ await RuntimeEvaluateAndCheck( }); [ConditionalTheory(nameof(RunningOnChrome))] - [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( + [InlineData("EvaluateBrowsableClass", "TestEvaluateFieldsNone", "testFieldsNone", 10)] + [InlineData("EvaluateBrowsableClass", "TestEvaluatePropertiesNone", "testPropertiesNone", 10)] + [InlineData("EvaluateBrowsableStruct", "TestEvaluateFieldsNone", "testFieldsNone", 10)] + [InlineData("EvaluateBrowsableStruct", "TestEvaluatePropertiesNone", "testPropertiesNone", 10)] + [InlineData("EvaluateBrowsableStaticClass", "TestEvaluateFieldsNone", "testFieldsNone", 10)] + [InlineData("EvaluateBrowsableStaticClass", "TestEvaluatePropertiesNone", "testPropertiesNone", 10)] + [InlineData("EvaluateBrowsableCustomPropertiesClass", "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) => @@ -880,23 +885,33 @@ public async Task EvaluateBrowsableNone(string outerClassName, string className, { list = TGetter("list", TObject("System.Collections.Generic.List", description: "Count = 2")), array = TGetter("array", TObject("int[]", description: "int[2]")), - text = TGetter("text", TString("text")) + text = TGetter("text", TString("text")), + nullNone = TGetter("nullNone", TObject("bool[]", is_null: true)), + valueTypeEnum = TGetter("valueTypeEnum", TEnum("DebuggerTests.SampleEnum", "yes")), + sampleStruct = TGetter("sampleStruct", TObject("DebuggerTests.SampleStructure", description: "DebuggerTests.SampleStructure")), + sampleClass = TGetter("sampleClass", TObject("DebuggerTests.SampleClass", description: "DebuggerTests.SampleClass")) }, "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") + text = TString("text"), + nullNone = TObject("bool[]", is_null: true), + valueTypeEnum = TEnum("DebuggerTests.SampleEnum", "yes"), + sampleStruct = TObject("DebuggerTests.SampleStructure", description: "DebuggerTests.SampleStructure"), + sampleClass = TObject("DebuggerTests.SampleClass", description: "DebuggerTests.SampleClass") }, "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)] + [ConditionalTheory(nameof(RunningOnChrome))] + [InlineData("EvaluateBrowsableClass", "TestEvaluateFieldsNever", "testFieldsNever", 10)] + [InlineData("EvaluateBrowsableClass", "TestEvaluatePropertiesNever", "testPropertiesNever", 10)] + [InlineData("EvaluateBrowsableStruct", "TestEvaluateFieldsNever", "testFieldsNever", 10)] + [InlineData("EvaluateBrowsableStruct", "TestEvaluatePropertiesNever", "testPropertiesNever", 10)] + [InlineData("EvaluateBrowsableStaticClass", "TestEvaluateFieldsNever", "testFieldsNever", 10)] + [InlineData("EvaluateBrowsableStaticClass", "TestEvaluatePropertiesNever", "testPropertiesNever", 10)] + [InlineData("EvaluateBrowsableCustomPropertiesClass", "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 }})", @@ -910,15 +925,18 @@ public async Task EvaluateBrowsableNever(string outerClassName, string className await CheckProps(testNeverProps, new { }, "testNeverProps#1"); - }); + }); [ConditionalTheory(nameof(RunningOnChrome))] - [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( + [InlineData("EvaluateBrowsableClass", "TestEvaluateFieldsCollapsed", "testFieldsCollapsed", 10)] + [InlineData("EvaluateBrowsableClass", "TestEvaluatePropertiesCollapsed", "testPropertiesCollapsed", 10)] + [InlineData("EvaluateBrowsableStruct", "TestEvaluateFieldsCollapsed", "testFieldsCollapsed", 10)] + [InlineData("EvaluateBrowsableStruct", "TestEvaluatePropertiesCollapsed", "testPropertiesCollapsed", 10)] + [InlineData("EvaluateBrowsableStaticClass", "TestEvaluateFieldsCollapsed", "testFieldsCollapsed", 10)] + [InlineData("EvaluateBrowsableStaticClass", "TestEvaluatePropertiesCollapsed", "testPropertiesCollapsed", 10)] + [InlineData("EvaluateBrowsableCustomPropertiesClass", "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) => @@ -933,24 +951,35 @@ public async Task EvaluateBrowsableCollapsed(string outerClassName, string class { listCollapsed = TGetter("listCollapsed", TObject("System.Collections.Generic.List", description: "Count = 2")), arrayCollapsed = TGetter("arrayCollapsed", TObject("int[]", description: "int[2]")), - textCollapsed = TGetter("textCollapsed", TString("textCollapsed")) + textCollapsed = TGetter("textCollapsed", TString("textCollapsed")), + nullCollapsed = TGetter("nullCollapsed", TObject("bool[]", is_null: true)), + valueTypeEnumCollapsed = TGetter("valueTypeEnumCollapsed", TEnum("DebuggerTests.SampleEnum", "yes")), + sampleStructCollapsed = TGetter("sampleStructCollapsed", TObject("DebuggerTests.SampleStructure", description: "DebuggerTests.SampleStructure")), + sampleClassCollapsed = TGetter("sampleClassCollapsed", TObject("DebuggerTests.SampleClass", description: "DebuggerTests.SampleClass")) }, "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") + textCollapsed = TString("textCollapsed"), + nullCollapsed = TObject("bool[]", is_null: true), + valueTypeEnumCollapsed = TEnum("DebuggerTests.SampleEnum", "yes"), + sampleStructCollapsed = TObject("DebuggerTests.SampleStructure", description: "DebuggerTests.SampleStructure"), + sampleClassCollapsed = TObject("DebuggerTests.SampleClass", description: "DebuggerTests.SampleClass") }, "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( + [ConditionalTheory(nameof(RunningOnChrome))] + [InlineData("EvaluateBrowsableClass", "TestEvaluateFieldsRootHidden", "testFieldsRootHidden", 10)] + [InlineData("EvaluateBrowsableClass", "TestEvaluatePropertiesRootHidden", "testPropertiesRootHidden", 10)] + [InlineData("EvaluateBrowsableStruct", "TestEvaluateFieldsRootHidden", "testFieldsRootHidden", 10)] + [InlineData("EvaluateBrowsableStruct", "TestEvaluatePropertiesRootHidden", "testPropertiesRootHidden", 10)] + [InlineData("EvaluateBrowsableStaticClass", "TestEvaluateFieldsRootHidden", "testFieldsRootHidden", 10)] + [InlineData("EvaluateBrowsableStaticClass", "TestEvaluatePropertiesRootHidden", "testPropertiesRootHidden", 10)] + [InlineData("EvaluateBrowsableCustomPropertiesClass", "TestEvaluatePropertiesRootHidden", "testPropertiesRootHidden", 5)] + public async Task EvaluateBrowsableRootHidden( + 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) => @@ -960,25 +989,51 @@ public async Task EvaluateBrowsableRootHidden(string outerClassName, string clas 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 list = refListProp + .Where(v => v["name"]?.Value() == "Items" || v["name"]?.Value() == "_items") + .FirstOrDefault(); + var refListElementsProp = await GetProperties(list["value"]["objectId"]?.Value()); + var (refArray, _) = await EvaluateOnCallFrame(id, "testPropertiesNone.array"); var refArrayProp = await GetProperties(refArray["objectId"]?.Value()); + var (refStruct, _) = await EvaluateOnCallFrame(id, "testPropertiesNone.sampleStruct"); + var refStructProp = await GetProperties(refStruct["objectId"]?.Value()); + + var (refClass, _) = await EvaluateOnCallFrame(id, "testPropertiesNone.sampleClass"); + var refClassProp = await GetProperties(refClass["objectId"]?.Value()); + + int refItemsCnt = refListElementsProp.Count() + refArrayProp.Count() + refStructProp.Count() + refClassProp.Count(); + Assert.Equal(refItemsCnt, testRootHiddenProps.Count()); + //in Console App names are in [] //adding variable name to make elements unique + foreach (var item in refListElementsProp) + { + item["name"] = string.Concat("listRootHidden[", item["name"], "]"); + CheckContainsJObject(testRootHiddenProps, item, item["name"].Value()); + } foreach (var item in refArrayProp) { item["name"] = string.Concat("arrayRootHidden[", item["name"], "]"); + CheckContainsJObject(testRootHiddenProps, item, item["name"].Value()); } - foreach (var item in refListElementsProp) + + // valuetype/class members unique names are created by concatenation with a dot + foreach (var item in refStructProp) { - item["name"] = string.Concat("listRootHidden[", item["name"], "]"); + item["name"] = string.Concat("sampleStructRootHidden.", item["name"]); + CheckContainsJObject(testRootHiddenProps, item, item["name"].Value()); } - var mergedRefItems = new JArray(refListElementsProp.Union(refArrayProp)); - Assert.Equal(mergedRefItems, testRootHiddenProps); - }); + foreach (var item in refClassProp) + { + item["name"] = string.Concat("sampleClassRootHidden.", item["name"]); + CheckContainsJObject(testRootHiddenProps, item, item["name"].Value()); + } + }); [ConditionalFact(nameof(RunningOnChrome))] public async Task EvaluateStaticAttributeInAssemblyNotRelatedButLoaded() => await CheckInspectLocalsAtBreakpointSite( @@ -1001,46 +1056,6 @@ await RuntimeEvaluateAndCheck( ("a.valueToCheck", TNumber(20))); }); - [ConditionalFact(nameof(RunningOnChrome))] - public async Task EvaluateProtectionLevels() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.GetPropertiesTests.DerivedClass", "InstanceMethod", 1, "InstanceMethod", - "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.GetPropertiesTests.DerivedClass:run'); })", - wait_for_event_fn: async (pause_location) => - { - var id = pause_location["callFrames"][0]["callFrameId"].Value(); - var (obj, _) = await EvaluateOnCallFrame(id, "this"); - var (pub, internalAndProtected, priv) = await GetPropertiesSortedByProtectionLevels(obj["objectId"]?.Value()); - - await CheckProps(pub, new - { - a = TNumber(4), - Base_AutoStringPropertyForOverrideWithField = TString("DerivedClass#Base_AutoStringPropertyForOverrideWithField"), - Base_GetterForOverrideWithField = TString("DerivedClass#Base_GetterForOverrideWithField"), - BaseBase_MemberForOverride = TString("DerivedClass#BaseBase_MemberForOverride"), - DateTime = TGetter("DateTime", TDateTime(new DateTime(2200, 5, 6, 7, 18, 9))), - _DTProp = TGetter("_DTProp", TDateTime(new DateTime(2200, 5, 6, 7, 8, 9))), - FirstName = TGetter("FirstName", TString("DerivedClass#FirstName")), - _base_dateTime = TGetter("_base_dateTime", TDateTime(new DateTime(2134, 5, 7, 1, 9, 2))), - LastName = TGetter("LastName", TString("BaseClass#LastName")) - }, "public"); - - await CheckProps(internalAndProtected, new - { - base_num = TNumber(5) - }, "internalAndProtected"); - - await CheckProps(priv, new - { - _stringField = TString("DerivedClass#_stringField"), - _dateTime = TDateTime(new DateTime(2020, 7, 6, 5, 4, 3)), - AutoStringProperty = TString("DerivedClass#AutoStringProperty"), - StringPropertyForOverrideWithAutoProperty = TString("DerivedClass#StringPropertyForOverrideWithAutoProperty"), - _base_name = TString("private_name"), - Base_AutoStringProperty = TString("base#Base_AutoStringProperty"), - DateTimeForOverride = TGetter("DateTimeForOverride", TDateTime(new DateTime(2190, 9, 7, 5, 3, 2))) - }, "private"); - }); - [ConditionalFact(nameof(RunningOnChrome))] public async Task StructureGetters() => await CheckInspectLocalsAtBreakpointSite( "DebuggerTests.StructureGetters", "Evaluate", 2, "Evaluate", @@ -1111,7 +1126,7 @@ await EvaluateOnCallFrameAndCheck(id, var (_, res) = await EvaluateOnCallFrame(id, "test.GetDefaultAndRequiredParamMixedTypes(\"a\", 23, true, 1.23f)", expect_ok: false); Assert.Contains("method 'test.GetDefaultAndRequiredParamMixedTypes(\"a\", 23, true, 1.23f)' - too many arguments passed", res.Error["message"]?.Value()); - }); + }); [Fact] public async Task EvaluateMethodWithLinq() => await CheckInspectLocalsAtBreakpointSite( @@ -1123,7 +1138,7 @@ public async Task EvaluateMethodWithLinq() => await CheckInspectLocalsAtBreakpoi await EvaluateOnCallFrameAndCheck(id, ("test.listToLinq.ToList()", TObject("System.Collections.Generic.List", description: "Count = 11")) ); - }); + }); } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs index cdd091414f3674..717ef7b9e924e5 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs @@ -36,12 +36,16 @@ public class GetPropertiesTests : DebuggerTests {"Base_GetterForOverrideWithField", (TString("DerivedClass#Base_GetterForOverrideWithField"), true)}, {"BaseBase_MemberForOverride", (TString("DerivedClass#BaseBase_MemberForOverride"), true)}, + // protected + {"b", (TBool(true), true)}, + // indexers don't show up in getprops // {"Item", (TSymbol("int { get; }"), true)}, // inherited private {"_base_name", (TString("private_name"), false)}, {"_base_dateTime", (TGetter("_base_dateTime"), false)}, + {"_base_autoProperty", (TString("private_autoproperty"), false)}, // inherited public {"Base_AutoStringProperty", (TString("base#Base_AutoStringProperty"), false)}, @@ -134,6 +138,9 @@ public class GetPropertiesTests : DebuggerTests {"FirstName", (TGetter("FirstName"), true)}, {"LastName", (TGetter("LastName"), true)}, + // protected + {"b", (TBool(true), true)}, + // indexers don't show up in getprops // {"Item", (TSymbol("int { get; }"), true)} }; @@ -401,5 +408,90 @@ private static void AssertHasOnlyExpectedProperties(string[] expected_names, IEn } } + public static TheoryData, Dictionary, Dictionary, string> GetDataForProtectionLevels() + { + var data = new TheoryData, Dictionary, Dictionary, string>(); + // object DerivedClass; should be 23 elements: + var public_props = new Dictionary() + { + // own + {"a", TNumber(4)}, + {"DateTime", TGetter("DateTime")}, + {"AutoStringProperty", TString("DerivedClass#AutoStringProperty")}, + {"FirstName", TGetter("FirstName")}, + {"DateTimeForOverride", TGetter("DateTimeForOverride")}, + + {"StringPropertyForOverrideWithAutoProperty", TString("DerivedClass#StringPropertyForOverrideWithAutoProperty")}, + {"Base_AutoStringPropertyForOverrideWithField", TString("DerivedClass#Base_AutoStringPropertyForOverrideWithField")}, + {"Base_GetterForOverrideWithField", TString("DerivedClass#Base_GetterForOverrideWithField")}, + {"BaseBase_MemberForOverride", TString("DerivedClass#BaseBase_MemberForOverride")}, + + // inherited public + {"Base_AutoStringProperty", TString("base#Base_AutoStringProperty")}, + {"LastName", TGetter("LastName")} + }; + + var internal_protected_props = new Dictionary(){ + // internal + {"b", TBool(true)}, + // inherited protected + {"base_num", TNumber(5)} + }; + + var private_props = new Dictionary(){ + {"_stringField", TString("DerivedClass#_stringField")}, + {"_dateTime", TDateTime(new DateTime(2020, 7, 6, 5, 4, 3))}, + {"_DTProp", TGetter("_DTProp")}, + + // inherited + {"_base_name", TString("private_name")}, + {"_base_autoProperty", TString("private_autoproperty")}, + {"_base_dateTime", TGetter("_base_dateTime")} + }; + data.Add(public_props, internal_protected_props, private_props, "DerivedClass"); + + // structure CloneableStruct: + public_props = new Dictionary() + { + // own + {"a", TNumber(4)}, + {"DateTime", TGetter("DateTime")}, + {"AutoStringProperty", TString("CloneableStruct#AutoStringProperty")}, + {"FirstName", TGetter("FirstName")}, + {"LastName", TGetter("LastName")} + }; + internal_protected_props = new Dictionary() + { + // internal + {"b", TBool(true)} + }; + private_props = new Dictionary() + { + {"_stringField", TString("CloneableStruct#_stringField")}, + {"_dateTime", TDateTime(new DateTime(2020, 7, 6, 5, 4, 3 + 3))}, + {"_DTProp", TGetter("_DTProp")} + }; + data.Add(public_props, internal_protected_props, private_props, "CloneableStruct"); + return data; + } + + [ConditionalTheory(nameof(RunningOnChrome))] + [MemberData(nameof(GetDataForProtectionLevels))] + public async Task PropertiesSortedByProtectionLevel( + Dictionary expectedPublic, Dictionary expectedProtInter, Dictionary expectedPriv, string entryMethod) => + await CheckInspectLocalsAtBreakpointSite( + $"DebuggerTests.GetPropertiesTests.{entryMethod}", "InstanceMethod", 1, "InstanceMethod", + $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.GetPropertiesTests.{entryMethod}:run'); }})", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var (obj, _) = await EvaluateOnCallFrame(id, "this"); + var (pub, internalAndProtected, priv) = await GetPropertiesSortedByProtectionLevels(obj["objectId"]?.Value()); + + await CheckProps(pub, expectedPublic, "public"); + await CheckProps(internalAndProtected, expectedProtInter, "internalAndProtected"); + await CheckProps(priv, expectedPriv, "private"); + }); + } } diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs index 80db1d2c310eed..ef663949f718c2 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 @@ -497,7 +497,7 @@ public void run() textListOfLists = new List> { textList, textList }; idx0 = 0; idx1 = 1; - } + } } public static void EvaluateLocals() @@ -509,13 +509,38 @@ public static void EvaluateLocals() } } - public static class EvaluateBrowsableProperties + public struct SampleStructure + { + public SampleStructure() { } + + public int Id = 100; + + internal bool IsStruct = true; + } + + public enum SampleEnum + { + yes = 0, + no = 1 + } + + public class SampleClass + { + public int ClassId = 200; + public List Items = new List { "should not be expanded" }; + } + + public static class EvaluateBrowsableClass { public class TestEvaluateFieldsNone { public List list = new List() { 1, 2 }; public int[] array = new int[] { 11, 22 }; public string text = "text"; + public bool[] nullNone = null; + public SampleEnum valueTypeEnum = new(); + public SampleStructure sampleStruct = new(); + public SampleClass sampleClass = new(); } public class TestEvaluatePropertiesNone @@ -523,12 +548,20 @@ public class TestEvaluatePropertiesNone public List list { get; set; } public int[] array { get; set; } public string text { get; set; } - + public bool[] nullNone { get; set; } + public SampleEnum valueTypeEnum { get; set; } + public SampleStructure sampleStruct { get; set; } + public SampleClass sampleClass { get; set; } + public TestEvaluatePropertiesNone() { list = new List() { 1, 2 }; array = new int[] { 11, 22 }; text = "text"; + nullNone = null; + valueTypeEnum = new(); + sampleStruct = new(); + sampleClass = new(); } } @@ -542,6 +575,18 @@ public class TestEvaluateFieldsNever [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] public string textNever = "textNever"; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public bool[] nullNever = null; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleEnum valueTypeEnumNever = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleStructure sampleStructNever = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleClass sampleClassNever = new(); } public class TestEvaluatePropertiesNever @@ -555,11 +600,27 @@ public class TestEvaluatePropertiesNever [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] public string textNever { get; set; } + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public bool[] nullNever { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleEnum valueTypeEnumNever { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleStructure sampleStructNever { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleClass sampleClassNever { get; set; } + public TestEvaluatePropertiesNever() { listNever = new List() { 1, 2 }; arrayNever = new int[] { 11, 22 }; textNever = "textNever"; + nullNever = null; + valueTypeEnumNever = new(); + sampleStructNever = new(); + sampleClassNever = new(); } } @@ -573,6 +634,18 @@ public class TestEvaluateFieldsCollapsed [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] public string textCollapsed = "textCollapsed"; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public bool[] nullCollapsed = null; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleEnum valueTypeEnumCollapsed = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleStructure sampleStructCollapsed = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleClass sampleClassCollapsed = new(); } public class TestEvaluatePropertiesCollapsed @@ -586,11 +659,27 @@ public class TestEvaluatePropertiesCollapsed [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] public string textCollapsed { get; set; } + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public bool[] nullCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleEnum valueTypeEnumCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleStructure sampleStructCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleClass sampleClassCollapsed { get; set; } + public TestEvaluatePropertiesCollapsed() { listCollapsed = new List() { 1, 2 }; arrayCollapsed = new int[] { 11, 22 }; textCollapsed = "textCollapsed"; + nullCollapsed = null; + valueTypeEnumCollapsed = new(); + sampleStructCollapsed = new(); + sampleClassCollapsed = new(); } } @@ -604,6 +693,18 @@ public class TestEvaluateFieldsRootHidden [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] public string textRootHidden = "textRootHidden"; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public bool[] nullRootHidden = null; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleEnum valueTypeEnumRootHidden = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleStructure sampleStructRootHidden = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleClass sampleClassRootHidden = new(); } public class TestEvaluatePropertiesRootHidden @@ -617,11 +718,260 @@ public class TestEvaluatePropertiesRootHidden [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] public string textRootHidden { get; set; } + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public bool[] nullRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleEnum valueTypeEnumRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleStructure sampleStructRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleClass sampleClassRootHidden { get; set; } + + public TestEvaluatePropertiesRootHidden() + { + listRootHidden = new List() { 1, 2 }; + arrayRootHidden = new int[] { 11, 22 }; + textRootHidden = "textRootHidden"; + nullRootHidden = null; + valueTypeEnumRootHidden = new(); + sampleStructRootHidden = new(); + sampleClassRootHidden = new(); + } + } + + 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 EvaluateBrowsableStruct + { + public struct TestEvaluateFieldsNone + { + public TestEvaluateFieldsNone() {} + public List list = new List() { 1, 2 }; + public int[] array = new int[] { 11, 22 }; + public string text = "text"; + public bool[] nullNone = null; + public SampleEnum valueTypeEnum = new(); + public SampleStructure sampleStruct = new(); + public SampleClass sampleClass = new(); + } + + public struct TestEvaluatePropertiesNone + { + public List list { get; set; } + public int[] array { get; set; } + public string text { get; set; } + public bool[] nullNone { get; set; } + public SampleEnum valueTypeEnum { get; set; } + public SampleStructure sampleStruct { get; set; } + public SampleClass sampleClass { get; set; } + + public TestEvaluatePropertiesNone() + { + list = new List() { 1, 2 }; + array = new int[] { 11, 22 }; + text = "text"; + nullNone = null; + valueTypeEnum = new(); + sampleStruct = new(); + sampleClass = new(); + } + } + + public struct TestEvaluateFieldsNever + { + public 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"; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public bool[] nullNever = null; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleEnum valueTypeEnumNever = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleStructure sampleStructNever = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleClass sampleClassNever = new(); + } + + public struct 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; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public bool[] nullNever { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleEnum valueTypeEnumNever { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleStructure sampleStructNever { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleClass sampleClassNever { get; set; } + + public TestEvaluatePropertiesNever() + { + listNever = new List() { 1, 2 }; + arrayNever = new int[] { 11, 22 }; + textNever = "textNever"; + nullNever = null; + valueTypeEnumNever = new(); + sampleStructNever = new(); + sampleClassNever = new(); + } + } + + public struct TestEvaluateFieldsCollapsed + { + public 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"; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public bool[] nullCollapsed = null; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleEnum valueTypeEnumCollapsed = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleStructure sampleStructCollapsed = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleClass sampleClassCollapsed = new(); + } + + public struct 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; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public bool[] nullCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleEnum valueTypeEnumCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleStructure sampleStructCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleClass sampleClassCollapsed { get; set; } + + public TestEvaluatePropertiesCollapsed() + { + listCollapsed = new List() { 1, 2 }; + arrayCollapsed = new int[] { 11, 22 }; + textCollapsed = "textCollapsed"; + nullCollapsed = null; + valueTypeEnumCollapsed = new(); + sampleStructCollapsed = new(); + sampleClassCollapsed = new(); + } + } + + public struct TestEvaluateFieldsRootHidden + { + public 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"; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public bool[] nullRootHidden = null; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleEnum valueTypeEnumRootHidden = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleStructure sampleStructRootHidden = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleClass sampleClassRootHidden = new(); + } + + public struct 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; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public bool[] nullRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleEnum valueTypeEnumRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleStructure sampleStructRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleClass sampleClassRootHidden { get; set; } + public TestEvaluatePropertiesRootHidden() { listRootHidden = new List() { 1, 2 }; arrayRootHidden = new int[] { 11, 22 }; textRootHidden = "textRootHidden"; + nullRootHidden = null; + valueTypeEnumRootHidden = new(); + sampleStructRootHidden = new(); + sampleClassRootHidden = new(); } } @@ -639,13 +989,18 @@ public static void Evaluate() } } - public static class EvaluateBrowsableStaticProperties + public static class EvaluateBrowsableStaticClass { 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 static bool[] nullNone = null; + public static SampleEnum valueTypeEnum = new(); + public static SampleStructure sampleStruct = new(); + public static SampleClass sampleClass = new(); } public class TestEvaluatePropertiesNone @@ -653,12 +1008,20 @@ public class TestEvaluatePropertiesNone public static List list { get; set; } public static int[] array { get; set; } public static string text { get; set; } - + public static bool[] nullNone { get; set; } + public static SampleEnum valueTypeEnum { get; set; } + public static SampleStructure sampleStruct { get; set; } + public static SampleClass sampleClass { get; set; } + public TestEvaluatePropertiesNone() { list = new List() { 1, 2 }; array = new int[] { 11, 22 }; text = "text"; + nullNone = null; + valueTypeEnum = new(); + sampleStruct = new(); + sampleClass = new(); } } @@ -672,6 +1035,18 @@ public class TestEvaluateFieldsNever [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] public static string textNever = "textNever"; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public static bool[] nullNever = null; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public static SampleEnum valueTypeEnumNever = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public static SampleStructure sampleStructNever = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public static SampleClass sampleClassNever = new(); } public class TestEvaluatePropertiesNever @@ -685,11 +1060,27 @@ public class TestEvaluatePropertiesNever [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] public static string textNever { get; set; } + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public static bool[] nullNever { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public static SampleEnum valueTypeEnumNever { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public static SampleStructure sampleStructNever { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public static SampleClass sampleClassNever { get; set; } + public TestEvaluatePropertiesNever() { listNever = new List() { 1, 2 }; arrayNever = new int[] { 11, 22 }; textNever = "textNever"; + nullNever = null; + valueTypeEnumNever = new(); + sampleStructNever = new(); + sampleClassNever = new(); } } @@ -703,6 +1094,18 @@ public class TestEvaluateFieldsCollapsed [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] public static string textCollapsed = "textCollapsed"; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public static bool[] nullCollapsed = null; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public static SampleEnum valueTypeEnumCollapsed = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public static SampleStructure sampleStructCollapsed = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public static SampleClass sampleClassCollapsed = new(); } public class TestEvaluatePropertiesCollapsed @@ -716,11 +1119,27 @@ public class TestEvaluatePropertiesCollapsed [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] public static string textCollapsed { get; set; } + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public static bool[] nullCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public static SampleEnum valueTypeEnumCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public static SampleStructure sampleStructCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public static SampleClass sampleClassCollapsed { get; set; } + public TestEvaluatePropertiesCollapsed() { listCollapsed = new List() { 1, 2 }; arrayCollapsed = new int[] { 11, 22 }; textCollapsed = "textCollapsed"; + nullCollapsed = null; + valueTypeEnumCollapsed = new(); + sampleStructCollapsed = new(); + sampleClassCollapsed = new(); } } @@ -734,6 +1153,18 @@ public class TestEvaluateFieldsRootHidden [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] public static string textRootHidden = "textRootHidden"; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public static bool[] nullRootHidden = null; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public static SampleEnum valueTypeEnumRootHidden = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public static SampleStructure sampleStructRootHidden = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public static SampleClass sampleClassRootHidden = new(); } public class TestEvaluatePropertiesRootHidden @@ -747,11 +1178,27 @@ public class TestEvaluatePropertiesRootHidden [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] public static string textRootHidden { get; set; } + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public static bool[] nullRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public static SampleEnum valueTypeEnumRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public static SampleStructure sampleStructRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public static SampleClass sampleClassRootHidden { get; set; } + public TestEvaluatePropertiesRootHidden() { listRootHidden = new List() { 1, 2 }; arrayRootHidden = new int[] { 11, 22 }; textRootHidden = "textRootHidden"; + nullRootHidden = null; + valueTypeEnumRootHidden = new(); + sampleStructRootHidden = new(); + sampleClassRootHidden = new(); } } @@ -769,13 +1216,17 @@ public static void Evaluate() } } - public static class EvaluateBrowsableCustomProperties + public static class EvaluateBrowsableCustomPropertiesClass { 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 bool[] nullNone { get { return null; } } + public SampleEnum valueTypeEnum { get { return new(); } } + public SampleStructure sampleStruct { get { return new(); } } + public SampleClass sampleClass { get { return new(); } } } public class TestEvaluatePropertiesNever @@ -788,6 +1239,18 @@ public class TestEvaluatePropertiesNever [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] public string textNever { get { return "textNever"; } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public bool[] nullNever { get { return null; } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleEnum valueTypeEnumNever { get { return new(); } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleStructure sampleStructNever { get { return new(); } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleClass sampleClassNever { get { return new(); } } } public class TestEvaluatePropertiesCollapsed @@ -800,6 +1263,18 @@ public class TestEvaluatePropertiesCollapsed [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] public string textCollapsed { get { return "textCollapsed"; } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public bool[] nullCollapsed { get { return null; } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleEnum valueTypeEnumCollapsed { get { return new(); } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleStructure sampleStructCollapsed { get { return new(); } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleClass sampleClassCollapsed { get { return new(); } } } public class TestEvaluatePropertiesRootHidden @@ -812,6 +1287,18 @@ public class TestEvaluatePropertiesRootHidden [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] public string textRootHidden { get { return "textRootHidden"; } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public bool[] nullRootHidden { get { return null; } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleEnum valueTypeEnumRootHidden { get { return new(); } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleStructure sampleStructRootHidden { get { return new(); } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleClass sampleClassRootHidden { get { return new(); } } } public static void Evaluate() @@ -857,8 +1344,8 @@ public class TestClass public double GetDouble(double param = 1.23) => param; public string GetString(string param = "1.23") => param; public string GetUnicodeString(string param = "żółć") => param; - - #nullable enable + +#nullable enable public bool? GetBoolNullable(bool? param = true) => param; public char? GetCharNullable(char? param = 'T') => param; public byte? GetByteNullable(byte? param = 1) => param; @@ -872,7 +1359,7 @@ public class TestClass public float? GetSingleNullable(float? param = 1.23f) => param; public double? GetDoubleNullable(double? param = 1.23) => param; public string? GetStringNullable(string? param = "1.23") => param; - #nullable disable +#nullable disable public bool GetNull(object param = null) => param == null ? true : false; public int GetDefaultAndRequiredParam(int requiredParam, int optionalParam = 3) => requiredParam + optionalParam; @@ -908,7 +1395,7 @@ public static void EvaluateMethods() { var stopHere = true; } - + public static class NestedClass1 { public static class NestedClass2 diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-get-properties-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-get-properties-test.cs index d591a34ce140ae..9217641eaafb34 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-get-properties-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-get-properties-test.cs @@ -30,6 +30,7 @@ public class BaseClass : BaseBaseClass, IName { private string _base_name; private DateTime _base_dateTime => new DateTime(2134, 5, 7, 1, 9, 2); + private string _base_autoProperty { get; set; } protected int base_num; public string Base_AutoStringProperty { get; set; } @@ -45,6 +46,7 @@ public class BaseClass : BaseBaseClass, IName public BaseClass() { _base_name = "private_name"; + _base_autoProperty = "private_autoproperty"; base_num = 5; Base_AutoStringProperty = "base#Base_AutoStringProperty"; DateTimeForOverride = new DateTime(2250, 4, 5, 6, 7, 8); @@ -64,6 +66,8 @@ public class DerivedClass : BaseClass, ICloneable private DateTime _dateTime = new DateTime(2020, 7, 6, 5, 4, 3); private DateTime _DTProp => new DateTime(2200, 5, 6, 7, 8, 9); + internal bool b = true; + public int a; public DateTime DateTime => _DTProp.AddMinutes(10); public string AutoStringProperty { get; set; } @@ -116,6 +120,8 @@ public struct CloneableStruct : ICloneable, IName private string _stringField; private DateTime _dateTime; private DateTime _DTProp => new DateTime(2200, 5, 6, 7, 8, 9); + + internal bool b = true; public int a; public DateTime DateTime => _DTProp.AddMinutes(10);