[wasm][debugger] Refactor of getProperties mechanism (#68486)
authorIlona Tomkowicz <32700855+ilonatommy@users.noreply.github.com>
Thu, 5 May 2022 13:01:55 +0000 (15:01 +0200)
committerGitHub <noreply@github.com>
Thu, 5 May 2022 13:01:55 +0000 (15:01 +0200)
* Merge main with @radical's refactoring.

* Fixed most tests.

* Added more Browsable tests: null + valuetype.

* Fixed trailing spaces and exposed the test.

* Fixed all GetPropertiesTests.

* Fixing RunOnCFOValueTypeResult.

* Fixed cloning to be deep.

* Reverted comments in test.

* Fixed browsable root hidden tests.

* Extended Browsable tests with a structure.

* Fixed TestSetValueOnObject.

* Add class testcase to browsable root hidden.

* All existing tests are passing.

* Cleanup, removing unused code.

* Added Browsable tests for nonstatic structures.

* Removed unnecessary comment.

* Removed whitespaces.

* Blocked tests failing/timeouting on Firefox.

13 files changed:
src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs
src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs
src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs
src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs [new file with mode: 0644]
src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs
src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs
src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs [new file with mode: 0644]
src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs
src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs
src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs
src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs
src/mono/wasm/debugger/tests/debugger-test/debugger-get-properties-test.cs

index 039f74b..47bc311 100644 (file)
@@ -348,6 +348,7 @@ namespace Microsoft.WebAssembly.Diagnostics
         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; }
index e9a4950..df8e334 100644 (file)
@@ -63,64 +63,67 @@ namespace Microsoft.WebAssembly.Diagnostics
 
     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<string>(), 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
index de3a5ba..5018e7c 100644 (file)
@@ -8,6 +8,7 @@ using System.Linq;
 using System.Net.Sockets;
 using System.Threading;
 using System.Threading.Tasks;
+using BrowserDebugProxy;
 using Microsoft.Extensions.Logging;
 using Newtonsoft.Json.Linq;
 
@@ -470,7 +471,7 @@ internal sealed class FirefoxMonoProxy : MonoProxy
                     var to = args?["to"].Value<string>().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
                     {
@@ -519,7 +520,7 @@ internal sealed class FirefoxMonoProxy : MonoProxy
                     //{"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
                     {
@@ -545,7 +546,7 @@ internal sealed class FirefoxMonoProxy : MonoProxy
                     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
                     {
@@ -699,11 +700,12 @@ internal sealed class FirefoxMonoProxy : MonoProxy
         return o;
     }
 
-    private static JObject ConvertToFirefoxContent(ValueOrError<JToken> res)
+    private static JObject ConvertToFirefoxContent(ValueOrError<GetMembersResult> 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 (file)
index 0000000..24a2eae
--- /dev/null
@@ -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<JObject> 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<JArray> 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<string>() == "null")
+                return new JArray();
+
+            var type = rootValue?["type"]?.Value<string>();
+            if (type != "object" && type != "function")
+                return new JArray();
+
+            if (!DotnetObjectId.TryParse(rootValue?["objectId"]?.Value<string>(), 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<string>() != "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<string>() == "Items" || m["name"]?.Value<string>() == "_items")
+                        .FirstOrDefault();
+                    if (memberNamedItems is not null &&
+                        (DotnetObjectId.TryParse(memberNamedItems["value"]?["objectId"]?.Value<string>(), 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<JArray> GetRootHiddenChildrenForProperty()
+            {
+                var resMethod = await sdbHelper.InvokeMethod(rootObjectId, token);
+                return await GetRootHiddenChildren(sdbHelper, resMethod, rootNamePrefix, rootTypeName, getCommandOptions, includeStatic, token);
+            }
+        }
+
+        public static Task<GetMembersResult> 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<JArray> ExpandFieldValues(
+            MonoSDBHelper sdbHelper,
+            DotnetObjectId id,
+            int containerTypeId,
+            IReadOnlyList<FieldTypeClass> 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<string>(), 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<GetMembersResult> 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<JArray> 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<Dictionary<string, JObject>> GetNonAutomaticPropertyValues(
+            MonoSDBHelper sdbHelper,
+            int typeId,
+            string containerTypeName,
+            ArraySegment<byte> getterParamsBuffer,
+            bool isAutoExpandable,
+            DotnetObjectId objectId,
+            bool isValueType,
+            bool isOwn,
+            CancellationToken token,
+            Dictionary<string, JObject> 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<bool>() == 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<string>();
+                            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<string>()] = 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<string>();
+                        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<GetMembersResult> 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<JObject>() { 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<byte> getPropertiesParamBuffer = commandParamsObjWriter.GetParameterBuffer();
+
+            var allMembers = new Dictionary<string, JObject>();
+            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<FieldTypeClass> 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<string, JObject> valuesDict, bool isOwn)
+            {
+                foreach (var item in namedValues)
+                {
+                    var key = item["name"]?.Value<string>();
+                    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<JToken> 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<bool>() == true)
+                return;
+
+            if (member["__section"]?.Value<string>() 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<JToken> Where(Func<JToken, bool> 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<JToken, bool> 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";
+    }
+}
index 63ceaa7..17690f8 100644 (file)
@@ -11,6 +11,7 @@ using System.IO;
 using Microsoft.CodeAnalysis.CSharp.Syntax;
 using System.Collections.Generic;
 using System.Net.WebSockets;
+using BrowserDebugProxy;
 
 namespace Microsoft.WebAssembly.Diagnostics
 {
@@ -54,7 +55,7 @@ namespace Microsoft.WebAssembly.Diagnostics
             {
                 if (DotnetObjectId.TryParse(objRet?["value"]?["objectId"]?.Value<string>(), 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<string>().Equals("_message"));
                     exceptionObjectMessage["value"]["value"] = objRet["value"]?["className"]?.Value<string>() + ": " + exceptionObjectMessage["value"]?["value"]?.Value<string>();
                     return exceptionObjectMessage["value"]?.Value<JObject>();
@@ -64,16 +65,12 @@ namespace Microsoft.WebAssembly.Diagnostics
 
             if (objRet["value"]?.Value<JObject>() != null)
                 return objRet["value"]?.Value<JObject>();
-            if (objRet["get"]?.Value<JObject>() != null)
-            {
-                if (DotnetObjectId.TryParse(objRet?["get"]?["objectIdValue"]?.Value<string>(), 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<int>(), objRet["name"].Value<string>(), token);
-                    return await GetValueFromObject(ret, token);
-                }
 
+            if (objRet["get"]?.Value<JObject>() != null &&
+                DotnetObjectId.TryParse(objRet?["get"]?["objectId"]?.Value<string>(), out DotnetObjectId getterObjectId))
+            {
+                var ret = await context.SdbAgent.InvokeMethod(getterObjectId, token);
+                return await GetValueFromObject(ret, token);
             }
             return null;
         }
@@ -150,7 +147,7 @@ namespace Microsoft.WebAssembly.Diagnostics
                 {
                     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 @@ namespace Microsoft.WebAssembly.Diagnostics
                 if (!DotnetObjectId.TryParse(objThis?["value"]?["objectId"]?.Value<string>(), out DotnetObjectId objectId))
                     return null;
 
-                ValueOrError<JToken> valueOrError = await proxy.RuntimeGetPropertiesInternal(sessionId, objectId, null, token);
+                ValueOrError<GetMembersResult> valueOrError = await proxy.RuntimeGetObjectMembers(sessionId, objectId, null, token);
                 if (valueOrError.IsError)
                 {
                     logger.LogDebug($"ResolveAsLocalOrThisMember failed with : {valueOrError.Error}");
@@ -261,7 +258,7 @@ namespace Microsoft.WebAssembly.Diagnostics
                     if (!DotnetObjectId.TryParse(resolvedObject?["objectId"]?.Value<string>(), out DotnetObjectId objectId))
                         return null;
 
-                    ValueOrError<JToken> valueOrError = await proxy.RuntimeGetPropertiesInternal(sessionId, objectId, null, token);
+                    ValueOrError<GetMembersResult> valueOrError = await proxy.RuntimeGetObjectMembers(sessionId, objectId, null, token);
                     if (valueOrError.IsError)
                     {
                         logger.LogDebug($"ResolveAsInstanceMember failed with : {valueOrError.Error}");
@@ -358,9 +355,9 @@ namespace Microsoft.WebAssembly.Diagnostics
                                 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<string>(), out DotnetObjectId arrayObjectId);
                                 rootObject["value"] = await context.SdbAgent.GetArrayValues(arrayObjectId.Value, token);
@@ -409,7 +406,18 @@ namespace Microsoft.WebAssembly.Diagnostics
                     if (!DotnetObjectId.TryParse(rootObject?["objectId"]?.Value<string>(), 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<int> 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<int>(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 @@ namespace Microsoft.WebAssembly.Diagnostics
                             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);
                     }
                 }
index 83bb614..48af878 100644 (file)
@@ -12,6 +12,7 @@ using Microsoft.Extensions.Logging;
 using Newtonsoft.Json;
 using Newtonsoft.Json.Linq;
 using System.Net.Http;
+using BrowserDebugProxy;
 
 namespace Microsoft.WebAssembly.Diagnostics
 {
@@ -475,16 +476,19 @@ namespace Microsoft.WebAssembly.Diagnostics
                         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 @@ namespace Microsoft.WebAssembly.Diagnostics
             }
             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 @@ namespace Microsoft.WebAssembly.Diagnostics
                     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 @@ namespace Microsoft.WebAssembly.Diagnostics
                 byte[] newBytes = Convert.FromBase64String(res.Value?["result"]?["value"]?["value"]?.Value<string>());
                 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 @@ namespace Microsoft.WebAssembly.Diagnostics
             return true;
         }
 
-        internal async Task<ValueOrError<JToken>> RuntimeGetPropertiesInternal(SessionId id, DotnetObjectId objectId, JToken args, CancellationToken token, bool sortByAccessLevel = false)
+        internal async Task<ValueOrError<GetMembersResult>> 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<bool>())
                 {
-                    objectValuesOpt |= GetObjectCommandOptions.AccessorPropertiesOnly;
-                    accessorPropertiesOnly = true;
+                    getObjectOptions |= GetObjectCommandOptions.AccessorPropertiesOnly;
                 }
                 if (args["ownProperties"] != null && args["ownProperties"].Value<bool>())
                 {
-                    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<JToken>.WithValue(sortByAccessLevel ? resScope.Value : resScope.Value?["result"])
-                            : ValueOrError<JToken>.WithError(resScope);
-                    }
+                            ? ValueOrError<GetMembersResult>.WithValue(
+                                new GetMembersResult((JArray)resScope.Value?["result"], sortByAccessLevel: false))
+                            : ValueOrError<GetMembersResult>.WithError(resScope);
                     case "valuetype":
-                    {
-                        var valType = context.SdbAgent.GetValueTypeClass(objectId.Value);
-                        if (valType == null)
-                            return ValueOrError<JToken>.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<JToken>.WithError($"Could not get properties for {objectId}"),
-                            _ => ValueOrError<JToken>.WithValue(sortByAccessLevel ? JObject.FromObject(new { result = resValue }) : resValue)
+                            null => ValueOrError<GetMembersResult>.WithError($"Could not get properties for {objectId}"),
+                            _ => ValueOrError<GetMembersResult>.WithValue(resValue)
                         };
-                    }
                     case "array":
-                    {
                         var resArr = await context.SdbAgent.GetArrayValues(objectId.Value, token);
-                        return ValueOrError<JToken>.WithValue(sortByAccessLevel ? JObject.FromObject(new { result = resArr }) : resArr);
-                    }
-                    case "methodId":
-                    {
-                        var resMethod = await context.SdbAgent.InvokeMethodInObject(objectId, objectId.SubValue, null, token);
-                        return ValueOrError<JToken>.WithValue(sortByAccessLevel ? JObject.FromObject(new { result = new JArray(resMethod) }) : new JArray(resMethod));
-                    }
+                        return ValueOrError<GetMembersResult>.WithValue(GetMembersResult.FromValues(resArr));
+                    case "method":
+                        var resMethod = await context.SdbAgent.InvokeMethod(objectId, token);
+                        return ValueOrError<GetMembersResult>.WithValue(GetMembersResult.FromValues(new JArray(resMethod)));
                     case "object":
-                    {
-                        var resObj = await context.SdbAgent.GetObjectValues(objectId.Value, objectValuesOpt, token, sortByAccessLevel);
-                        return ValueOrError<JToken>.WithValue(sortByAccessLevel ? resObj[0] : resObj);
-                    }
+                        var resObj = await MemberObjectsExplorer.GetObjectMemberValues(
+                            context.SdbAgent, objectId.Value, getObjectOptions, token, sortByAccessLevel, includeStatic: true);
+                        return ValueOrError<GetMembersResult>.WithValue(resObj);
                     case "pointer":
-                    {
                         var resPointer = new JArray { await context.SdbAgent.GetPointerContent(objectId.Value, token) };
-                        return ValueOrError<JToken>.WithValue(sortByAccessLevel ? JObject.FromObject(new { result = resPointer }) : resPointer);
-                    }
+                        return ValueOrError<GetMembersResult>.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<string>();
                         if (res.IsOk && value_json_str == null)
-                            return ValueOrError<JToken>.WithError($"Internal error: Could not find expected __value_as_json_string__ field in the result: {res}");
+                            return ValueOrError<GetMembersResult>.WithError(
+                                $"Internal error: Could not find expected __value_as_json_string__ field in the result: {res}");
 
                         return value_json_str != null
-                                    ? ValueOrError<JToken>.WithValue(sortByAccessLevel ? JObject.FromObject(new { result = JArray.Parse(value_json_str) }) : JArray.Parse(value_json_str))
-                                    : ValueOrError<JToken>.WithError(res);
-                    }
+                                    ? ValueOrError<GetMembersResult>.WithValue(GetMembersResult.FromValues(JArray.Parse(value_json_str)))
+                                    : ValueOrError<GetMembersResult>.WithError(res);
                     default:
-                        return ValueOrError<JToken>.WithError($"RuntimeGetProperties: unknown object id scheme: {objectId.Scheme}");
+                        return ValueOrError<GetMembersResult>.WithError($"RuntimeGetProperties: unknown object id scheme: {objectId.Scheme}");
                 }
             }
             catch (Exception ex)
             {
-                return ValueOrError<JToken>.WithError($"RuntimeGetProperties: Failed to get properties for {objectId}: {ex}");
+                return ValueOrError<GetMembersResult>.WithError($"RuntimeGetProperties: Failed to get properties for {objectId}: {ex}");
             }
         }
 
@@ -1105,7 +1095,8 @@ namespace Microsoft.WebAssembly.Diagnostics
                         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<string>().Equals("_message"));
                         var data = JObject.FromObject(new
                         {
index a5a6000..a3e0fb5 100644 (file)
@@ -16,7 +16,7 @@ using Microsoft.CodeAnalysis.CSharp;
 using System.Reflection;
 using System.Text;
 using System.Runtime.CompilerServices;
-using System.Diagnostics;
+using BrowserDebugProxy;
 
 namespace Microsoft.WebAssembly.Diagnostics
 {
@@ -696,110 +696,16 @@ namespace Microsoft.WebAssembly.Diagnostics
         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<JArray> 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<JArray> 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<int> typesToGetProperties = new List<int>();
-            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<JArray> 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 @@ namespace Microsoft.WebAssembly.Diagnostics
         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 @@ namespace Microsoft.WebAssembly.Diagnostics
             this.varName = varName;
         }
 
+        public async Task<JObject> 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 @@ namespace Microsoft.WebAssembly.Diagnostics
         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 @@ namespace Microsoft.WebAssembly.Diagnostics
             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<int> TypeIsInitialized(int typeId, CancellationToken token)
@@ -1374,23 +1299,24 @@ namespace Microsoft.WebAssembly.Diagnostics
 
             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 @@ namespace Microsoft.WebAssembly.Diagnostics
                 //.Replace("System.Decimal", "decimal")
                 .ToString();
 
-        internal async Task<MonoBinaryReader> GetCAttrsFromType(int objectId, int typeId, string attrName, CancellationToken token)
+        internal async Task<MonoBinaryReader> GetCAttrsFromType(int typeId, string attrName, CancellationToken token)
         {
             using var commandParamsWriter = new MonoBinaryWriter();
             commandParamsWriter.Write(typeId);
@@ -1442,7 +1368,7 @@ namespace Microsoft.WebAssembly.Diagnostics
                     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 @@ namespace Microsoft.WebAssembly.Diagnostics
             return retDebuggerCmdReader.ReadInt32();
         }
 
-        public async Task<string> GetValueFromDebuggerDisplayAttribute(int objectId, int typeId, CancellationToken token)
+        public async Task<string> 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<string>(value: "", type: "object", description: "", writable: false, objectId: $"dotnet:object:{objectId}");
+                var thisObj = CreateJObject<string>(value: "", type: "object", description: "", writable: false, objectId: dotnetObjectId.ToString());
                 thisObj["name"] = "this";
                 objectValues.Add(thisObj);
 
@@ -1579,7 +1509,8 @@ namespace Microsoft.WebAssembly.Diagnostics
             }
             return new ArrayDimensions(rank);
         }
-        public async Task<List<int>> GetTypeIdFromObject(int object_id, bool withParents, CancellationToken token)
+
+        public async Task<List<int>> GetTypeIdsForObject(int object_id, bool withParents, CancellationToken token)
         {
             List<int> ret = new List<int>();
             using var commandParamsWriter = new MonoBinaryWriter();
@@ -1604,7 +1535,7 @@ namespace Microsoft.WebAssembly.Diagnostics
 
         public async Task<string> 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 @@ namespace Microsoft.WebAssembly.Diagnostics
 
             return $"{returnType} {methodName} {parameters}";
         }
-        public async Task<JObject> InvokeMethod(ArraySegment<byte> valueTypeBuffer, int methodId, string varName, CancellationToken token)
+
+        public async Task<JObject> InvokeMethod(ArraySegment<byte> 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<JObject> 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<JObject> InvokeMethodInObject(DotnetObjectId objectId, int methodId, string varName, CancellationToken token)
+        public Task<JObject> 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>();
+                int? embeddedMethodId = args["methodId"]?.Value<int>();
+
+                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<bool>() == 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<int> GetPropertyMethodIdByName(int typeId, string propertyName, CancellationToken token)
@@ -1707,68 +1664,12 @@ namespace Microsoft.WebAssembly.Diagnostics
             return -1;
         }
 
-        public async Task<JArray> CreateJArrayForProperties(int typeId, ElementType elementType, ArraySegment<byte> 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<string>().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<JObject> 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 @@ namespace Microsoft.WebAssembly.Diagnostics
             return false;
         }
 
-        private static JObject CreateJObject<T>(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>(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 @@ namespace Microsoft.WebAssembly.Diagnostics
             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 @@ namespace Microsoft.WebAssembly.Diagnostics
         public async Task<JObject> 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 @@ namespace Microsoft.WebAssembly.Diagnostics
             if (await IsDelegate(objectId, token))
             {
                 if (typeIdFromAttribute != -1)
-                {
                     className = await GetTypeName(typeIdFromAttribute, token);
-                }
 
                 description = await GetDelegateMethodDescription(objectId, token);
                 if (description == "")
-                {
                     return CreateJObject<string>(className.ToString(), "symbol", className.ToString(), false);
-                }
             }
             return CreateJObject<string>(null, "object", description, false, className, $"dotnet:object:{objectId}");
         }
 
-        public async Task<JObject> 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<JObject> 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<string>(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<string>();
-                if (className.Equals("System.Guid"))
-                    description = description.ToUpperInvariant(); //to keep the old behavior
-            }
-            else if (isBoxed && numFields == 1) {
-                return fieldValueType;
-            }
-            return CreateJObject<string>(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<JObject> CreateJObjectForNull(MonoBinaryReader retDebuggerCmdReader, CancellationToken token)
@@ -1996,9 +1909,14 @@ namespace Microsoft.WebAssembly.Diagnostics
             return CreateJObject<string>(null, "object", className, false, className, null, null, "null");
         }
 
-        public async Task<JObject> CreateJObjectForVariableValue(MonoBinaryReader retDebuggerCmdReader, string name, bool isOwn, int typeIdFromAttribute, bool forDebuggerDisplayAttribute, CancellationToken token)
+        public async Task<JObject> 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 @@ namespace Microsoft.WebAssembly.Diagnostics
                 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 @@ namespace Microsoft.WebAssembly.Diagnostics
             {
                 if (isOwn)
                     ret["isOwn"] = true;
-                ret["name"] = name;
+                if (!string.IsNullOrEmpty(name))
+                    ret["name"] = name;
             }
             return ret;
         }
@@ -2163,7 +2082,7 @@ namespace Microsoft.WebAssembly.Diagnostics
                         fieldName.StartsWith ("<>8__", StringComparison.Ordinal);
         }
 
-        public async Task<JArray> GetHoistedLocalVariables(int objectId, JArray asyncLocals, CancellationToken token)
+        public async Task<JArray> GetHoistedLocalVariables(int objectId, IEnumerable<JToken> asyncLocals, CancellationToken token)
         {
             JArray asyncLocalsFull = new JArray();
             List<int> objectsAlreadyRead = new();
@@ -2182,8 +2101,9 @@ namespace Microsoft.WebAssembly.Diagnostics
                     {
                         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 @@ namespace Microsoft.WebAssembly.Diagnostics
                 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 @@ namespace Microsoft.WebAssembly.Diagnostics
             {
                 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 @@ namespace Microsoft.WebAssembly.Diagnostics
             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 @@ namespace Microsoft.WebAssembly.Diagnostics
             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 @@ namespace Microsoft.WebAssembly.Diagnostics
             return retDebuggerCmdReader.ReadInt32();
         }
 
-        public async Task<JArray> GetValuesFromDebuggerProxyAttribute(int objectId, int typeId, CancellationToken token)
+        // FIXME: support valuetypes
+        public async Task<GetMembersResult> 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<string>(), 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<int> 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 @@ namespace Microsoft.WebAssembly.Diagnostics
                             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<string>(), 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<JArray> 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<FieldTypeClass>();
-            var objects = new Dictionary<string, JToken>();
-            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<FieldTypeClass> fields, Dictionary<string, JToken> 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<string>(), 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<string>() != "array")
-                {
-                    resultValue = await GetObjectValues(rootHiddenObjectIdInt, getCommandType, token);
-                    DotnetObjectId.TryParse(resultValue[0]?["value"]?["objectId"]?.Value<string>(), 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<JArray> GetFieldsValues(List<FieldTypeClass> 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<string>())).Any())
-                        continue;
-                    if (getCommandType.HasFlag(GetObjectCommandOptions.WithSetter))
-                    {
-                        var command_params_writer_to_set = new MonoBinaryWriter();
-                        command_params_writer_to_set.Write(objectId);
-                        command_params_writer_to_set.Write(1);
-                        command_params_writer_to_set.Write(field.Id);
-                        var (data, length) = command_params_writer_to_set.ToBase64();
+            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<GetMembersResult> 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<FieldTypeClass>, List<FieldTypeClass>)> FilterFieldsByDebuggerBrowsable(List<FieldTypeClass> fields, int typeId, CancellationToken token)
-            {
-                if (fields.Count == 0)
-                    return (fields, new List<FieldTypeClass>());
-
-                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<FieldTypeClass>());
-
-                var collapsedFields = new List<FieldTypeClass>();
-                var rootHiddenFields = new List<FieldTypeClass>();
-                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<JArray> GetProperties(JArray props, List<FieldTypeClass> 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<string>();
-                    // 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<string>(), 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<JObject> GetMethodProxy(JObject objectId, CancellationToken token)
+        {
+            var containerId = objectId["containerId"].Value<int>();
+            var methodId = objectId["methodId"].Value<int>();
+            var isValueType = objectId["isValueType"].Value<bool>();
+            return await InvokeMethod(containerId, methodId, isValueType, token);
         }
 
         public async Task<JArray> 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 @@ namespace Microsoft.WebAssembly.Diagnostics
                 arr.Add(item);
         }
 
-        public static void TryAddRange(this Dictionary<string, JToken> dict, JArray addedArr)
-        {
-            foreach (var item in addedArr)
-            {
-                var key = item["name"]?.Value<string>();
-                if (key == null)
-                    continue;
-                dict.TryAdd(key, item);
-            }
-        }
-
-        public static JArray GetValuesByKeys(this Dictionary<string, JToken> dict, List<string> 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<string>() == "object" && obj["subtype"]?.Value<string>() == "null";
 
diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs
new file mode 100644 (file)
index 0000000..a9732a5
--- /dev/null
@@ -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<ValueTypeClass> 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<FieldTypeClass> fieldTypes = await sdbAgent.GetTypeFields(typeId, token);
+            // statics should not be in valueType fields: CallFunctionOnTests.PropertyGettersTest
+            IEnumerable<FieldTypeClass> 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<JObject> 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<string>();
+                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<string>(null, "object", description, false, className, Id.ToString(), null, null, true, true, IsEnum);
+            return obj;
+        }
+
+        public async Task<JArray> 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<GetMembersResult> 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<JToken> 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<string>(), out DebuggerBrowsableState state))
+                {
+                    visibleFields.Add(field);
+                    continue;
+                }
+                var fieldValue = field["value"] ?? field["get"];
+                string typeName = fieldValue?["className"]?.Value<string>();
+                JArray fieldMembers = await MemberObjectsExplorer.GetExpandedMemberValues(
+                    sdbHelper, typeName, field["name"]?.Value<string>(), 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<string, JObject>();
+            foreach (var f in fields)
+                allMembers[f["name"].Value<string>()] = 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";
+    }
+}
index 447b06a..8bf4347 100644 (file)
@@ -386,6 +386,16 @@ namespace DebuggerTests
             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<JToken> CheckValueType(JToken locals, string name, string class_name, string description=null)
         {
             var l = GetAndAssertObjectWithName(locals, name);
@@ -738,20 +748,21 @@ namespace DebuggerTests
 
                 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<string>());
+                    var actual_props = await GetProperties(actual_obj["value"]["objectId"]?.Value<string>());
                     await CheckProps(actual_props, exp_val, $"{label}-{exp_name}");
                 }
                 else if (exp_val["__custom_type"] != null && exp_val["__custom_type"]?.Value<string>() == "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}");
                 }
             }
         }
index ce77119..1b693c1 100644 (file)
@@ -503,22 +503,22 @@ namespace DebuggerTests
             "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<string>();
+               var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
 
-                var (_, res) = await EvaluateOnCallFrame(id, "this.objToTest.MyMethodWrong()", expect_ok: false );
-                Assert.Contains($"Method 'MyMethodWrong' not found", res.Error["message"]?.Value<string>());
+               var (_, res) = await EvaluateOnCallFrame(id, "this.objToTest.MyMethodWrong()", expect_ok: false );
+               Assert.Contains($"Method 'MyMethodWrong' not found", res.Error["message"]?.Value<string>());
 
-                (_, 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<string>());
+               (_, 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<string>());
 
-                (_, res) = await EvaluateOnCallFrame(id, "this.CallMethodWithParm(\"1\")", expect_ok: false );
-                Assert.Contains("Unable to evaluate method 'this.CallMethodWithParm(\"1\")'", res.Error["message"]?.Value<string>());
+               (_, res) = await EvaluateOnCallFrame(id, "this.CallMethodWithParm(\"1\")", expect_ok: false );
+               Assert.Contains("Unable to evaluate method 'this.CallMethodWithParm(\"1\")'", res.Error["message"]?.Value<string>());
 
-                (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjNull.MyMethod()", expect_ok: false );
-                Assert.Contains("Expression 'this.ParmToTestObjNull.MyMethod' evaluated to null", res.Error["message"]?.Value<string>());
+               (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjNull.MyMethod()", expect_ok: false );
+               Assert.Contains("Expression 'this.ParmToTestObjNull.MyMethod' evaluated to null", res.Error["message"]?.Value<string>());
 
-                (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjException.MyMethod()", expect_ok: false );
-                Assert.Contains("Cannot invoke method 'MyMethod'", res.Error["message"]?.Value<string>());
+               (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjException.MyMethod()", expect_ok: false );
+               Assert.Contains("Cannot invoke method 'MyMethod'", res.Error["message"]?.Value<string>());
            });
 
         [Fact]
@@ -679,18 +679,18 @@ namespace DebuggerTests
             "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<string>();
+               var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
 
-                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 @@ namespace DebuggerTests
             "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<string>();
+               var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
 
-                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 @@ namespace DebuggerTests
             $"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<string>();
+               var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
 
-                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 @@ namespace DebuggerTests
             "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<string>();
+               var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
 
-                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<string>();
+               var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
 
-                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 @@ namespace DebuggerTests
             "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<string>();
+               var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
 
-                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 @@ namespace DebuggerTests
                     ("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 @@ namespace DebuggerTests
             "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<string>();
+               var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
 
-                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<string>(), "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<string>(), "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<string>(), "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<string>(), "wrong error message");
            });
 
-         [ConditionalFact(nameof(RunningOnChrome))]
-         public async Task AsyncLocalsInContinueWithBlock() => await CheckInspectLocalsAtBreakpointSite(
-            "DebuggerTests.AsyncTests.ContinueWithTests", "ContinueWithStaticAsync", 4, "<ContinueWithStaticAsync>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<string>());
-                var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
+        [ConditionalFact(nameof(RunningOnChrome))]
+        public async Task AsyncLocalsInContinueWithBlock() => await CheckInspectLocalsAtBreakpointSite(
+           "DebuggerTests.AsyncTests.ContinueWithTests", "ContinueWithStaticAsync", 4, "<ContinueWithStaticAsync>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<string>());
+               var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
 
-                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 @@ namespace DebuggerTests
            });
 
         [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 @@ namespace DebuggerTests
                     {
                         list = TGetter("list", TObject("System.Collections.Generic.List<int>", 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<int>", 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 @@ namespace DebuggerTests
                 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 @@ namespace DebuggerTests
                     {
                         listCollapsed = TGetter("listCollapsed", TObject("System.Collections.Generic.List<int>", 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<int>", 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 @@ namespace DebuggerTests
                 var (testRootHidden, _) = await EvaluateOnCallFrame(id, localVarName);
                 await CheckValue(testRootHidden, TObject($"DebuggerTests.{outerClassName}.{className}"), nameof(testRootHidden));
                 var testRootHiddenProps = await GetProperties(testRootHidden["objectId"]?.Value<string>());
+
                 var (refList, _) = await EvaluateOnCallFrame(id, "testPropertiesNone.list");
                 var refListProp = await GetProperties(refList["objectId"]?.Value<string>());
-                var refListElementsProp = await GetProperties(refListProp[0]["value"]["objectId"]?.Value<string>());
+                var list = refListProp
+                    .Where(v => v["name"]?.Value<string>() == "Items" || v["name"]?.Value<string>() == "_items")
+                    .FirstOrDefault();
+                var refListElementsProp = await GetProperties(list["value"]["objectId"]?.Value<string>());
+
                 var (refArray, _) = await EvaluateOnCallFrame(id, "testPropertiesNone.array");
                 var refArrayProp = await GetProperties(refArray["objectId"]?.Value<string>());
 
+                var (refStruct, _) = await EvaluateOnCallFrame(id, "testPropertiesNone.sampleStruct");
+                var refStructProp = await GetProperties(refStruct["objectId"]?.Value<string>());
+
+                var (refClass, _) = await EvaluateOnCallFrame(id, "testPropertiesNone.sampleClass");
+                var refClassProp = await GetProperties(refClass["objectId"]?.Value<string>());
+
+                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<string>());
+                }
                 foreach (var item in refArrayProp)
                 {
                     item["name"] = string.Concat("arrayRootHidden[", item["name"], "]");
+                    CheckContainsJObject(testRootHiddenProps, item, item["name"].Value<string>());
                 }
-                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<string>());
                 }
-                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<string>());
+                }
+            });
 
         [ConditionalFact(nameof(RunningOnChrome))]
         public async Task EvaluateStaticAttributeInAssemblyNotRelatedButLoaded() => await CheckInspectLocalsAtBreakpointSite(
@@ -1002,46 +1057,6 @@ namespace DebuggerTests
            });
 
         [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<string>();
-                var (obj, _) = await EvaluateOnCallFrame(id, "this");
-                var (pub, internalAndProtected, priv) = await GetPropertiesSortedByProtectionLevels(obj["objectId"]?.Value<string>());
-
-                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",
             "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.StructureGetters:Evaluate'); })",
@@ -1111,7 +1126,7 @@ namespace DebuggerTests
 
                 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<string>());
-           });
+            });
 
         [Fact]
         public async Task EvaluateMethodWithLinq() => await CheckInspectLocalsAtBreakpointSite(
@@ -1123,7 +1138,7 @@ namespace DebuggerTests
                 await EvaluateOnCallFrameAndCheck(id,
                    ("test.listToLinq.ToList()", TObject("System.Collections.Generic.List<int>", description: "Count = 11"))
                    );
-           });
+            });
     }
 
 }
index cdd0914..717ef7b 100644 (file)
@@ -36,12 +36,16 @@ namespace 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 @@ namespace 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 @@ namespace DebuggerTests
             }
         }
 
+         public static TheoryData<Dictionary<string, JObject>, Dictionary<string, JObject>, Dictionary<string, JObject>, string> GetDataForProtectionLevels()
+        {
+            var data = new TheoryData<Dictionary<string, JObject>, Dictionary<string, JObject>, Dictionary<string, JObject>, string>();
+            // object DerivedClass; should be 23 elements:
+            var public_props = new Dictionary<string, JObject>()
+            {
+                //  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<string, JObject>(){
+                // internal
+                {"b",                               TBool(true)},
+                // inherited protected
+                {"base_num",                        TNumber(5)}
+            };
+            
+            var private_props = new Dictionary<string, JObject>(){
+                {"_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<string, JObject>()
+            {
+                // own
+                {"a",                       TNumber(4)},
+                {"DateTime",                TGetter("DateTime")},
+                {"AutoStringProperty",      TString("CloneableStruct#AutoStringProperty")},
+                {"FirstName",               TGetter("FirstName")},
+                {"LastName",                TGetter("LastName")}
+            };
+            internal_protected_props = new Dictionary<string, JObject>()
+            {
+                // internal
+                {"b",                       TBool(true)}
+            };
+            private_props = new Dictionary<string, JObject>()
+            {
+                {"_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<string, JObject> expectedPublic, Dictionary<string, JObject> expectedProtInter, Dictionary<string, JObject> 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<string>();
+                var (obj, _) = await EvaluateOnCallFrame(id, "this");
+                var (pub, internalAndProtected, priv) = await GetPropertiesSortedByProtectionLevels(obj["objectId"]?.Value<string>());
+
+                await CheckProps(pub, expectedPublic, "public");
+                await CheckProps(internalAndProtected, expectedProtInter, "internalAndProtected");
+                await CheckProps(priv, expectedPriv, "private");
+           });
+
     }
 }
index 80db1d2..ef66394 100644 (file)
@@ -497,7 +497,7 @@ namespace DebuggerTests
                 textListOfLists = new List<List<string>> { textList, textList };
                 idx0 = 0;
                 idx1 = 1;
-            }        
+            }
         }
 
         public static void EvaluateLocals()
@@ -509,13 +509,38 @@ namespace DebuggerTests
         }
     }
 
-    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<string> Items = new List<string> { "should not be expanded" };
+    }
+
+    public static class EvaluateBrowsableClass
     {
         public class TestEvaluateFieldsNone
         {
             public List<int> list = new List<int>() { 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 @@ namespace DebuggerTests
             public List<int> 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<int>() { 1, 2 };
                 array = new int[] { 11, 22 };
                 text = "text";
+                nullNone = null;
+                valueTypeEnum = new();
+                sampleStruct = new();
+                sampleClass = new();
             }
         }
 
@@ -542,6 +575,18 @@ namespace DebuggerTests
 
             [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 @@ namespace DebuggerTests
             [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<int>() { 1, 2 };
                 arrayNever = new int[] { 11, 22 };
                 textNever = "textNever";
+                nullNever = null;
+                valueTypeEnumNever = new();
+                sampleStructNever = new();
+                sampleClassNever = new();
             }
         }
 
@@ -573,6 +634,18 @@ namespace DebuggerTests
 
             [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 @@ namespace DebuggerTests
             [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<int>() { 1, 2 };
                 arrayCollapsed = new int[] { 11, 22 };
                 textCollapsed = "textCollapsed";
+                nullCollapsed = null;
+                valueTypeEnumCollapsed = new();
+                sampleStructCollapsed = new();
+                sampleClassCollapsed = new();
             }
         }
 
@@ -604,6 +693,18 @@ namespace DebuggerTests
 
             [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 @@ namespace DebuggerTests
             [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<int>() { 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<int> list = new List<int>() { 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<int> 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<int>() { 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<int> listNever = new List<int>() { 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<int> 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<int>() { 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<int> listCollapsed = new List<int>() { 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<int> 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<int>() { 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<int> listRootHidden = new List<int>() { 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<int> 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<int>() { 1, 2 };
                 arrayRootHidden = new int[] { 11, 22 };
                 textRootHidden = "textRootHidden";
+                nullRootHidden = null;
+                valueTypeEnumRootHidden = new();
+                sampleStructRootHidden = new();
+                sampleClassRootHidden = new();
             }
         }
 
@@ -639,13 +989,18 @@ namespace DebuggerTests
         }
     }
 
-    public static class EvaluateBrowsableStaticProperties
+    public static class EvaluateBrowsableStaticClass
     {
         public class TestEvaluateFieldsNone
         {
             public static List<int> list = new List<int>() { 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 @@ namespace DebuggerTests
             public static List<int> 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<int>() { 1, 2 };
                 array = new int[] { 11, 22 };
                 text = "text";
+                nullNone = null;
+                valueTypeEnum = new();
+                sampleStruct = new();
+                sampleClass = new();
             }
         }
 
@@ -672,6 +1035,18 @@ namespace DebuggerTests
 
             [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 @@ namespace DebuggerTests
             [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<int>() { 1, 2 };
                 arrayNever = new int[] { 11, 22 };
                 textNever = "textNever";
+                nullNever = null;
+                valueTypeEnumNever = new();
+                sampleStructNever = new();
+                sampleClassNever = new();
             }
         }
 
@@ -703,6 +1094,18 @@ namespace DebuggerTests
 
             [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 @@ namespace DebuggerTests
             [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<int>() { 1, 2 };
                 arrayCollapsed = new int[] { 11, 22 };
                 textCollapsed = "textCollapsed";
+                nullCollapsed = null;
+                valueTypeEnumCollapsed = new();
+                sampleStructCollapsed = new();
+                sampleClassCollapsed = new();
             }
         }
 
@@ -734,6 +1153,18 @@ namespace DebuggerTests
 
             [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 @@ namespace DebuggerTests
             [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<int>() { 1, 2 };
                 arrayRootHidden = new int[] { 11, 22 };
                 textRootHidden = "textRootHidden";
+                nullRootHidden = null;
+                valueTypeEnumRootHidden = new();
+                sampleStructRootHidden = new();
+                sampleClassRootHidden = new();
             }
         }
 
@@ -769,13 +1216,17 @@ namespace DebuggerTests
         }
     }
 
-    public static class EvaluateBrowsableCustomProperties
+    public static class EvaluateBrowsableCustomPropertiesClass
     {
         public class TestEvaluatePropertiesNone
         {
             public List<int> list { get { return new List<int>() { 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 @@ namespace DebuggerTests
 
             [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 @@ namespace DebuggerTests
 
             [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 @@ namespace DebuggerTests
 
             [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 @@ namespace DebuggerTests
             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 @@ namespace DebuggerTests
             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 class NoNamespaceClass
     {
         var stopHere = true;
     }
-    
+
     public static class NestedClass1
     {
         public static class NestedClass2
index d591a34..9217641 100644 (file)
@@ -30,6 +30,7 @@ namespace DebuggerTests.GetPropertiesTests
     {
         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 @@ namespace DebuggerTests.GetPropertiesTests
         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 @@ namespace DebuggerTests.GetPropertiesTests
         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 @@ namespace DebuggerTests.GetPropertiesTests
         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);