From b3389b7bcc2ecf4626a663c012ec2f80ad8c50de Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Mon, 11 Apr 2022 16:22:05 +0200 Subject: [PATCH 01/18] Merge main with @radical's refactoring. --- .../debugger/BrowserDebugProxy/DebugStore.cs | 1 + .../BrowserDebugProxy/DevToolsHelper.cs | 77 +- .../MemberObjectsExplorer.cs | 619 ++++++++++++++ .../MemberReferenceResolver.cs | 43 +- .../debugger/BrowserDebugProxy/MonoProxy.cs | 76 +- .../BrowserDebugProxy/MonoSDBHelper.cs | 801 ++++++------------ .../BrowserDebugProxy/ValueTypeClass.cs | 288 +++++++ 7 files changed, 1273 insertions(+), 632 deletions(-) create mode 100644 src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs create mode 100644 src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs index 2ee855dd861508..78774603a7c21a 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs @@ -332,6 +332,7 @@ internal class MethodInfo internal bool IsEnCMethod; internal LocalScopeHandleCollection localScopes; public bool IsStatic() => (methodDef.Attributes & MethodAttributes.Static) != 0; + public MethodAttributes Attributes => methodDef.Attributes; public int IsAsync { get; set; } public DebuggerAttributesInfo DebuggerAttrInfo { get; set; } public TypeInfo TypeInfo { get; } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs index 8d08275c7bb907..4700a43de2578c 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs @@ -63,64 +63,67 @@ public MessageId(string sessionId, int id) internal 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 JObject ValueAsJson { get; init; } + public static bool TryParse(JToken jToken, out DotnetObjectId objectId) => TryParse(jToken?.Value(), out objectId); public static bool TryParse(string id, out DotnetObjectId objectId) { objectId = null; - try - { - if (id == null) - return false; - - if (!id.StartsWith("dotnet:")) - return false; + if (id == null) + return false; - string[] parts = id.Split(":"); + if (!id.StartsWith("dotnet:")) + return false; - if (parts.Length < 3) - return false; + string[] parts = id.Split(":", 3); - objectId = new DotnetObjectId(parts[1], int.Parse(parts[2])); - switch (objectId.Scheme) - { - case "methodId": - if (parts.Length > 4) - { - objectId.SubValue = int.Parse(parts[3]); - objectId.IsValueType = parts[4] == "ValueType"; - return true; - } - return false; - } - return true; - } - catch (Exception) - { + if (parts.Length < 3) return false; - } + + objectId = new DotnetObjectId(parts[1], parts[2]); + return true; } public DotnetObjectId(string scheme, int value) - { - Scheme = scheme; - Value = value; - } + : this(scheme, value.ToString()) { } - public override string ToString() + public DotnetObjectId(string scheme, string value) { - switch (Scheme) + Scheme = scheme; + if (int.TryParse(value, out int ival)) { - case "methodId": - return $"dotnet:{Scheme}:{Value}:{SubValue}"; + _intValue = ival; + } + else + { + try + { + ValueAsJson = JObject.Parse(value); + } + catch (JsonReaderException) { } } - return $"dotnet:{Scheme}:{Value}"; } + + public override string ToString() + => _intValue != null + ? $"dotnet:{Scheme}:{_intValue}" + : $"dotnet:{Scheme}:{ValueAsJson}"; } public struct Result diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs new file mode 100644 index 00000000000000..a29849c716819a --- /dev/null +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs @@ -0,0 +1,619 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.WebAssembly.Diagnostics; +using Newtonsoft.Json.Linq; + +namespace BrowserDebugProxy +{ + internal static class MemberObjectsExplorer + { + private static bool IsACollectionType(string typeName) + => typeName is not null && + (typeName.StartsWith("System.Collections.Generic", StringComparison.Ordinal) || + typeName.EndsWith("[]", StringComparison.Ordinal)); + + private static string GetNamePrefixForValues(string memberName, string typeName, bool isOwn, DebuggerBrowsableState? state) + { + if (isOwn || state != DebuggerBrowsableState.RootHidden) + return memberName; + + string justClassName = Path.GetExtension(typeName); + if (justClassName[0] == '.') + justClassName = justClassName[1..]; + return $"{memberName} ({justClassName})"; + } + + private static async Task ReadFieldValue(MonoSDBHelper sdbHelper, MonoBinaryReader reader, FieldTypeClass field, int objectId, 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)); + + 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, + fieldValueType, + length = length + })); + } + + return fieldValue; + } + + private static async Task GetRootHiddenChildren(MonoSDBHelper sdbHelper, JObject root, string rootNamePrefix, GetObjectCommandOptions getCommandOptions, CancellationToken token) + { + if (root?["value"]?["type"]?.Value() != "object") + { + //Console.WriteLine ($"GetRootHiddenChildren: not an object: {root} - name: {rootNamePrefix}"); + return new JArray(); + } + + if (root?["value"]?["type"]?.Value() == "object" && root?["value"]?["subtype"]?.Value() == "null") + { + // FIXME: test for this + //Console.WriteLine ($"GetRootHiddenChildren: null root named {rootNamePrefix}: {root}"); + return new JArray(); + } + + if (!DotnetObjectId.TryParse(root?["value"]?["objectId"]?.Value(), out DotnetObjectId rootObjectId)) + { + throw new Exception($"Cannot parse object id from {root} for {rootNamePrefix}"); + // return null; + } + + // 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; + + // DotnetObjectId rootObjectId = rootObjectId; + //Console.WriteLine ($"GetRootHiddenChildren: rootName: {rootNamePrefix}: {root}"); + // FIXME: this just assumes that the first returned member will be an array `Items`! + // if (subtype == null || subtype?.Value() != "array") + + // FIXME: test for valuetype collection + if (rootObjectId.Scheme is "object" or "valuetype") + { + GetMembersResult members = await GetObjectMemberValues(sdbHelper, rootObjectId.Value, getCommandOptions, token); + //Console.WriteLine ($"GetObjectMemberValues on that returned: {members}"); + + var memberNamedItems = members.Where(m => m["name"]?.Value() == "Items").FirstOrDefault(); + if (memberNamedItems is not null && + DotnetObjectId.TryParse(memberNamedItems["value"]?["objectId"]?.Value(), out DotnetObjectId itemsObjectId) && + itemsObjectId.Scheme == "array") + { + rootObjectId = itemsObjectId; + } + } + + if (rootObjectId.Scheme == "array") + { + JArray resultValue = await sdbHelper.GetArrayValues(rootObjectId.Value, token); + + // root hidden item name has to be unique, so we concatenate the root's name to it + foreach (var item in resultValue) + item["name"] = string.Concat(rootNamePrefix, "[", item["name"], "]"); + + return resultValue; + } + else + { + return new JArray(); + } + } + + private static async Task GetExpandedFieldValues( + MonoSDBHelper sdbHelper, + int objectId, + int containerTypeId, + IReadOnlyList fields, + GetObjectCommandOptions getCommandOptions, + bool isOwn, + CancellationToken token) + { + JArray fieldValues = new JArray(); + if (fields.Count == 0) + return fieldValues; + + // Console.WriteLine ($"GetFieldsValues: ENTER for {objectId}"); + // FIXME: What is this doing? is there a test for it? + if (getCommandOptions.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); + MonoBinaryReader retDebuggerCmdReader = //isValueType + // ? await SendDebuggerAgentCommand(CmdType.GetValues, commandParamsWriter, token) + await sdbHelper.SendDebuggerAgentCommand(CmdObject.RefGetValues, commandParamsWriter, token); + + // FIXME: needed only if there is a RootHidden field + var typeInfo = await sdbHelper.GetTypeInfo(containerTypeId, token); + var typeFieldsBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableFields; + var typePropertiesBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableProperties; + + int numFieldsRead = 0; + foreach (FieldTypeClass field in fields) + { + long initialPos = retDebuggerCmdReader.BaseStream.Position; + int valtype = retDebuggerCmdReader.ReadByte(); + retDebuggerCmdReader.BaseStream.Position = initialPos; + // Console.WriteLine ($"create value for {field.Name}, field.TypeId: {field.TypeId}"); + + JObject fieldValue = await ReadFieldValue(sdbHelper, retDebuggerCmdReader, field, objectId, valtype, isOwn, getCommandOptions, token); + numFieldsRead++; + + if (!typeFieldsBrowsableInfo.TryGetValue(field.Name, out DebuggerBrowsableState? state)) + { + // for backing fields, we should get it from the properties + typePropertiesBrowsableInfo.TryGetValue(field.Name, out state); + } + + fieldValue["__state"] = state?.ToString(); + if (state != DebuggerBrowsableState.RootHidden) + { + fieldValues.Add(fieldValue); + continue; + } + + string namePrefix = field.Name; + string containerTypeName = await sdbHelper.GetTypeName(containerTypeId, token); + namePrefix = GetNamePrefixForValues(field.Name, containerTypeName, isOwn, state); + //Console.WriteLine ($"fieldName: {field.Name}, prefix: {namePrefix}, container: {containerTypeName}"); + + var enumeratedValues = await GetRootHiddenChildren(sdbHelper, fieldValue, namePrefix, getCommandOptions, token); + if (enumeratedValues != null) + fieldValues.AddRange(enumeratedValues); + } + + if (numFieldsRead != fields.Count) + throw new Exception($"Bug: Got {numFieldsRead} instead of expected {fields.Count} field values"); + + // Console.WriteLine ($"GetFieldsValues: Exit for {objectId}: {objFields}"); + return fieldValues; + } + + public static Task GetValueTypeMemberValues(MonoSDBHelper sdbHelper, int valueTypeId, GetObjectCommandOptions getCommandOptions, CancellationToken token, bool sortByAccessLevel = false) + { + if (!sdbHelper.valueTypes.TryGetValue(valueTypeId, out ValueTypeClass valueType)) + throw new ArgumentException($"Could not find any valuetype with id: {valueTypeId}", nameof(valueTypeId)); + + return valueType.GetMemberValues(sdbHelper, getCommandOptions, sortByAccessLevel, token); + } + + public static async Task GetExpandedMemberValues(MonoSDBHelper sdbHelper, string typeName, string namePrefix, JObject value, DebuggerBrowsableState? state, CancellationToken token) + { + //Console.WriteLine ($"- GetExpandedMemberValues: {typeName}, {namePrefix}, {value}, state: {state}"); + if (state == DebuggerBrowsableState.RootHidden) + { + if (IsACollectionType(typeName)) + return await GetRootHiddenChildren(sdbHelper, value, namePrefix, GetObjectCommandOptions.None, token); + + //Console.WriteLine ($"- AddMemberValue: adding hidden obj for {typeName} - {namePrefix} with value: {value}, state: {state}"); + return GetHiddenElement(); + } + else if (state is DebuggerBrowsableState.Never) + { + return GetHiddenElement(); + } + + // existing.Add(value); + return new JArray(value); + + JArray GetHiddenElement() + { + return new JArray(JObject.FromObject(new + { + name = namePrefix, + __hidden = true + })); + } + } + + public static async Task GetNonAutomaticPropertyValues( + MonoSDBHelper sdbHelper, + int typeId, + string containerTypeName, + ArraySegment getterParamsBuffer, + bool isAutoExpandable, + string objectIdStr, + bool isValueType, + bool isOwn, + CancellationToken token, + IDictionary allMembers, + IReadOnlyList fields, + bool includeStatic = false) + { + using var retDebuggerCmdReader = await sdbHelper.GetTypePropertiesReader(typeId, token); + if (retDebuggerCmdReader == null) + return null; + + // Console.WriteLine ($"-- GetNonAutomaticPropertyValues: {objectIdStr}"); + if (!DotnetObjectId.TryParse(objectIdStr, out DotnetObjectId objectId)) + return null; + + var nProperties = retDebuggerCmdReader.ReadInt32(); + var typeInfo = await sdbHelper.GetTypeInfo(typeId, token); + var typePropertiesBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableProperties; + + //Console.WriteLine ($"GetNonAutomaticPropertyValues containerTypeName: {containerTypeName}"); + // foreach (var kvp in allMembers) + // Console.WriteLine ($"\t{kvp.Key}"); + //Console.WriteLine ($"-- GetNonAutomaticPropertyValues - processing them now"); + + 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)// || await MethodIsStatic(getMethodId, token)) + 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); + //Console.WriteLine ($"GetNonAutomaticPropertyValues: propName: {propName}"); + + // FIXME: if it's accessorPropertiesOnly, and we want to skip any auto-props, then we need the backing field + // here to detect that.. or have access to the FieldTypeClasses + if (allMembers.TryGetValue(propName, out JObject backingField)) + { + //Console.WriteLine ($"\tFound {propName} property is allMembers: {backingField}"); + if (backingField["__isBackingField"]?.Value() == true) + { + // FIXME: this should be checked only for this type - add a typeID to it? + // 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) + { + // FIXME: primitive types? + string namePrefix = GetNamePrefixForValues(propName, containerTypeName, isOwn, state); + + string backingFieldTypeName = backingField["value"]?["className"]?.Value(); + var expanded = await GetExpandedMemberValues(sdbHelper, backingFieldTypeName, namePrefix, backingField, state, token); + //Console.WriteLine ($"And GetExpandedMemberValues for {propName} returned: {expanded}"); + backingField.Remove(); + allMembers.Remove(propName); + foreach (JObject evalue in expanded) + allMembers[evalue["name"].Value()] = evalue; + } + } + + // derived type already had a member by this name + continue; + } + else if (fields?.Where(f => f.Name == propName && f.IsBackingField)?.Any() == true) + { + //Console.WriteLine ($"\t* {propName} is a backing field.. not adding the corresponding getter"); + } + else + { + //Console.WriteLine ($"\tnot an existing one.. so, maybe not a backingField: {propName}"); + string returnTypeName = await sdbHelper.GetReturnType(getMethodId, token); + JObject propRet = null; + //logger.LogDebug($"* GetNonAutomaticPropertyValues: propertyNameStr: {propertyNameStr}, auto: {isAutoExpandable}"); + if (isAutoExpandable || (state is DebuggerBrowsableState.RootHidden && IsACollectionType(returnTypeName))) + propRet = await sdbHelper.InvokeMethod(getterParamsBuffer, getMethodId, token, name: propName); + else + propRet = GetNotAutoExpandableObject(getMethodId, propName); + + if (isOwn) + propRet["isOwn"] = true; + + // Console.WriteLine ($"- prop {propRet["name"]?.Value()}: {getterAttrs}"); + propRet["__section"] = getterAttrs switch + { + MethodAttributes.Private => "private", + MethodAttributes.Public => "result", + _ => "internal" + }; + propRet["__state"] = state?.ToString(); + + string namePrefix = GetNamePrefixForValues(propName, containerTypeName, isOwn, state); + ret.Result.AddRange(await GetExpandedMemberValues(sdbHelper, returnTypeName, namePrefix, propRet, state, token)); + } + } + + // Console.WriteLine ($"GetNonAutomaticPropertyValues returning {ret}"); + return ret; + + 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 + " ()", + //methodId = methodId, + //objectIdValue = objectIdStr + }, + name = propertyName + }); + } + } + + + public static async Task GetObjectMemberValues(MonoSDBHelper sdbHelper, int objectId, GetObjectCommandOptions getCommandType, CancellationToken token, bool sortByAccessLevel = false, bool includeStatic = false) + { + if (await sdbHelper.IsDelegate(objectId, token)) + { + var description = await sdbHelper.GetDelegateMethodDescription(objectId, token); + var objValues = JObject.FromObject(new + { + value = new + { + type = "symbol", + value = description, + description + }, + name = "Target" + }); + + return GetMembersResult.FromValues(new List() { objValues }); + } + + // 1 + var typeIdsIncludingParents = await sdbHelper.GetTypeIdsForObject(objectId, true, token); + + // 2 + if (!getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerDisplayAttribute)) + { + GetMembersResult debuggerProxy = await sdbHelper.GetValuesFromDebuggerProxyAttribute(objectId, typeIdsIncludingParents[0], token); + if (debuggerProxy != null) + return debuggerProxy; + } + + // 3. GetProperties + + using var commandParamsObjWriter = new MonoBinaryWriter(); + commandParamsObjWriter.WriteObj(new DotnetObjectId("object", objectId), sdbHelper); + ArraySegment getPropertiesParamBuffer = commandParamsObjWriter.GetParameterBuffer(); + + // RuntimeGetPropertiesResult result = new(); + + // skip adding any members, if they were in a more derived type! + // FIXME: change to HashSet + var allMembers = new Dictionary(); + // var allMembersDict = new Dictionary(); + // var memberValuesByName = new Dictionary(); + for (int i = 0; i < typeIdsIncludingParents.Count; i++) + { + int typeId = typeIdsIncludingParents[i]; + string typeName = await sdbHelper.GetTypeName(typeId, token); + // 0th id is for the object itself, and then its parents + bool isOwn = i == 0; + IReadOnlyList thisTypeFields = await sdbHelper.GetTypeFields(typeId, token); + if (!includeStatic) + thisTypeFields = thisTypeFields.Where(f => !f.Attributes.HasFlag(FieldAttributes.Static)).ToList(); + + // logger.LogDebug($"** i: {i}, {typeName}, numFields: {thisTypeFields.Count}, getCommandOptions: {getCommandOptions}"); + + // case: + // derived type overrides with an automatic property with a getter + + // if (!getCommandOptions.HasFlag(GetObjectCommandOptions.AccessorPropertiesOnly) && thisTypeFields.Count > 0) + if (thisTypeFields.Count > 0) + { + // Add fields + var allFields = await GetExpandedFieldValues(sdbHelper, objectId, typeId, thisTypeFields, getCommandType, isOwn: isOwn, token); + //Console.WriteLine ($"And fields got: {allFields}"); + + if (getCommandType.HasFlag(GetObjectCommandOptions.AccessorPropertiesOnly)) + { + foreach (var f in allFields) + f["__hidden"] = true; + } + // AddOnlyNewValuesByNameTo(new JArray(allFields.Where(f => f["__isBackingField"]?.Value() == true)), allMembers); + // else + AddOnlyNewValuesByNameTo(allFields, allMembers); + } + + GetMembersResult props = await GetNonAutomaticPropertyValues( + sdbHelper, + typeId, + typeName, + getPropertiesParamBuffer, + getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute), + $"dotnet:object:{objectId}", + isValueType: false, + isOwn, + token, + allMembers, + thisTypeFields); + //Console.WriteLine ($"And GetNonAutomaticPropertyValues returned {props}"); + + // TODO: merge with GetNonAuto* + if (getCommandType.HasFlag(GetObjectCommandOptions.WithProperties)) + { + // JArray propertiesArray = await GetExpandedPropertyValues(props.Result, allMembers, typeId, isOwn, token); + AddOnlyNewValuesByNameTo(props.Result, 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;*/ + } + + // FIXME: SplitMemberValuesByProtectionLevel needs ProtectionLevel, and Name + return GetMembersResult.FromValues(allMembers.Values, sortByAccessLevel); + + static void AddOnlyNewValuesByNameTo(JArray namedValues, IDictionary valuesDict)//, IDictionary fields) + { + foreach (var item in namedValues) + { + var key = item["name"]?.Value(); + if (key != null) + { + if (!valuesDict.ContainsKey(key))// && !fields.ContainsKey(key)) + { + // //Console.WriteLine ($"- AddOnlyNewValuesByNameTo: adding {key} = {item}"); + // FIXME: + valuesDict.Add(key, item as JObject); + } + } + } + } + } + + } + + internal class GetMembersResult : IEnumerable + { + // public: + public JArray Result { get; set; } + // private: + public JArray PrivateMembers { get; set; } + // other: + public JArray OtherMembers { get; set; } + + public GetMembersResult() { } + + public GetMembersResult(JObject value, bool IsFlattened = false) + { + // ToDo + } + + public static GetMembersResult FromValues(JArray values, bool splitMembersByAccessLevel = false) => + FromValues((IEnumerable)values.GetEnumerator(), splitMembersByAccessLevel); + + + public static GetMembersResult FromValues(IEnumerable values, bool splitMembersByAccessLevel = false) + { + GetMembersResult result = new(); + if (splitMembersByAccessLevel) + { + foreach (var member in values) + result.Split(member); + return result; + } + result.Result.AddRange(values); + return result; + } + + private void Split(JToken member) + { + if (member["__hidden"]?.Value() == true) + return; + + if (member["__section"]?.Value() is not string section) + { + Result.Add(member); + return; + } + + switch (section) + { + case "private": + PrivateMembers.Add(member); + return; + case "internal": + OtherMembers.Add(member); + return; + default: + Result.Add(member); + return; + } + } + + public GetMembersResult Clone() => new GetMembersResult() { Result = Result, PrivateMembers = PrivateMembers, OtherMembers = OtherMembers }; + + public IEnumerator GetEnumerator() => new JToken[] { Result, PrivateMembers, OtherMembers }.ToList().GetEnumerator(); + + public IEnumerable Where(Func predicate) + { + foreach (var item in Result) + { + if (predicate(item)) + { + yield return item; + } + } + foreach (var item in PrivateMembers) + { + if (predicate(item)) + { + yield return item; + } + } + foreach (var item in OtherMembers) + { + if (predicate(item)) + { + yield return item; + } + } + } + + internal JToken FirstOrDefault(Func p) => Result.FirstOrDefault(p) ?? PrivateMembers.FirstOrDefault() ?? OtherMembers.FirstOrDefault(); + internal void Add(JObject thisObj) => Result.Add(thisObj); + internal JArray Flatten() => new JArray(Result, PrivateMembers, OtherMembers); + } +} diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs index 1f136d84d18b92..eaf7818a50e559 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Generic; using System.Net.WebSockets; +using BrowserDebugProxy; namespace Microsoft.WebAssembly.Diagnostics { @@ -54,7 +55,7 @@ public async Task GetValueFromObject(JToken objRet, CancellationToken t { if (DotnetObjectId.TryParse(objRet?["value"]?["objectId"]?.Value(), out DotnetObjectId objectId)) { - var exceptionObject = await context.SdbAgent.GetObjectValues(objectId.Value, GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.OwnProperties, token); + GetMembersResult exceptionObject = await context.SdbAgent.GetTypeMemberValues(objectId, GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.OwnProperties, token); var exceptionObjectMessage = exceptionObject.FirstOrDefault(attr => attr["name"].Value().Equals("_message")); exceptionObjectMessage["value"]["value"] = objRet["value"]?["className"]?.Value() + ": " + exceptionObjectMessage["value"]?["value"]?.Value(); return exceptionObjectMessage["value"]?.Value(); @@ -64,16 +65,12 @@ public async Task GetValueFromObject(JToken objRet, CancellationToken t if (objRet["value"]?.Value() != null) return objRet["value"]?.Value(); - if (objRet["get"]?.Value() != null) - { - if (DotnetObjectId.TryParse(objRet?["get"]?["objectIdValue"]?.Value(), out DotnetObjectId objectId)) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.WriteObj(objectId, context.SdbAgent); - var ret = await context.SdbAgent.InvokeMethod(commandParamsWriter.GetParameterBuffer(), objRet["get"]["methodId"].Value(), objRet["name"].Value(), token); - return await GetValueFromObject(ret, token); - } + if (objRet["get"]?.Value() != null && + DotnetObjectId.TryParse(objRet?["get"]?["objectId"]?.Value(), out DotnetObjectId getterObjectId)) + { + var ret = await context.SdbAgent.InvokeMethod(getterObjectId, token); + return await GetValueFromObject(ret, token); } return null; } @@ -150,7 +147,7 @@ async Task FindStaticMemberInType(string name, int typeId) { using var commandParamsObjWriter = new MonoBinaryWriter(); commandParamsObjWriter.Write(0); //param count - var retMethod = await context.SdbAgent.InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, "methodRet", token); + var retMethod = await context.SdbAgent.InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, token); return await GetValueFromObject(retMethod, token); } return null; @@ -234,7 +231,7 @@ async Task ResolveAsLocalOrThisMember(string name) if (!DotnetObjectId.TryParse(objThis?["value"]?["objectId"]?.Value(), out DotnetObjectId objectId)) return null; - ValueOrError valueOrError = await proxy.RuntimeGetPropertiesInternal(sessionId, objectId, null, token); + ValueOrError valueOrError = await proxy.RuntimeGetObjectMembers(sessionId, objectId, null, token); if (valueOrError.IsError) { logger.LogDebug($"ResolveAsLocalOrThisMember failed with : {valueOrError.Error}"); @@ -261,7 +258,7 @@ async Task ResolveAsInstanceMember(string expr, JObject baseObject) if (!DotnetObjectId.TryParse(resolvedObject?["objectId"]?.Value(), out DotnetObjectId objectId)) return null; - ValueOrError valueOrError = await proxy.RuntimeGetPropertiesInternal(sessionId, objectId, null, token); + ValueOrError valueOrError = await proxy.RuntimeGetObjectMembers(sessionId, objectId, null, token); if (valueOrError.IsError) { logger.LogDebug($"ResolveAsInstanceMember failed with : {valueOrError.Error}"); @@ -358,9 +355,10 @@ public async Task Resolve(ElementAccessExpressionSyntax elementAccess, rootObject["value"] = await context.SdbAgent.GetArrayValues(objectId.Value, token); return (JObject)rootObject["value"][elementIdx]["value"]; case "object": - var typeIds = await context.SdbAgent.GetTypeIdFromObject(objectId.Value, true, token); + var typeIds = await context.SdbAgent.GetTypeIdsForObject(objectId.Value, true, token); int methodId = await context.SdbAgent.GetMethodIdByName(typeIds[0], "ToArray", token); - var toArrayRetMethod = await context.SdbAgent.InvokeMethodInObject(objectId, methodId, elementAccess.Expression.ToString(), token); + // var toArrayRetMethod = await context.SdbAgent.InvokeMethodInObject(objectId.Value, methodId, elementAccess.Expression.ToString(), token); + var toArrayRetMethod = await context.SdbAgent.InvokeMethod(objectId.Value, methodId, isValueType: false, token); rootObject = await GetValueFromObject(toArrayRetMethod, token); DotnetObjectId.TryParse(rootObject?["objectId"]?.Value(), out DotnetObjectId arrayObjectId); rootObject["value"] = await context.SdbAgent.GetArrayValues(arrayObjectId.Value, token); @@ -409,7 +407,18 @@ public async Task Resolve(InvocationExpressionSyntax method, Dictionary if (!DotnetObjectId.TryParse(rootObject?["objectId"]?.Value(), out DotnetObjectId objectId)) throw new ExpressionEvaluationFailedException($"Cannot invoke method '{methodName}' on invalid object id: {rootObject}"); - var typeIds = await context.SdbAgent.GetTypeIdFromObject(objectId.Value, true, token); + List typeIds; + if (objectId.IsValueType) + { + if (!context.SdbAgent.valueTypes.TryGetValue(objectId.Value, out ValueTypeClass valueType)) + throw new Exception($"Could not find valuetype {objectId}"); + + typeIds = new List(1) { valueType.TypeId }; + } + else + { + typeIds = await context.SdbAgent.GetTypeIdsForObject(objectId.Value, true, token); + } int methodId = await context.SdbAgent.GetMethodIdByName(typeIds[0], methodName, token); var className = await context.SdbAgent.GetTypeNameOriginal(typeIds[0], token); if (methodId == 0) //try to search on System.Linq.Enumerable @@ -481,7 +490,7 @@ public async Task Resolve(InvocationExpressionSyntax method, Dictionary if (!await commandParamsObjWriter.WriteConst(methodParamsInfo[argIndex].TypeCode, methodParamsInfo[argIndex].Value, context.SdbAgent, token)) throw new InternalErrorException($"Unable to write optional parameter {methodParamsInfo[argIndex].Name} value in method '{methodName}' to the mono buffer."); } - var retMethod = await context.SdbAgent.InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, "methodRet", token); + var retMethod = await context.SdbAgent.InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, token); return await GetValueFromObject(retMethod, token); } } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index f398ce0a1a29eb..c1e94948134f01 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -13,6 +13,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.Net.Http; +using BrowserDebugProxy; namespace Microsoft.WebAssembly.Diagnostics { @@ -469,7 +470,7 @@ protected override async Task AcceptCommand(MessageId id, string method, J 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}"); @@ -656,7 +657,7 @@ private async Task CallOnFunction(MessageId id, JObject args, Cancellation switch (objectId.Scheme) { case "object": - case "methodId": + case "method": args["details"] = await context.SdbAgent.GetObjectProxy(objectId.Value, token); break; case "valuetype": @@ -700,7 +701,7 @@ private async Task CallOnFunction(MessageId id, JObject args, Cancellation byte[] newBytes = Convert.FromBase64String(res.Value?["result"]?["value"]?["value"]?.Value()); var retDebuggerCmdReader = new MonoBinaryReader(newBytes); retDebuggerCmdReader.ReadByte(); //number of objects returned. - var obj = await context.SdbAgent.CreateJObjectForVariableValue(retDebuggerCmdReader, "ret", false, -1, false, token); + var obj = await context.SdbAgent.CreateJObjectForVariableValue(retDebuggerCmdReader, "ret", token); /*JTokenType? res_value_type = res.Value?["result"]?["value"]?.Type;*/ res = Result.OkFromObject(new { result = obj["value"]}); SendResponse(id, res, token); @@ -731,83 +732,71 @@ private async Task OnSetVariableValue(MessageId id, int scopeId, string va return true; } - internal async Task> RuntimeGetPropertiesInternal(SessionId id, DotnetObjectId objectId, JToken args, CancellationToken token, bool sortByAccessLevel = false) + internal async Task> RuntimeGetObjectMembers(SessionId id, DotnetObjectId objectId, JToken args, CancellationToken token, bool sortByAccessLevel = false) { var context = GetContext(id); - var accessorPropertiesOnly = false; - GetObjectCommandOptions objectValuesOpt = GetObjectCommandOptions.WithProperties; + //var accessorPropertiesOnly = false; + GetObjectCommandOptions getObjectOptions = GetObjectCommandOptions.WithProperties; if (args != null) { if (args["accessorPropertiesOnly"] != null && args["accessorPropertiesOnly"].Value()) { - objectValuesOpt |= GetObjectCommandOptions.AccessorPropertiesOnly; - accessorPropertiesOnly = true; + getObjectOptions |= GetObjectCommandOptions.AccessorPropertiesOnly; + //accessorPropertiesOnly = true; } if (args["ownProperties"] != null && args["ownProperties"].Value()) { - objectValuesOpt |= GetObjectCommandOptions.OwnProperties; + getObjectOptions |= GetObjectCommandOptions.OwnProperties; } } - try { + // Console.WriteLine ($"* RuntimeGetPropertiesInternal: {objectId}"); + try + { switch (objectId.Scheme) { case "scope": - { Result resScope = await GetScopeProperties(id, objectId.Value, token); return resScope.IsOk - ? ValueOrError.WithValue(sortByAccessLevel ? resScope.Value : resScope.Value?["result"]) - : ValueOrError.WithError(resScope); - } + ? ValueOrError.WithValue(new GetMembersResult(resScope.Value, IsFlattened: false)) + : ValueOrError.WithError(resScope); case "valuetype": - { var valType = context.SdbAgent.GetValueTypeClass(objectId.Value); if (valType == null) - return ValueOrError.WithError($"Internal Error: No valuetype found for {objectId}."); - var resValue = await valType.GetValues(context.SdbAgent, accessorPropertiesOnly, token); + return ValueOrError.WithError($"Internal Error: No valuetype found for {objectId}."); + var resValue = await valType.GetMemberValues(context.SdbAgent, getObjectOptions, sortByAccessLevel, token); return resValue switch { - null => ValueOrError.WithError($"Could not get properties for {objectId}"), - _ => ValueOrError.WithValue(sortByAccessLevel ? JObject.FromObject(new { result = resValue }) : resValue) + null => ValueOrError.WithError($"Could not get properties for {objectId}"), + _ => ValueOrError.WithValue(resValue) }; - } case "array": - { var resArr = await context.SdbAgent.GetArrayValues(objectId.Value, token); - return ValueOrError.WithValue(sortByAccessLevel ? JObject.FromObject(new { result = resArr }) : resArr); - } - case "methodId": - { - var resMethod = await context.SdbAgent.InvokeMethodInObject(objectId, objectId.SubValue, null, token); - return ValueOrError.WithValue(sortByAccessLevel ? JObject.FromObject(new { result = new JArray(resMethod) }) : new JArray(resMethod)); - } + return ValueOrError.WithValue(GetMembersResult.FromValues(resArr)); + case "method": + var resMethod = await context.SdbAgent.InvokeMethod(objectId, token); + return ValueOrError.WithValue(GetMembersResult.FromValues(new JArray(resMethod))); case "object": - { - var resObj = await context.SdbAgent.GetObjectValues(objectId.Value, objectValuesOpt, token, sortByAccessLevel); - return ValueOrError.WithValue(sortByAccessLevel ? resObj[0] : resObj); - } + var resObj = await MemberObjectsExplorer.GetObjectMemberValues(context.SdbAgent, objectId.Value, getObjectOptions, token, sortByAccessLevel); + return ValueOrError.WithValue(resObj); case "pointer": - { var resPointer = new JArray { await context.SdbAgent.GetPointerContent(objectId.Value, token) }; - return ValueOrError.WithValue(sortByAccessLevel ? JObject.FromObject(new { result = resPointer }) : resPointer); - } + return ValueOrError.WithValue(GetMembersResult.FromValues(resPointer)); case "cfo_res": - { Result res = await SendMonoCommand(id, MonoCommands.GetDetails(RuntimeId, objectId.Value, args), token); string value_json_str = res.Value["result"]?["value"]?["__value_as_json_string__"]?.Value(); if (res.IsOk && value_json_str == null) - return ValueOrError.WithError($"Internal error: Could not find expected __value_as_json_string__ field in the result: {res}"); + return ValueOrError.WithError($"Internal error: Could not find expected __value_as_json_string__ field in the result: {res}"); return value_json_str != null - ? ValueOrError.WithValue(sortByAccessLevel ? JObject.FromObject(new { result = JArray.Parse(value_json_str) }) : JArray.Parse(value_json_str)) - : ValueOrError.WithError(res); - } + ? ValueOrError.WithValue(GetMembersResult.FromValues(JArray.Parse(value_json_str))) + : ValueOrError.WithError(res); default: - return ValueOrError.WithError($"RuntimeGetProperties: unknown object id scheme: {objectId.Scheme}"); + return ValueOrError.WithError($"RuntimeGetProperties: unknown object id scheme: {objectId.Scheme}"); } } catch (Exception ex) { - return ValueOrError.WithError($"RuntimeGetProperties: Failed to get properties for {objectId}: {ex}"); + return ValueOrError.WithError($"RuntimeGetProperties: Failed to get properties for {objectId}: {ex}"); } } @@ -1074,7 +1063,8 @@ private async Task OnReceiveDebuggerAgentEvent(SessionId sessionId, JObjec string reason = "exception"; int object_id = retDebuggerCmdReader.ReadInt32(); var caught = retDebuggerCmdReader.ReadByte(); - var exceptionObject = await context.SdbAgent.GetObjectValues(object_id, GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.OwnProperties, token); + var exceptionObject = await MemberObjectsExplorer.GetObjectMemberValues( + context.SdbAgent, object_id, GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.OwnProperties, token); var exceptionObjectMessage = exceptionObject.FirstOrDefault(attr => attr["name"].Value().Equals("_message")); var data = JObject.FromObject(new { diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index eb71825d767eb7..cae93919f65b91 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -18,6 +18,8 @@ using System.Runtime.CompilerServices; using System.Diagnostics; using System.Reflection.Metadata; +using System.Dynamic; +using BrowserDebugProxy; namespace Microsoft.WebAssembly.Diagnostics { @@ -697,110 +699,16 @@ internal class FieldTypeClass public string Name { get; } public int TypeId { get; } public bool IsNotPrivate { get; } - public FieldAttributes ProtectionLevel { get; } - public FieldTypeClass(int id, string name, int typeId, bool isNotPrivate, FieldAttributes protectionLevel) + public bool IsBackingField { get; } + public FieldAttributes Attributes { get; } + public FieldTypeClass(int id, string name, int typeId, bool isBackingField, FieldAttributes attributes) { Id = id; Name = name; TypeId = typeId; - IsNotPrivate = isNotPrivate; - ProtectionLevel = protectionLevel; - } - } - internal class ValueTypeClass - { - private readonly JArray json; - private readonly int typeId; - private readonly bool autoExpand; - private readonly int id; - private JArray proxy; - private JArray jsonProps; - - public byte[] Buffer { get; } - - public ValueTypeClass(byte[] buffer, JArray json, int typeId, bool expand_properties, int valueTypeId) - { - Buffer = buffer; - this.json = json; - this.typeId = typeId; - jsonProps = null; - proxy = null; - autoExpand = expand_properties; - id = valueTypeId; - } - - public async Task GetProxy(MonoSDBHelper sdbAgent, CancellationToken token) - { - if (proxy != null) - return proxy; - proxy = new JArray(json); - - var retDebuggerCmdReader = await sdbAgent.GetTypePropertiesReader(typeId, token); - if (retDebuggerCmdReader == null) - return null; - - var nProperties = retDebuggerCmdReader.ReadInt32(); - - for (int i = 0; i < nProperties; i++) - { - retDebuggerCmdReader.ReadInt32(); //propertyId - string propertyNameStr = retDebuggerCmdReader.ReadString(); - - var getMethodId = retDebuggerCmdReader.ReadInt32(); - retDebuggerCmdReader.ReadInt32(); //setmethod - retDebuggerCmdReader.ReadInt32(); //attrs - if (await sdbAgent.MethodIsStatic(getMethodId, token)) - continue; - using var command_params_writer_to_proxy = new MonoBinaryWriter(); - command_params_writer_to_proxy.Write(getMethodId); - command_params_writer_to_proxy.Write(Buffer); - command_params_writer_to_proxy.Write(0); - var (data, length) = command_params_writer_to_proxy.ToBase64(); - proxy.Add(JObject.FromObject(new - { - get = JObject.FromObject(new - { - commandSet = CommandSet.Vm, - command = CmdVM.InvokeMethod, - buffer = data, - length = length, - id = MonoSDBHelper.GetNewId() - }), - name = propertyNameStr - })); - } - return proxy; - } - - public async Task GetProperties(MonoSDBHelper sdbAgent, CancellationToken token) - { - JArray ret = new JArray(); - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(typeId); - using var retDebuggerCmdReader = await sdbAgent.SendDebuggerAgentCommand(CmdType.GetParents, commandParamsWriter, token); - var parentsCount = retDebuggerCmdReader.ReadInt32(); - List typesToGetProperties = new List(); - typesToGetProperties.Add(typeId); - for (int i = 0; i < parentsCount; i++) - { - typesToGetProperties.Add(retDebuggerCmdReader.ReadInt32()); - } - for (int i = 0; i < typesToGetProperties.Count; i++) - { - var properties = await sdbAgent.CreateJArrayForProperties(typesToGetProperties[i], ElementType.ValueType, Buffer, json, autoExpand, $"dotnet:valuetype:{id}", i == 0, token); - ret = new JArray(ret.Union(properties)); - } - return ret; - } - - public async Task GetValues(MonoSDBHelper sdbAgent, bool accessorPropertiesOnly, CancellationToken token) - { - if (jsonProps == null) - jsonProps = await GetProperties(sdbAgent, token); - if (accessorPropertiesOnly) - return jsonProps; - var ret = new JArray(json.Union(jsonProps)); - return ret; + IsNotPrivate = (Attributes & FieldAttributes.FieldAccessMask & FieldAttributes.Public) != 0; + Attributes = attributes; + IsBackingField = isBackingField; } } @@ -809,6 +717,8 @@ internal class PointerValue public long address; public int typeId; public string varName; + private JObject _value; + public PointerValue(long address, int typeId, string varName) { this.address = address; @@ -816,6 +726,22 @@ public PointerValue(long address, int typeId, string varName) this.varName = varName; } + public async Task GetValue(MonoSDBHelper sdbHelper, CancellationToken token) + { + if (_value == null) + { + using var commandParamsWriter = new MonoBinaryWriter(); + commandParamsWriter.Write(address); + commandParamsWriter.Write(typeId); + using var retDebuggerCmdReader = await sdbHelper.SendDebuggerAgentCommand(CmdPointer.GetValue, commandParamsWriter, token); + string displayVarName = varName; + if (int.TryParse(varName, out _)) + displayVarName = $"[{varName}]"; + _value = await sdbHelper.CreateJObjectForVariableValue(retDebuggerCmdReader, "*" + displayVarName, token); + } + + return _value; + } } internal class MonoSDBHelper { @@ -839,6 +765,7 @@ internal class MonoSDBHelper private Regex regexForAsyncLocals = new Regex(@"\<([^)]*)\>", RegexOptions.Singleline); public static int GetNewId() { return cmdId++; } + public static int GetNewObjectId() => Interlocked.Increment(ref debuggerObjectId); public MonoSDBHelper(MonoProxy proxy, ILogger logger, SessionId sessionId) { @@ -1319,7 +1246,7 @@ public async Task GetFieldValue(int typeId, int fieldId, CancellationTo commandParamsWriter.Write(fieldId); using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdType.GetValues, commandParamsWriter, token); - return await CreateJObjectForVariableValue(retDebuggerCmdReader, "", false, -1, false, token); + return await CreateJObjectForVariableValue(retDebuggerCmdReader, "", token); } public async Task TypeIsInitialized(int typeId, CancellationToken token) @@ -1375,23 +1302,24 @@ public async Task> GetTypeFields(int typeId, CancellationTo for (int i = 0 ; i < nFields; i++) { - bool isNotPrivate = false; int fieldId = retDebuggerCmdReader.ReadInt32(); //fieldId string fieldNameStr = retDebuggerCmdReader.ReadString(); int fieldTypeId = retDebuggerCmdReader.ReadInt32(); //typeId int attrs = retDebuggerCmdReader.ReadInt32(); //attrs + FieldAttributes fieldAttrs = (FieldAttributes)attrs; int isSpecialStatic = retDebuggerCmdReader.ReadInt32(); //is_special_static - if (((attrs & (int)MethodAttributes.Public) != 0)) - isNotPrivate = true; if (isSpecialStatic == 1) continue; + + bool isBackingField = false; if (fieldNameStr.Contains("k__BackingField")) { + isBackingField = true; fieldNameStr = fieldNameStr.Replace("k__BackingField", ""); fieldNameStr = fieldNameStr.Replace("<", ""); fieldNameStr = fieldNameStr.Replace(">", ""); } - ret.Add(new FieldTypeClass(fieldId, fieldNameStr, fieldTypeId, isNotPrivate, (FieldAttributes)((attrs) & (int)FieldAttributes.FieldAccessMask))); + ret.Add(new FieldTypeClass(fieldId, fieldNameStr, fieldTypeId, isBackingField, fieldAttrs)); } typeInfo.FieldsList = ret; return ret; @@ -1416,7 +1344,7 @@ private static string ReplaceCommonClassNames(string className) => //.Replace("System.Decimal", "decimal") .ToString(); - internal async Task GetCAttrsFromType(int objectId, int typeId, string attrName, CancellationToken token) + internal async Task GetCAttrsFromType(int typeId, string attrName, CancellationToken token) { using var commandParamsWriter = new MonoBinaryWriter(); commandParamsWriter.Write(typeId); @@ -1443,7 +1371,7 @@ internal async Task GetCAttrsFromType(int objectId, int typeId for (int j = 0; j < parmCount; j++) { //to typed_args - await CreateJObjectForVariableValue(retDebuggerCmdReader, "varName", false, -1, false, token); + await CreateJObjectForVariableValue(retDebuggerCmdReader, "varName", token); } } } @@ -1464,11 +1392,11 @@ public async Task GetAssemblyFromType(int type_id, CancellationToken token) return retDebuggerCmdReader.ReadInt32(); } - public async Task GetValueFromDebuggerDisplayAttribute(int objectId, int typeId, CancellationToken token) + public async Task GetValueFromDebuggerDisplayAttribute(DotnetObjectId dotnetObjectId, int typeId, CancellationToken token) { string expr = ""; try { - var getCAttrsRetReader = await GetCAttrsFromType(objectId, typeId, "System.Diagnostics.DebuggerDisplayAttribute", token); + var getCAttrsRetReader = await GetCAttrsFromType(typeId, "System.Diagnostics.DebuggerDisplayAttribute", token); if (getCAttrsRetReader == null) return null; @@ -1480,9 +1408,17 @@ public async Task GetValueFromDebuggerDisplayAttribute(int objectId, int 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); + // logger.LogDebug($"** GetValueFromDebuggerDisplayAttribute calling GetObjectMemberValues"); + GetMembersResult members = await GetTypeMemberValues( + dotnetObjectId, + GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.ForDebuggerDisplayAttribute, + token); + // FIXME: flatten, it's flattened already + members.Flatten(); + // JArray objectValues = new JArray(members.JObject); + JArray objectValues = new JArray(members.Result); - var thisObj = CreateJObject(value: "", type: "object", description: "", writable: false, objectId: $"dotnet:object:{objectId}"); + var thisObj = CreateJObject(value: "", type: "object", description: "", writable: false, objectId: dotnetObjectId.ToString()); thisObj["name"] = "this"; objectValues.Add(thisObj); @@ -1580,7 +1516,8 @@ public async Task GetArrayDimensions(int object_id, Cancellatio } return new ArrayDimensions(rank); } - public async Task> GetTypeIdFromObject(int object_id, bool withParents, CancellationToken token) + + public async Task> GetTypeIdsForObject(int object_id, bool withParents, CancellationToken token) { List ret = new List(); using var commandParamsWriter = new MonoBinaryWriter(); @@ -1605,7 +1542,7 @@ public async Task> GetTypeIdFromObject(int object_id, bool withParents public async Task GetClassNameFromObject(int object_id, CancellationToken token) { - var type_id = await GetTypeIdFromObject(object_id, false, token); + var type_id = await GetTypeIdsForObject(object_id, false, token); return await GetTypeName(type_id[0], token); } @@ -1666,26 +1603,54 @@ public async Task GetDelegateMethodDescription(int objectId, Cancellatio return $"{returnType} {methodName} {parameters}"; } - public async Task InvokeMethod(ArraySegment valueTypeBuffer, int methodId, string varName, CancellationToken token) + + public async Task InvokeMethod(ArraySegment argsBuffer, int methodId, CancellationToken token, string name = null) { using var commandParamsWriter = new MonoBinaryWriter(); commandParamsWriter.Write(methodId); - commandParamsWriter.Write(valueTypeBuffer); + commandParamsWriter.Write(argsBuffer); commandParamsWriter.Write(0); using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdVM.InvokeMethod, commandParamsWriter, token); retDebuggerCmdReader.ReadByte(); //number of objects returned. - return await CreateJObjectForVariableValue(retDebuggerCmdReader, varName, false, -1, false, token); + return await CreateJObjectForVariableValue(retDebuggerCmdReader, name, token); } - public async Task InvokeMethodInObject(DotnetObjectId objectId, int methodId, string varName, CancellationToken token) + public Task InvokeMethod(int objectId, int methodId, bool isValueType, CancellationToken token) { - 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 (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 Task InvokeMethod(DotnetObjectId dotnetObjectId, CancellationToken token, int methodId = -1) + { + if (dotnetObjectId.Scheme == "method") + { + JObject args = dotnetObjectId.ValueAsJson; + int? objectId = args["containerId"]?.Value(); + int? embeddedMethodId = args["methodId"]?.Value(); + + return objectId == null || embeddedMethodId == null + ? throw new ArgumentException($"Invalid object id for a method, with missing container, or methodId", nameof(dotnetObjectId)) + : InvokeMethod(objectId.Value, embeddedMethodId.Value, isValueType: args["isValueType"]?.Value() == true, token); + } + + return dotnetObjectId.Scheme is "object" or "valuetype" + ? InvokeMethod(dotnetObjectId.Value, methodId, isValueType: dotnetObjectId.IsValueType, token) + : throw new ArgumentException($"Cannot invoke method with id {methodId} on {dotnetObjectId}", nameof(dotnetObjectId)); + } + + // FIXME: removable? + public async Task GetPropertyMethodIdByName(int typeId, string propertyName, CancellationToken token) { using var retDebuggerCmdReader = await GetTypePropertiesReader(typeId, token); @@ -1708,68 +1673,12 @@ public async Task GetPropertyMethodIdByName(int typeId, string propertyName return -1; } - public async Task CreateJArrayForProperties(int typeId, ElementType elementType, ArraySegment object_buffer, JArray attributes, bool isAutoExpandable, string objectIdStr, bool isOwn, CancellationToken token) - { - JArray ret = new JArray(); - using var retDebuggerCmdReader = await GetTypePropertiesReader(typeId, token); - if (retDebuggerCmdReader == null) - return null; - if (!DotnetObjectId.TryParse(objectIdStr, out DotnetObjectId objectId)) - return null; - var nProperties = retDebuggerCmdReader.ReadInt32(); - for (int i = 0 ; i < nProperties; i++) - { - retDebuggerCmdReader.ReadInt32(); //propertyId - string propertyNameStr = retDebuggerCmdReader.ReadString(); - var getMethodId = retDebuggerCmdReader.ReadInt32(); - retDebuggerCmdReader.ReadInt32(); //setmethod - var attrs = retDebuggerCmdReader.ReadInt32(); //attrs - if (getMethodId == 0 || await GetParamCount(getMethodId, token) != 0 || await MethodIsStatic(getMethodId, token)) - continue; - JObject propRet = null; - if (attributes.Where(attribute => attribute["name"].Value().Equals(propertyNameStr)).Any()) - continue; - if (isAutoExpandable) - { - try { - propRet = await InvokeMethod(object_buffer, getMethodId, propertyNameStr, token); - } - catch (Exception) - { - continue; - } - } - else - { - propRet = JObject.FromObject(new { - get = new - { - type = "function", - objectId = $"dotnet:methodId:{objectId.Value}:{getMethodId}:{elementType}", - className = "Function", - description = "get " + propertyNameStr + " ()", - methodId = getMethodId, - objectIdValue = objectIdStr - }, - name = propertyNameStr - }); - } - if (isOwn) - propRet["isOwn"] = true; - ret.Add(propRet); - } - return ret; - } public async Task GetPointerContent(int pointerId, CancellationToken token) { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(pointerValues[pointerId].address); - commandParamsWriter.Write(pointerValues[pointerId].typeId); - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdPointer.GetValue, commandParamsWriter, token); - var varName = pointerValues[pointerId].varName; - if (int.TryParse(varName, out _)) - varName = $"[{varName}]"; - return await CreateJObjectForVariableValue(retDebuggerCmdReader, "*" + varName, false, -1, false, token); + if (!pointerValues.TryGetValue(pointerId, out PointerValue pointerValue)) + throw new ArgumentException($"Could not find any pointer with id: {pointerId}", nameof(pointerId)); + + return await pointerValue.GetValue(this, token); } public static bool AutoExpandable(string className) { @@ -1790,7 +1699,7 @@ private static bool AutoInvokeToString(string className) { return false; } - private static JObject CreateJObject(T value, string type, string description, bool writable, string className = null, string objectId = null, string __custom_type = null, string subtype = null, bool isValueType = false, bool expanded = false, bool isEnum = false) + public static JObject CreateJObject(T value, string type, string description, bool writable, string className = null, string objectId = null, string __custom_type = null, string subtype = null, bool isValueType = false, bool expanded = false, bool isEnum = false) { var ret = JObject.FromObject(new { value = new @@ -1851,7 +1760,7 @@ public async Task CreateJObjectForPtr(ElementType etype, MonoBinaryRead int pointerId = 0; if (valueAddress != 0 && className != "(void*)") { - pointerId = Interlocked.Increment(ref debuggerObjectId); + pointerId = GetNewObjectId(); type = "object"; value = className; pointerValues[pointerId] = new PointerValue(valueAddress, typeId, name); @@ -1887,11 +1796,11 @@ public async Task CreateJObjectForArray(MonoBinaryReader retDebuggerCmd public async Task CreateJObjectForObject(MonoBinaryReader retDebuggerCmdReader, int typeIdFromAttribute, bool forDebuggerDisplayAttribute, CancellationToken token) { var objectId = retDebuggerCmdReader.ReadInt32(); - var type_id = await GetTypeIdFromObject(objectId, false, token); + var type_id = await GetTypeIdsForObject(objectId, false, token); string className = await GetTypeName(type_id[0], token); string debuggerDisplayAttribute = null; if (!forDebuggerDisplayAttribute) - debuggerDisplayAttribute = await GetValueFromDebuggerDisplayAttribute(objectId, type_id[0], token); + debuggerDisplayAttribute = await GetValueFromDebuggerDisplayAttribute(new DotnetObjectId("object", objectId), type_id[0], token); var description = className.ToString(); if (debuggerDisplayAttribute != null) @@ -1900,65 +1809,89 @@ public async Task CreateJObjectForObject(MonoBinaryReader retDebuggerCm if (await IsDelegate(objectId, token)) { if (typeIdFromAttribute != -1) - { className = await GetTypeName(typeIdFromAttribute, token); - } description = await GetDelegateMethodDescription(objectId, token); if (description == "") - { return CreateJObject(className.ToString(), "symbol", className.ToString(), false); - } } return CreateJObject(null, "object", description, false, className, $"dotnet:object:{objectId}"); } - public async Task CreateJObjectForValueType(MonoBinaryReader retDebuggerCmdReader, string name, long initialPos, CancellationToken token) - { - JObject fieldValueType = null; - var isEnum = retDebuggerCmdReader.ReadByte(); + private static readonly string[] s_primitiveTypeNames = new[] + { + "bool", + "char", + "string", + "byte", + "sbyte", + "int", + "uint", + "long", + "ulong", + "short", + "ushort", + "float", + "double", + "object", // object in primitive? + }; + + private static bool IsPrimitiveType(string simplifiedClassName) + => s_primitiveTypeNames.Contains(simplifiedClassName); + + public async Task CreateJObjectForValueType(MonoBinaryReader retDebuggerCmdReader, string name, long initialPos, bool forDebuggerDisplayAttribute, CancellationToken token) + { + // Console.WriteLine ($"** CreateJObjectFromValueType ENTER, name: {name}"); + // FIXME: debugger proxy + //JObject fieldValueType = null; + 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(); + + // Console.WriteLine ($"-- CreateJObjectFromValueType: name: {name}, isBoxed: {isBoxed}, numValues: {numValues}, typeId: {typeId}, className: {className}"); + // Console.WriteLine ($"-- CreateJObjectFromValueType: name: {name}, isBoxed: {isBoxed}, numValues: {numValues}, typeId: {typeId}, className: {className}"); + 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 it isNull==true, to correctly advance the reader + var value = await CreateJObjectForVariableValue(retDebuggerCmdReader, name, token); if (isNull != 0) + { + // //Console.WriteLine ($"nullable, not null.. "); return value; + } else + { + // //Console.WriteLine ($"nullable is null, and the value we got: {value}"); return CreateJObject(null, "object", className, false, className, null, null, "null", true); + } } - for (int i = 0; i < numFields ; i++) + + if (isBoxed)// && numValues == 1) { - fieldValueType = await CreateJObjectForVariableValue(retDebuggerCmdReader, fields.ElementAt(i).Name, true, fields.ElementAt(i).TypeId, false, token); - valueTypeFields.Add(fieldValueType); + if (IsPrimitiveType(className)) + { + var value = await CreateJObjectForVariableValue(retDebuggerCmdReader, name: null, token); + return value; + } } - long endPos = retDebuggerCmdReader.BaseStream.Position; - var valueTypeId = Interlocked.Increment(ref debuggerObjectId); - - retDebuggerCmdReader.BaseStream.Position = initialPos; - byte[] valueTypeBuffer = new byte[endPos - initialPos]; - retDebuggerCmdReader.Read(valueTypeBuffer, 0, (int)(endPos - initialPos)); - retDebuggerCmdReader.BaseStream.Position = endPos; - valueTypes[valueTypeId] = new ValueTypeClass(valueTypeBuffer, valueTypeFields, typeId, AutoExpandable(className), valueTypeId); - if (AutoInvokeToString(className) || isEnum == 1) { - int methodId = await GetMethodIdByName(typeId, "ToString", token); - var retMethod = await InvokeMethod(valueTypeBuffer, methodId, "methodRet", token); - description = retMethod["value"]?["value"].Value(); - if (className.Equals("System.Guid")) - description = description.ToUpper(); //to keep the old behavior - } - else if (isBoxed && numFields == 1) { - return fieldValueType; - } - return CreateJObject(null, "object", description, false, className, $"dotnet:valuetype:{valueTypeId}", null, null, true, true, isEnum == 1); + ValueTypeClass valueType = await ValueTypeClass.CreateFromReader( + this, + retDebuggerCmdReader, + initialPos, + className, + typeId, + numValues, + token); + valueTypes[valueType.Id.Value] = valueType; + + // FIXME: check if this gets called more than once.. maybe with different values of forDebugg.. ? + return await valueType.ToJObject(this, isEnum, forDebuggerDisplayAttribute, token); } public async Task CreateJObjectForNull(MonoBinaryReader retDebuggerCmdReader, CancellationToken token) @@ -1997,9 +1930,14 @@ public async Task CreateJObjectForNull(MonoBinaryReader retDebuggerCmdR return CreateJObject(null, "object", className, false, className, null, null, "null"); } - public async Task CreateJObjectForVariableValue(MonoBinaryReader retDebuggerCmdReader, string name, bool isOwn, int typeIdFromAttribute, bool forDebuggerDisplayAttribute, CancellationToken token) + public async Task CreateJObjectForVariableValue(MonoBinaryReader retDebuggerCmdReader, + string name, + CancellationToken token, + bool isOwn = false, + int typeIdForObject = -1, + bool forDebuggerDisplayAttribute = false) { - long initialPos = retDebuggerCmdReader == null ? 0 : retDebuggerCmdReader.BaseStream.Position; + long initialPos = /*retDebuggerCmdReader == null ? 0 : */retDebuggerCmdReader.BaseStream.Position; ElementType etype = (ElementType)retDebuggerCmdReader.ReadByte(); JObject ret = null; switch (etype) { @@ -2103,12 +2041,12 @@ public async Task CreateJObjectForVariableValue(MonoBinaryReader retDeb case ElementType.Class: case ElementType.Object: { - ret = await CreateJObjectForObject(retDebuggerCmdReader, typeIdFromAttribute, forDebuggerDisplayAttribute, token); + ret = await CreateJObjectForObject(retDebuggerCmdReader, typeIdForObject, forDebuggerDisplayAttribute, token); break; } case ElementType.ValueType: { - ret = await CreateJObjectForValueType(retDebuggerCmdReader, name, initialPos, token); + ret = await CreateJObjectForValueType(retDebuggerCmdReader, name, initialPos, forDebuggerDisplayAttribute, token); break; } case (ElementType)ValueTypeId.Null: @@ -2131,7 +2069,8 @@ public async Task CreateJObjectForVariableValue(MonoBinaryReader retDeb { if (isOwn) ret["isOwn"] = true; - ret["name"] = name; + if (!string.IsNullOrEmpty(name)) + ret["name"] = name; } return ret; } @@ -2164,7 +2103,7 @@ private static bool IsClosureReferenceField (string fieldName) fieldName.StartsWith ("<>8__", StringComparison.Ordinal); } - public async Task GetHoistedLocalVariables(int objectId, JArray asyncLocals, CancellationToken token) + public async Task GetHoistedLocalVariables(int objectId, IEnumerable asyncLocals, CancellationToken token) { JArray asyncLocalsFull = new JArray(); List objectsAlreadyRead = new(); @@ -2183,8 +2122,10 @@ public async Task GetHoistedLocalVariables(int objectId, JArray asyncLoc { if (!objectsAlreadyRead.Contains(dotnetObjectId.Value)) { - var asyncLocalsFromObject = await GetObjectValues(dotnetObjectId.Value, GetObjectCommandOptions.WithProperties, token); - var hoistedLocalVariable = await GetHoistedLocalVariables(dotnetObjectId.Value, asyncLocalsFromObject, token); + var asyncProxyMembersFromObject = await MemberObjectsExplorer.GetObjectMemberValues( + this, dotnetObjectId.Value, GetObjectCommandOptions.WithProperties, token); + // is it supposed to be an infinite loop? + var hoistedLocalVariable = await GetHoistedLocalVariables(dotnetObjectId.Value, asyncProxyMembersFromObject.Flatten(), token); asyncLocalsFull = new JArray(asyncLocalsFull.Union(hoistedLocalVariable)); } } @@ -2224,8 +2165,11 @@ public async Task StackFrameGetValues(MethodInfoWithDebugInformation met using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdFrame.GetThis, commandParamsWriter, token); retDebuggerCmdReader.ReadByte(); //ignore type var objectId = retDebuggerCmdReader.ReadInt32(); - var asyncLocals = await GetObjectValues(objectId, GetObjectCommandOptions.WithProperties, token); - asyncLocals = await GetHoistedLocalVariables(objectId, asyncLocals, token); + logger.LogDebug($"** StackFrameGetValues calling GetObjectMemberValues"); + // infinite loop again? + GetMembersResult asyncProxyMembers = await MemberObjectsExplorer.GetObjectMemberValues(this, objectId, GetObjectCommandOptions.WithProperties, token); + var asyncLocals = await GetHoistedLocalVariables(objectId, asyncProxyMembers.Flatten(), token); + // //Console.WriteLine ($"** StackFrameGetValues returning {asyncLocals}"); return asyncLocals; } @@ -2235,7 +2179,7 @@ public async Task StackFrameGetValues(MethodInfoWithDebugInformation met { try { - var var_json = await CreateJObjectForVariableValue(localsDebuggerCmdReader, var.Name, false, -1, false, token); + var var_json = await CreateJObjectForVariableValue(localsDebuggerCmdReader, var.Name, token); locals.Add(var_json); } catch (Exception ex) @@ -2247,7 +2191,7 @@ public async Task StackFrameGetValues(MethodInfoWithDebugInformation met if (!method.Info.IsStatic()) { using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdFrame.GetThis, commandParamsWriter, token); - var var_json = await CreateJObjectForVariableValue(retDebuggerCmdReader, "this", false, -1, false, token); + var var_json = await CreateJObjectForVariableValue(retDebuggerCmdReader, "this", token); var_json.Add("fieldOffset", -1); locals.Add(var_json); } @@ -2273,7 +2217,7 @@ public async Task GetArrayValues(int arrayId, CancellationToken token) JArray array = new JArray(); for (int i = 0 ; i < dimensions.TotalLength; i++) { - var var_json = await CreateJObjectForVariableValue(retDebuggerCmdReader, dimensions.GetArrayIndexString(i), isOwn : false, -1, forDebuggerDisplayAttribute : false, token); + var var_json = await CreateJObjectForVariableValue(retDebuggerCmdReader, dimensions.GetArrayIndexString(i), token); array.Add(var_json); } return array; @@ -2330,28 +2274,79 @@ public async Task GetTypeByName(string typeToSearch, CancellationToken toke return retDebuggerCmdReader.ReadInt32(); } - public async Task GetValuesFromDebuggerProxyAttribute(int objectId, int typeId, CancellationToken token) + // FIXME: support valuetypes + public async Task GetValuesFromDebuggerProxyAttribute(int objectId, int typeId, CancellationToken token) { - try { - var getCAttrsRetReader = await GetCAttrsFromType(objectId, typeId, "System.Diagnostics.DebuggerTypeProxyAttribute", token); - var methodId = -1; - if (getCAttrsRetReader == null) + // logger.LogInformation($"GetValuesFromDebuggerProxyAttribute: ENTER"); + 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)) + { + ctorArgsWriter.Write((int)1); // num args + ctorArgsWriter.Write(valueType.Buffer); + } + else + { + ctorArgsWriter.Write((byte)0); //not used + ctorArgsWriter.Write(0); //not used + ctorArgsWriter.Write((int)1); // num args + ctorArgsWriter.Write((byte)ElementType.Object); + ctorArgsWriter.Write(objectId); + } + + var retMethod = await InvokeMethod(ctorArgsWriter.GetParameterBuffer(), methodId, token); + // logger.LogInformation($"* GetValuesFromDebuggerProxyAttribute got from InvokeMethod: {retMethod}"); + if (!DotnetObjectId.TryParse(retMethod?["value"]?["objectId"]?.Value(), out DotnetObjectId dotnetObjectId)) + throw new Exception($"Invoking .ctor ({methodId}) for DebuggerTypeProxy on type {typeId} returned {retMethod}"); + + GetMembersResult members = await GetTypeMemberValues(dotnetObjectId, + GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.ForDebuggerProxyAttribute, + token); + + // logger.LogInformation($"* GetValuesFromDebuggerProxyAttribute returning for obj: {objectId} {members}"); + return members; + } + catch (Exception e) + { + logger.LogInformation($"Could not evaluate DebuggerTypeProxyAttribute of type {await GetTypeName(typeId, token)} - {e}"); + } + + return null; + } + + private async Task FindDebuggerProxyConstructorIdFor(int typeId, CancellationToken token) + { + try + { + var getCAttrsRetReader = await GetCAttrsFromType(typeId, "System.Diagnostics.DebuggerTypeProxyAttribute", token); + if (getCAttrsRetReader == null) + return -1; + + var methodId = -1; var parmCount = getCAttrsRetReader.ReadInt32(); - invokeParamsWriter.Write((int)1); for (int j = 0; j < parmCount; j++) { var monoTypeId = getCAttrsRetReader.ReadByte(); + // FIXME: DebuggerTypeProxyAttribute(string) - not supported if ((ValueTypeId)monoTypeId != ValueTypeId.Type) continue; + var cAttrTypeId = getCAttrsRetReader.ReadInt32(); using var commandParamsWriter = new MonoBinaryWriter(); commandParamsWriter.Write(cAttrTypeId); var className = await GetTypeNameOriginal(cAttrTypeId, token); + // //Console.WriteLine ($"FindDebuggerProxyConstructorIdFor: className: {className}"); if (className.IndexOf('[') > 0) { className = className.Remove(className.IndexOf('[')); @@ -2365,320 +2360,50 @@ public async Task GetValuesFromDebuggerProxyAttribute(int objectId, int var assemblyIdArg = await GetAssemblyIdFromType(genericTypeArgs[k], token); var assemblyNameArg = await GetFullAssemblyName(assemblyIdArg, token); var classNameArg = await GetTypeNameOriginal(genericTypeArgs[k], token); - typeToSearch += classNameArg +", " + assemblyNameArg; + typeToSearch += classNameArg + ", " + assemblyNameArg; if (k + 1 < genericTypeArgs.Count) typeToSearch += "], ["; else typeToSearch += "]"; } typeToSearch += "]"; - typeToSearch += ", " + assemblyName; + typeToSearch += ", " + assemblyName; var genericTypeId = await GetTypeByName(typeToSearch, token); if (genericTypeId < 0) - return null; + break; + // //Console.WriteLine ($"FindDebuggerProxyConstructorIdFor: genericTypeid: {genericTypeId}"); methodId = await GetMethodIdByName(genericTypeId, ".ctor", token); } else + { + // //Console.WriteLine ($"FindDebuggerProxyConstructorIdFor: genericTypeid: {cAttrTypeId}"); methodId = await GetMethodIdByName(cAttrTypeId, ".ctor", token); - invokeParamsWriter.Write((byte)ElementType.Object); - invokeParamsWriter.Write(objectId); + } - var retMethod = await InvokeMethod(invokeParamsWriter.GetParameterBuffer(), methodId, "methodRet", token); - DotnetObjectId.TryParse(retMethod?["value"]?["objectId"]?.Value(), out DotnetObjectId dotnetObjectId); - var displayAttrs = await GetObjectValues(dotnetObjectId.Value, GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.ForDebuggerProxyAttribute, token); - return displayAttrs; + break; } + + return methodId; } catch (Exception e) { logger.LogDebug($"Could not evaluate DebuggerTypeProxyAttribute of type {await GetTypeName(typeId, token)} - {e}"); } - return null; - } - - public async Task GetObjectValues(int objectId, GetObjectCommandOptions getCommandType, CancellationToken token, bool sortByAccessLevel = false) - { - var typeIdsIncludingParents = await GetTypeIdFromObject(objectId, true, token); - if (!getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerDisplayAttribute)) - { - var debuggerProxy = await GetValuesFromDebuggerProxyAttribute(objectId, typeIdsIncludingParents[0], token); - if (debuggerProxy != null) - return sortByAccessLevel ? - new JArray(JObject.FromObject( new { result = debuggerProxy, internalProperties = new JArray(), privateProperties = new JArray() } )) : - debuggerProxy; - } - if (await IsDelegate(objectId, token)) - { - var description = await GetDelegateMethodDescription(objectId, token); - var objValues = new JArray(JObject.FromObject(new - { - value = new - { - type = "symbol", - value = description, - description - }, - name = "Target" - })); - return sortByAccessLevel ? - new JArray(JObject.FromObject(new { result = objValues, internalProperties = new JArray(), privateProperties = new JArray() })) : - objValues; - } - var allFields = new List(); - var objects = new Dictionary(); - for (int i = 0; i < typeIdsIncludingParents.Count; i++) - { - int typeId = typeIdsIncludingParents[i]; - // 0th id is for the object itself, and then its parents - bool isOwn = i == 0; - var fields = await GetTypeFields(typeId, token); - allFields.AddRange(fields); - - if (!getCommandType.HasFlag(GetObjectCommandOptions.AccessorPropertiesOnly)) - { - var (collapsedFields, rootHiddenFields) = await FilterFieldsByDebuggerBrowsable(fields, typeId, token); - - var collapsedFieldsValues = await GetFieldsValues(collapsedFields, isOwn); - var hiddenFieldsValues = await GetFieldsValues(rootHiddenFields, isOwn, isRootHidden: true); - - objects.TryAddRange(collapsedFieldsValues); - objects.TryAddRange(hiddenFieldsValues); - } - - if (!getCommandType.HasFlag(GetObjectCommandOptions.WithProperties)) - return sortByAccessLevel ? - SegregatePropertiesByAccessLevel(allFields, objects, token) : - new JArray(objects.Values); - - using var commandParamsObjWriter = new MonoBinaryWriter(); - commandParamsObjWriter.WriteObj(new DotnetObjectId("object", objectId), this); - var props = await CreateJArrayForProperties( - typeId, - ElementType.Class, - commandParamsObjWriter.GetParameterBuffer(), - new JArray(objects.Values), - getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute), - $"dotnet:object:{objectId}", - i == 0, - token); - var properties = await GetProperties(props, allFields, typeId, token); - objects.TryAddRange(properties); - - // ownProperties - // Note: ownProperties should mean that we return members of the klass itself, - // but we are going to ignore that here, because otherwise vscode/chrome don't - // seem to ask for inherited fields at all. - //if (ownProperties) - //break; - /*if (accessorPropertiesOnly) - break;*/ - } - return sortByAccessLevel ? - SegregatePropertiesByAccessLevel(allFields, objects, token) : - new JArray(objects.Values); - - JArray SegregatePropertiesByAccessLevel(List fields, Dictionary objectsDict, CancellationToken token) - { - if (fields.Count == 0) - return new JArray(JObject.FromObject(new { result = new JArray(objectsDict.Values) })); - - var pubNames = fields.Where(field => field.ProtectionLevel == FieldAttributes.Public) - .Select(field => field.Name).ToList(); - var privNames = fields.Where(field => - (field.ProtectionLevel == FieldAttributes.Private || - field.ProtectionLevel == FieldAttributes.FamANDAssem) && - // when field is inherited it is listed both in Private and Public, to avoid duplicates: - pubNames.All(pubFieldName => pubFieldName != field.Name)) - .Select(field => field.Name).ToList(); - //protected == family, internal == assembly - var protectedAndInternalNames = fields.Where(field => - field.ProtectionLevel == FieldAttributes.Family || - field.ProtectionLevel == FieldAttributes.Assembly || - field.ProtectionLevel == FieldAttributes.FamORAssem) - .Select(field => field.Name).ToList(); - var accessorProperties = objectsDict - .Where(obj => ( - pubNames.All(name => name != obj.Key) && - privNames.All(name => name != obj.Key) && - protectedAndInternalNames.All(name => name != obj.Key))) - .Select((k, v) => k.Value); - - var pubProperties = objectsDict.GetValuesByKeys(pubNames); - var privProperties = objectsDict.GetValuesByKeys(privNames); - var protAndIntProperties = objectsDict.GetValuesByKeys(protectedAndInternalNames); - pubProperties.AddRange(new JArray(accessorProperties)); - - return new JArray(JObject.FromObject(new - { - result = pubProperties, - internalProperties = protAndIntProperties, - privateProperties = privProperties - })); - } - - async Task AppendRootHiddenChildren(JObject root, JArray expandedCollection) - { - if (!DotnetObjectId.TryParse(root?["value"]?["objectId"]?.Value(), out DotnetObjectId rootHiddenObjectId)) - return; - - var resultValue = new JArray(); - // collections require extracting items to get inner values; items are of array type - // arrays have "subtype": "array" field, collections don't - var subtype = root?["value"]?["subtype"]; - var rootHiddenObjectIdInt = rootHiddenObjectId.Value; - if (subtype == null || subtype?.Value() != "array") - { - resultValue = await GetObjectValues(rootHiddenObjectIdInt, getCommandType, token); - DotnetObjectId.TryParse(resultValue[0]?["value"]?["objectId"]?.Value(), out DotnetObjectId objectId2); - rootHiddenObjectIdInt = objectId2.Value; - } - resultValue = await GetArrayValues(rootHiddenObjectIdInt, token); - // root hidden item name has to be unique, so we concatenate the root's name to it - foreach (var item in resultValue) - { - item["name"] = string.Concat(root["name"], "[", item["name"], "]"); - expandedCollection.Add(item); - } - } - - async Task GetFieldsValues(List fields, bool isOwn, bool isRootHidden = false) - { - JArray objFields = new JArray(); - if (fields.Count == 0) - return objFields; - - if (getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute)) - fields = fields.Where(field => field.IsNotPrivate).ToList(); - - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(objectId); - commandParamsWriter.Write(fields.Count); - foreach (var field in fields) - commandParamsWriter.Write(field.Id); - var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdObject.RefGetValues, commandParamsWriter, token); - - foreach (var field in fields) - { - long initialPos = retDebuggerCmdReader.BaseStream.Position; - int valtype = retDebuggerCmdReader.ReadByte(); - retDebuggerCmdReader.BaseStream.Position = initialPos; - var fieldValue = await CreateJObjectForVariableValue(retDebuggerCmdReader, field.Name, isOwn: isOwn, field.TypeId, getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerDisplayAttribute), token); - if (objects.Where((k, v) => k.Equals(fieldValue["name"].Value())).Any()) - continue; - if (getCommandType.HasFlag(GetObjectCommandOptions.WithSetter)) - { - var command_params_writer_to_set = new MonoBinaryWriter(); - command_params_writer_to_set.Write(objectId); - command_params_writer_to_set.Write(1); - command_params_writer_to_set.Write(field.Id); - var (data, length) = command_params_writer_to_set.ToBase64(); - - 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; - } - - async Task<(List, List)> FilterFieldsByDebuggerBrowsable(List fields, int typeId, CancellationToken token) - { - if (fields.Count == 0) - return (fields, new List()); - - var typeInfo = await GetTypeInfo(typeId, token); - var typeFieldsBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableFields; - var typeProperitesBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableProperties; - if (typeFieldsBrowsableInfo == null || typeFieldsBrowsableInfo.Count == 0) - return (fields, new List()); - - var collapsedFields = new List(); - var rootHiddenFields = new List(); - foreach (var field in fields) - { - if (!typeFieldsBrowsableInfo.TryGetValue(field.Name, out DebuggerBrowsableState? state)) - { - if (!typeProperitesBrowsableInfo.TryGetValue(field.Name, out DebuggerBrowsableState? propState)) - { - collapsedFields.Add(field); - continue; - } - state = propState; - } - switch (state) - { - case DebuggerBrowsableState.Never: - break; - case DebuggerBrowsableState.RootHidden: - var typeName = await GetTypeName(field.TypeId, token); - if (typeName.StartsWith("System.Collections.Generic", StringComparison.Ordinal) || - typeName.EndsWith("[]", StringComparison.Ordinal)) - rootHiddenFields.Add(field); - break; - case DebuggerBrowsableState.Collapsed: - collapsedFields.Add(field); - break; - default: - throw new NotImplementedException($"DebuggerBrowsableState: {state}"); - } - } - return (collapsedFields, rootHiddenFields); - } - - async Task GetProperties(JArray props, List fields, int typeId, CancellationToken token) - { - var typeInfo = await GetTypeInfo(typeId, token); - var typeProperitesBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableProperties; - var regularProps = new JArray(); - foreach (var p in props) - { - var propName = p["name"].Value(); - // if property has a backing field - avoid adding a duplicate - if (fields.Any(field => field.Name == propName)) - continue; - if (!typeProperitesBrowsableInfo.TryGetValue(propName, out DebuggerBrowsableState? state)) - { - regularProps.Add(p); - continue; - } - switch (state) - { - case DebuggerBrowsableState.Never: - break; - case DebuggerBrowsableState.RootHidden: - DotnetObjectId rootObjId; - DotnetObjectId.TryParse(p["get"]["objectId"].Value(), out rootObjId); - var rootObject = await InvokeMethodInObject(rootObjId, rootObjId.SubValue, propName, token); - await AppendRootHiddenChildren(rootObject, regularProps); - break; - case DebuggerBrowsableState.Collapsed: - regularProps.Add(p); - break; - default: - throw new NotImplementedException($"DebuggerBrowsableState: {state}"); - } - } - return regularProps; - } + return -1; } + public Task GetTypeMemberValues(DotnetObjectId dotnetObjectId, GetObjectCommandOptions getObjectOptions, CancellationToken token, bool sortByAccessLevel = false) + => dotnetObjectId.IsValueType + ? MemberObjectsExplorer.GetValueTypeMemberValues(this, dotnetObjectId.Value, getObjectOptions, token) + : MemberObjectsExplorer.GetObjectMemberValues(this, dotnetObjectId.Value, getObjectOptions, token); + public async Task GetObjectProxy(int objectId, CancellationToken token) { - var ret = await GetObjectValues(objectId, GetObjectCommandOptions.WithSetter, token); - var typeIds = await GetTypeIdFromObject(objectId, true, token); + GetMembersResult members = await MemberObjectsExplorer.GetObjectMemberValues(this, objectId, GetObjectCommandOptions.WithSetter, token); + // FIXME: will this mean that the result object won't get gc'ed? + JArray ret = members.Flatten();// originally: members.Flatten().Result; // why? + var typeIds = await GetTypeIdsForObject(objectId, true, token); foreach (var typeId in typeIds) { var retDebuggerCmdReader = await GetTypePropertiesReader(typeId, token); @@ -2811,6 +2536,12 @@ public static void AddRange(this JArray arr, JArray addedArr) arr.Add(item); } + public static void AddRange(this JArray arr, IEnumerable addedArr) + { + foreach (var item in addedArr) + arr.Add(item); + } + public static void TryAddRange(this Dictionary dict, JArray addedArr) { foreach (var item in addedArr) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs new file mode 100644 index 00000000000000..2b5df2fb260b83 --- /dev/null +++ b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs @@ -0,0 +1,288 @@ +// 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 class ValueTypeClass + { + private readonly JArray fields; + private readonly bool autoExpand; + private JArray proxy; + private GetMembersResult _combinedResult; + // private GetMembersResult _combinedStaticResult; + private bool propertiesExpanded; + + public string ClassName { get; init; } + public DotnetObjectId Id { get; init; } + public byte[] Buffer { get; init; } + public int TypeId { get; init; } + + public ValueTypeClass(byte[] buffer, string className, JArray fields, int typeId, DotnetObjectId objectId) + { + Buffer = buffer; + this.fields = fields; + ClassName = className; + TypeId = typeId; + autoExpand = ShouldAutoExpand(className); + Id = objectId; + } + + public override string ToString() => $"{{ ValueTypeClass: typeId: {TypeId}, Id: {Id}, Id: {Id}, fields: {fields} }}"; + + public static async Task CreateFromReader( + MonoSDBHelper sdbAgent, + MonoBinaryReader cmdReader, + long initialPos, + string className, + int typeId, + int numValues, + CancellationToken token) + { + var typeInfo = await sdbAgent.GetTypeInfo(typeId, token); + var typeFieldsBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableFields; + var typePropertiesBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableProperties; + + IReadOnlyList fieldTypes = await sdbAgent.GetTypeFields(typeId, token); + IEnumerable writableFields = fieldTypes.Where(f => !f.Attributes.HasFlag(FieldAttributes.Literal) && !f.Attributes.HasFlag(FieldAttributes.Static)); + + // FIXME: save the field values buffer, and expand on demand + int numWritableFields = writableFields.Count(); + // if (numWritableFields != numValues) + // throw new Exception($"Bug: CreateFromReader: writableFields({numWritableFields}) != numValues({numValues}))"); + + // FIXME: add the static oens too? and tests for that! EvaluateOnCallFrame has some? + JArray fields = new(); + foreach (var field in writableFields) + { + var fieldValue = await sdbAgent.CreateJObjectForVariableValue(cmdReader, field.Name, token, true, field.TypeId, false); + + // state == DebuggerBrowsableState.Never) + // { + // continue; + // } + + fieldValue["__section"] = field.Attributes switch + { + FieldAttributes.Private => "private", + FieldAttributes.Public => "result", + _ => "internal" + }; + + if (field.IsBackingField) + fieldValue["__isBackingField"] = true; + + // should be only when not backing + string typeName = await sdbAgent.GetTypeName(field.TypeId, token); + typeFieldsBrowsableInfo.TryGetValue(field.Name, out DebuggerBrowsableState? state); + + fieldValue["__state"] = state?.ToString(); + fields.Merge(await MemberObjectsExplorer.GetExpandedMemberValues(sdbAgent, typeName, field.Name, fieldValue, state, token)); + } + + 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; + + // FIXME: e combine into single GetNewValueTypeClass() + var valueTypeId = MonoSDBHelper.GetNewObjectId(); + var dotnetObjectId = new DotnetObjectId("valuetype", valueTypeId); + return new ValueTypeClass(valueTypeBuffer, className, fields, typeId, dotnetObjectId); + } + + public async Task ToJObject(MonoSDBHelper sdbAgent, bool isEnum, bool forDebuggerDisplayAttribute, CancellationToken token) + { + string description = ClassName; + // FIXME: isEnum to .. some flag, or field? + if (ShouldAutoInvokeToString(ClassName) || isEnum) + { + int methodId = await sdbAgent.GetMethodIdByName(TypeId, "ToString", token); + var retMethod = await sdbAgent.InvokeMethod(Buffer, methodId, token); + description = retMethod["value"]?["value"].Value(); + if (ClassName.Equals("System.Guid")) + description = description.ToUpper(); //to keep the old behavior + } + else if (!forDebuggerDisplayAttribute) + { + string displayString = await sdbAgent.GetValueFromDebuggerDisplayAttribute(Id, TypeId, token); + if (displayString != null) + description = displayString; + } + + // Console.WriteLine ($"* CreateJObjectForVariableValue: *new* (name: {name}) {valueTypeId}: {valueTypes[valueTypeId]}");//, and valuetype.fields: {valueTypeFields}"); + var obj = MonoSDBHelper.CreateJObject(null, "object", description, false, ClassName, Id.ToString(), null, null, true, true, isEnum); + // Console.WriteLine ($"** CreateJObjectFromValueType EXIT, name: {name}, returning {obj}"); + return obj; + } + + public async Task GetProxy(MonoSDBHelper sdbHelper, CancellationToken token) + { + if (proxy != null) + return proxy; + + var retDebuggerCmdReader = await sdbHelper.GetTypePropertiesReader(TypeId, token); + if (retDebuggerCmdReader == null) + return null; + + proxy = new JArray(fields); + // fields.Result, + // fields.PrivateProperties, + // fields.InternalProperties); + + 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; + } + + // FIXME: this is flattening + public async Task GetMemberValues(MonoSDBHelper sdbHelper, GetObjectCommandOptions getObjectOptions, bool sortByAccessLevel, CancellationToken token) + { + // Console.WriteLine ($"--------- ValueTypeClass.GetMemberValues: {getObjectOptions}, sort: {sortByAccessLevel}, propertiesExpanded: {propertiesExpanded}"); + // 1 + // if (properties == null) + if (!propertiesExpanded) + { + await ExpandPropertyValues(sdbHelper, token); + propertiesExpanded = true; + } + // Console.WriteLine($"_combined: for {ClassName} {_combinedResult}"); + + // 2 + GetMembersResult result = null; + if (!getObjectOptions.HasFlag(GetObjectCommandOptions.ForDebuggerDisplayAttribute)) + { + // Console.WriteLine ($"\tget values from proxy"); + // FIXME: cache? + result = await sdbHelper.GetValuesFromDebuggerProxyAttribute(Id.Value, TypeId, token); + } + + if (result == null && getObjectOptions.HasFlag(GetObjectCommandOptions.AccessorPropertiesOnly)) + { + // Console.WriteLine ($"\tget only properties"); + // 3 - just properties -- though GetObjectMEmbers seems to be ignoring this! + result = _combinedResult.Clone(); + List toRemove = new(); + foreach (JToken jt in result) + { + if (jt is not JObject obj || obj["get"] != null) + continue; + + // if (obj["isBackingField"]?.Value() is var isBackingField + // && isBackingField.GetValueOrDefault(false) == false) + // returning backing fields as fields, instead of properties + { + toRemove.Add(jt); + } + } + foreach (var jt in toRemove) + { + // Console.WriteLine ($"\tremoving: {jt}"); + jt.Remove(); + } + } + + if (result == null) + { + // 4 - fields + properties + result = _combinedResult.Clone(); + } + + return result; + } + + public async Task ExpandPropertyValues(MonoSDBHelper sdbHelper, CancellationToken token) + { + // Console.WriteLine ($"-- ExpandPropertyValues: fields: {fields}"); + + using var commandParamsWriter = new MonoBinaryWriter(); + commandParamsWriter.Write(TypeId); + using MonoBinaryReader getParentsReader = await sdbHelper.SendDebuggerAgentCommand(CmdType.GetParents, commandParamsWriter, token); + int numParents = getParentsReader.ReadInt32(); + + List typesToGetProperties = new(); + typesToGetProperties.Add(TypeId); + + // FIXME: this list can be removed.. but also need to process for object's own typeId first + for (int i = 0; i < numParents; i++) + typesToGetProperties.Add(getParentsReader.ReadInt32()); + + var allMembers = new Dictionary(); + foreach (var f in fields) + allMembers[f["name"].Value()] = f as JObject; + + for (int i = 0; i < typesToGetProperties.Count; i++) + { + //FIXME: change GetNonAutomaticPropertyValues to return a jobject instead + GetMembersResult res = await MemberObjectsExplorer.GetNonAutomaticPropertyValues( + sdbHelper, + typesToGetProperties[i], + ClassName, + Buffer, + autoExpand, + Id.ToString(), + isValueType: true, + isOwn: i == 0, + token, + allMembers, null); + + foreach (JObject v in res) + allMembers[v["name"].Value()] = v; + } + + _combinedResult = GetMembersResult.FromValues(allMembers.Values, splitMembersByAccessLevel: true); + } + + 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"; + } +} From 944a46be0d521bccdacf545d19bd3a48d3a4b9a6 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 13 Apr 2022 12:38:08 +0200 Subject: [PATCH 02/18] Fixed most tests. --- .../MemberObjectsExplorer.cs | 132 +++++++++--------- .../debugger/BrowserDebugProxy/MonoProxy.cs | 22 +-- .../BrowserDebugProxy/MonoSDBHelper.cs | 29 +++- .../BrowserDebugProxy/ValueTypeClass.cs | 50 +++---- .../DebuggerTestSuite/DebuggerTestBase.cs | 9 +- 5 files changed, 131 insertions(+), 111 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs index a29849c716819a..14ede56f43b09d 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs @@ -35,7 +35,7 @@ private static string GetNamePrefixForValues(string memberName, string typeName, return $"{memberName} ({justClassName})"; } - private static async Task ReadFieldValue(MonoSDBHelper sdbHelper, MonoBinaryReader reader, FieldTypeClass field, int objectId, int fieldValueType, bool isOwn, GetObjectCommandOptions getObjectOptions, CancellationToken token) + private static async Task ReadFieldValue(MonoSDBHelper sdbHelper, MonoBinaryReader reader, FieldTypeClass field, int objectId, TypeInfoWithDebugInformation typeInfo, int fieldValueType, bool isOwn, GetObjectCommandOptions getObjectOptions, CancellationToken token) { var fieldValue = await sdbHelper.CreateJObjectForVariableValue( reader, @@ -45,6 +45,16 @@ private static async Task ReadFieldValue(MonoSDBHelper sdbHelper, MonoB 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 should get it from the properties + typePropertiesBrowsableInfo.TryGetValue(field.Name, out state); + } + fieldValue["__state"] = state?.ToString(); + fieldValue["__section"] = field.Attributes switch { FieldAttributes.Private => "private", @@ -168,8 +178,6 @@ private static async Task GetExpandedFieldValues( // FIXME: needed only if there is a RootHidden field var typeInfo = await sdbHelper.GetTypeInfo(containerTypeId, token); - var typeFieldsBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableFields; - var typePropertiesBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableProperties; int numFieldsRead = 0; foreach (FieldTypeClass field in fields) @@ -177,29 +185,24 @@ private static async Task GetExpandedFieldValues( long initialPos = retDebuggerCmdReader.BaseStream.Position; int valtype = retDebuggerCmdReader.ReadByte(); retDebuggerCmdReader.BaseStream.Position = initialPos; - // Console.WriteLine ($"create value for {field.Name}, field.TypeId: {field.TypeId}"); - JObject fieldValue = await ReadFieldValue(sdbHelper, retDebuggerCmdReader, field, objectId, valtype, isOwn, getCommandOptions, token); + JObject fieldValue = await ReadFieldValue(sdbHelper, retDebuggerCmdReader, field, objectId, typeInfo, valtype, isOwn, getCommandOptions, token); numFieldsRead++; - if (!typeFieldsBrowsableInfo.TryGetValue(field.Name, out DebuggerBrowsableState? state)) - { - // for backing fields, we should get it from the properties - typePropertiesBrowsableInfo.TryGetValue(field.Name, out state); - } - - fieldValue["__state"] = state?.ToString(); - if (state != DebuggerBrowsableState.RootHidden) + if (!Enum.TryParse(fieldValue["__state"].Value(), out DebuggerBrowsableState fieldState) + || fieldState == DebuggerBrowsableState.Collapsed) { fieldValues.Add(fieldValue); continue; } + if (fieldState == DebuggerBrowsableState.Never) + continue; + string namePrefix = field.Name; string containerTypeName = await sdbHelper.GetTypeName(containerTypeId, token); - namePrefix = GetNamePrefixForValues(field.Name, containerTypeName, isOwn, state); - //Console.WriteLine ($"fieldName: {field.Name}, prefix: {namePrefix}, container: {containerTypeName}"); - + namePrefix = GetNamePrefixForValues(field.Name, containerTypeName, isOwn, fieldState); + var enumeratedValues = await GetRootHiddenChildren(sdbHelper, fieldValue, namePrefix, getCommandOptions, token); if (enumeratedValues != null) fieldValues.AddRange(enumeratedValues); @@ -208,7 +211,6 @@ private static async Task GetExpandedFieldValues( if (numFieldsRead != fields.Count) throw new Exception($"Bug: Got {numFieldsRead} instead of expected {fields.Count} field values"); - // Console.WriteLine ($"GetFieldsValues: Exit for {objectId}: {objFields}"); return fieldValues; } @@ -255,7 +257,7 @@ public static async Task GetNonAutomaticPropertyValues( string containerTypeName, ArraySegment getterParamsBuffer, bool isAutoExpandable, - string objectIdStr, + DotnetObjectId objectId, bool isValueType, bool isOwn, CancellationToken token, @@ -267,19 +269,10 @@ public static async Task GetNonAutomaticPropertyValues( if (retDebuggerCmdReader == null) return null; - // Console.WriteLine ($"-- GetNonAutomaticPropertyValues: {objectIdStr}"); - if (!DotnetObjectId.TryParse(objectIdStr, out DotnetObjectId objectId)) - return null; - var nProperties = retDebuggerCmdReader.ReadInt32(); var typeInfo = await sdbHelper.GetTypeInfo(typeId, token); var typePropertiesBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableProperties; - //Console.WriteLine ($"GetNonAutomaticPropertyValues containerTypeName: {containerTypeName}"); - // foreach (var kvp in allMembers) - // Console.WriteLine ($"\t{kvp.Key}"); - //Console.WriteLine ($"-- GetNonAutomaticPropertyValues - processing them now"); - GetMembersResult ret = new(); for (int i = 0; i < nProperties; i++) { @@ -298,7 +291,6 @@ public static async Task GetNonAutomaticPropertyValues( getterAttrs &= MethodAttributes.MemberAccessMask; typePropertiesBrowsableInfo.TryGetValue(propName, out DebuggerBrowsableState? state); - //Console.WriteLine ($"GetNonAutomaticPropertyValues: propName: {propName}"); // FIXME: if it's accessorPropertiesOnly, and we want to skip any auto-props, then we need the backing field // here to detect that.. or have access to the FieldTypeClasses @@ -324,7 +316,6 @@ public static async Task GetNonAutomaticPropertyValues( string backingFieldTypeName = backingField["value"]?["className"]?.Value(); var expanded = await GetExpandedMemberValues(sdbHelper, backingFieldTypeName, namePrefix, backingField, state, token); - //Console.WriteLine ($"And GetExpandedMemberValues for {propName} returned: {expanded}"); backingField.Remove(); allMembers.Remove(propName); foreach (JObject evalue in expanded) @@ -341,10 +332,8 @@ public static async Task GetNonAutomaticPropertyValues( } else { - //Console.WriteLine ($"\tnot an existing one.. so, maybe not a backingField: {propName}"); string returnTypeName = await sdbHelper.GetReturnType(getMethodId, token); JObject propRet = null; - //logger.LogDebug($"* GetNonAutomaticPropertyValues: propertyNameStr: {propertyNameStr}, auto: {isAutoExpandable}"); if (isAutoExpandable || (state is DebuggerBrowsableState.RootHidden && IsACollectionType(returnTypeName))) propRet = await sdbHelper.InvokeMethod(getterParamsBuffer, getMethodId, token, name: propName); else @@ -353,7 +342,6 @@ public static async Task GetNonAutomaticPropertyValues( if (isOwn) propRet["isOwn"] = true; - // Console.WriteLine ($"- prop {propRet["name"]?.Value()}: {getterAttrs}"); propRet["__section"] = getterAttrs switch { MethodAttributes.Private => "private", @@ -363,11 +351,10 @@ public static async Task GetNonAutomaticPropertyValues( propRet["__state"] = state?.ToString(); string namePrefix = GetNamePrefixForValues(propName, containerTypeName, isOwn, state); - ret.Result.AddRange(await GetExpandedMemberValues(sdbHelper, returnTypeName, namePrefix, propRet, state, token)); + var expandedMembers = await GetExpandedMemberValues(sdbHelper, returnTypeName, namePrefix, propRet, state, token); + ret.Result.AddRange(expandedMembers); } } - - // Console.WriteLine ($"GetNonAutomaticPropertyValues returning {ret}"); return ret; JObject GetNotAutoExpandableObject(int methodId, string propertyName) @@ -428,17 +415,13 @@ public static async Task GetObjectMemberValues(MonoSDBHelper s // 3. GetProperties + DotnetObjectId id = new DotnetObjectId("object", objectId); using var commandParamsObjWriter = new MonoBinaryWriter(); - commandParamsObjWriter.WriteObj(new DotnetObjectId("object", objectId), sdbHelper); + commandParamsObjWriter.WriteObj(id, sdbHelper); ArraySegment getPropertiesParamBuffer = commandParamsObjWriter.GetParameterBuffer(); - // RuntimeGetPropertiesResult result = new(); - - // skip adding any members, if they were in a more derived type! // FIXME: change to HashSet var allMembers = new Dictionary(); - // var allMembersDict = new Dictionary(); - // var memberValuesByName = new Dictionary(); for (int i = 0; i < typeIdsIncludingParents.Count; i++) { int typeId = typeIdsIncludingParents[i]; @@ -449,17 +432,13 @@ public static async Task GetObjectMemberValues(MonoSDBHelper s if (!includeStatic) thisTypeFields = thisTypeFields.Where(f => !f.Attributes.HasFlag(FieldAttributes.Static)).ToList(); - // logger.LogDebug($"** i: {i}, {typeName}, numFields: {thisTypeFields.Count}, getCommandOptions: {getCommandOptions}"); - // case: // derived type overrides with an automatic property with a getter // if (!getCommandOptions.HasFlag(GetObjectCommandOptions.AccessorPropertiesOnly) && thisTypeFields.Count > 0) if (thisTypeFields.Count > 0) { - // Add fields var allFields = await GetExpandedFieldValues(sdbHelper, objectId, typeId, thisTypeFields, getCommandType, isOwn: isOwn, token); - //Console.WriteLine ($"And fields got: {allFields}"); if (getCommandType.HasFlag(GetObjectCommandOptions.AccessorPropertiesOnly)) { @@ -471,26 +450,25 @@ public static async Task GetObjectMemberValues(MonoSDBHelper s AddOnlyNewValuesByNameTo(allFields, allMembers); } + // skip loading properties if not necessary + if (!getCommandType.HasFlag(GetObjectCommandOptions.WithProperties)) + return GetMembersResult.FromValues(allMembers.Values, sortByAccessLevel); + GetMembersResult props = await GetNonAutomaticPropertyValues( sdbHelper, typeId, typeName, getPropertiesParamBuffer, getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute), - $"dotnet:object:{objectId}", + id, isValueType: false, isOwn, token, allMembers, thisTypeFields); - //Console.WriteLine ($"And GetNonAutomaticPropertyValues returned {props}"); // TODO: merge with GetNonAuto* - if (getCommandType.HasFlag(GetObjectCommandOptions.WithProperties)) - { - // JArray propertiesArray = await GetExpandedPropertyValues(props.Result, allMembers, typeId, isOwn, token); - AddOnlyNewValuesByNameTo(props.Result, allMembers); - } + AddOnlyNewValuesByNameTo(props.Result, allMembers); // ownProperties // Note: ownProperties should mean that we return members of the klass itself, @@ -512,11 +490,10 @@ static void AddOnlyNewValuesByNameTo(JArray namedValues, IDictionary(); if (key != null) { - if (!valuesDict.ContainsKey(key))// && !fields.ContainsKey(key)) + if (!valuesDict.TryAdd(key, item as JObject)) { - // //Console.WriteLine ($"- AddOnlyNewValuesByNameTo: adding {key} = {item}"); - // FIXME: - valuesDict.Add(key, item as JObject); + //Console.WriteLine($"- AddOnlyNewValuesByNameTo: skipping {key}"); + //Logger.LogDebug($"- AddOnlyNewValuesByNameTo: skipping {key} = {item}"); } } } @@ -534,18 +511,32 @@ internal class GetMembersResult : IEnumerable // other: public JArray OtherMembers { get; set; } - public GetMembersResult() { } + 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(JObject value, bool IsFlattened = false) + public GetMembersResult(JArray value, bool IsFlattened = false) { - // ToDo + Result = value; + PrivateMembers = new JArray(); + OtherMembers = new JArray(); } - public static GetMembersResult FromValues(JArray values, bool splitMembersByAccessLevel = false) => - FromValues((IEnumerable)values.GetEnumerator(), splitMembersByAccessLevel); + public static GetMembersResult FromValues(IEnumerable values, bool splitMembersByAccessLevel = false) => + FromValues(new JArray(values), splitMembersByAccessLevel); - public static GetMembersResult FromValues(IEnumerable values, bool splitMembersByAccessLevel = false) + public static GetMembersResult FromValues(JArray values, bool splitMembersByAccessLevel = false) { GetMembersResult result = new(); if (splitMembersByAccessLevel) @@ -585,7 +576,7 @@ private void Split(JToken member) public GetMembersResult Clone() => new GetMembersResult() { Result = Result, PrivateMembers = PrivateMembers, OtherMembers = OtherMembers }; - public IEnumerator GetEnumerator() => new JToken[] { Result, PrivateMembers, OtherMembers }.ToList().GetEnumerator(); + public IEnumerator GetEnumerator() => Flatten().GetEnumerator(); public IEnumerable Where(Func predicate) { @@ -612,8 +603,19 @@ public IEnumerable Where(Func predicate) } } - internal JToken FirstOrDefault(Func p) => Result.FirstOrDefault(p) ?? PrivateMembers.FirstOrDefault() ?? OtherMembers.FirstOrDefault(); + internal JToken FirstOrDefault(Func p) => Result.FirstOrDefault(p) ?? PrivateMembers.FirstOrDefault(p) ?? OtherMembers.FirstOrDefault(p); + internal void Add(JObject thisObj) => Result.Add(thisObj); - internal JArray Flatten() => new JArray(Result, PrivateMembers, OtherMembers); + + internal JArray Flatten() + { + var result = new JArray(); + result.AddRange(Result); + result.AddRange(PrivateMembers); + result.AddRange(OtherMembers); + return result; + } + + public override string ToString() => $"{JObject}\n"; } } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index c1e94948134f01..a5dea625f738f1 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -475,11 +475,14 @@ protected override async Task AcceptCommand(MessageId id, string method, J { 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; } @@ -656,8 +659,12 @@ private async Task CallOnFunction(MessageId id, JObject args, Cancellation } switch (objectId.Scheme) { - case "object": case "method": + // FIXME: + Console.WriteLine($"IN GetMethodProxy: UNFINISHED METHOD {objectId}"); + args["details"] = await context.SdbAgent.GetMethodProxy(objectId.ValueAsJson, token); + break; + case "object": args["details"] = await context.SdbAgent.GetObjectProxy(objectId.Value, token); break; case "valuetype": @@ -673,12 +680,11 @@ private async Task CallOnFunction(MessageId id, JObject args, Cancellation args["details"] = await context.SdbAgent.GetArrayValuesProxy(objectId.Value, token); break; case "cfo_res": - { + // FIXME: RunOnCFOValueTypeResult 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, @@ -749,7 +755,6 @@ internal async Task> RuntimeGetObjectMembers(Sess getObjectOptions |= GetObjectCommandOptions.OwnProperties; } } - // Console.WriteLine ($"* RuntimeGetPropertiesInternal: {objectId}"); try { switch (objectId.Scheme) @@ -757,7 +762,8 @@ internal async Task> RuntimeGetObjectMembers(Sess case "scope": Result resScope = await GetScopeProperties(id, objectId.Value, token); return resScope.IsOk - ? ValueOrError.WithValue(new GetMembersResult(resScope.Value, IsFlattened: false)) + ? ValueOrError.WithValue( + new GetMembersResult((JArray)resScope.Value?["result"], IsFlattened: false)) : ValueOrError.WithError(resScope); case "valuetype": var valType = context.SdbAgent.GetValueTypeClass(objectId.Value); @@ -776,7 +782,7 @@ internal async Task> RuntimeGetObjectMembers(Sess var resMethod = await context.SdbAgent.InvokeMethod(objectId, token); return ValueOrError.WithValue(GetMembersResult.FromValues(new JArray(resMethod))); case "object": - var resObj = await MemberObjectsExplorer.GetObjectMemberValues(context.SdbAgent, objectId.Value, getObjectOptions, token, sortByAccessLevel); + var resObj = await MemberObjectsExplorer.GetObjectMemberValues(context.SdbAgent, objectId.Value, getObjectOptions, token, sortByAccessLevel, includeStatic: true); return ValueOrError.WithValue(resObj); case "pointer": var resPointer = new JArray { await context.SdbAgent.GetPointerContent(objectId.Value, token) }; diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index cae93919f65b91..6dfbf9883613e4 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -1414,9 +1414,8 @@ public async Task GetValueFromDebuggerDisplayAttribute(DotnetObjectId do GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.ForDebuggerDisplayAttribute, token); // FIXME: flatten, it's flattened already - members.Flatten(); // JArray objectValues = new JArray(members.JObject); - JArray objectValues = new JArray(members.Result); + JArray objectValues = new JArray(members.Flatten()); var thisObj = CreateJObject(value: "", type: "object", description: "", writable: false, objectId: dotnetObjectId.ToString()); thisObj["name"] = "this"; @@ -2168,8 +2167,9 @@ public async Task StackFrameGetValues(MethodInfoWithDebugInformation met logger.LogDebug($"** StackFrameGetValues calling GetObjectMemberValues"); // infinite loop again? GetMembersResult asyncProxyMembers = await MemberObjectsExplorer.GetObjectMemberValues(this, objectId, GetObjectCommandOptions.WithProperties, token); + //Console.WriteLine($"asyncProxyMembers: {asyncProxyMembers}"); var asyncLocals = await GetHoistedLocalVariables(objectId, asyncProxyMembers.Flatten(), token); - // //Console.WriteLine ($"** StackFrameGetValues returning {asyncLocals}"); + //Console.WriteLine($"** StackFrameGetValues returning {asyncLocals}"); return asyncLocals; } @@ -2277,13 +2277,14 @@ public async Task GetTypeByName(string typeToSearch, CancellationToken toke // FIXME: support valuetypes public async Task GetValuesFromDebuggerProxyAttribute(int objectId, int typeId, CancellationToken token) { - // logger.LogInformation($"GetValuesFromDebuggerProxyAttribute: ENTER"); + //Console.WriteLine($"GetValuesFromDebuggerProxyAttribute: ENTER"); try { int methodId = await FindDebuggerProxyConstructorIdFor(typeId, token); if (methodId == -1) { - // logger.LogInformation($"GetValuesFromDebuggerProxyAttribute: could not find proxy constructor id for objectId {objectId}"); + //Console.WriteLine($"GetValuesFromDebuggerProxyAttribute: could not find proxy constructor id for objectId {objectId}"); + logger.LogInformation($"GetValuesFromDebuggerProxyAttribute: could not find proxy constructor id for objectId {objectId}"); return null; } @@ -2306,7 +2307,8 @@ public async Task GetValuesFromDebuggerProxyAttribute(int obje } var retMethod = await InvokeMethod(ctorArgsWriter.GetParameterBuffer(), methodId, token); - // logger.LogInformation($"* GetValuesFromDebuggerProxyAttribute got from InvokeMethod: {retMethod}"); + //Console.WriteLine($"* GetValuesFromDebuggerProxyAttribute got from InvokeMethod: {retMethod}"); + logger.LogInformation($"* GetValuesFromDebuggerProxyAttribute got from InvokeMethod: {retMethod}"); if (!DotnetObjectId.TryParse(retMethod?["value"]?["objectId"]?.Value(), out DotnetObjectId dotnetObjectId)) throw new Exception($"Invoking .ctor ({methodId}) for DebuggerTypeProxy on type {typeId} returned {retMethod}"); @@ -2314,11 +2316,12 @@ public async Task GetValuesFromDebuggerProxyAttribute(int obje GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.ForDebuggerProxyAttribute, token); - // logger.LogInformation($"* GetValuesFromDebuggerProxyAttribute returning for obj: {objectId} {members}"); + //Console.WriteLine($"* GetValuesFromDebuggerProxyAttribute returning for obj: {objectId} {members}"); return members; } catch (Exception e) { + Console.WriteLine($"Could not evaluate DebuggerTypeProxyAttribute of type {await GetTypeName(typeId, token)} - {e}"); logger.LogInformation($"Could not evaluate DebuggerTypeProxyAttribute of type {await GetTypeName(typeId, token)} - {e}"); } @@ -2335,6 +2338,7 @@ private async Task FindDebuggerProxyConstructorIdFor(int typeId, Cancellati var methodId = -1; var parmCount = getCAttrsRetReader.ReadInt32(); + // why are we breaking after the first loop? Why this logic was changed? for (int j = 0; j < parmCount; j++) { var monoTypeId = getCAttrsRetReader.ReadByte(); @@ -2398,10 +2402,21 @@ public Task GetTypeMemberValues(DotnetObjectId dotnetObjectId, ? MemberObjectsExplorer.GetValueTypeMemberValues(this, dotnetObjectId.Value, getObjectOptions, token) : MemberObjectsExplorer.GetObjectMemberValues(this, dotnetObjectId.Value, getObjectOptions, token); + + public async Task GetMethodProxy(JObject objectId, CancellationToken token) + { + var containerId = objectId["containerId"].Value(); + var methodId = objectId["methodId"].Value(); + var isValueType = objectId["isValueType"].Value(); + return await InvokeMethod(containerId, methodId, isValueType, token); + } + public async Task GetObjectProxy(int objectId, CancellationToken token) { + //Console.WriteLine($"-- GetObjectProxy"); GetMembersResult members = await MemberObjectsExplorer.GetObjectMemberValues(this, objectId, GetObjectCommandOptions.WithSetter, token); // FIXME: will this mean that the result object won't get gc'ed? + //Console.WriteLine($"-- GetObjectProxy members: {members}"); JArray ret = members.Flatten();// originally: members.Flatten().Result; // why? var typeIds = await GetTypeIdsForObject(objectId, true, token); foreach (var typeId in typeIds) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs index 2b5df2fb260b83..04caa32373ba2b 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs @@ -178,48 +178,30 @@ public async Task GetProxy(MonoSDBHelper sdbHelper, CancellationToken to // FIXME: this is flattening public async Task GetMemberValues(MonoSDBHelper sdbHelper, GetObjectCommandOptions getObjectOptions, bool sortByAccessLevel, CancellationToken token) { - // Console.WriteLine ($"--------- ValueTypeClass.GetMemberValues: {getObjectOptions}, sort: {sortByAccessLevel}, propertiesExpanded: {propertiesExpanded}"); // 1 - // if (properties == null) if (!propertiesExpanded) { await ExpandPropertyValues(sdbHelper, token); propertiesExpanded = true; } - // Console.WriteLine($"_combined: for {ClassName} {_combinedResult}"); // 2 GetMembersResult result = null; if (!getObjectOptions.HasFlag(GetObjectCommandOptions.ForDebuggerDisplayAttribute)) { - // Console.WriteLine ($"\tget values from proxy"); // 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)) { - // Console.WriteLine ($"\tget only properties"); - // 3 - just properties -- though GetObjectMEmbers seems to be ignoring this! + // 3 - just properties result = _combinedResult.Clone(); - List toRemove = new(); - foreach (JToken jt in result) - { - if (jt is not JObject obj || obj["get"] != null) - continue; - - // if (obj["isBackingField"]?.Value() is var isBackingField - // && isBackingField.GetValueOrDefault(false) == false) - // returning backing fields as fields, instead of properties - { - toRemove.Add(jt); - } - } - foreach (var jt in toRemove) - { - // Console.WriteLine ($"\tremoving: {jt}"); - jt.Remove(); - } + RemovePropertiesFrom(result.Result); + RemovePropertiesFrom(result.PrivateMembers); + RemovePropertiesFrom(result.OtherMembers); } if (result == null) @@ -229,11 +211,25 @@ public async Task GetMemberValues(MonoSDBHelper sdbHelper, Get } return result; + + static void RemovePropertiesFrom(JArray collection) + { + List toRemove = new(); + foreach (JToken jt in collection) + { + if (jt is not JObject obj || obj["get"] != null) + continue; + toRemove.Add(jt); + } + foreach (var jt in toRemove) + { + collection.Remove(jt); + } + } } public async Task ExpandPropertyValues(MonoSDBHelper sdbHelper, CancellationToken token) { - // Console.WriteLine ($"-- ExpandPropertyValues: fields: {fields}"); using var commandParamsWriter = new MonoBinaryWriter(); commandParamsWriter.Write(TypeId); @@ -260,13 +256,13 @@ public async Task ExpandPropertyValues(MonoSDBHelper sdbHelper, CancellationToke ClassName, Buffer, autoExpand, - Id.ToString(), + Id, isValueType: true, isOwn: i == 0, token, allMembers, null); - foreach (JObject v in res) + foreach (JObject v in res.Flatten()) allMembers[v["name"].Value()] = v; } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs index a1b9dac6d94f6e..e269832fdc1740 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs @@ -754,20 +754,21 @@ internal async Task CheckProps(JToken actual, object exp_o, string label, int nu Assert.True(actual_obj != null, $"[{label}] not value found for property named '{exp_name}'"); - var actual_val = actual_obj["value"]; if (exp_val.Type == JTokenType.Array) { - var actual_props = await GetProperties(actual_val["objectId"]?.Value()); + var actual_props = await GetProperties(actual_obj["value"]["objectId"]?.Value()); await CheckProps(actual_props, exp_val, $"{label}-{exp_name}"); } else if (exp_val["__custom_type"] != null && exp_val["__custom_type"]?.Value() == "getter") { // hack: for getters, actual won't have a .value + // are we doing it on purpose? Why? CHECK if properties are displayed in Browser/VS, if not revert the value field here + // we should be leaving properties, not their backing fields await CheckCustomType(actual_obj, exp_val, $"{label}#{exp_name}"); } else { - await CheckValue(actual_val, exp_val, $"{label}#{exp_name}"); + await CheckValue(actual_obj["value"], exp_val, $"{label}#{exp_name}"); } } } @@ -857,7 +858,7 @@ internal async Task GetObjectOnLocals(JToken locals, string name) return await GetProperties(objectId); } - + /* @fn_args is for use with `Runtime.callFunctionOn` only */ internal async Task GetProperties(string id, JToken fn_args = null, bool? own_properties = null, bool? accessors_only = null, bool expect_ok = true) { From 5e5f09e5af9a91c8f3b1cb6e4deec84142b064ee Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 13 Apr 2022 13:27:43 +0200 Subject: [PATCH 03/18] Added more Browsable tests: null + valuetype. --- .../EvaluateOnCallFrameTests.cs | 18 ++- .../debugger-test/debugger-evaluate-test.cs | 124 ++++++++++++++++++ 2 files changed, 137 insertions(+), 5 deletions(-) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 6512d3ab9aaae8..039e73ec49cfd4 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -876,14 +876,18 @@ public async Task EvaluateBrowsableNone(string outerClassName, string className, { list = TGetter("list", TObject("System.Collections.Generic.List", description: "Count = 2")), array = TGetter("array", TObject("int[]", description: "int[2]")), - text = TGetter("text", TString("text")) + text = TGetter("text", TString("text")), + nullNone = TGetter("nullNone", TObject("bool[]", is_null: true)), + valueTypeEnum = TGetter("valueTypeEnum", TEnum("DebuggerTests.EvaluateBrowsableProperties.SampleEnum", "yes")) }, "testNoneProps#1"); else await CheckProps(testNoneProps, new { list = TObject("System.Collections.Generic.List", description: "Count = 2"), array = TObject("int[]", description: "int[2]"), - text = TString("text") + text = TString("text"), + nullNone = TObject("bool[]", is_null: true), + valueTypeEnum = TEnum("DebuggerTests.EvaluateBrowsableProperties.SampleEnum", "yes") }, "testNoneProps#1"); }); @@ -929,14 +933,18 @@ public async Task EvaluateBrowsableCollapsed(string outerClassName, string class { listCollapsed = TGetter("listCollapsed", TObject("System.Collections.Generic.List", description: "Count = 2")), arrayCollapsed = TGetter("arrayCollapsed", TObject("int[]", description: "int[2]")), - textCollapsed = TGetter("textCollapsed", TString("textCollapsed")) + textCollapsed = TGetter("textCollapsed", TString("textCollapsed")), + nullCollapsed = TGetter("nullCollapsed", TObject("bool[]", is_null: true)), + valueTypeEnumCollapsed = TGetter("valueTypeEnumCollapsed", TEnum("DebuggerTests.EvaluateBrowsableProperties.SampleEnum", "yes")) }, "testCollapsedProps#1"); else await CheckProps(testCollapsedProps, new { listCollapsed = TObject("System.Collections.Generic.List", description: "Count = 2"), arrayCollapsed = TObject("int[]", description: "int[2]"), - textCollapsed = TString("textCollapsed") + textCollapsed = TString("textCollapsed"), + nullCollapsed = TObject("bool[]", is_null: true), + valueTypeEnumCollapsed = TEnum("DebuggerTests.EvaluateBrowsableProperties.SampleEnum", "yes") }, "testCollapsedProps#1"); }); @@ -945,7 +953,7 @@ public async Task EvaluateBrowsableCollapsed(string outerClassName, string class [InlineData("EvaluateBrowsableProperties", "TestEvaluatePropertiesRootHidden", "testPropertiesRootHidden", 10)] [InlineData("EvaluateBrowsableStaticProperties", "TestEvaluateFieldsRootHidden", "testFieldsRootHidden", 10)] [InlineData("EvaluateBrowsableStaticProperties", "TestEvaluatePropertiesRootHidden", "testPropertiesRootHidden", 10)] - [InlineData("EvaluateBrowsableCustomProperties", "TestEvaluatePropertiesRootHidden", "testPropertiesRootHidden", 5, true)] + // [InlineData("EvaluateBrowsableCustomProperties", "TestEvaluatePropertiesRootHidden", "testPropertiesRootHidden", 5, true)] public async Task EvaluateBrowsableRootHidden(string outerClassName, string className, string localVarName, int breakLine, bool isCustomGetter = false) => await CheckInspectLocalsAtBreakpointSite( $"DebuggerTests.{outerClassName}", "Evaluate", breakLine, "Evaluate", $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.{outerClassName}:Evaluate'); 1 }})", diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs index 80db1d2c310eed..c7a16743c0dee5 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs @@ -511,11 +511,20 @@ public static void EvaluateLocals() public static class EvaluateBrowsableProperties { + public enum SampleEnum + { + yes = 0, + no = 1 + } + public class TestEvaluateFieldsNone { public List list = new List() { 1, 2 }; public int[] array = new int[] { 11, 22 }; public string text = "text"; + public bool[] nullNone = null; + public SampleEnum valueTypeEnum = new(); + } public class TestEvaluatePropertiesNone @@ -523,12 +532,16 @@ public class TestEvaluatePropertiesNone public List list { get; set; } public int[] array { get; set; } public string text { get; set; } + public bool[] nullNone { get; set; } + public SampleEnum valueTypeEnum { get; set; } public TestEvaluatePropertiesNone() { list = new List() { 1, 2 }; array = new int[] { 11, 22 }; text = "text"; + nullNone = null; + valueTypeEnum = new(); } } @@ -542,6 +555,12 @@ public class TestEvaluateFieldsNever [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] public string textNever = "textNever"; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public bool[] nullNever = null; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleEnum valueTypeEnumNever = new(); } public class TestEvaluatePropertiesNever @@ -555,11 +574,19 @@ public class TestEvaluatePropertiesNever [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] public string textNever { get; set; } + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public bool[] nullNever { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleEnum valueTypeEnumNever { get; set; } + public TestEvaluatePropertiesNever() { listNever = new List() { 1, 2 }; arrayNever = new int[] { 11, 22 }; textNever = "textNever"; + nullNever = null; + valueTypeEnumNever = new(); } } @@ -573,6 +600,12 @@ public class TestEvaluateFieldsCollapsed [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] public string textCollapsed = "textCollapsed"; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public bool[] nullCollapsed = null; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleEnum valueTypeEnumCollapsed = new(); } public class TestEvaluatePropertiesCollapsed @@ -586,11 +619,19 @@ public class TestEvaluatePropertiesCollapsed [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] public string textCollapsed { get; set; } + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public bool[] nullCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleEnum valueTypeEnumCollapsed { get; set; } + public TestEvaluatePropertiesCollapsed() { listCollapsed = new List() { 1, 2 }; arrayCollapsed = new int[] { 11, 22 }; textCollapsed = "textCollapsed"; + nullCollapsed = null; + valueTypeEnumCollapsed = new(); } } @@ -604,6 +645,12 @@ public class TestEvaluateFieldsRootHidden [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] public string textRootHidden = "textRootHidden"; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public bool[] nullRootHidden = null; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleEnum valueTypeEnumRootHidden = new(); } public class TestEvaluatePropertiesRootHidden @@ -617,11 +664,19 @@ public class TestEvaluatePropertiesRootHidden [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] public string textRootHidden { get; set; } + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public bool[] nullRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleEnum valueTypeEnumRootHidden { get; set; } + public TestEvaluatePropertiesRootHidden() { listRootHidden = new List() { 1, 2 }; arrayRootHidden = new int[] { 11, 22 }; textRootHidden = "textRootHidden"; + nullRootHidden = null; + valueTypeEnumRootHidden = new(); } } @@ -646,6 +701,9 @@ public class TestEvaluateFieldsNone public static List list = new List() { 1, 2 }; public static int[] array = new int[] { 11, 22 }; public static string text = "text"; + + public static bool[] nullNone = null; + public static DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnum = new(); } public class TestEvaluatePropertiesNone @@ -653,12 +711,16 @@ public class TestEvaluatePropertiesNone public static List list { get; set; } public static int[] array { get; set; } public static string text { get; set; } + public static bool[] nullNone { get; set; } + public static DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnum { get; set; } public TestEvaluatePropertiesNone() { list = new List() { 1, 2 }; array = new int[] { 11, 22 }; text = "text"; + nullNone = null; + valueTypeEnum = new(); } } @@ -672,6 +734,12 @@ public class TestEvaluateFieldsNever [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] public static string textNever = "textNever"; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public static bool[] nullNever = null; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public static DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnumNever = new(); } public class TestEvaluatePropertiesNever @@ -685,11 +753,19 @@ public class TestEvaluatePropertiesNever [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] public static string textNever { get; set; } + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public static bool[] nullNever { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public static DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnumNever { get; set; } + public TestEvaluatePropertiesNever() { listNever = new List() { 1, 2 }; arrayNever = new int[] { 11, 22 }; textNever = "textNever"; + nullNever = null; + valueTypeEnumNever = new(); } } @@ -703,6 +779,12 @@ public class TestEvaluateFieldsCollapsed [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] public static string textCollapsed = "textCollapsed"; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public static bool[] nullCollapsed = null; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public static DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnumCollapsed = new(); } public class TestEvaluatePropertiesCollapsed @@ -716,11 +798,19 @@ public class TestEvaluatePropertiesCollapsed [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] public static string textCollapsed { get; set; } + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public static bool[] nullCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public static DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnumCollapsed { get; set; } + public TestEvaluatePropertiesCollapsed() { listCollapsed = new List() { 1, 2 }; arrayCollapsed = new int[] { 11, 22 }; textCollapsed = "textCollapsed"; + nullCollapsed = null; + valueTypeEnumCollapsed = new(); } } @@ -734,6 +824,12 @@ public class TestEvaluateFieldsRootHidden [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] public static string textRootHidden = "textRootHidden"; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public static bool[] nullRootHidden = null; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public static DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnumRootHidden = new(); } public class TestEvaluatePropertiesRootHidden @@ -747,11 +843,19 @@ public class TestEvaluatePropertiesRootHidden [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] public static string textRootHidden { get; set; } + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public static bool[] nullRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public static DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnumRootHidden { get; set; } + public TestEvaluatePropertiesRootHidden() { listRootHidden = new List() { 1, 2 }; arrayRootHidden = new int[] { 11, 22 }; textRootHidden = "textRootHidden"; + nullRootHidden = null; + valueTypeEnumRootHidden = new(); } } @@ -776,6 +880,8 @@ public class TestEvaluatePropertiesNone public List list { get { return new List() { 1, 2 }; } } public int[] array { get { return new int[] { 11, 22 }; } } public string text { get { return "text"; } } + public bool[] nullNone { get { return null; } } + public DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnum { get { return new(); } } } public class TestEvaluatePropertiesNever @@ -788,6 +894,12 @@ public class TestEvaluatePropertiesNever [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] public string textNever { get { return "textNever"; } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public bool[] nullNever { get { return null; } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnumNever { get { return new(); } } } public class TestEvaluatePropertiesCollapsed @@ -800,6 +912,12 @@ public class TestEvaluatePropertiesCollapsed [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] public string textCollapsed { get { return "textCollapsed"; } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public bool[] nullCollapsed { get { return null; } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnumCollapsed { get { return new(); } } } public class TestEvaluatePropertiesRootHidden @@ -812,6 +930,12 @@ public class TestEvaluatePropertiesRootHidden [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] public string textRootHidden { get { return "textRootHidden"; } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public bool[] nullRootHidden { get { return null; } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnumRootHidden { get { return new(); } } } public static void Evaluate() From 0511349368fe9e2f233338c4334515f783c31a6a Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 13 Apr 2022 14:38:16 +0200 Subject: [PATCH 04/18] Fixed trailing spaces and exposed the test. --- .../wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs | 3 ++- .../debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs index 14ede56f43b09d..fbb51a4e2e1484 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs @@ -121,6 +121,7 @@ private static async Task GetRootHiddenChildren(MonoSDBHelper sdbHelper, // FIXME: test for valuetype collection if (rootObjectId.Scheme is "object" or "valuetype") { + // it does not work, SendDebuggerAgentCommand fails GetMembersResult members = await GetObjectMemberValues(sdbHelper, rootObjectId.Value, getCommandOptions, token); //Console.WriteLine ($"GetObjectMemberValues on that returned: {members}"); @@ -202,7 +203,7 @@ private static async Task GetExpandedFieldValues( string namePrefix = field.Name; string containerTypeName = await sdbHelper.GetTypeName(containerTypeId, token); namePrefix = GetNamePrefixForValues(field.Name, containerTypeName, isOwn, fieldState); - + var enumeratedValues = await GetRootHiddenChildren(sdbHelper, fieldValue, namePrefix, getCommandOptions, token); if (enumeratedValues != null) fieldValues.AddRange(enumeratedValues); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 039e73ec49cfd4..1fb241a31ec08f 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -953,7 +953,7 @@ public async Task EvaluateBrowsableCollapsed(string outerClassName, string class [InlineData("EvaluateBrowsableProperties", "TestEvaluatePropertiesRootHidden", "testPropertiesRootHidden", 10)] [InlineData("EvaluateBrowsableStaticProperties", "TestEvaluateFieldsRootHidden", "testFieldsRootHidden", 10)] [InlineData("EvaluateBrowsableStaticProperties", "TestEvaluatePropertiesRootHidden", "testPropertiesRootHidden", 10)] - // [InlineData("EvaluateBrowsableCustomProperties", "TestEvaluatePropertiesRootHidden", "testPropertiesRootHidden", 5, true)] + [InlineData("EvaluateBrowsableCustomProperties", "TestEvaluatePropertiesRootHidden", "testPropertiesRootHidden", 5, true)] public async Task EvaluateBrowsableRootHidden(string outerClassName, string className, string localVarName, int breakLine, bool isCustomGetter = false) => await CheckInspectLocalsAtBreakpointSite( $"DebuggerTests.{outerClassName}", "Evaluate", breakLine, "Evaluate", $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.{outerClassName}:Evaluate'); 1 }})", From 1836e6c415000b95b22da0c2ab3ec07a09a89589 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 14 Apr 2022 12:06:35 +0200 Subject: [PATCH 05/18] Fixed all GetPropertiesTests. --- .../MemberObjectsExplorer.cs | 42 ++++----- .../BrowserDebugProxy/ValueTypeClass.cs | 2 +- .../EvaluateOnCallFrameTests.cs | 40 -------- .../DebuggerTestSuite/GetPropertiesTests.cs | 92 +++++++++++++++++++ .../debugger-get-properties-test.cs | 6 ++ 5 files changed, 115 insertions(+), 67 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs index fbb51a4e2e1484..628ead83d78856 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs @@ -263,7 +263,7 @@ public static async Task GetNonAutomaticPropertyValues( bool isOwn, CancellationToken token, IDictionary allMembers, - IReadOnlyList fields, + IReadOnlyList fields = null, bool includeStatic = false) { using var retDebuggerCmdReader = await sdbHelper.GetTypePropertiesReader(typeId, token); @@ -297,7 +297,6 @@ public static async Task GetNonAutomaticPropertyValues( // here to detect that.. or have access to the FieldTypeClasses if (allMembers.TryGetValue(propName, out JObject backingField)) { - //Console.WriteLine ($"\tFound {propName} property is allMembers: {backingField}"); if (backingField["__isBackingField"]?.Value() == true) { // FIXME: this should be checked only for this type - add a typeID to it? @@ -324,12 +323,13 @@ public static async Task GetNonAutomaticPropertyValues( } } - // derived type already had a member by this name + // derived type already had a member of this name continue; } - else if (fields?.Where(f => f.Name == propName && f.IsBackingField)?.Any() == true) + else if (fields?.FirstOrDefault(f => f.Name == propName && f.IsBackingField) != null) { - //Console.WriteLine ($"\t* {propName} is a backing field.. not adding the corresponding getter"); + // no test for it or never happens; either find a testcase or remove + //Console.WriteLine($"\t* {propName} is a backing field.. not adding the corresponding getter"); } else { @@ -340,9 +340,7 @@ public static async Task GetNonAutomaticPropertyValues( else propRet = GetNotAutoExpandableObject(getMethodId, propName); - if (isOwn) - propRet["isOwn"] = true; - + propRet["isOwn"] = isOwn; propRet["__section"] = getterAttrs switch { MethodAttributes.Private => "private", @@ -353,7 +351,8 @@ public static async Task GetNonAutomaticPropertyValues( string namePrefix = GetNamePrefixForValues(propName, containerTypeName, isOwn, state); var expandedMembers = await GetExpandedMemberValues(sdbHelper, returnTypeName, namePrefix, propRet, state, token); - ret.Result.AddRange(expandedMembers); + foreach (var member in expandedMembers) + ret.Add(member as JObject); } } return ret; @@ -415,7 +414,6 @@ public static async Task GetObjectMemberValues(MonoSDBHelper s } // 3. GetProperties - DotnetObjectId id = new DotnetObjectId("object", objectId); using var commandParamsObjWriter = new MonoBinaryWriter(); commandParamsObjWriter.WriteObj(id, sdbHelper); @@ -427,7 +425,7 @@ public static async Task GetObjectMemberValues(MonoSDBHelper s { int typeId = typeIdsIncludingParents[i]; string typeName = await sdbHelper.GetTypeName(typeId, token); - // 0th id is for the object itself, and then its parents + // 0th id is for the object itself, and then its ancestors bool isOwn = i == 0; IReadOnlyList thisTypeFields = await sdbHelper.GetTypeFields(typeId, token); if (!includeStatic) @@ -435,8 +433,6 @@ public static async Task GetObjectMemberValues(MonoSDBHelper s // case: // derived type overrides with an automatic property with a getter - - // if (!getCommandOptions.HasFlag(GetObjectCommandOptions.AccessorPropertiesOnly) && thisTypeFields.Count > 0) if (thisTypeFields.Count > 0) { var allFields = await GetExpandedFieldValues(sdbHelper, objectId, typeId, thisTypeFields, getCommandType, isOwn: isOwn, token); @@ -446,9 +442,7 @@ public static async Task GetObjectMemberValues(MonoSDBHelper s foreach (var f in allFields) f["__hidden"] = true; } - // AddOnlyNewValuesByNameTo(new JArray(allFields.Where(f => f["__isBackingField"]?.Value() == true)), allMembers); - // else - AddOnlyNewValuesByNameTo(allFields, allMembers); + AddOnlyNewValuesByNameTo(allFields, allMembers, isOwn); } // skip loading properties if not necessary @@ -469,7 +463,7 @@ public static async Task GetObjectMemberValues(MonoSDBHelper s thisTypeFields); // TODO: merge with GetNonAuto* - AddOnlyNewValuesByNameTo(props.Result, allMembers); + AddOnlyNewValuesByNameTo(props.Flatten(), allMembers, isOwn); // ownProperties // Note: ownProperties should mean that we return members of the klass itself, @@ -480,22 +474,18 @@ public static async Task GetObjectMemberValues(MonoSDBHelper s /*if (accessorPropertiesOnly) break;*/ } - - // FIXME: SplitMemberValuesByProtectionLevel needs ProtectionLevel, and Name + // inherited private fields/props should not be visible, skip them + //var legitimateMembers = allMembers.Where(kvp => !(kvp.Value["isOwn"]?.Value() != true && kvp.Value["__section"]?.Value() == "private")).Select(kvp => kvp.Value); return GetMembersResult.FromValues(allMembers.Values, sortByAccessLevel); - static void AddOnlyNewValuesByNameTo(JArray namedValues, IDictionary valuesDict)//, IDictionary fields) + static void AddOnlyNewValuesByNameTo(JArray namedValues, IDictionary valuesDict, bool isOwn) { foreach (var item in namedValues) { var key = item["name"]?.Value(); if (key != null) { - if (!valuesDict.TryAdd(key, item as JObject)) - { - //Console.WriteLine($"- AddOnlyNewValuesByNameTo: skipping {key}"); - //Logger.LogDebug($"- AddOnlyNewValuesByNameTo: skipping {key} = {item}"); - } + valuesDict.TryAdd(key, item as JObject); } } } @@ -606,7 +596,7 @@ public IEnumerable Where(Func predicate) internal JToken FirstOrDefault(Func p) => Result.FirstOrDefault(p) ?? PrivateMembers.FirstOrDefault(p) ?? OtherMembers.FirstOrDefault(p); - internal void Add(JObject thisObj) => Result.Add(thisObj); + internal void Add(JObject thisObj) => Split(thisObj); internal JArray Flatten() { diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs index 04caa32373ba2b..b50b38a0289181 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs @@ -260,7 +260,7 @@ public async Task ExpandPropertyValues(MonoSDBHelper sdbHelper, CancellationToke isValueType: true, isOwn: i == 0, token, - allMembers, null); + allMembers); foreach (JObject v in res.Flatten()) allMembers[v["name"].Value()] = v; diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 1fb241a31ec08f..c0b87c62821e82 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -1005,46 +1005,6 @@ await RuntimeEvaluateAndCheck( ("a.valueToCheck", TNumber(20))); }); - [Fact] - public async Task EvaluateProtectionLevels() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.GetPropertiesTests.DerivedClass", "InstanceMethod", 1, "InstanceMethod", - "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.GetPropertiesTests.DerivedClass:run'); })", - wait_for_event_fn: async (pause_location) => - { - var id = pause_location["callFrames"][0]["callFrameId"].Value(); - var (obj, _) = await EvaluateOnCallFrame(id, "this"); - var (pub, internalAndProtected, priv) = await GetPropertiesSortedByProtectionLevels(obj["objectId"]?.Value()); - - await CheckProps(pub, new - { - a = TNumber(4), - Base_AutoStringPropertyForOverrideWithField = TString("DerivedClass#Base_AutoStringPropertyForOverrideWithField"), - Base_GetterForOverrideWithField = TString("DerivedClass#Base_GetterForOverrideWithField"), - BaseBase_MemberForOverride = TString("DerivedClass#BaseBase_MemberForOverride"), - DateTime = TGetter("DateTime", TDateTime(new DateTime(2200, 5, 6, 7, 18, 9))), - _DTProp = TGetter("_DTProp", TDateTime(new DateTime(2200, 5, 6, 7, 8, 9))), - FirstName = TGetter("FirstName", TString("DerivedClass#FirstName")), - _base_dateTime = TGetter("_base_dateTime", TDateTime(new DateTime(2134, 5, 7, 1, 9, 2))), - LastName = TGetter("LastName", TString("BaseClass#LastName")) - }, "public"); - - await CheckProps(internalAndProtected, new - { - base_num = TNumber(5) - }, "internalAndProtected"); - - await CheckProps(priv, new - { - _stringField = TString("DerivedClass#_stringField"), - _dateTime = TDateTime(new DateTime(2020, 7, 6, 5, 4, 3)), - AutoStringProperty = TString("DerivedClass#AutoStringProperty"), - StringPropertyForOverrideWithAutoProperty = TString("DerivedClass#StringPropertyForOverrideWithAutoProperty"), - _base_name = TString("private_name"), - Base_AutoStringProperty = TString("base#Base_AutoStringProperty"), - DateTimeForOverride = TGetter("DateTimeForOverride", TDateTime(new DateTime(2190, 9, 7, 5, 3, 2))) - }, "private"); - }); - [Fact] public async Task StructureGetters() => await CheckInspectLocalsAtBreakpointSite( "DebuggerTests.StructureGetters", "Evaluate", 2, "Evaluate", diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs index f91ba3c012effb..86fb8140a84dd7 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs @@ -36,12 +36,16 @@ public class GetPropertiesTests : DebuggerTestBase {"Base_GetterForOverrideWithField", (TString("DerivedClass#Base_GetterForOverrideWithField"), true)}, {"BaseBase_MemberForOverride", (TString("DerivedClass#BaseBase_MemberForOverride"), true)}, + // protected + {"b", (TBool(true), true)}, + // indexers don't show up in getprops // {"Item", (TSymbol("int { get; }"), true)}, // inherited private {"_base_name", (TString("private_name"), false)}, {"_base_dateTime", (TGetter("_base_dateTime"), false)}, + {"_base_autoProperty", (TString("private_autoproperty"), false)}, // inherited public {"Base_AutoStringProperty", (TString("base#Base_AutoStringProperty"), false)}, @@ -134,6 +138,9 @@ public class GetPropertiesTests : DebuggerTestBase {"FirstName", (TGetter("FirstName"), true)}, {"LastName", (TGetter("LastName"), true)}, + // protected + {"b", (TBool(true), true)}, + // indexers don't show up in getprops // {"Item", (TSymbol("int { get; }"), true)} }; @@ -401,5 +408,90 @@ private static void AssertHasOnlyExpectedProperties(string[] expected_names, IEn } } + public static TheoryData, Dictionary, Dictionary, string> GetDataForProtectionLevels() + { + var data = new TheoryData, Dictionary, Dictionary, string>(); + // object DerivedClass 23 elements: + var public_props = new Dictionary() + { + // own + {"a", TNumber(4)}, + {"DateTime", TGetter("DateTime")}, + {"AutoStringProperty", TString("DerivedClass#AutoStringProperty")}, + {"FirstName", TGetter("FirstName")}, + {"DateTimeForOverride", TGetter("DateTimeForOverride")}, + + {"StringPropertyForOverrideWithAutoProperty", TString("DerivedClass#StringPropertyForOverrideWithAutoProperty")}, + {"Base_AutoStringPropertyForOverrideWithField", TString("DerivedClass#Base_AutoStringPropertyForOverrideWithField")}, // should be twice + {"Base_GetterForOverrideWithField", TString("DerivedClass#Base_GetterForOverrideWithField")}, // should be twice + {"BaseBase_MemberForOverride", TString("DerivedClass#BaseBase_MemberForOverride")}, // should be 3 times + + // inherited public + {"Base_AutoStringProperty", TString("base#Base_AutoStringProperty")}, + {"LastName", TGetter("LastName")} + }; + + var internal_protected_props = new Dictionary(){ + // internal + {"b", TBool(true)}, + // inherited protected + {"base_num", TNumber(5)} + }; + + var private_props = new Dictionary(){ + {"_stringField", TString("DerivedClass#_stringField")}, + {"_dateTime", TDateTime(new DateTime(2020, 7, 6, 5, 4, 3))}, + {"_DTProp", TGetter("_DTProp")}, + + // inherited + {"_base_name", TString("private_name")}, + {"_base_autoProperty", TString("private_autoproperty")}, + {"_base_dateTime", TGetter("_base_dateTime")} + }; + data.Add(public_props, internal_protected_props, private_props, "DerivedClass"); + + // structure CloneableStruct: + public_props = new Dictionary() + { + // own + {"a", TNumber(4)}, + {"DateTime", TGetter("DateTime")}, + {"AutoStringProperty", TString("CloneableStruct#AutoStringProperty")}, + {"FirstName", TGetter("FirstName")}, + {"LastName", TGetter("LastName")} + }; + internal_protected_props = new Dictionary() + { + // internal + {"b", TBool(true)} + }; + private_props = new Dictionary() + { + {"_stringField", TString("CloneableStruct#_stringField")}, + {"_dateTime", TDateTime(new DateTime(2020, 7, 6, 5, 4, 3 + 3))}, + {"_DTProp", TGetter("_DTProp")} + }; + data.Add(public_props, internal_protected_props, private_props, "CloneableStruct"); + return data; + } + + [Theory] + [MemberData(nameof(GetDataForProtectionLevels))] + public async Task PropertiesSortedByProtectionLevel( + Dictionary expectedPublic, Dictionary expectedProtInter, Dictionary expectedPriv, string entryMethod) => + await CheckInspectLocalsAtBreakpointSite( + $"DebuggerTests.GetPropertiesTests.{entryMethod}", "InstanceMethod", 1, "InstanceMethod", + $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.GetPropertiesTests.{entryMethod}:run'); }})", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var (obj, _) = await EvaluateOnCallFrame(id, "this"); + var (pub, internalAndProtected, priv) = await GetPropertiesSortedByProtectionLevels(obj["objectId"]?.Value()); + + await CheckProps(pub, expectedPublic, "public"); + await CheckProps(internalAndProtected, expectedProtInter, "internalAndProtected"); + await CheckProps(priv, expectedPriv, "private"); + }); + } } diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-get-properties-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-get-properties-test.cs index d591a34ce140ae..9217641eaafb34 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-get-properties-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-get-properties-test.cs @@ -30,6 +30,7 @@ public class BaseClass : BaseBaseClass, IName { private string _base_name; private DateTime _base_dateTime => new DateTime(2134, 5, 7, 1, 9, 2); + private string _base_autoProperty { get; set; } protected int base_num; public string Base_AutoStringProperty { get; set; } @@ -45,6 +46,7 @@ public class BaseClass : BaseBaseClass, IName public BaseClass() { _base_name = "private_name"; + _base_autoProperty = "private_autoproperty"; base_num = 5; Base_AutoStringProperty = "base#Base_AutoStringProperty"; DateTimeForOverride = new DateTime(2250, 4, 5, 6, 7, 8); @@ -64,6 +66,8 @@ public class DerivedClass : BaseClass, ICloneable private DateTime _dateTime = new DateTime(2020, 7, 6, 5, 4, 3); private DateTime _DTProp => new DateTime(2200, 5, 6, 7, 8, 9); + internal bool b = true; + public int a; public DateTime DateTime => _DTProp.AddMinutes(10); public string AutoStringProperty { get; set; } @@ -116,6 +120,8 @@ public struct CloneableStruct : ICloneable, IName private string _stringField; private DateTime _dateTime; private DateTime _DTProp => new DateTime(2200, 5, 6, 7, 8, 9); + + internal bool b = true; public int a; public DateTime DateTime => _DTProp.AddMinutes(10); From 3c7a2a848c97317c4036e6abc60b24b4f2989a93 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 14 Apr 2022 14:08:54 +0200 Subject: [PATCH 06/18] Fixing RunOnCFOValueTypeResult. --- .../MemberObjectsExplorer.cs | 21 ++++++------- .../debugger/BrowserDebugProxy/MonoProxy.cs | 4 +-- .../BrowserDebugProxy/ValueTypeClass.cs | 15 +++++---- .../DebuggerTestSuite/CallFunctionOnTests.cs | 31 ++++++++++++------- .../DebuggerTestSuite/GetPropertiesTests.cs | 2 +- 5 files changed, 39 insertions(+), 34 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs index 628ead83d78856..1b3ba752c5b044 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs @@ -252,7 +252,7 @@ JArray GetHiddenElement() } } - public static async Task GetNonAutomaticPropertyValues( + public static async Task> GetNonAutomaticPropertyValues( MonoSDBHelper sdbHelper, int typeId, string containerTypeName, @@ -352,10 +352,16 @@ public static async Task GetNonAutomaticPropertyValues( string namePrefix = GetNamePrefixForValues(propName, containerTypeName, isOwn, state); var expandedMembers = await GetExpandedMemberValues(sdbHelper, returnTypeName, namePrefix, propRet, state, token); foreach (var member in expandedMembers) - ret.Add(member as JObject); + { + var key = member["name"]?.Value(); + if (key != null) + { + allMembers.TryAdd(key, member as JObject); + } + } } } - return ret; + return allMembers; JObject GetNotAutoExpandableObject(int methodId, string propertyName) { @@ -431,8 +437,6 @@ public static async Task GetObjectMemberValues(MonoSDBHelper s if (!includeStatic) thisTypeFields = thisTypeFields.Where(f => !f.Attributes.HasFlag(FieldAttributes.Static)).ToList(); - // case: - // derived type overrides with an automatic property with a getter if (thisTypeFields.Count > 0) { var allFields = await GetExpandedFieldValues(sdbHelper, objectId, typeId, thisTypeFields, getCommandType, isOwn: isOwn, token); @@ -449,7 +453,7 @@ public static async Task GetObjectMemberValues(MonoSDBHelper s if (!getCommandType.HasFlag(GetObjectCommandOptions.WithProperties)) return GetMembersResult.FromValues(allMembers.Values, sortByAccessLevel); - GetMembersResult props = await GetNonAutomaticPropertyValues( + allMembers = (Dictionary)await GetNonAutomaticPropertyValues( sdbHelper, typeId, typeName, @@ -462,9 +466,6 @@ public static async Task GetObjectMemberValues(MonoSDBHelper s allMembers, thisTypeFields); - // TODO: merge with GetNonAuto* - AddOnlyNewValuesByNameTo(props.Flatten(), allMembers, isOwn); - // 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 @@ -474,8 +475,6 @@ public static async Task GetObjectMemberValues(MonoSDBHelper s /*if (accessorPropertiesOnly) break;*/ } - // inherited private fields/props should not be visible, skip them - //var legitimateMembers = allMembers.Where(kvp => !(kvp.Value["isOwn"]?.Value() != true && kvp.Value["__section"]?.Value() == "private")).Select(kvp => kvp.Value); return GetMembersResult.FromValues(allMembers.Values, sortByAccessLevel); static void AddOnlyNewValuesByNameTo(JArray namedValues, IDictionary valuesDict, bool isOwn) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index a5dea625f738f1..2e08ae26968052 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -741,14 +741,12 @@ private async Task OnSetVariableValue(MessageId id, int scopeId, string va internal async Task> RuntimeGetObjectMembers(SessionId id, DotnetObjectId objectId, JToken args, CancellationToken token, bool sortByAccessLevel = false) { var context = GetContext(id); - //var accessorPropertiesOnly = false; GetObjectCommandOptions getObjectOptions = GetObjectCommandOptions.WithProperties; if (args != null) { if (args["accessorPropertiesOnly"] != null && args["accessorPropertiesOnly"].Value()) { getObjectOptions |= GetObjectCommandOptions.AccessorPropertiesOnly; - //accessorPropertiesOnly = true; } if (args["ownProperties"] != null && args["ownProperties"].Value()) { @@ -769,7 +767,7 @@ internal async Task> RuntimeGetObjectMembers(Sess var valType = context.SdbAgent.GetValueTypeClass(objectId.Value); if (valType == null) return ValueOrError.WithError($"Internal Error: No valuetype found for {objectId}."); - var resValue = await valType.GetMemberValues(context.SdbAgent, getObjectOptions, sortByAccessLevel, token); + var resValue = await valType.GetMemberValues(context.SdbAgent, getObjectOptions, sortByAccessLevel, token); // but not here return resValue switch { null => ValueOrError.WithError($"Could not get properties for {objectId}"), diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs index b50b38a0289181..1463c954c8f2d6 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs @@ -181,7 +181,7 @@ public async Task GetMemberValues(MonoSDBHelper sdbHelper, Get // 1 if (!propertiesExpanded) { - await ExpandPropertyValues(sdbHelper, token); + await ExpandPropertyValues(sdbHelper, sortByAccessLevel, token); propertiesExpanded = true; } @@ -197,7 +197,7 @@ public async Task GetMemberValues(MonoSDBHelper sdbHelper, Get if (result == null && getObjectOptions.HasFlag(GetObjectCommandOptions.AccessorPropertiesOnly)) { - // 3 - just properties + // 3 - just properties, skip fields result = _combinedResult.Clone(); RemovePropertiesFrom(result.Result); RemovePropertiesFrom(result.PrivateMembers); @@ -228,7 +228,7 @@ static void RemovePropertiesFrom(JArray collection) } } - public async Task ExpandPropertyValues(MonoSDBHelper sdbHelper, CancellationToken token) + public async Task ExpandPropertyValues(MonoSDBHelper sdbHelper, bool splitMembersByAccessLevel, CancellationToken token) { using var commandParamsWriter = new MonoBinaryWriter(); @@ -250,7 +250,7 @@ public async Task ExpandPropertyValues(MonoSDBHelper sdbHelper, CancellationToke for (int i = 0; i < typesToGetProperties.Count; i++) { //FIXME: change GetNonAutomaticPropertyValues to return a jobject instead - GetMembersResult res = await MemberObjectsExplorer.GetNonAutomaticPropertyValues( + var res = await MemberObjectsExplorer.GetNonAutomaticPropertyValues( sdbHelper, typesToGetProperties[i], ClassName, @@ -261,12 +261,11 @@ public async Task ExpandPropertyValues(MonoSDBHelper sdbHelper, CancellationToke isOwn: i == 0, token, allMembers); - - foreach (JObject v in res.Flatten()) - allMembers[v["name"].Value()] = v; + foreach (var kvp in res) + allMembers[kvp.Key] = kvp.Value; } - _combinedResult = GetMembersResult.FromValues(allMembers.Values, splitMembersByAccessLevel: true); + _combinedResult = GetMembersResult.FromValues(allMembers.Values, splitMembersByAccessLevel); } private static bool ShouldAutoExpand(string className) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/CallFunctionOnTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/CallFunctionOnTests.cs index 03819fe99c00e2..d67094469b3b26 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/CallFunctionOnTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/CallFunctionOnTests.cs @@ -291,24 +291,18 @@ public async Task RunOnCFOValueTypeResult(bool roundtrip) => await RunCallFuncti roundtrip: roundtrip, test_fn: async (result) => { + // WHY? + // the order of sending requests makes a difference. Why? When we first ask about obj_accessors then obj_own does not have fields - // getProperties (own=false) - var obj_accessors = await cli.SendCommand("Runtime.getProperties", JObject.FromObject(new - { - objectId = result.Value["result"]["objectId"].Value(), - accessorPropertiesOnly = true, - ownProperties = false - }), token); - AssertEqual(0, obj_accessors.Value["result"].Count(), "obj_accessors-count"); - - // getProperties (own=true) - // isOwn = true, accessorPropertiesOnly = false + // 1st: + ////////////////////////////////// var obj_own = await cli.SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = false, ownProperties = true }), token); + // Console.WriteLine($"obj_own: {obj_own}"); var obj_own_val = obj_own.Value["result"]; var dt = new DateTime(2020, 1, 2, 3, 4, 5); @@ -318,6 +312,21 @@ public async Task RunOnCFOValueTypeResult(bool roundtrip) => await RunCallFuncti gs = TValueType("Math.GenericStruct") }, $"obj_own-props"); + ////////////////////////////////// + + + // 2nd: + ///////////////////////////////// + var obj_accessors = await cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + { + objectId = result.Value["result"]["objectId"].Value(), + accessorPropertiesOnly = true, + ownProperties = false + }), token); + AssertEqual(0, obj_accessors.Value["result"].Count(), "obj_accessors-count"); + // Console.WriteLine($"obj_accessors: {obj_accessors}"); + ///////////////////////////////////// + var gs_props = await GetObjectOnLocals(obj_own_val, "gs"); await CheckProps(gs_props, new { diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs index 86fb8140a84dd7..4f838001ffcd24 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs @@ -411,7 +411,7 @@ private static void AssertHasOnlyExpectedProperties(string[] expected_names, IEn public static TheoryData, Dictionary, Dictionary, string> GetDataForProtectionLevels() { var data = new TheoryData, Dictionary, Dictionary, string>(); - // object DerivedClass 23 elements: + // object DerivedClass; should be 23 elements: var public_props = new Dictionary() { // own From cf265d59eabffe4fb0930311ea94466b0c31f5fa Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 14 Apr 2022 15:05:18 +0200 Subject: [PATCH 07/18] Fixed cloning to be deep. --- .../debugger/BrowserDebugProxy/MemberObjectsExplorer.cs | 7 ++++++- src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs index 1b3ba752c5b044..98d9c6b115ad66 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs @@ -564,7 +564,12 @@ private void Split(JToken member) } } - public GetMembersResult Clone() => new GetMembersResult() { Result = Result, PrivateMembers = PrivateMembers, OtherMembers = OtherMembers }; + public GetMembersResult Clone() => new GetMembersResult() + { + Result = (JArray)Result.DeepClone(), + PrivateMembers = (JArray)PrivateMembers.DeepClone(), + OtherMembers = (JArray)OtherMembers.DeepClone() + }; public IEnumerator GetEnumerator() => Flatten().GetEnumerator(); diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index 2e08ae26968052..ec9407b99fb6aa 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -767,7 +767,7 @@ internal async Task> RuntimeGetObjectMembers(Sess var valType = context.SdbAgent.GetValueTypeClass(objectId.Value); if (valType == null) return ValueOrError.WithError($"Internal Error: No valuetype found for {objectId}."); - var resValue = await valType.GetMemberValues(context.SdbAgent, getObjectOptions, sortByAccessLevel, token); // but not here + var resValue = await valType.GetMemberValues(context.SdbAgent, getObjectOptions, sortByAccessLevel, token); return resValue switch { null => ValueOrError.WithError($"Could not get properties for {objectId}"), From 4c6ebc629f4a461b83d8267591389d92d17734f3 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 14 Apr 2022 15:15:08 +0200 Subject: [PATCH 08/18] Reverted comments in test. --- .../DebuggerTestSuite/CallFunctionOnTests.cs | 31 +++++++------------ 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/CallFunctionOnTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/CallFunctionOnTests.cs index d67094469b3b26..03819fe99c00e2 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/CallFunctionOnTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/CallFunctionOnTests.cs @@ -291,18 +291,24 @@ public async Task RunOnCFOValueTypeResult(bool roundtrip) => await RunCallFuncti roundtrip: roundtrip, test_fn: async (result) => { - // WHY? - // the order of sending requests makes a difference. Why? When we first ask about obj_accessors then obj_own does not have fields - // 1st: - ////////////////////////////////// + // getProperties (own=false) + var obj_accessors = await cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + { + objectId = result.Value["result"]["objectId"].Value(), + accessorPropertiesOnly = true, + ownProperties = false + }), token); + AssertEqual(0, obj_accessors.Value["result"].Count(), "obj_accessors-count"); + + // getProperties (own=true) + // isOwn = true, accessorPropertiesOnly = false var obj_own = await cli.SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = false, ownProperties = true }), token); - // Console.WriteLine($"obj_own: {obj_own}"); var obj_own_val = obj_own.Value["result"]; var dt = new DateTime(2020, 1, 2, 3, 4, 5); @@ -312,21 +318,6 @@ public async Task RunOnCFOValueTypeResult(bool roundtrip) => await RunCallFuncti gs = TValueType("Math.GenericStruct") }, $"obj_own-props"); - ////////////////////////////////// - - - // 2nd: - ///////////////////////////////// - var obj_accessors = await cli.SendCommand("Runtime.getProperties", JObject.FromObject(new - { - objectId = result.Value["result"]["objectId"].Value(), - accessorPropertiesOnly = true, - ownProperties = false - }), token); - AssertEqual(0, obj_accessors.Value["result"].Count(), "obj_accessors-count"); - // Console.WriteLine($"obj_accessors: {obj_accessors}"); - ///////////////////////////////////// - var gs_props = await GetObjectOnLocals(obj_own_val, "gs"); await CheckProps(gs_props, new { From a261dca3441118eec3ce0211ff9a6210a64dbf76 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Tue, 19 Apr 2022 10:52:46 +0200 Subject: [PATCH 09/18] Fixed browsable root hidden tests. --- .../MemberObjectsExplorer.cs | 61 ++++++++----------- .../debugger/BrowserDebugProxy/MonoProxy.cs | 2 +- 2 files changed, 28 insertions(+), 35 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs index 98d9c6b115ad66..db531b4d90abb5 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs @@ -90,47 +90,39 @@ private static async Task ReadFieldValue(MonoSDBHelper sdbHelper, MonoB private static async Task GetRootHiddenChildren(MonoSDBHelper sdbHelper, JObject root, string rootNamePrefix, GetObjectCommandOptions getCommandOptions, CancellationToken token) { if (root?["value"]?["type"]?.Value() != "object") - { - //Console.WriteLine ($"GetRootHiddenChildren: not an object: {root} - name: {rootNamePrefix}"); return new JArray(); - } if (root?["value"]?["type"]?.Value() == "object" && root?["value"]?["subtype"]?.Value() == "null") - { - // FIXME: test for this - //Console.WriteLine ($"GetRootHiddenChildren: null root named {rootNamePrefix}: {root}"); return new JArray(); - } if (!DotnetObjectId.TryParse(root?["value"]?["objectId"]?.Value(), out DotnetObjectId rootObjectId)) - { throw new Exception($"Cannot parse object id from {root} for {rootNamePrefix}"); - // return null; - } - - // 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; - // DotnetObjectId rootObjectId = rootObjectId; - //Console.WriteLine ($"GetRootHiddenChildren: rootName: {rootNamePrefix}: {root}"); - // FIXME: this just assumes that the first returned member will be an array `Items`! - // if (subtype == null || subtype?.Value() != "array") - - // FIXME: test for valuetype collection + // unpack object/valuetype collection to be of array scheme if (rootObjectId.Scheme is "object" or "valuetype") { - // it does not work, SendDebuggerAgentCommand fails - GetMembersResult members = await GetObjectMemberValues(sdbHelper, rootObjectId.Value, getCommandOptions, token); - //Console.WriteLine ($"GetObjectMemberValues on that returned: {members}"); - - var memberNamedItems = members.Where(m => m["name"]?.Value() == "Items").FirstOrDefault(); - if (memberNamedItems is not null && - DotnetObjectId.TryParse(memberNamedItems["value"]?["objectId"]?.Value(), out DotnetObjectId itemsObjectId) && - itemsObjectId.Scheme == "array") + GetMembersResult members = null; + if (rootObjectId.Scheme is "object") + { + members = await GetObjectMemberValues(sdbHelper, rootObjectId.Value, getCommandOptions, token); + } + else { - rootObjectId = itemsObjectId; + wasValueType = true; + var valType = sdbHelper.GetValueTypeClass(rootObjectId.Value); + if (valType != null) + members = await valType.GetMemberValues(sdbHelper, getCommandOptions, false, token); + } + + if (members != null) + { + var memberNamedItems = members.Where(m => m["name"]?.Value() == "Items").FirstOrDefault(); + if (memberNamedItems is not null && + DotnetObjectId.TryParse(memberNamedItems["value"]?["objectId"]?.Value(), out DotnetObjectId itemsObjectId) && + itemsObjectId.Scheme == "array") + { + rootObjectId = itemsObjectId; + } } } @@ -515,11 +507,12 @@ public GetMembersResult() OtherMembers = new JArray(); } - public GetMembersResult(JArray value, bool IsFlattened = false) + public GetMembersResult(JArray value, bool sortByAccessLevel) { - Result = value; - PrivateMembers = new JArray(); - OtherMembers = new JArray(); + var t = FromValues(value, sortByAccessLevel); + Result = t.Result; + PrivateMembers = t.PrivateMembers; + OtherMembers = t.OtherMembers; } public static GetMembersResult FromValues(IEnumerable values, bool splitMembersByAccessLevel = false) => diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index ec9407b99fb6aa..708fec41dd5e82 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -761,7 +761,7 @@ internal async Task> RuntimeGetObjectMembers(Sess Result resScope = await GetScopeProperties(id, objectId.Value, token); return resScope.IsOk ? ValueOrError.WithValue( - new GetMembersResult((JArray)resScope.Value?["result"], IsFlattened: false)) + new GetMembersResult((JArray)resScope.Value?["result"], sortByAccessLevel: false)) : ValueOrError.WithError(resScope); case "valuetype": var valType = context.SdbAgent.GetValueTypeClass(objectId.Value); From 4deb26f66e065d26002cfac06c0396c5010d0d6d Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Tue, 19 Apr 2022 15:37:44 +0200 Subject: [PATCH 10/18] Extended Browsable tests with a structure. --- .../BrowserDebugProxy/DevToolsHelper.cs | 2 +- .../MemberObjectsExplorer.cs | 80 ++++++---- .../BrowserDebugProxy/MonoSDBHelper.cs | 5 +- .../BrowserDebugProxy/ValueTypeClass.cs | 27 ++-- .../EvaluateOnCallFrameTests.cs | 24 ++- .../debugger-test/debugger-evaluate-test.cs | 149 +++++++++++++----- 6 files changed, 188 insertions(+), 99 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs index 4700a43de2578c..d938acabc59d2c 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs @@ -76,7 +76,7 @@ public int Value } } public int SubValue { get; set; } - public bool IsValueType { get; set; } + public bool IsValueType => Scheme == "valuetype"; public JObject ValueAsJson { get; init; } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs index db531b4d90abb5..01482dc202de5b 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs @@ -89,40 +89,50 @@ private static async Task ReadFieldValue(MonoSDBHelper sdbHelper, MonoB private static async Task GetRootHiddenChildren(MonoSDBHelper sdbHelper, JObject root, string rootNamePrefix, GetObjectCommandOptions getCommandOptions, CancellationToken token) { - if (root?["value"]?["type"]?.Value() != "object") + var rootValue = root?["value"] ?? root["get"]; + + if (rootValue?["subtype"]?.Value() == "null") return new JArray(); - if (root?["value"]?["type"]?.Value() == "object" && root?["value"]?["subtype"]?.Value() == "null") + var type = rootValue?["type"]?.Value(); + if (type != "object" && type != "function") return new JArray(); - if (!DotnetObjectId.TryParse(root?["value"]?["objectId"]?.Value(), out DotnetObjectId rootObjectId)) + if (!DotnetObjectId.TryParse(rootValue?["objectId"]?.Value(), out DotnetObjectId rootObjectId)) throw new Exception($"Cannot parse object id from {root} for {rootNamePrefix}"); - // unpack object/valuetype collection to be of array scheme - if (rootObjectId.Scheme is "object" or "valuetype") + // if it's an accessor + if (root["get"] != null) + return await GetRootHiddenChildrenForFunction(); + + if (rootValue?["type"]?.Value() != "object") + return new JArray(); + + if (rootObjectId.Scheme is "valuetype") { - GetMembersResult members = null; - if (rootObjectId.Scheme is "object") - { - members = await GetObjectMemberValues(sdbHelper, rootObjectId.Value, getCommandOptions, token); - } - else - { - wasValueType = true; - var valType = sdbHelper.GetValueTypeClass(rootObjectId.Value); - if (valType != null) - members = await valType.GetMemberValues(sdbHelper, getCommandOptions, false, token); - } + var valType = sdbHelper.GetValueTypeClass(rootObjectId.Value); + if (valType == null || valType.IsEnum) + return new JArray(); + + GetMembersResult members = await valType.GetMemberValues(sdbHelper, getCommandOptions, false, token); + JArray resultValue = members.Flatten(); + // indexing valuetype does not make sense so dot is used for creating unique names + foreach (var item in resultValue) + item["name"] = $"{rootNamePrefix}.{item["name"]}"; + return resultValue; + } - if (members != null) + // unpack object collection to be of array scheme + if (rootObjectId.Scheme is "object") + { + GetMembersResult members = await GetObjectMemberValues(sdbHelper, rootObjectId.Value, getCommandOptions, token); + var memberNamedItems = members.Where(m => m["name"]?.Value() == "Items").FirstOrDefault(); + if (memberNamedItems is not null && + (DotnetObjectId.TryParse(memberNamedItems["value"]?["objectId"]?.Value(), out DotnetObjectId itemsObjectId) || + DotnetObjectId.TryParse(memberNamedItems["get"]?["objectId"]?.Value(), out itemsObjectId)) && + itemsObjectId.Scheme == "array") { - var memberNamedItems = members.Where(m => m["name"]?.Value() == "Items").FirstOrDefault(); - if (memberNamedItems is not null && - DotnetObjectId.TryParse(memberNamedItems["value"]?["objectId"]?.Value(), out DotnetObjectId itemsObjectId) && - itemsObjectId.Scheme == "array") - { - rootObjectId = itemsObjectId; - } + rootObjectId = itemsObjectId; } } @@ -132,7 +142,7 @@ private static async Task GetRootHiddenChildren(MonoSDBHelper sdbHelper, // 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(rootNamePrefix, "[", item["name"], "]"); + item["name"] = $"{rootNamePrefix}[{item["name"]}]"; return resultValue; } @@ -140,6 +150,12 @@ private static async Task GetRootHiddenChildren(MonoSDBHelper sdbHelper, { return new JArray(); } + + async Task GetRootHiddenChildrenForFunction() + { + var resMethod = await sdbHelper.InvokeMethod(rootObjectId, token); + return await GetRootHiddenChildren(sdbHelper, resMethod, rootNamePrefix, getCommandOptions, token); + } } private static async Task GetExpandedFieldValues( @@ -217,21 +233,18 @@ public static Task GetValueTypeMemberValues(MonoSDBHelper sdbH public static async Task GetExpandedMemberValues(MonoSDBHelper sdbHelper, string typeName, string namePrefix, JObject value, DebuggerBrowsableState? state, CancellationToken token) { - //Console.WriteLine ($"- GetExpandedMemberValues: {typeName}, {namePrefix}, {value}, state: {state}"); if (state == DebuggerBrowsableState.RootHidden) { - if (IsACollectionType(typeName)) - return await GetRootHiddenChildren(sdbHelper, value, namePrefix, GetObjectCommandOptions.None, token); + if (MonoSDBHelper.IsPrimitiveType(typeName)) + return GetHiddenElement(); + + return await GetRootHiddenChildren(sdbHelper, value, namePrefix, GetObjectCommandOptions.None, token); - //Console.WriteLine ($"- AddMemberValue: adding hidden obj for {typeName} - {namePrefix} with value: {value}, state: {state}"); - return GetHiddenElement(); } else if (state is DebuggerBrowsableState.Never) { return GetHiddenElement(); } - - // existing.Add(value); return new JArray(value); JArray GetHiddenElement() @@ -321,7 +334,6 @@ public static async Task> GetNonAutomaticPropertyVa else if (fields?.FirstOrDefault(f => f.Name == propName && f.IsBackingField) != null) { // no test for it or never happens; either find a testcase or remove - //Console.WriteLine($"\t* {propName} is a backing field.. not adding the corresponding getter"); } else { diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index 6dfbf9883613e4..41393855d57cd9 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -1835,7 +1835,7 @@ public async Task CreateJObjectForObject(MonoBinaryReader retDebuggerCm "object", // object in primitive? }; - private static bool IsPrimitiveType(string simplifiedClassName) + public static bool IsPrimitiveType(string simplifiedClassName) => s_primitiveTypeNames.Contains(simplifiedClassName); public async Task CreateJObjectForValueType(MonoBinaryReader retDebuggerCmdReader, string name, long initialPos, bool forDebuggerDisplayAttribute, CancellationToken token) @@ -1886,11 +1886,12 @@ public async Task CreateJObjectForValueType(MonoBinaryReader retDebugge className, typeId, numValues, + isEnum, token); valueTypes[valueType.Id.Value] = valueType; // FIXME: check if this gets called more than once.. maybe with different values of forDebugg.. ? - return await valueType.ToJObject(this, isEnum, forDebuggerDisplayAttribute, token); + return await valueType.ToJObject(this, forDebuggerDisplayAttribute, token); } public async Task CreateJObjectForNull(MonoBinaryReader retDebuggerCmdReader, CancellationToken token) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs index 1463c954c8f2d6..246a015edd4c71 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs @@ -27,15 +27,20 @@ internal class ValueTypeClass 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, DotnetObjectId objectId) + 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; ClassName = className; TypeId = typeId; autoExpand = ShouldAutoExpand(className); Id = objectId; + IsEnum = isEnum; } public override string ToString() => $"{{ ValueTypeClass: typeId: {TypeId}, Id: {Id}, Id: {Id}, fields: {fields} }}"; @@ -47,6 +52,7 @@ public static async Task CreateFromReader( string className, int typeId, int numValues, + bool isEnum, CancellationToken token) { var typeInfo = await sdbAgent.GetTypeInfo(typeId, token); @@ -58,8 +64,6 @@ public static async Task CreateFromReader( // FIXME: save the field values buffer, and expand on demand int numWritableFields = writableFields.Count(); - // if (numWritableFields != numValues) - // throw new Exception($"Bug: CreateFromReader: writableFields({numWritableFields}) != numValues({numValues}))"); // FIXME: add the static oens too? and tests for that! EvaluateOnCallFrame has some? JArray fields = new(); @@ -96,17 +100,13 @@ public static async Task CreateFromReader( cmdReader.Read(valueTypeBuffer, 0, (int)(endPos - initialPos)); cmdReader.BaseStream.Position = endPos; - // FIXME: e combine into single GetNewValueTypeClass() - var valueTypeId = MonoSDBHelper.GetNewObjectId(); - var dotnetObjectId = new DotnetObjectId("valuetype", valueTypeId); - return new ValueTypeClass(valueTypeBuffer, className, fields, typeId, dotnetObjectId); + return new ValueTypeClass(valueTypeBuffer, className, fields, typeId, isEnum); } - public async Task ToJObject(MonoSDBHelper sdbAgent, bool isEnum, bool forDebuggerDisplayAttribute, CancellationToken token) + public async Task ToJObject(MonoSDBHelper sdbAgent, bool forDebuggerDisplayAttribute, CancellationToken token) { string description = ClassName; - // FIXME: isEnum to .. some flag, or field? - if (ShouldAutoInvokeToString(ClassName) || isEnum) + if (ShouldAutoInvokeToString(ClassName) || IsEnum) { int methodId = await sdbAgent.GetMethodIdByName(TypeId, "ToString", token); var retMethod = await sdbAgent.InvokeMethod(Buffer, methodId, token); @@ -121,9 +121,7 @@ public async Task ToJObject(MonoSDBHelper sdbAgent, bool isEnum, bool f description = displayString; } - // Console.WriteLine ($"* CreateJObjectForVariableValue: *new* (name: {name}) {valueTypeId}: {valueTypes[valueTypeId]}");//, and valuetype.fields: {valueTypeFields}"); - var obj = MonoSDBHelper.CreateJObject(null, "object", description, false, ClassName, Id.ToString(), null, null, true, true, isEnum); - // Console.WriteLine ($"** CreateJObjectFromValueType EXIT, name: {name}, returning {obj}"); + var obj = MonoSDBHelper.CreateJObject(null, "object", description, false, ClassName, Id.ToString(), null, null, true, true, IsEnum); return obj; } @@ -137,9 +135,6 @@ public async Task GetProxy(MonoSDBHelper sdbHelper, CancellationToken to return null; proxy = new JArray(fields); - // fields.Result, - // fields.PrivateProperties, - // fields.InternalProperties); var nProperties = retDebuggerCmdReader.ReadInt32(); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index c0b87c62821e82..0ee33da117c97d 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -878,7 +878,8 @@ public async Task EvaluateBrowsableNone(string outerClassName, string className, array = TGetter("array", TObject("int[]", description: "int[2]")), text = TGetter("text", TString("text")), nullNone = TGetter("nullNone", TObject("bool[]", is_null: true)), - valueTypeEnum = TGetter("valueTypeEnum", TEnum("DebuggerTests.EvaluateBrowsableProperties.SampleEnum", "yes")) + valueTypeEnum = TGetter("valueTypeEnum", TEnum("DebuggerTests.SampleEnum", "yes")), + sampleStruct = TGetter("sampleStruct", TObject("DebuggerTests.SampleStructure", description: "DebuggerTests.SampleStructure")) }, "testNoneProps#1"); else await CheckProps(testNoneProps, new @@ -887,7 +888,8 @@ public async Task EvaluateBrowsableNone(string outerClassName, string className, array = TObject("int[]", description: "int[2]"), text = TString("text"), nullNone = TObject("bool[]", is_null: true), - valueTypeEnum = TEnum("DebuggerTests.EvaluateBrowsableProperties.SampleEnum", "yes") + valueTypeEnum = TEnum("DebuggerTests.SampleEnum", "yes"), + sampleStruct = TObject("DebuggerTests.SampleStructure", description: "DebuggerTests.SampleStructure") }, "testNoneProps#1"); }); @@ -935,7 +937,8 @@ public async Task EvaluateBrowsableCollapsed(string outerClassName, string class arrayCollapsed = TGetter("arrayCollapsed", TObject("int[]", description: "int[2]")), textCollapsed = TGetter("textCollapsed", TString("textCollapsed")), nullCollapsed = TGetter("nullCollapsed", TObject("bool[]", is_null: true)), - valueTypeEnumCollapsed = TGetter("valueTypeEnumCollapsed", TEnum("DebuggerTests.EvaluateBrowsableProperties.SampleEnum", "yes")) + valueTypeEnumCollapsed = TGetter("valueTypeEnumCollapsed", TEnum("DebuggerTests.SampleEnum", "yes")), + sampleStructCollapsed = TGetter("sampleStructCollapsed", TObject("DebuggerTests.SampleStructure", description: "DebuggerTests.SampleStructure")) }, "testCollapsedProps#1"); else await CheckProps(testCollapsedProps, new @@ -944,7 +947,8 @@ public async Task EvaluateBrowsableCollapsed(string outerClassName, string class arrayCollapsed = TObject("int[]", description: "int[2]"), textCollapsed = TString("textCollapsed"), nullCollapsed = TObject("bool[]", is_null: true), - valueTypeEnumCollapsed = TEnum("DebuggerTests.EvaluateBrowsableProperties.SampleEnum", "yes") + valueTypeEnumCollapsed = TEnum("DebuggerTests.SampleEnum", "yes"), + sampleStructCollapsed = TObject("DebuggerTests.SampleStructure", description: "DebuggerTests.SampleStructure") }, "testCollapsedProps#1"); }); @@ -964,12 +968,17 @@ public async Task EvaluateBrowsableRootHidden(string outerClassName, string clas var (testRootHidden, _) = await EvaluateOnCallFrame(id, localVarName); await CheckValue(testRootHidden, TObject($"DebuggerTests.{outerClassName}.{className}"), nameof(testRootHidden)); var testRootHiddenProps = await GetProperties(testRootHidden["objectId"]?.Value()); + var (refList, _) = await EvaluateOnCallFrame(id, "testPropertiesNone.list"); var refListProp = await GetProperties(refList["objectId"]?.Value()); var refListElementsProp = await GetProperties(refListProp[0]["value"]["objectId"]?.Value()); + var (refArray, _) = await EvaluateOnCallFrame(id, "testPropertiesNone.array"); var refArrayProp = await GetProperties(refArray["objectId"]?.Value()); + var (refStructCollection, _) = await EvaluateOnCallFrame(id, "testPropertiesNone.sampleStruct"); + var refStructCollectionProp = await GetProperties(refStructCollection["objectId"]?.Value()); + //in Console App names are in [] //adding variable name to make elements unique foreach (var item in refArrayProp) @@ -980,7 +989,12 @@ public async Task EvaluateBrowsableRootHidden(string outerClassName, string clas { item["name"] = string.Concat("listRootHidden[", item["name"], "]"); } - var mergedRefItems = new JArray(refListElementsProp.Union(refArrayProp)); + // indexing valuetype does not make sense so dot is used for creating unique names + foreach (var item in refStructCollectionProp) + { + item["name"] = string.Concat("sampleStructRootHidden.", item["name"]); + } + var mergedRefItems = new JArray(refListElementsProp.Union(refArrayProp).Union(refStructCollectionProp)); Assert.Equal(mergedRefItems, testRootHiddenProps); }); diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs index c7a16743c0dee5..34302557cd716c 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs @@ -48,7 +48,7 @@ public static void EvaluateLocalsFromAnotherAssembly() var asm = System.Reflection.Assembly.LoadFrom("debugger-test-with-source-link.dll"); var myType = asm.GetType("DebuggerTests.ClassToCheckFieldValue"); var myMethod = myType.GetConstructor(new Type[] { }); - var a = myMethod.Invoke(new object[]{}); + var a = myMethod.Invoke(new object[] { }); } } @@ -497,7 +497,7 @@ public void run() textListOfLists = new List> { textList, textList }; idx0 = 0; idx1 = 1; - } + } } public static void EvaluateLocals() @@ -509,14 +509,23 @@ public static void EvaluateLocals() } } - public static class EvaluateBrowsableProperties + public struct SampleStructure { - public enum SampleEnum - { - yes = 0, - no = 1 - } + public SampleStructure() { } + + public int Id = 100; + + internal bool IsStruct = true; + } + + public enum SampleEnum + { + yes = 0, + no = 1 + } + public static class EvaluateBrowsableProperties + { public class TestEvaluateFieldsNone { public List list = new List() { 1, 2 }; @@ -524,7 +533,8 @@ public class TestEvaluateFieldsNone public string text = "text"; public bool[] nullNone = null; public SampleEnum valueTypeEnum = new(); - + public SampleStructure sampleStruct = new(); + } public class TestEvaluatePropertiesNone @@ -534,7 +544,8 @@ public class TestEvaluatePropertiesNone public string text { get; set; } public bool[] nullNone { get; set; } public SampleEnum valueTypeEnum { get; set; } - + public SampleStructure sampleStruct { get; set; } + public TestEvaluatePropertiesNone() { list = new List() { 1, 2 }; @@ -542,6 +553,7 @@ public TestEvaluatePropertiesNone() text = "text"; nullNone = null; valueTypeEnum = new(); + sampleStruct = new(); } } @@ -558,9 +570,12 @@ public class TestEvaluateFieldsNever [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(); } public class TestEvaluatePropertiesNever @@ -576,10 +591,13 @@ public class TestEvaluatePropertiesNever [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; } + public TestEvaluatePropertiesNever() { listNever = new List() { 1, 2 }; @@ -587,6 +605,7 @@ public TestEvaluatePropertiesNever() textNever = "textNever"; nullNever = null; valueTypeEnumNever = new(); + sampleStructNever = new(); } } @@ -603,9 +622,12 @@ public class TestEvaluateFieldsCollapsed [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(); } public class TestEvaluatePropertiesCollapsed @@ -621,10 +643,13 @@ public class TestEvaluatePropertiesCollapsed [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; } + public TestEvaluatePropertiesCollapsed() { listCollapsed = new List() { 1, 2 }; @@ -632,6 +657,7 @@ public TestEvaluatePropertiesCollapsed() textCollapsed = "textCollapsed"; nullCollapsed = null; valueTypeEnumCollapsed = new(); + sampleStructCollapsed = new(); } } @@ -648,9 +674,12 @@ public class TestEvaluateFieldsRootHidden [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(); } public class TestEvaluatePropertiesRootHidden @@ -670,6 +699,9 @@ public class TestEvaluatePropertiesRootHidden [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] public SampleEnum valueTypeEnumRootHidden { get; set; } + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleStructure sampleStructRootHidden { get; set; } + public TestEvaluatePropertiesRootHidden() { listRootHidden = new List() { 1, 2 }; @@ -677,6 +709,7 @@ public TestEvaluatePropertiesRootHidden() textRootHidden = "textRootHidden"; nullRootHidden = null; valueTypeEnumRootHidden = new(); + sampleStructRootHidden = new(); } } @@ -701,9 +734,10 @@ public class TestEvaluateFieldsNone public static List list = new List() { 1, 2 }; public static int[] array = new int[] { 11, 22 }; public static string text = "text"; - + public static bool[] nullNone = null; - public static DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnum = new(); + public static SampleEnum valueTypeEnum = new(); + public SampleStructure sampleStruct = new(); } public class TestEvaluatePropertiesNone @@ -712,8 +746,9 @@ public class TestEvaluatePropertiesNone public static int[] array { get; set; } public static string text { get; set; } public static bool[] nullNone { get; set; } - public static DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnum { get; set; } - + public static SampleEnum valueTypeEnum { get; set; } + public SampleStructure sampleStruct { get; set; } + public TestEvaluatePropertiesNone() { list = new List() { 1, 2 }; @@ -721,6 +756,7 @@ public TestEvaluatePropertiesNone() text = "text"; nullNone = null; valueTypeEnum = new(); + sampleStruct = new(); } } @@ -737,9 +773,12 @@ public class TestEvaluateFieldsNever [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 DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnumNever = new(); + public SampleStructure sampleStructNever = new(); } public class TestEvaluatePropertiesNever @@ -755,9 +794,12 @@ public class TestEvaluatePropertiesNever [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 DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnumNever { get; set; } + public SampleStructure sampleStructNever { get; set; } public TestEvaluatePropertiesNever() { @@ -766,6 +808,7 @@ public TestEvaluatePropertiesNever() textNever = "textNever"; nullNever = null; valueTypeEnumNever = new(); + sampleStructNever = new(); } } @@ -782,9 +825,12 @@ public class TestEvaluateFieldsCollapsed [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 DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnumCollapsed = new(); + public SampleStructure sampleStructCollapsed = new(); } public class TestEvaluatePropertiesCollapsed @@ -800,9 +846,12 @@ public class TestEvaluatePropertiesCollapsed [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] public static bool[] nullCollapsed { get; set; } - + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] - public static DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnumCollapsed { get; set; } + public static SampleEnum valueTypeEnumCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleStructure sampleStructCollapsed { get; set; } public TestEvaluatePropertiesCollapsed() { @@ -811,6 +860,7 @@ public TestEvaluatePropertiesCollapsed() textCollapsed = "textCollapsed"; nullCollapsed = null; valueTypeEnumCollapsed = new(); + sampleStructCollapsed = new(); } } @@ -827,9 +877,12 @@ public class TestEvaluateFieldsRootHidden [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] public static bool[] nullRootHidden = null; - + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] - public static DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnumRootHidden = new(); + public static SampleEnum valueTypeEnumRootHidden = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleStructure sampleStructRootHidden = new(); } public class TestEvaluatePropertiesRootHidden @@ -847,7 +900,10 @@ public class TestEvaluatePropertiesRootHidden public static bool[] nullRootHidden { get; set; } [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] - public static DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnumRootHidden { get; set; } + public static SampleEnum valueTypeEnumRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleStructure sampleStructRootHidden { get; set; } public TestEvaluatePropertiesRootHidden() { @@ -856,6 +912,7 @@ public TestEvaluatePropertiesRootHidden() textRootHidden = "textRootHidden"; nullRootHidden = null; valueTypeEnumRootHidden = new(); + sampleStructRootHidden = new(); } } @@ -881,7 +938,8 @@ public class TestEvaluatePropertiesNone public int[] array { get { return new int[] { 11, 22 }; } } public string text { get { return "text"; } } public bool[] nullNone { get { return null; } } - public DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnum { get { return new(); } } + public SampleEnum valueTypeEnum { get { return new(); } } + public SampleStructure sampleStruct { get { return new(); } } } public class TestEvaluatePropertiesNever @@ -897,9 +955,12 @@ public class TestEvaluatePropertiesNever [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 DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnumNever { get { return new(); } } + public SampleStructure sampleStructNever { get { return new(); } } } public class TestEvaluatePropertiesCollapsed @@ -915,9 +976,12 @@ public class TestEvaluatePropertiesCollapsed [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] public bool[] nullCollapsed { get { return null; } } - + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] - public DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnumCollapsed { get { return new(); } } + public SampleEnum valueTypeEnumCollapsed { get { return new(); } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleStructure sampleStructCollapsed { get { return new(); } } } public class TestEvaluatePropertiesRootHidden @@ -933,9 +997,12 @@ public class TestEvaluatePropertiesRootHidden [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] public bool[] nullRootHidden { get { return null; } } - + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] - public DebuggerTests.EvaluateBrowsableProperties.SampleEnum valueTypeEnumRootHidden { get { return new(); } } + public SampleEnum valueTypeEnumRootHidden { get { return new(); } } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleStructure sampleStructRootHidden { get { return new(); } } } public static void Evaluate() @@ -981,8 +1048,8 @@ public class TestClass public double GetDouble(double param = 1.23) => param; public string GetString(string param = "1.23") => param; public string GetUnicodeString(string param = "żółć") => param; - - #nullable enable + +#nullable enable public bool? GetBoolNullable(bool? param = true) => param; public char? GetCharNullable(char? param = 'T') => param; public byte? GetByteNullable(byte? param = 1) => param; @@ -996,7 +1063,7 @@ public class TestClass public float? GetSingleNullable(float? param = 1.23f) => param; public double? GetDoubleNullable(double? param = 1.23) => param; public string? GetStringNullable(string? param = "1.23") => param; - #nullable disable +#nullable disable public bool GetNull(object param = null) => param == null ? true : false; public int GetDefaultAndRequiredParam(int requiredParam, int optionalParam = 3) => requiredParam + optionalParam; @@ -1032,7 +1099,7 @@ public static void EvaluateMethods() { var stopHere = true; } - + public static class NestedClass1 { public static class NestedClass2 From 2e2294851e34a44fa7c9fb97026ffc702c8b18bb Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 20 Apr 2022 09:03:26 +0200 Subject: [PATCH 11/18] Fixed TestSetValueOnObject. --- .../wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs index 01482dc202de5b..041ad94f05d4e4 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs @@ -79,8 +79,9 @@ private static async Task ReadFieldValue(MonoSDBHelper sdbHelper, MonoB commandSet = CommandSet.ObjectRef, command = CmdObject.RefSetValues, buffer = data, - fieldValueType, - length = length + valtype = fieldValueType, + length = length, + id = MonoSDBHelper.GetNewId() })); } From 589d4dbcfcac97edd48ad2d736f1e1017e1d2385 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 20 Apr 2022 10:28:49 +0200 Subject: [PATCH 12/18] Add class testcase to browsable root hidden. --- .../MemberObjectsExplorer.cs | 42 +++++++--- .../EvaluateOnCallFrameTests.cs | 35 +++++--- .../debugger-test/debugger-evaluate-test.cs | 81 ++++++++++++++++--- 3 files changed, 127 insertions(+), 31 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs index 041ad94f05d4e4..436fe9e20ade98 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs @@ -88,7 +88,13 @@ private static async Task ReadFieldValue(MonoSDBHelper sdbHelper, MonoB return fieldValue; } - private static async Task GetRootHiddenChildren(MonoSDBHelper sdbHelper, JObject root, string rootNamePrefix, GetObjectCommandOptions getCommandOptions, CancellationToken token) + private static async Task GetRootHiddenChildren( + MonoSDBHelper sdbHelper, + JObject root, + string rootNamePrefix, + string rootTypeName, + GetObjectCommandOptions getCommandOptions, + CancellationToken token) { var rootValue = root?["value"] ?? root["get"]; @@ -104,7 +110,7 @@ private static async Task GetRootHiddenChildren(MonoSDBHelper sdbHelper, // if it's an accessor if (root["get"] != null) - return await GetRootHiddenChildrenForFunction(); + return await GetRootHiddenChildrenForProperty(); if (rootValue?["type"]?.Value() != "object") return new JArray(); @@ -123,17 +129,26 @@ private static async Task GetRootHiddenChildren(MonoSDBHelper sdbHelper, return resultValue; } - // unpack object collection to be of array scheme + // unpack object if (rootObjectId.Scheme is "object") { GetMembersResult members = await GetObjectMemberValues(sdbHelper, rootObjectId.Value, getCommandOptions, token); - var memberNamedItems = members.Where(m => m["name"]?.Value() == "Items").FirstOrDefault(); - if (memberNamedItems is not null && - (DotnetObjectId.TryParse(memberNamedItems["value"]?["objectId"]?.Value(), out DotnetObjectId itemsObjectId) || - DotnetObjectId.TryParse(memberNamedItems["get"]?["objectId"]?.Value(), out itemsObjectId)) && - itemsObjectId.Scheme == "array") + + if (!IsACollectionType(rootTypeName)) { - rootObjectId = itemsObjectId; + // is a class with members + return members.Flatten(); + } + else + { + // a collection - expose elements to be of array scheme + var memberNamedItems = members.Where(m => m["name"]?.Value() == "Items").FirstOrDefault(); + if (memberNamedItems is not null && + (DotnetObjectId.TryParse(memberNamedItems["value"]?["objectId"]?.Value(), out DotnetObjectId itemsObjectId)) && + itemsObjectId.Scheme == "array") + { + rootObjectId = itemsObjectId; + } } } @@ -152,10 +167,10 @@ private static async Task GetRootHiddenChildren(MonoSDBHelper sdbHelper, return new JArray(); } - async Task GetRootHiddenChildrenForFunction() + async Task GetRootHiddenChildrenForProperty() { var resMethod = await sdbHelper.InvokeMethod(rootObjectId, token); - return await GetRootHiddenChildren(sdbHelper, resMethod, rootNamePrefix, getCommandOptions, token); + return await GetRootHiddenChildren(sdbHelper, resMethod, rootNamePrefix, rootTypeName, getCommandOptions, token); } } @@ -212,8 +227,9 @@ private static async Task GetExpandedFieldValues( 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, getCommandOptions, token); + var enumeratedValues = await GetRootHiddenChildren(sdbHelper, fieldValue, namePrefix, typeName, getCommandOptions, token); if (enumeratedValues != null) fieldValues.AddRange(enumeratedValues); } @@ -239,7 +255,7 @@ public static async Task GetExpandedMemberValues(MonoSDBHelper sdbHelper if (MonoSDBHelper.IsPrimitiveType(typeName)) return GetHiddenElement(); - return await GetRootHiddenChildren(sdbHelper, value, namePrefix, GetObjectCommandOptions.None, token); + return await GetRootHiddenChildren(sdbHelper, value, namePrefix, typeName, GetObjectCommandOptions.None, token); } else if (state is DebuggerBrowsableState.Never) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 0ee33da117c97d..45c849a9e92b4c 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -879,7 +879,8 @@ public async Task EvaluateBrowsableNone(string outerClassName, string className, 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")) + 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 @@ -889,7 +890,8 @@ public async Task EvaluateBrowsableNone(string outerClassName, string className, text = TString("text"), nullNone = TObject("bool[]", is_null: true), valueTypeEnum = TEnum("DebuggerTests.SampleEnum", "yes"), - sampleStruct = TObject("DebuggerTests.SampleStructure", description: "DebuggerTests.SampleStructure") + sampleStruct = TObject("DebuggerTests.SampleStructure", description: "DebuggerTests.SampleStructure"), + sampleClass = TObject("DebuggerTests.SampleClass", description: "DebuggerTests.SampleClass") }, "testNoneProps#1"); }); @@ -938,7 +940,8 @@ public async Task EvaluateBrowsableCollapsed(string outerClassName, string class 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")) + 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 @@ -948,7 +951,8 @@ public async Task EvaluateBrowsableCollapsed(string outerClassName, string class textCollapsed = TString("textCollapsed"), nullCollapsed = TObject("bool[]", is_null: true), valueTypeEnumCollapsed = TEnum("DebuggerTests.SampleEnum", "yes"), - sampleStructCollapsed = TObject("DebuggerTests.SampleStructure", description: "DebuggerTests.SampleStructure") + sampleStructCollapsed = TObject("DebuggerTests.SampleStructure", description: "DebuggerTests.SampleStructure"), + sampleClassCollapsed = TObject("DebuggerTests.SampleClass", description: "DebuggerTests.SampleClass") }, "testCollapsedProps#1"); }); @@ -976,25 +980,38 @@ public async Task EvaluateBrowsableRootHidden(string outerClassName, string clas var (refArray, _) = await EvaluateOnCallFrame(id, "testPropertiesNone.array"); var refArrayProp = await GetProperties(refArray["objectId"]?.Value()); - var (refStructCollection, _) = await EvaluateOnCallFrame(id, "testPropertiesNone.sampleStruct"); - var refStructCollectionProp = await GetProperties(refStructCollection["objectId"]?.Value()); + var (refStruct, _) = await EvaluateOnCallFrame(id, "testPropertiesNone.sampleStruct"); + var refStructProp = await GetProperties(refStruct["objectId"]?.Value()); + + var (refClass, _) = await EvaluateOnCallFrame(id, "testPropertiesNone.sampleClass"); + var refClassProp = await GetProperties(refClass["objectId"]?.Value()); + + JArray mergedRefItems = new(); //in Console App names are in [] //adding variable name to make elements unique foreach (var item in refArrayProp) { item["name"] = string.Concat("arrayRootHidden[", item["name"], "]"); + mergedRefItems.Add(item); } foreach (var item in refListElementsProp) { item["name"] = string.Concat("listRootHidden[", item["name"], "]"); + mergedRefItems.Add(item); } - // indexing valuetype does not make sense so dot is used for creating unique names - foreach (var item in refStructCollectionProp) + + // valuetype/class members unique names are created by concatenation with a dot + foreach (var item in refStructProp) { item["name"] = string.Concat("sampleStructRootHidden.", item["name"]); + mergedRefItems.Add(item); + } + foreach (var item in refClassProp) + { + item["name"] = string.Concat("sampleClassRootHidden.", item["name"]); + mergedRefItems.Add(item); } - var mergedRefItems = new JArray(refListElementsProp.Union(refArrayProp).Union(refStructCollectionProp)); Assert.Equal(mergedRefItems, testRootHiddenProps); }); diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs index 34302557cd716c..131020fee5e0c3 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs @@ -524,6 +524,12 @@ public enum SampleEnum no = 1 } + public class SampleClass + { + public int ClassId = 200; + public List Items = new List { "should not be expanded" }; + } + public static class EvaluateBrowsableProperties { public class TestEvaluateFieldsNone @@ -534,7 +540,7 @@ public class TestEvaluateFieldsNone public bool[] nullNone = null; public SampleEnum valueTypeEnum = new(); public SampleStructure sampleStruct = new(); - + public SampleClass sampleClass = new(); } public class TestEvaluatePropertiesNone @@ -545,6 +551,7 @@ public class TestEvaluatePropertiesNone public bool[] nullNone { get; set; } public SampleEnum valueTypeEnum { get; set; } public SampleStructure sampleStruct { get; set; } + public SampleClass sampleClass { get; set; } public TestEvaluatePropertiesNone() { @@ -554,6 +561,7 @@ public TestEvaluatePropertiesNone() nullNone = null; valueTypeEnum = new(); sampleStruct = new(); + sampleClass = new(); } } @@ -576,6 +584,9 @@ public class TestEvaluateFieldsNever [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 @@ -598,6 +609,9 @@ public class TestEvaluatePropertiesNever [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] public SampleStructure sampleStructNever { get; set; } + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleClass sampleClassNever { get; set; } + public TestEvaluatePropertiesNever() { listNever = new List() { 1, 2 }; @@ -606,6 +620,7 @@ public TestEvaluatePropertiesNever() nullNever = null; valueTypeEnumNever = new(); sampleStructNever = new(); + sampleClassNever = new(); } } @@ -628,6 +643,9 @@ public class TestEvaluateFieldsCollapsed [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 @@ -649,6 +667,9 @@ public class TestEvaluatePropertiesCollapsed [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() { @@ -658,6 +679,7 @@ public TestEvaluatePropertiesCollapsed() nullCollapsed = null; valueTypeEnumCollapsed = new(); sampleStructCollapsed = new(); + sampleClassCollapsed = new(); } } @@ -680,6 +702,9 @@ public class TestEvaluateFieldsRootHidden [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 @@ -701,6 +726,9 @@ public class TestEvaluatePropertiesRootHidden [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() { @@ -710,6 +738,7 @@ public TestEvaluatePropertiesRootHidden() nullRootHidden = null; valueTypeEnumRootHidden = new(); sampleStructRootHidden = new(); + sampleClassRootHidden = new(); } } @@ -737,7 +766,8 @@ public class TestEvaluateFieldsNone public static bool[] nullNone = null; public static SampleEnum valueTypeEnum = new(); - public SampleStructure sampleStruct = new(); + public static SampleStructure sampleStruct = new(); + public static SampleClass sampleClass = new(); } public class TestEvaluatePropertiesNone @@ -747,7 +777,8 @@ public class TestEvaluatePropertiesNone public static string text { get; set; } public static bool[] nullNone { get; set; } public static SampleEnum valueTypeEnum { get; set; } - public SampleStructure sampleStruct { get; set; } + public static SampleStructure sampleStruct { get; set; } + public static SampleClass sampleClass { get; set; } public TestEvaluatePropertiesNone() { @@ -757,6 +788,7 @@ public TestEvaluatePropertiesNone() nullNone = null; valueTypeEnum = new(); sampleStruct = new(); + sampleClass = new(); } } @@ -778,7 +810,10 @@ public class TestEvaluateFieldsNever public static SampleEnum valueTypeEnumNever = new(); [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] - public SampleStructure sampleStructNever = new(); + public static SampleStructure sampleStructNever = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public static SampleClass sampleClassNever = new(); } public class TestEvaluatePropertiesNever @@ -799,7 +834,10 @@ public class TestEvaluatePropertiesNever public static SampleEnum valueTypeEnumNever { get; set; } [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] - public SampleStructure sampleStructNever { get; set; } + public static SampleStructure sampleStructNever { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public static SampleClass sampleClassNever { get; set; } public TestEvaluatePropertiesNever() { @@ -809,6 +847,7 @@ public TestEvaluatePropertiesNever() nullNever = null; valueTypeEnumNever = new(); sampleStructNever = new(); + sampleClassNever = new(); } } @@ -830,7 +869,10 @@ public class TestEvaluateFieldsCollapsed public static SampleEnum valueTypeEnumCollapsed = new(); [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] - public SampleStructure sampleStructCollapsed = new(); + public static SampleStructure sampleStructCollapsed = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public static SampleClass sampleClassCollapsed = new(); } public class TestEvaluatePropertiesCollapsed @@ -851,7 +893,10 @@ public class TestEvaluatePropertiesCollapsed public static SampleEnum valueTypeEnumCollapsed { get; set; } [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] - public SampleStructure sampleStructCollapsed { get; set; } + public static SampleStructure sampleStructCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public static SampleClass sampleClassCollapsed { get; set; } public TestEvaluatePropertiesCollapsed() { @@ -861,6 +906,7 @@ public TestEvaluatePropertiesCollapsed() nullCollapsed = null; valueTypeEnumCollapsed = new(); sampleStructCollapsed = new(); + sampleClassCollapsed = new(); } } @@ -882,7 +928,10 @@ public class TestEvaluateFieldsRootHidden public static SampleEnum valueTypeEnumRootHidden = new(); [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] - public SampleStructure sampleStructRootHidden = new(); + public static SampleStructure sampleStructRootHidden = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public static SampleClass sampleClassRootHidden = new(); } public class TestEvaluatePropertiesRootHidden @@ -903,7 +952,10 @@ public class TestEvaluatePropertiesRootHidden public static SampleEnum valueTypeEnumRootHidden { get; set; } [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] - public SampleStructure sampleStructRootHidden { get; set; } + public static SampleStructure sampleStructRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public static SampleClass sampleClassRootHidden { get; set; } public TestEvaluatePropertiesRootHidden() { @@ -913,6 +965,7 @@ public TestEvaluatePropertiesRootHidden() nullRootHidden = null; valueTypeEnumRootHidden = new(); sampleStructRootHidden = new(); + sampleClassRootHidden = new(); } } @@ -940,6 +993,7 @@ public class TestEvaluatePropertiesNone 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 @@ -961,6 +1015,9 @@ public class TestEvaluatePropertiesNever [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 @@ -982,6 +1039,9 @@ public class TestEvaluatePropertiesCollapsed [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 @@ -1003,6 +1063,9 @@ public class TestEvaluatePropertiesRootHidden [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() From 1ea0cde779a787e4d72647729d6ba0b8dfcd2081 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Mon, 25 Apr 2022 11:54:22 +0200 Subject: [PATCH 13/18] All existing tests are passing. --- .../MemberObjectsExplorer.cs | 159 ++++++----- .../MemberReferenceResolver.cs | 2 +- .../debugger/BrowserDebugProxy/MonoProxy.cs | 15 +- .../BrowserDebugProxy/MonoSDBHelper.cs | 39 +-- .../BrowserDebugProxy/ValueTypeClass.cs | 76 ++++-- .../DebuggerTestSuite/DebuggerTestBase.cs | 10 + .../EvaluateOnCallFrameTests.cs | 256 +++++++++--------- .../debugger-test/debugger-evaluate-test.cs | 6 +- 8 files changed, 301 insertions(+), 262 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs index 436fe9e20ade98..c63d5ade521ea5 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs @@ -94,6 +94,7 @@ private static async Task GetRootHiddenChildren( string rootNamePrefix, string rootTypeName, GetObjectCommandOptions getCommandOptions, + bool includeStatic, CancellationToken token) { var rootValue = root?["value"] ?? root["get"]; @@ -115,34 +116,33 @@ private static async Task GetRootHiddenChildren( if (rootValue?["type"]?.Value() != "object") return new JArray(); - if (rootObjectId.Scheme is "valuetype") + // unpack object/valuetype + if (rootObjectId.Scheme is "object" or "valuetype") { - var valType = sdbHelper.GetValueTypeClass(rootObjectId.Value); - if (valType == null || valType.IsEnum) - return new JArray(); - - GetMembersResult members = await valType.GetMemberValues(sdbHelper, getCommandOptions, false, token); - JArray resultValue = members.Flatten(); - // indexing valuetype does not make sense so dot is used for creating unique names - foreach (var item in resultValue) - item["name"] = $"{rootNamePrefix}.{item["name"]}"; - return resultValue; - } - - // unpack object - if (rootObjectId.Scheme is "object") - { - GetMembersResult members = await GetObjectMemberValues(sdbHelper, rootObjectId.Value, getCommandOptions, token); + 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 with members - return members.Flatten(); + // is a class/valuetype with members + var resultValue = members.Flatten(); + foreach (var item in resultValue) + item["name"] = $"{rootNamePrefix}.{item["name"]}"; + return resultValue; } else { // a collection - expose elements to be of array scheme - var memberNamedItems = members.Where(m => m["name"]?.Value() == "Items").FirstOrDefault(); + var memberNamedItems = members + .Where(m => m["name"]?.Value() == "Items" || m["name"]?.Value() == "_items") + .FirstOrDefault(); if (memberNamedItems is not null && (DotnetObjectId.TryParse(memberNamedItems["value"]?["objectId"]?.Value(), out DotnetObjectId itemsObjectId)) && itemsObjectId.Scheme == "array") @@ -170,38 +170,47 @@ private static async Task GetRootHiddenChildren( async Task GetRootHiddenChildrenForProperty() { var resMethod = await sdbHelper.InvokeMethod(rootObjectId, token); - return await GetRootHiddenChildren(sdbHelper, resMethod, rootNamePrefix, rootTypeName, getCommandOptions, token); + return await GetRootHiddenChildren(sdbHelper, resMethod, rootNamePrefix, rootTypeName, getCommandOptions, includeStatic, token); } } - private static async Task GetExpandedFieldValues( + public static Task GetTypeMemberValues( MonoSDBHelper sdbHelper, - int objectId, + DotnetObjectId dotnetObjectId, + GetObjectCommandOptions getObjectOptions, + CancellationToken token, + bool sortByAccessLevel = false, + bool includeStatic = false) + => dotnetObjectId.IsValueType + ? GetValueTypeMemberValues(sdbHelper, dotnetObjectId.Value, getObjectOptions, token, sortByAccessLevel, includeStatic) + : GetObjectMemberValues(sdbHelper, dotnetObjectId.Value, getObjectOptions, token, sortByAccessLevel, includeStatic); + + public static async Task ExpandFieldValues( + MonoSDBHelper sdbHelper, + DotnetObjectId id, int containerTypeId, IReadOnlyList fields, GetObjectCommandOptions getCommandOptions, bool isOwn, + bool includeStatic, CancellationToken token) { JArray fieldValues = new JArray(); if (fields.Count == 0) return fieldValues; - // Console.WriteLine ($"GetFieldsValues: ENTER for {objectId}"); - // FIXME: What is this doing? is there a test for it? if (getCommandOptions.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute)) fields = fields.Where(field => field.IsNotPrivate).ToList(); using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(objectId); + commandParamsWriter.Write(id.Value); commandParamsWriter.Write(fields.Count); foreach (var field in fields) commandParamsWriter.Write(field.Id); - MonoBinaryReader retDebuggerCmdReader = //isValueType - // ? await SendDebuggerAgentCommand(CmdType.GetValues, commandParamsWriter, token) - await sdbHelper.SendDebuggerAgentCommand(CmdObject.RefGetValues, commandParamsWriter, token); + MonoBinaryReader retDebuggerCmdReader = id.IsValueType + ? await sdbHelper.SendDebuggerAgentCommand(CmdType.GetValues, commandParamsWriter, token) : + await sdbHelper.SendDebuggerAgentCommand(CmdObject.RefGetValues, commandParamsWriter, token); - // FIXME: needed only if there is a RootHidden field var typeInfo = await sdbHelper.GetTypeInfo(containerTypeId, token); int numFieldsRead = 0; @@ -211,7 +220,7 @@ private static async Task GetExpandedFieldValues( int valtype = retDebuggerCmdReader.ReadByte(); retDebuggerCmdReader.BaseStream.Position = initialPos; - JObject fieldValue = await ReadFieldValue(sdbHelper, retDebuggerCmdReader, field, objectId, typeInfo, valtype, isOwn, getCommandOptions, token); + JObject fieldValue = await ReadFieldValue(sdbHelper, retDebuggerCmdReader, field, id.Value, typeInfo, valtype, isOwn, getCommandOptions, token); numFieldsRead++; if (!Enum.TryParse(fieldValue["__state"].Value(), out DebuggerBrowsableState fieldState) @@ -229,7 +238,8 @@ private static async Task GetExpandedFieldValues( namePrefix = GetNamePrefixForValues(field.Name, containerTypeName, isOwn, fieldState); string typeName = await sdbHelper.GetTypeName(field.TypeId, token); - var enumeratedValues = await GetRootHiddenChildren(sdbHelper, fieldValue, namePrefix, typeName, getCommandOptions, token); + var enumeratedValues = await GetRootHiddenChildren( + sdbHelper, fieldValue, namePrefix, typeName, getCommandOptions, includeStatic, token); if (enumeratedValues != null) fieldValues.AddRange(enumeratedValues); } @@ -240,22 +250,29 @@ private static async Task GetExpandedFieldValues( return fieldValues; } - public static Task GetValueTypeMemberValues(MonoSDBHelper sdbHelper, int valueTypeId, GetObjectCommandOptions getCommandOptions, CancellationToken token, bool sortByAccessLevel = false) + public static Task GetValueTypeMemberValues( + MonoSDBHelper sdbHelper, int valueTypeId, GetObjectCommandOptions getCommandOptions, CancellationToken token, bool sortByAccessLevel = false, bool includeStatic = false) { - if (!sdbHelper.valueTypes.TryGetValue(valueTypeId, out ValueTypeClass valueType)) - throw new ArgumentException($"Could not find any valuetype with id: {valueTypeId}", nameof(valueTypeId)); - - return valueType.GetMemberValues(sdbHelper, getCommandOptions, sortByAccessLevel, token); + return sdbHelper.valueTypes.TryGetValue(valueTypeId, out ValueTypeClass valueType) + ? valueType.GetMemberValues(sdbHelper, getCommandOptions, sortByAccessLevel, includeStatic, token) + : throw new ArgumentException($"Could not find any valuetype with id: {valueTypeId}", nameof(valueTypeId)); } - public static async Task GetExpandedMemberValues(MonoSDBHelper sdbHelper, string typeName, string namePrefix, JObject value, DebuggerBrowsableState? state, CancellationToken token) + public static async Task GetExpandedMemberValues( + MonoSDBHelper sdbHelper, + string typeName, + string namePrefix, + JObject value, + DebuggerBrowsableState? state, + bool includeStatic, + CancellationToken token) { - if (state == DebuggerBrowsableState.RootHidden) + if (state is DebuggerBrowsableState.RootHidden) { if (MonoSDBHelper.IsPrimitiveType(typeName)) return GetHiddenElement(); - return await GetRootHiddenChildren(sdbHelper, value, namePrefix, typeName, GetObjectCommandOptions.None, token); + return await GetRootHiddenChildren(sdbHelper, value, namePrefix, typeName, GetObjectCommandOptions.None, includeStatic, token); } else if (state is DebuggerBrowsableState.Never) @@ -285,7 +302,6 @@ public static async Task> GetNonAutomaticPropertyVa bool isOwn, CancellationToken token, IDictionary allMembers, - IReadOnlyList fields = null, bool includeStatic = false) { using var retDebuggerCmdReader = await sdbHelper.GetTypePropertiesReader(typeId, token); @@ -304,7 +320,7 @@ public static async Task> GetNonAutomaticPropertyVa var getMethodId = retDebuggerCmdReader.ReadInt32(); retDebuggerCmdReader.ReadInt32(); //setmethod var attrs = (PropertyAttributes)retDebuggerCmdReader.ReadInt32(); //attrs - if (getMethodId == 0 || await sdbHelper.GetParamCount(getMethodId, token) != 0)// || await MethodIsStatic(getMethodId, token)) + if (getMethodId == 0 || await sdbHelper.GetParamCount(getMethodId, token) != 0) continue; if (!includeStatic && await sdbHelper.MethodIsStatic(getMethodId, token)) continue; @@ -315,8 +331,6 @@ public static async Task> GetNonAutomaticPropertyVa typePropertiesBrowsableInfo.TryGetValue(propName, out DebuggerBrowsableState? state); - // FIXME: if it's accessorPropertiesOnly, and we want to skip any auto-props, then we need the backing field - // here to detect that.. or have access to the FieldTypeClasses if (allMembers.TryGetValue(propName, out JObject backingField)) { if (backingField["__isBackingField"]?.Value() == true) @@ -337,7 +351,8 @@ public static async Task> GetNonAutomaticPropertyVa string namePrefix = GetNamePrefixForValues(propName, containerTypeName, isOwn, state); string backingFieldTypeName = backingField["value"]?["className"]?.Value(); - var expanded = await GetExpandedMemberValues(sdbHelper, backingFieldTypeName, namePrefix, backingField, state, token); + var expanded = await GetExpandedMemberValues( + sdbHelper, backingFieldTypeName, namePrefix, backingField, state, includeStatic, token); backingField.Remove(); allMembers.Remove(propName); foreach (JObject evalue in expanded) @@ -348,16 +363,21 @@ public static async Task> GetNonAutomaticPropertyVa // derived type already had a member of this name continue; } - else if (fields?.FirstOrDefault(f => f.Name == propName && f.IsBackingField) != null) - { - // no test for it or never happens; either find a testcase or remove - } else { string returnTypeName = await sdbHelper.GetReturnType(getMethodId, token); JObject propRet = null; if (isAutoExpandable || (state is DebuggerBrowsableState.RootHidden && IsACollectionType(returnTypeName))) - propRet = await sdbHelper.InvokeMethod(getterParamsBuffer, getMethodId, token, name: propName); + { + try + { + propRet = await sdbHelper.InvokeMethod(getterParamsBuffer, getMethodId, token, name: propName); + } + catch (Exception) + { + continue; + } + } else propRet = GetNotAutoExpandableObject(getMethodId, propName); @@ -371,7 +391,8 @@ public static async Task> GetNonAutomaticPropertyVa propRet["__state"] = state?.ToString(); string namePrefix = GetNamePrefixForValues(propName, containerTypeName, isOwn, state); - var expandedMembers = await GetExpandedMemberValues(sdbHelper, returnTypeName, namePrefix, propRet, state, token); + var expandedMembers = await GetExpandedMemberValues( + sdbHelper, returnTypeName, namePrefix, propRet, state, includeStatic, token); foreach (var member in expandedMembers) { var key = member["name"]?.Value(); @@ -400,17 +421,20 @@ JObject GetNotAutoExpandableObject(int methodId, string propertyName) type = "function", objectId = $"dotnet:method:{methodIdArgs.ToString(Newtonsoft.Json.Formatting.None)}", className = "Function", - description = "get " + propertyName + " ()", - //methodId = methodId, - //objectIdValue = objectIdStr + description = "get " + propertyName + " ()" }, name = propertyName }); } } - - public static async Task GetObjectMemberValues(MonoSDBHelper sdbHelper, int objectId, GetObjectCommandOptions getCommandType, CancellationToken token, bool sortByAccessLevel = false, bool includeStatic = false) + public static async Task GetObjectMemberValues( + MonoSDBHelper sdbHelper, + int objectId, + GetObjectCommandOptions getCommandType, + CancellationToken token, + bool sortByAccessLevel = false, + bool includeStatic = false) { if (await sdbHelper.IsDelegate(objectId, token)) { @@ -446,7 +470,6 @@ public static async Task GetObjectMemberValues(MonoSDBHelper s commandParamsObjWriter.WriteObj(id, sdbHelper); ArraySegment getPropertiesParamBuffer = commandParamsObjWriter.GetParameterBuffer(); - // FIXME: change to HashSet var allMembers = new Dictionary(); for (int i = 0; i < typeIdsIncludingParents.Count; i++) { @@ -457,10 +480,10 @@ public static async Task GetObjectMemberValues(MonoSDBHelper s IReadOnlyList thisTypeFields = await sdbHelper.GetTypeFields(typeId, token); if (!includeStatic) thisTypeFields = thisTypeFields.Where(f => !f.Attributes.HasFlag(FieldAttributes.Static)).ToList(); - if (thisTypeFields.Count > 0) { - var allFields = await GetExpandedFieldValues(sdbHelper, objectId, typeId, thisTypeFields, getCommandType, isOwn: isOwn, token); + var allFields = await ExpandFieldValues( + sdbHelper, id, typeId, thisTypeFields, getCommandType, isOwn, includeStatic, token); if (getCommandType.HasFlag(GetObjectCommandOptions.AccessorPropertiesOnly)) { @@ -484,8 +507,7 @@ public static async Task GetObjectMemberValues(MonoSDBHelper s isValueType: false, isOwn, token, - allMembers, - thisTypeFields); + allMembers); // ownProperties // Note: ownProperties should mean that we return members of the klass itself, @@ -513,13 +535,13 @@ static void AddOnlyNewValuesByNameTo(JArray namedValues, IDictionary JObject.FromObject(new @@ -547,7 +569,6 @@ public GetMembersResult(JArray value, bool sortByAccessLevel) public static GetMembersResult FromValues(IEnumerable values, bool splitMembersByAccessLevel = false) => FromValues(new JArray(values), splitMembersByAccessLevel); - public static GetMembersResult FromValues(JArray values, bool splitMembersByAccessLevel = false) { GetMembersResult result = new(); @@ -593,8 +614,6 @@ private void Split(JToken member) OtherMembers = (JArray)OtherMembers.DeepClone() }; - public IEnumerator GetEnumerator() => Flatten().GetEnumerator(); - public IEnumerable Where(Func predicate) { foreach (var item in Result) @@ -620,9 +639,10 @@ public IEnumerable Where(Func predicate) } } - internal JToken FirstOrDefault(Func p) => Result.FirstOrDefault(p) ?? PrivateMembers.FirstOrDefault(p) ?? OtherMembers.FirstOrDefault(p); - - internal void Add(JObject thisObj) => Split(thisObj); + internal JToken FirstOrDefault(Func p) + => Result.FirstOrDefault(p) + ?? PrivateMembers.FirstOrDefault(p) + ?? OtherMembers.FirstOrDefault(p); internal JArray Flatten() { @@ -632,7 +652,6 @@ internal JArray Flatten() result.AddRange(OtherMembers); return result; } - public override string ToString() => $"{JObject}\n"; } } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs index eaf7818a50e559..3cb785b2ff11ac 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs @@ -55,7 +55,7 @@ public async Task GetValueFromObject(JToken objRet, CancellationToken t { if (DotnetObjectId.TryParse(objRet?["value"]?["objectId"]?.Value(), out DotnetObjectId objectId)) { - GetMembersResult exceptionObject = await context.SdbAgent.GetTypeMemberValues(objectId, GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.OwnProperties, token); + GetMembersResult exceptionObject = await MemberObjectsExplorer.GetTypeMemberValues(context.SdbAgent, objectId, GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.OwnProperties, token); var exceptionObjectMessage = exceptionObject.FirstOrDefault(attr => attr["name"].Value().Equals("_message")); exceptionObjectMessage["value"]["value"] = objRet["value"]?["className"]?.Value() + ": " + exceptionObjectMessage["value"]?["value"]?.Value(); return exceptionObjectMessage["value"]?.Value(); diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index 708fec41dd5e82..b38b7a3f19e948 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -660,8 +660,6 @@ private async Task CallOnFunction(MessageId id, JObject args, Cancellation switch (objectId.Scheme) { case "method": - // FIXME: - Console.WriteLine($"IN GetMethodProxy: UNFINISHED METHOD {objectId}"); args["details"] = await context.SdbAgent.GetMethodProxy(objectId.ValueAsJson, token); break; case "object": @@ -680,7 +678,6 @@ private async Task CallOnFunction(MessageId id, JObject args, Cancellation args["details"] = await context.SdbAgent.GetArrayValuesProxy(objectId.Value, token); break; case "cfo_res": - // FIXME: RunOnCFOValueTypeResult 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); @@ -764,10 +761,8 @@ internal async Task> RuntimeGetObjectMembers(Sess new GetMembersResult((JArray)resScope.Value?["result"], sortByAccessLevel: false)) : ValueOrError.WithError(resScope); case "valuetype": - var valType = context.SdbAgent.GetValueTypeClass(objectId.Value); - if (valType == null) - return ValueOrError.WithError($"Internal Error: No valuetype found for {objectId}."); - var resValue = await valType.GetMemberValues(context.SdbAgent, getObjectOptions, sortByAccessLevel, token); + var resValue = await MemberObjectsExplorer.GetValueTypeMemberValues( + context.SdbAgent, objectId.Value, getObjectOptions, token, sortByAccessLevel, includeStatic: false); return resValue switch { null => ValueOrError.WithError($"Could not get properties for {objectId}"), @@ -780,7 +775,8 @@ internal async Task> RuntimeGetObjectMembers(Sess var resMethod = await context.SdbAgent.InvokeMethod(objectId, token); return ValueOrError.WithValue(GetMembersResult.FromValues(new JArray(resMethod))); case "object": - var resObj = await MemberObjectsExplorer.GetObjectMemberValues(context.SdbAgent, objectId.Value, getObjectOptions, token, sortByAccessLevel, includeStatic: true); + var resObj = await MemberObjectsExplorer.GetObjectMemberValues( + context.SdbAgent, objectId.Value, getObjectOptions, token, sortByAccessLevel, includeStatic: true); return ValueOrError.WithValue(resObj); case "pointer": var resPointer = new JArray { await context.SdbAgent.GetPointerContent(objectId.Value, token) }; @@ -789,7 +785,8 @@ internal async Task> RuntimeGetObjectMembers(Sess Result res = await SendMonoCommand(id, MonoCommands.GetDetails(RuntimeId, objectId.Value, args), token); string value_json_str = res.Value["result"]?["value"]?["__value_as_json_string__"]?.Value(); if (res.IsOk && value_json_str == null) - return ValueOrError.WithError($"Internal error: Could not find expected __value_as_json_string__ field in the result: {res}"); + return ValueOrError.WithError( + $"Internal error: Could not find expected __value_as_json_string__ field in the result: {res}"); return value_json_str != null ? ValueOrError.WithValue(GetMembersResult.FromValues(JArray.Parse(value_json_str))) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index 41393855d57cd9..c090a5eb3abe1b 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -1840,7 +1840,6 @@ public static bool IsPrimitiveType(string simplifiedClassName) public async Task CreateJObjectForValueType(MonoBinaryReader retDebuggerCmdReader, string name, long initialPos, bool forDebuggerDisplayAttribute, CancellationToken token) { - // Console.WriteLine ($"** CreateJObjectFromValueType ENTER, name: {name}"); // FIXME: debugger proxy //JObject fieldValueType = null; var isEnum = retDebuggerCmdReader.ReadByte() == 1; @@ -1849,28 +1848,19 @@ public async Task CreateJObjectForValueType(MonoBinaryReader retDebugge var className = await GetTypeName(typeId, token); var numValues = retDebuggerCmdReader.ReadInt32(); - // Console.WriteLine ($"-- CreateJObjectFromValueType: name: {name}, isBoxed: {isBoxed}, numValues: {numValues}, typeId: {typeId}, className: {className}"); - // Console.WriteLine ($"-- CreateJObjectFromValueType: name: {name}, isBoxed: {isBoxed}, numValues: {numValues}, typeId: {typeId}, className: {className}"); 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(); - // Read the value, even it isNull==true, to correctly advance the reader + // Read the value, even if isNull==true, to correctly advance the reader var value = await CreateJObjectForVariableValue(retDebuggerCmdReader, name, token); if (isNull != 0) - { - // //Console.WriteLine ($"nullable, not null.. "); return value; - } else - { - // //Console.WriteLine ($"nullable is null, and the value we got: {value}"); return CreateJObject(null, "object", className, false, className, null, null, "null", true); - } } - - if (isBoxed)// && numValues == 1) + if (isBoxed && numValues == 1) { if (IsPrimitiveType(className)) { @@ -2278,13 +2268,11 @@ public async Task GetTypeByName(string typeToSearch, CancellationToken toke // FIXME: support valuetypes public async Task GetValuesFromDebuggerProxyAttribute(int objectId, int typeId, CancellationToken token) { - //Console.WriteLine($"GetValuesFromDebuggerProxyAttribute: ENTER"); try { int methodId = await FindDebuggerProxyConstructorIdFor(typeId, token); if (methodId == -1) { - //Console.WriteLine($"GetValuesFromDebuggerProxyAttribute: could not find proxy constructor id for objectId {objectId}"); logger.LogInformation($"GetValuesFromDebuggerProxyAttribute: could not find proxy constructor id for objectId {objectId}"); return null; } @@ -2295,8 +2283,12 @@ public async Task GetValuesFromDebuggerProxyAttribute(int obje // FIXME: move method invocation to valueTypeclass? if (valueTypes.TryGetValue(objectId, out var valueType)) { - ctorArgsWriter.Write((int)1); // num args - ctorArgsWriter.Write(valueType.Buffer); + //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 { @@ -2308,7 +2300,6 @@ public async Task GetValuesFromDebuggerProxyAttribute(int obje } var retMethod = await InvokeMethod(ctorArgsWriter.GetParameterBuffer(), methodId, token); - //Console.WriteLine($"* GetValuesFromDebuggerProxyAttribute got from InvokeMethod: {retMethod}"); logger.LogInformation($"* GetValuesFromDebuggerProxyAttribute got from InvokeMethod: {retMethod}"); if (!DotnetObjectId.TryParse(retMethod?["value"]?["objectId"]?.Value(), out DotnetObjectId dotnetObjectId)) throw new Exception($"Invoking .ctor ({methodId}) for DebuggerTypeProxy on type {typeId} returned {retMethod}"); @@ -2317,12 +2308,10 @@ public async Task GetValuesFromDebuggerProxyAttribute(int obje GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.ForDebuggerProxyAttribute, token); - //Console.WriteLine($"* GetValuesFromDebuggerProxyAttribute returning for obj: {objectId} {members}"); return members; } catch (Exception e) { - Console.WriteLine($"Could not evaluate DebuggerTypeProxyAttribute of type {await GetTypeName(typeId, token)} - {e}"); logger.LogInformation($"Could not evaluate DebuggerTypeProxyAttribute of type {await GetTypeName(typeId, token)} - {e}"); } @@ -2339,7 +2328,6 @@ private async Task FindDebuggerProxyConstructorIdFor(int typeId, Cancellati var methodId = -1; var parmCount = getCAttrsRetReader.ReadInt32(); - // why are we breaking after the first loop? Why this logic was changed? for (int j = 0; j < parmCount; j++) { var monoTypeId = getCAttrsRetReader.ReadByte(); @@ -2351,7 +2339,6 @@ private async Task FindDebuggerProxyConstructorIdFor(int typeId, Cancellati using var commandParamsWriter = new MonoBinaryWriter(); commandParamsWriter.Write(cAttrTypeId); var className = await GetTypeNameOriginal(cAttrTypeId, token); - // //Console.WriteLine ($"FindDebuggerProxyConstructorIdFor: className: {className}"); if (className.IndexOf('[') > 0) { className = className.Remove(className.IndexOf('[')); @@ -2376,15 +2363,9 @@ private async Task FindDebuggerProxyConstructorIdFor(int typeId, Cancellati var genericTypeId = await GetTypeByName(typeToSearch, token); if (genericTypeId < 0) break; - // //Console.WriteLine ($"FindDebuggerProxyConstructorIdFor: genericTypeid: {genericTypeId}"); - methodId = await GetMethodIdByName(genericTypeId, ".ctor", token); + cAttrTypeId = genericTypeId; } - else - { - // //Console.WriteLine ($"FindDebuggerProxyConstructorIdFor: genericTypeid: {cAttrTypeId}"); - methodId = await GetMethodIdByName(cAttrTypeId, ".ctor", token); - } - + methodId = await GetMethodIdByName(cAttrTypeId, ".ctor", token); break; } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs index 246a015edd4c71..c48acec006cc71 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs @@ -16,14 +16,14 @@ namespace BrowserDebugProxy { internal class ValueTypeClass { - private readonly JArray fields; private readonly bool autoExpand; private JArray proxy; private GetMembersResult _combinedResult; - // private GetMembersResult _combinedStaticResult; private bool propertiesExpanded; + private bool fieldsExpanded; + private string className; + private JArray fields; - public string ClassName { get; init; } public DotnetObjectId Id { get; init; } public byte[] Buffer { get; init; } public int TypeId { get; init; } @@ -36,7 +36,7 @@ public ValueTypeClass(byte[] buffer, string className, JArray fields, int typeId Buffer = buffer; this.fields = fields; - ClassName = className; + this.className = className; TypeId = typeId; autoExpand = ShouldAutoExpand(className); Id = objectId; @@ -71,11 +71,6 @@ public static async Task CreateFromReader( { var fieldValue = await sdbAgent.CreateJObjectForVariableValue(cmdReader, field.Name, token, true, field.TypeId, false); - // state == DebuggerBrowsableState.Never) - // { - // continue; - // } - fieldValue["__section"] = field.Attributes switch { FieldAttributes.Private => "private", @@ -85,13 +80,13 @@ public static async Task CreateFromReader( if (field.IsBackingField) fieldValue["__isBackingField"] = true; + else + { + typeFieldsBrowsableInfo.TryGetValue(field.Name, out DebuggerBrowsableState? state); + fieldValue["__state"] = state?.ToString(); + } - // should be only when not backing - string typeName = await sdbAgent.GetTypeName(field.TypeId, token); - typeFieldsBrowsableInfo.TryGetValue(field.Name, out DebuggerBrowsableState? state); - - fieldValue["__state"] = state?.ToString(); - fields.Merge(await MemberObjectsExplorer.GetExpandedMemberValues(sdbAgent, typeName, field.Name, fieldValue, state, token)); + fields.Add(fieldValue); } long endPos = cmdReader.BaseStream.Position; @@ -105,13 +100,13 @@ public static async Task CreateFromReader( public async Task ToJObject(MonoSDBHelper sdbAgent, bool forDebuggerDisplayAttribute, CancellationToken token) { - string description = ClassName; - if (ShouldAutoInvokeToString(ClassName) || IsEnum) + string description = className; + if (ShouldAutoInvokeToString(className) || IsEnum) { int methodId = await sdbAgent.GetMethodIdByName(TypeId, "ToString", token); - var retMethod = await sdbAgent.InvokeMethod(Buffer, methodId, token); + var retMethod = await sdbAgent.InvokeMethod(Buffer, methodId, token, "methodRet"); description = retMethod["value"]?["value"].Value(); - if (ClassName.Equals("System.Guid")) + if (className.Equals("System.Guid")) description = description.ToUpper(); //to keep the old behavior } else if (!forDebuggerDisplayAttribute) @@ -121,7 +116,7 @@ public async Task ToJObject(MonoSDBHelper sdbAgent, bool forDebuggerDis description = displayString; } - var obj = MonoSDBHelper.CreateJObject(null, "object", description, false, ClassName, Id.ToString(), null, null, true, true, IsEnum); + var obj = MonoSDBHelper.CreateJObject(null, "object", description, false, className, Id.ToString(), null, null, true, true, IsEnum); return obj; } @@ -134,6 +129,11 @@ public async Task GetProxy(MonoSDBHelper sdbHelper, CancellationToken to if (retDebuggerCmdReader == null) return null; + if (!fieldsExpanded) + { + await ExpandedFieldValues(sdbHelper, includeStatic: false, token); + fieldsExpanded = true; + } proxy = new JArray(fields); var nProperties = retDebuggerCmdReader.ReadInt32(); @@ -170,13 +170,13 @@ public async Task GetProxy(MonoSDBHelper sdbHelper, CancellationToken to return proxy; } - // FIXME: this is flattening - public async Task GetMemberValues(MonoSDBHelper sdbHelper, GetObjectCommandOptions getObjectOptions, bool sortByAccessLevel, CancellationToken token) + public async Task GetMemberValues( + MonoSDBHelper sdbHelper, GetObjectCommandOptions getObjectOptions, bool sortByAccessLevel, bool includeStatic, CancellationToken token) { // 1 if (!propertiesExpanded) { - await ExpandPropertyValues(sdbHelper, sortByAccessLevel, token); + await ExpandPropertyValues(sdbHelper, sortByAccessLevel, includeStatic, token); propertiesExpanded = true; } @@ -223,9 +223,27 @@ static void RemovePropertiesFrom(JArray collection) } } - public async Task ExpandPropertyValues(MonoSDBHelper sdbHelper, bool splitMembersByAccessLevel, CancellationToken token) + public async Task ExpandedFieldValues(MonoSDBHelper sdbHelper, bool includeStatic, CancellationToken token) { + JArray visibleFields = new(); + foreach (JObject field in fields) + { + if (!Enum.TryParse(field["__state"]?.Value(), out DebuggerBrowsableState state)) + { + visibleFields.Add(field); + continue; + } + var fieldValue = field["value"] ?? field["get"]; + string typeName = fieldValue?["className"]?.Value(); + JArray fieldMembers = await MemberObjectsExplorer.GetExpandedMemberValues( + sdbHelper, typeName, field["name"]?.Value(), field, state, includeStatic, token); + visibleFields.AddRange(fieldMembers); + } + fields = visibleFields; + } + public async Task ExpandPropertyValues(MonoSDBHelper sdbHelper, bool splitMembersByAccessLevel, bool includeStatic, CancellationToken token) + { using var commandParamsWriter = new MonoBinaryWriter(); commandParamsWriter.Write(TypeId); using MonoBinaryReader getParentsReader = await sdbHelper.SendDebuggerAgentCommand(CmdType.GetParents, commandParamsWriter, token); @@ -239,6 +257,11 @@ public async Task ExpandPropertyValues(MonoSDBHelper sdbHelper, bool splitMember typesToGetProperties.Add(getParentsReader.ReadInt32()); var allMembers = new Dictionary(); + if (!fieldsExpanded) + { + await ExpandedFieldValues(sdbHelper, includeStatic, token); + fieldsExpanded = true; + } foreach (var f in fields) allMembers[f["name"].Value()] = f as JObject; @@ -248,14 +271,15 @@ public async Task ExpandPropertyValues(MonoSDBHelper sdbHelper, bool splitMember var res = await MemberObjectsExplorer.GetNonAutomaticPropertyValues( sdbHelper, typesToGetProperties[i], - ClassName, + className, Buffer, autoExpand, Id, isValueType: true, isOwn: i == 0, token, - allMembers); + allMembers, + includeStatic); foreach (var kvp in res) allMembers[kvp.Key] = kvp.Value; } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs index e269832fdc1740..bfadef670fb2ab 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs @@ -405,6 +405,16 @@ internal void CheckContentValue(JToken token, string value) Assert.Equal(value, val); } + internal void CheckContainsJObject(JToken locals, JToken comparedTo, string name) + { + var val = GetAndAssertObjectWithName(locals, name); + JObject refValue = (JObject)val["value"]; + refValue?.Property("objectId")?.Remove(); + JObject comparedToValue = (JObject)comparedTo["value"]; + comparedToValue?.Property("objectId")?.Remove(); + Assert.Equal(val, comparedTo); + } + internal async Task CheckValueType(JToken locals, string name, string class_name, string description=null) { var l = GetAndAssertObjectWithName(locals, name); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 45c849a9e92b4c..26b63df2daeabc 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -499,22 +499,22 @@ public async Task EvaluateSimpleMethodCallsError() => await CheckInspectLocalsAt "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateMethodTestsClass:EvaluateMethods'); })", wait_for_event_fn: async (pause_location) => { - var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); - var (_, res) = await EvaluateOnCallFrame(id, "this.objToTest.MyMethodWrong()", expect_ok: false ); - Assert.Contains($"Method 'MyMethodWrong' not found", res.Error["message"]?.Value()); + var (_, res) = await EvaluateOnCallFrame(id, "this.objToTest.MyMethodWrong()", expect_ok: false ); + Assert.Contains($"Method 'MyMethodWrong' not found", res.Error["message"]?.Value()); - (_, res) = await EvaluateOnCallFrame(id, "this.objToTest.MyMethod(1)", expect_ok: false); - Assert.Contains("Cannot invoke method 'this.objToTest.MyMethod(1)' - too many arguments passed", res.Error["message"]?.Value()); + (_, res) = await EvaluateOnCallFrame(id, "this.objToTest.MyMethod(1)", expect_ok: false); + Assert.Contains("Cannot invoke method 'this.objToTest.MyMethod(1)' - too many arguments passed", res.Error["message"]?.Value()); - (_, res) = await EvaluateOnCallFrame(id, "this.CallMethodWithParm(\"1\")", expect_ok: false ); - Assert.Contains("Unable to evaluate method 'this.CallMethodWithParm(\"1\")'", res.Error["message"]?.Value()); + (_, res) = await EvaluateOnCallFrame(id, "this.CallMethodWithParm(\"1\")", expect_ok: false ); + Assert.Contains("Unable to evaluate method 'this.CallMethodWithParm(\"1\")'", res.Error["message"]?.Value()); - (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjNull.MyMethod()", expect_ok: false ); - Assert.Contains("Expression 'this.ParmToTestObjNull.MyMethod' evaluated to null", res.Error["message"]?.Value()); + (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjNull.MyMethod()", expect_ok: false ); + Assert.Contains("Expression 'this.ParmToTestObjNull.MyMethod' evaluated to null", res.Error["message"]?.Value()); - (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjException.MyMethod()", expect_ok: false ); - Assert.Contains("Cannot invoke method 'MyMethod'", res.Error["message"]?.Value()); + (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjException.MyMethod()", expect_ok: false ); + Assert.Contains("Cannot invoke method 'MyMethod'", res.Error["message"]?.Value()); }); [Fact] @@ -675,18 +675,18 @@ public async Task EvaluateSimpleMethodCallsCheckChangedValue() => await CheckIns "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateMethodTestsClass:EvaluateMethods'); })", wait_for_event_fn: async (pause_location) => { - var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); - var frame = pause_location["callFrames"][0]; - var props = await GetObjectOnFrame(frame, "this"); - CheckNumber(props, "a", 1); + var frame = pause_location["callFrames"][0]; + var props = await GetObjectOnFrame(frame, "this"); + CheckNumber(props, "a", 1); - await EvaluateOnCallFrameAndCheck(id, - ("this.CallMethodChangeValue()", TObject("object", is_null : true))); + await EvaluateOnCallFrameAndCheck(id, + ("this.CallMethodChangeValue()", TObject("object", is_null : true))); - frame = pause_location["callFrames"][0]; - props = await GetObjectOnFrame(frame, "this"); - CheckNumber(props, "a", 11); + frame = pause_location["callFrames"][0]; + props = await GetObjectOnFrame(frame, "this"); + CheckNumber(props, "a", 11); }); [Fact] @@ -695,16 +695,16 @@ public async Task EvaluateStaticClass() => await CheckInspectLocalsAtBreakpointS "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateMethodTestsClass:EvaluateMethods'); })", wait_for_event_fn: async (pause_location) => { - var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); - var frame = pause_location["callFrames"][0]; + var frame = pause_location["callFrames"][0]; - await EvaluateOnCallFrameAndCheck(id, - ("DebuggerTests.EvaluateStaticClass.StaticField1", TNumber(10))); - await EvaluateOnCallFrameAndCheck(id, - ("DebuggerTests.EvaluateStaticClass.StaticProperty1", TString("StaticProperty1"))); - await EvaluateOnCallFrameAndCheck(id, - ("DebuggerTests.EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented"))); + await EvaluateOnCallFrameAndCheck(id, + ("DebuggerTests.EvaluateStaticClass.StaticField1", TNumber(10))); + await EvaluateOnCallFrameAndCheck(id, + ("DebuggerTests.EvaluateStaticClass.StaticProperty1", TString("StaticProperty1"))); + await EvaluateOnCallFrameAndCheck(id, + ("DebuggerTests.EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented"))); }); [Theory] @@ -716,17 +716,17 @@ public async Task EvaluateStaticClassFromStaticMethod(string type, string method $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] {type}:{method}'); }})", wait_for_event_fn: async (pause_location) => { - var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); - var frame = pause_location["callFrames"][0]; + var frame = pause_location["callFrames"][0]; - await EvaluateOnCallFrameAndCheck(id, - ("EvaluateStaticClass.StaticField1", TNumber(10)), - ("EvaluateStaticClass.StaticProperty1", TString("StaticProperty1")), - ("EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented")), - ("DebuggerTests.EvaluateStaticClass.StaticField1", TNumber(10)), - ("DebuggerTests.EvaluateStaticClass.StaticProperty1", TString("StaticProperty1")), - ("DebuggerTests.EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented"))); + await EvaluateOnCallFrameAndCheck(id, + ("EvaluateStaticClass.StaticField1", TNumber(10)), + ("EvaluateStaticClass.StaticProperty1", TString("StaticProperty1")), + ("EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented")), + ("DebuggerTests.EvaluateStaticClass.StaticField1", TNumber(10)), + ("DebuggerTests.EvaluateStaticClass.StaticProperty1", TString("StaticProperty1")), + ("DebuggerTests.EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented"))); }); [Fact] @@ -735,17 +735,17 @@ public async Task EvaluateNonStaticClassWithStaticFields() => await CheckInspect "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateMethodTestsClass:EvaluateAsyncMethods'); })", wait_for_event_fn: async (pause_location) => { - var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); - var frame = pause_location["callFrames"][0]; + var frame = pause_location["callFrames"][0]; - await EvaluateOnCallFrameAndCheck(id, - ("DebuggerTests.EvaluateNonStaticClassWithStaticFields.StaticField1", TNumber(10)), - ("DebuggerTests.EvaluateNonStaticClassWithStaticFields.StaticProperty1", TString("StaticProperty1")), - ("DebuggerTests.EvaluateNonStaticClassWithStaticFields.StaticPropertyWithError", TString("System.Exception: not implemented")), - ("EvaluateNonStaticClassWithStaticFields.StaticField1", TNumber(10)), - ("EvaluateNonStaticClassWithStaticFields.StaticProperty1", TString("StaticProperty1")), - ("EvaluateNonStaticClassWithStaticFields.StaticPropertyWithError", TString("System.Exception: not implemented"))); + await EvaluateOnCallFrameAndCheck(id, + ("DebuggerTests.EvaluateNonStaticClassWithStaticFields.StaticField1", TNumber(10)), + ("DebuggerTests.EvaluateNonStaticClassWithStaticFields.StaticProperty1", TString("StaticProperty1")), + ("DebuggerTests.EvaluateNonStaticClassWithStaticFields.StaticPropertyWithError", TString("System.Exception: not implemented")), + ("EvaluateNonStaticClassWithStaticFields.StaticField1", TNumber(10)), + ("EvaluateNonStaticClassWithStaticFields.StaticProperty1", TString("StaticProperty1")), + ("EvaluateNonStaticClassWithStaticFields.StaticPropertyWithError", TString("System.Exception: not implemented"))); }); [Fact] @@ -754,17 +754,17 @@ public async Task EvaluateStaticClassesNested() => await CheckInspectLocalsAtBre "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateMethodTestsClass:EvaluateMethods'); })", wait_for_event_fn: async (pause_location) => { - var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); - var frame = pause_location["callFrames"][0]; + var frame = pause_location["callFrames"][0]; - await EvaluateOnCallFrameAndCheck(id, - ("DebuggerTests.EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticField1", TNumber(3)), - ("DebuggerTests.EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticProperty1", TString("StaticProperty3")), - ("DebuggerTests.EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticPropertyWithError", TString("System.Exception: not implemented 3")), - ("EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticField1", TNumber(3)), - ("EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticProperty1", TString("StaticProperty3")), - ("EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticPropertyWithError", TString("System.Exception: not implemented 3"))); + await EvaluateOnCallFrameAndCheck(id, + ("DebuggerTests.EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticField1", TNumber(3)), + ("DebuggerTests.EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticProperty1", TString("StaticProperty3")), + ("DebuggerTests.EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticPropertyWithError", TString("System.Exception: not implemented 3")), + ("EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticField1", TNumber(3)), + ("EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticProperty1", TString("StaticProperty3")), + ("EvaluateStaticClass.NestedClass1.NestedClass2.NestedClass3.StaticPropertyWithError", TString("System.Exception: not implemented 3"))); }); [Fact] @@ -773,14 +773,14 @@ public async Task EvaluateStaticClassesNestedWithNoNamespace() => await CheckIns "window.setTimeout(function() { invoke_static_method ('[debugger-test] NoNamespaceClass:EvaluateMethods'); })", wait_for_event_fn: async (pause_location) => { - var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); - var frame = pause_location["callFrames"][0]; + var frame = pause_location["callFrames"][0]; - await EvaluateOnCallFrameAndCheck(id, - ("NoNamespaceClass.NestedClass1.NestedClass2.NestedClass3.StaticField1", TNumber(30)), - ("NoNamespaceClass.NestedClass1.NestedClass2.NestedClass3.StaticProperty1", TString("StaticProperty30")), - ("NoNamespaceClass.NestedClass1.NestedClass2.NestedClass3.StaticPropertyWithError", TString("System.Exception: not implemented 30"))); + await EvaluateOnCallFrameAndCheck(id, + ("NoNamespaceClass.NestedClass1.NestedClass2.NestedClass3.StaticField1", TNumber(30)), + ("NoNamespaceClass.NestedClass1.NestedClass2.NestedClass3.StaticProperty1", TString("StaticProperty30")), + ("NoNamespaceClass.NestedClass1.NestedClass2.NestedClass3.StaticPropertyWithError", TString("System.Exception: not implemented 30"))); }); [Fact] @@ -803,7 +803,7 @@ await EvaluateOnCallFrameAndCheck(id_second, ("EvaluateStaticClass.StaticField1", TNumber(10)), ("EvaluateStaticClass.StaticProperty1", TString("StaticProperty1")), ("EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented"))); - }); + }); [Fact] public async Task EvaluateStaticClassInvalidField() => await CheckInspectLocalsAtBreakpointSite( @@ -811,36 +811,36 @@ public async Task EvaluateStaticClassInvalidField() => await CheckInspectLocalsA "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateMethodTestsClass:EvaluateMethods'); })", wait_for_event_fn: async (pause_location) => { - var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); - var frame = pause_location["callFrames"][0]; + var frame = pause_location["callFrames"][0]; - var (_, res) = await EvaluateOnCallFrame(id, "DebuggerTests.EvaluateStaticClass.StaticProperty2", expect_ok: false); - AssertEqual("Failed to resolve member access for DebuggerTests.EvaluateStaticClass.StaticProperty2", res.Error["result"]?["description"]?.Value(), "wrong error message"); + var (_, res) = await EvaluateOnCallFrame(id, "DebuggerTests.EvaluateStaticClass.StaticProperty2", expect_ok: false); + AssertEqual("Failed to resolve member access for DebuggerTests.EvaluateStaticClass.StaticProperty2", res.Error["result"]?["description"]?.Value(), "wrong error message"); - (_, res) = await EvaluateOnCallFrame(id, "DebuggerTests.InvalidEvaluateStaticClass.StaticProperty2", expect_ok: false); - AssertEqual("Failed to resolve member access for DebuggerTests.InvalidEvaluateStaticClass.StaticProperty2", res.Error["result"]?["description"]?.Value(), "wrong error message"); + (_, res) = await EvaluateOnCallFrame(id, "DebuggerTests.InvalidEvaluateStaticClass.StaticProperty2", expect_ok: false); + AssertEqual("Failed to resolve member access for DebuggerTests.InvalidEvaluateStaticClass.StaticProperty2", res.Error["result"]?["description"]?.Value(), "wrong error message"); }); - [Fact] - public async Task AsyncLocalsInContinueWithBlock() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.AsyncTests.ContinueWithTests", "ContinueWithStaticAsync", 4, "b__3_0", - "window.setTimeout(function() { invoke_static_method('[debugger-test] DebuggerTests.AsyncTests.ContinueWithTests:RunAsync'); })", - wait_for_event_fn: async (pause_location) => - { - var frame_locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); - var id = pause_location["callFrames"][0]["callFrameId"].Value(); + [Fact] + public async Task AsyncLocalsInContinueWithBlock() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.AsyncTests.ContinueWithTests", "ContinueWithStaticAsync", 4, "b__3_0", + "window.setTimeout(function() { invoke_static_method('[debugger-test] DebuggerTests.AsyncTests.ContinueWithTests:RunAsync'); })", + wait_for_event_fn: async (pause_location) => + { + var frame_locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); - await EvaluateOnCallFrameAndCheck(id, - ($"t.Status", TEnum("System.Threading.Tasks.TaskStatus", "RanToCompletion")), - ($" t.Status", TEnum("System.Threading.Tasks.TaskStatus", "RanToCompletion")) - ); - - await EvaluateOnCallFrameFail(id, - ("str", "ReferenceError"), - (" str", "ReferenceError") - ); - }); + await EvaluateOnCallFrameAndCheck(id, + ($"t.Status", TEnum("System.Threading.Tasks.TaskStatus", "RanToCompletion")), + ($" t.Status", TEnum("System.Threading.Tasks.TaskStatus", "RanToCompletion")) + ); + + await EvaluateOnCallFrameFail(id, + ("str", "ReferenceError"), + (" str", "ReferenceError") + ); + }); [Fact] public async Task EvaluateConstantValueUsingRuntimeEvaluate() => await CheckInspectLocalsAtBreakpointSite( @@ -857,10 +857,13 @@ await RuntimeEvaluateAndCheck( }); [Theory] - [InlineData("EvaluateBrowsableProperties", "TestEvaluateFieldsNone", "testFieldsNone", 10)] - [InlineData("EvaluateBrowsableProperties", "TestEvaluatePropertiesNone", "testPropertiesNone", 10)] - [InlineData("EvaluateBrowsableCustomProperties", "TestEvaluatePropertiesNone", "testPropertiesNone", 5, true)] - public async Task EvaluateBrowsableNone(string outerClassName, string className, string localVarName, int breakLine, bool isCustomGetter = false) => await CheckInspectLocalsAtBreakpointSite( + [InlineData("EvaluateBrowsableClass", "TestEvaluateFieldsNone", "testFieldsNone", 10)] + [InlineData("EvaluateBrowsableClass", "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) => @@ -893,14 +896,14 @@ public async Task EvaluateBrowsableNone(string outerClassName, string className, 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)] + [InlineData("EvaluateBrowsableClass", "TestEvaluateFieldsNever", "testFieldsNever", 10)] + [InlineData("EvaluateBrowsableClass", "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 }})", @@ -914,15 +917,16 @@ public async Task EvaluateBrowsableNever(string outerClassName, string className await CheckProps(testNeverProps, new { }, "testNeverProps#1"); - }); + }); [Theory] - [InlineData("EvaluateBrowsableProperties", "TestEvaluateFieldsCollapsed", "testFieldsCollapsed", 10)] - [InlineData("EvaluateBrowsableProperties", "TestEvaluatePropertiesCollapsed", "testPropertiesCollapsed", 10)] - [InlineData("EvaluateBrowsableStaticProperties", "TestEvaluateFieldsCollapsed", "testFieldsCollapsed", 10)] - [InlineData("EvaluateBrowsableStaticProperties", "TestEvaluatePropertiesCollapsed", "testPropertiesCollapsed", 10)] - [InlineData("EvaluateBrowsableCustomProperties", "TestEvaluatePropertiesCollapsed", "testPropertiesCollapsed", 5, true)] - public async Task EvaluateBrowsableCollapsed(string outerClassName, string className, string localVarName, int breakLine, bool isCustomGetter = false) => await CheckInspectLocalsAtBreakpointSite( + [InlineData("EvaluateBrowsableClass", "TestEvaluateFieldsCollapsed", "testFieldsCollapsed", 10)] + [InlineData("EvaluateBrowsableClass", "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) => @@ -954,15 +958,16 @@ public async Task EvaluateBrowsableCollapsed(string outerClassName, string class 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( + [InlineData("EvaluateBrowsableClass", "TestEvaluateFieldsRootHidden", "testFieldsRootHidden", 10)] + [InlineData("EvaluateBrowsableClass", "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) => @@ -975,7 +980,10 @@ public async Task EvaluateBrowsableRootHidden(string outerClassName, string clas var (refList, _) = await EvaluateOnCallFrame(id, "testPropertiesNone.list"); var refListProp = await GetProperties(refList["objectId"]?.Value()); - var refListElementsProp = await GetProperties(refListProp[0]["value"]["objectId"]?.Value()); + var list = refListProp + .Where(v => v["name"]?.Value() == "Items" || v["name"]?.Value() == "_items") + .FirstOrDefault(); + var refListElementsProp = await GetProperties(list["value"]["objectId"]?.Value()); var (refArray, _) = await EvaluateOnCallFrame(id, "testPropertiesNone.array"); var refArrayProp = await GetProperties(refArray["objectId"]?.Value()); @@ -986,34 +994,34 @@ public async Task EvaluateBrowsableRootHidden(string outerClassName, string clas var (refClass, _) = await EvaluateOnCallFrame(id, "testPropertiesNone.sampleClass"); var refClassProp = await GetProperties(refClass["objectId"]?.Value()); - JArray mergedRefItems = new(); + 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 refArrayProp) - { - item["name"] = string.Concat("arrayRootHidden[", item["name"], "]"); - mergedRefItems.Add(item); - } foreach (var item in refListElementsProp) { item["name"] = string.Concat("listRootHidden[", item["name"], "]"); - mergedRefItems.Add(item); + CheckContainsJObject(testRootHiddenProps, item, item["name"].Value()); + } + foreach (var item in refArrayProp) + { + item["name"] = string.Concat("arrayRootHidden[", item["name"], "]"); + CheckContainsJObject(testRootHiddenProps, item, item["name"].Value()); } // valuetype/class members unique names are created by concatenation with a dot foreach (var item in refStructProp) { item["name"] = string.Concat("sampleStructRootHidden.", item["name"]); - mergedRefItems.Add(item); + CheckContainsJObject(testRootHiddenProps, item, item["name"].Value()); } foreach (var item in refClassProp) { item["name"] = string.Concat("sampleClassRootHidden.", item["name"]); - mergedRefItems.Add(item); + CheckContainsJObject(testRootHiddenProps, item, item["name"].Value()); } - Assert.Equal(mergedRefItems, testRootHiddenProps); - }); + }); [Fact] public async Task EvaluateStaticAttributeInAssemblyNotRelatedButLoaded() => await CheckInspectLocalsAtBreakpointSite( @@ -1106,7 +1114,7 @@ await EvaluateOnCallFrameAndCheck(id, var (_, res) = await EvaluateOnCallFrame(id, "test.GetDefaultAndRequiredParamMixedTypes(\"a\", 23, true, 1.23f)", expect_ok: false); Assert.Contains("method 'test.GetDefaultAndRequiredParamMixedTypes(\"a\", 23, true, 1.23f)' - too many arguments passed", res.Error["message"]?.Value()); - }); + }); [Fact] public async Task EvaluateMethodWithLinq() => await CheckInspectLocalsAtBreakpointSite( @@ -1118,7 +1126,7 @@ public async Task EvaluateMethodWithLinq() => await CheckInspectLocalsAtBreakpoi await EvaluateOnCallFrameAndCheck(id, ("test.listToLinq.ToList()", TObject("System.Collections.Generic.List", description: "Count = 11")) ); - }); + }); } } diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs index 131020fee5e0c3..7316195c449393 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs @@ -530,7 +530,7 @@ public class SampleClass public List Items = new List { "should not be expanded" }; } - public static class EvaluateBrowsableProperties + public static class EvaluateBrowsableClass { public class TestEvaluateFieldsNone { @@ -756,7 +756,7 @@ public static void Evaluate() } } - public static class EvaluateBrowsableStaticProperties + public static class EvaluateBrowsableStaticClass { public class TestEvaluateFieldsNone { @@ -983,7 +983,7 @@ public static void Evaluate() } } - public static class EvaluateBrowsableCustomProperties + public static class EvaluateBrowsableCustomPropertiesClass { public class TestEvaluatePropertiesNone { From 0106bf211579c2b2ff12c46d66d8945a9b298d78 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Mon, 25 Apr 2022 12:42:20 +0200 Subject: [PATCH 14/18] Cleanup, removing unused code. --- .../MemberObjectsExplorer.cs | 10 ++-- .../MemberReferenceResolver.cs | 1 - .../BrowserDebugProxy/MonoSDBHelper.cs | 53 ++----------------- .../BrowserDebugProxy/ValueTypeClass.cs | 34 +++++------- .../DebuggerTestSuite/GetPropertiesTests.cs | 6 +-- .../debugger-test/debugger-evaluate-test.cs | 2 +- 6 files changed, 26 insertions(+), 80 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs index c63d5ade521ea5..774d60bfb2d171 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs @@ -50,7 +50,7 @@ private static async Task ReadFieldValue(MonoSDBHelper sdbHelper, MonoB if (!typeFieldsBrowsableInfo.TryGetValue(field.Name, out DebuggerBrowsableState? state)) { - // for backing fields, we should get it from the properties + // for backing fields, we are getting it from the properties typePropertiesBrowsableInfo.TryGetValue(field.Name, out state); } fieldValue["__state"] = state?.ToString(); @@ -291,7 +291,7 @@ JArray GetHiddenElement() } } - public static async Task> GetNonAutomaticPropertyValues( + public static async Task> GetNonAutomaticPropertyValues( MonoSDBHelper sdbHelper, int typeId, string containerTypeName, @@ -301,7 +301,7 @@ public static async Task> GetNonAutomaticPropertyVa bool isValueType, bool isOwn, CancellationToken token, - IDictionary allMembers, + Dictionary allMembers, bool includeStatic = false) { using var retDebuggerCmdReader = await sdbHelper.GetTypePropertiesReader(typeId, token); @@ -335,7 +335,6 @@ public static async Task> GetNonAutomaticPropertyVa { if (backingField["__isBackingField"]?.Value() == true) { - // FIXME: this should be checked only for this type - add a typeID to it? // Update backingField's access with the one from the property getter backingField["__section"] = getterAttrs switch { @@ -347,7 +346,6 @@ public static async Task> GetNonAutomaticPropertyVa if (state is not null) { - // FIXME: primitive types? string namePrefix = GetNamePrefixForValues(propName, containerTypeName, isOwn, state); string backingFieldTypeName = backingField["value"]?["className"]?.Value(); @@ -497,7 +495,7 @@ public static async Task GetObjectMemberValues( if (!getCommandType.HasFlag(GetObjectCommandOptions.WithProperties)) return GetMembersResult.FromValues(allMembers.Values, sortByAccessLevel); - allMembers = (Dictionary)await GetNonAutomaticPropertyValues( + allMembers = await GetNonAutomaticPropertyValues( sdbHelper, typeId, typeName, diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs index 3cb785b2ff11ac..c8c77a6cabd368 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs @@ -357,7 +357,6 @@ public async Task Resolve(ElementAccessExpressionSyntax elementAccess, case "object": 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.Value, methodId, elementAccess.Expression.ToString(), token); var toArrayRetMethod = await context.SdbAgent.InvokeMethod(objectId.Value, methodId, isValueType: false, token); rootObject = await GetValueFromObject(toArrayRetMethod, token); DotnetObjectId.TryParse(rootObject?["objectId"]?.Value(), out DotnetObjectId arrayObjectId); diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index c090a5eb3abe1b..37e2403f7b2d70 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -1401,20 +1401,17 @@ public async Task GetValueFromDebuggerDisplayAttribute(DotnetObjectId do 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); - // logger.LogDebug($"** GetValueFromDebuggerDisplayAttribute calling GetObjectMemberValues"); GetMembersResult members = await GetTypeMemberValues( dotnetObjectId, GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.ForDebuggerDisplayAttribute, token); - // FIXME: flatten, it's flattened already - // JArray objectValues = new JArray(members.JObject); JArray objectValues = new JArray(members.Flatten()); var thisObj = CreateJObject(value: "", type: "object", description: "", writable: false, objectId: dotnetObjectId.ToString()); @@ -1648,8 +1645,6 @@ public Task InvokeMethod(DotnetObjectId dotnetObjectId, CancellationTok : throw new ArgumentException($"Cannot invoke method with id {methodId} on {dotnetObjectId}", nameof(dotnetObjectId)); } - // FIXME: removable? - public async Task GetPropertyMethodIdByName(int typeId, string propertyName, CancellationToken token) { using var retDebuggerCmdReader = await GetTypePropertiesReader(typeId, token); @@ -1832,16 +1827,15 @@ public async Task CreateJObjectForObject(MonoBinaryReader retDebuggerCm "ushort", "float", "double", - "object", // object in primitive? }; public static bool IsPrimitiveType(string simplifiedClassName) => s_primitiveTypeNames.Contains(simplifiedClassName); - public async Task CreateJObjectForValueType(MonoBinaryReader retDebuggerCmdReader, string name, long initialPos, bool forDebuggerDisplayAttribute, CancellationToken token) + public async Task CreateJObjectForValueType( + MonoBinaryReader retDebuggerCmdReader, string name, long initialPos, bool forDebuggerDisplayAttribute, CancellationToken token) { // FIXME: debugger proxy - //JObject fieldValueType = null; var isEnum = retDebuggerCmdReader.ReadByte() == 1; var isBoxed = retDebuggerCmdReader.ReadByte() == 1; var typeId = retDebuggerCmdReader.ReadInt32(); @@ -1879,8 +1873,6 @@ public async Task CreateJObjectForValueType(MonoBinaryReader retDebugge isEnum, token); valueTypes[valueType.Id.Value] = valueType; - - // FIXME: check if this gets called more than once.. maybe with different values of forDebugg.. ? return await valueType.ToJObject(this, forDebuggerDisplayAttribute, token); } @@ -2114,7 +2106,6 @@ public async Task GetHoistedLocalVariables(int objectId, IEnumerable StackFrameGetValues(MethodInfoWithDebugInformation met using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdFrame.GetThis, commandParamsWriter, token); retDebuggerCmdReader.ReadByte(); //ignore type var objectId = retDebuggerCmdReader.ReadInt32(); - logger.LogDebug($"** StackFrameGetValues calling GetObjectMemberValues"); - // infinite loop again? GetMembersResult asyncProxyMembers = await MemberObjectsExplorer.GetObjectMemberValues(this, objectId, GetObjectCommandOptions.WithProperties, token); - //Console.WriteLine($"asyncProxyMembers: {asyncProxyMembers}"); var asyncLocals = await GetHoistedLocalVariables(objectId, asyncProxyMembers.Flatten(), token); - //Console.WriteLine($"** StackFrameGetValues returning {asyncLocals}"); return asyncLocals; } @@ -2395,11 +2382,8 @@ public async Task GetMethodProxy(JObject objectId, CancellationToken to public async Task GetObjectProxy(int objectId, CancellationToken token) { - //Console.WriteLine($"-- GetObjectProxy"); GetMembersResult members = await MemberObjectsExplorer.GetObjectMemberValues(this, objectId, GetObjectCommandOptions.WithSetter, token); - // FIXME: will this mean that the result object won't get gc'ed? - //Console.WriteLine($"-- GetObjectProxy members: {members}"); - JArray ret = members.Flatten();// originally: members.Flatten().Result; // why? + JArray ret = members.Flatten(); var typeIds = await GetTypeIdsForObject(objectId, true, token); foreach (var typeId in typeIds) { @@ -2533,35 +2517,6 @@ public static void AddRange(this JArray arr, JArray addedArr) arr.Add(item); } - public static void AddRange(this JArray arr, IEnumerable addedArr) - { - foreach (var item in addedArr) - arr.Add(item); - } - - public static void TryAddRange(this Dictionary dict, JArray addedArr) - { - foreach (var item in addedArr) - { - var key = item["name"]?.Value(); - if (key == null) - continue; - dict.TryAdd(key, item); - } - } - - public static JArray GetValuesByKeys(this Dictionary dict, List keys) - { - var result = new JArray(); - foreach (var name in keys) - { - if (!dict.TryGetValue(name, out var obj)) - continue; - result.Add(obj); - } - return result; - } - public static bool IsNullValuedObject(this JObject obj) => obj != null && obj["type"]?.Value() == "object" && obj["subtype"]?.Value() == "null"; diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs index c48acec006cc71..bda0111fa7dea2 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs @@ -60,12 +60,11 @@ public static async Task CreateFromReader( var typePropertiesBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableProperties; IReadOnlyList fieldTypes = await sdbAgent.GetTypeFields(typeId, token); - IEnumerable writableFields = fieldTypes.Where(f => !f.Attributes.HasFlag(FieldAttributes.Literal) && !f.Attributes.HasFlag(FieldAttributes.Static)); + // statics should not be in valueType fields: CallFunctionOnTests.PropertyGettersTest + IEnumerable writableFields = fieldTypes + .Where(f => !f.Attributes.HasFlag(FieldAttributes.Literal) + && !f.Attributes.HasFlag(FieldAttributes.Static)); - // FIXME: save the field values buffer, and expand on demand - int numWritableFields = writableFields.Count(); - - // FIXME: add the static oens too? and tests for that! EvaluateOnCallFrame has some? JArray fields = new(); foreach (var field in writableFields) { @@ -249,28 +248,26 @@ public async Task ExpandPropertyValues(MonoSDBHelper sdbHelper, bool splitMember using MonoBinaryReader getParentsReader = await sdbHelper.SendDebuggerAgentCommand(CmdType.GetParents, commandParamsWriter, token); int numParents = getParentsReader.ReadInt32(); - List typesToGetProperties = new(); - typesToGetProperties.Add(TypeId); - - // FIXME: this list can be removed.. but also need to process for object's own typeId first - for (int i = 0; i < numParents; i++) - typesToGetProperties.Add(getParentsReader.ReadInt32()); - - var allMembers = new Dictionary(); if (!fieldsExpanded) { await ExpandedFieldValues(sdbHelper, includeStatic, token); fieldsExpanded = true; } + + var allMembers = new Dictionary(); foreach (var f in fields) allMembers[f["name"].Value()] = f as JObject; - for (int i = 0; i < typesToGetProperties.Count; i++) + int typeId = TypeId; + var parentsCntPlusSelf = numParents + 1; + for (int i = 0; i < parentsCntPlusSelf; i++) { - //FIXME: change GetNonAutomaticPropertyValues to return a jobject instead - var res = await MemberObjectsExplorer.GetNonAutomaticPropertyValues( + // isParent: + if (i != 0) typeId = getParentsReader.ReadInt32(); + + allMembers = await MemberObjectsExplorer.GetNonAutomaticPropertyValues( sdbHelper, - typesToGetProperties[i], + typeId, className, Buffer, autoExpand, @@ -280,10 +277,7 @@ public async Task ExpandPropertyValues(MonoSDBHelper sdbHelper, bool splitMember token, allMembers, includeStatic); - foreach (var kvp in res) - allMembers[kvp.Key] = kvp.Value; } - _combinedResult = GetMembersResult.FromValues(allMembers.Values, splitMembersByAccessLevel); } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs index 4f838001ffcd24..e37d8a4f25a8a9 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs @@ -422,9 +422,9 @@ public static TheoryData, Dictionary Date: Tue, 26 Apr 2022 11:35:21 +0200 Subject: [PATCH 15/18] Added Browsable tests for nonstatic structures. --- .../EvaluateOnCallFrameTests.cs | 8 + .../debugger-test/debugger-evaluate-test.cs | 233 ++++++++++++++++++ 2 files changed, 241 insertions(+) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 26b63df2daeabc..934e3086454f5e 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -859,6 +859,8 @@ await RuntimeEvaluateAndCheck( [Theory] [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)] @@ -901,6 +903,8 @@ public async Task EvaluateBrowsableNone( [Theory] [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)] @@ -922,6 +926,8 @@ public async Task EvaluateBrowsableNever(string outerClassName, string className [Theory] [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)] @@ -963,6 +969,8 @@ public async Task EvaluateBrowsableCollapsed( [Theory] [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)] diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs index e98f47c5f85ee3..ef663949f718c2 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs @@ -756,6 +756,239 @@ public static void Evaluate() } } + public static class EvaluateBrowsableStruct + { + public struct TestEvaluateFieldsNone + { + public TestEvaluateFieldsNone() {} + public List list = new List() { 1, 2 }; + public int[] array = new int[] { 11, 22 }; + public string text = "text"; + public bool[] nullNone = null; + public SampleEnum valueTypeEnum = new(); + public SampleStructure sampleStruct = new(); + public SampleClass sampleClass = new(); + } + + public struct TestEvaluatePropertiesNone + { + public List list { get; set; } + public int[] array { get; set; } + public string text { get; set; } + public bool[] nullNone { get; set; } + public SampleEnum valueTypeEnum { get; set; } + public SampleStructure sampleStruct { get; set; } + public SampleClass sampleClass { get; set; } + + public TestEvaluatePropertiesNone() + { + list = new List() { 1, 2 }; + array = new int[] { 11, 22 }; + text = "text"; + nullNone = null; + valueTypeEnum = new(); + sampleStruct = new(); + sampleClass = new(); + } + } + + public struct TestEvaluateFieldsNever + { + public TestEvaluateFieldsNever() {} + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public List listNever = new List() { 1, 2 }; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public int[] arrayNever = new int[] { 11, 22 }; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public string textNever = "textNever"; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public bool[] nullNever = null; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleEnum valueTypeEnumNever = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleStructure sampleStructNever = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleClass sampleClassNever = new(); + } + + public struct TestEvaluatePropertiesNever + { + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public List listNever { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public int[] arrayNever { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public string textNever { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public bool[] nullNever { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleEnum valueTypeEnumNever { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleStructure sampleStructNever { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + public SampleClass sampleClassNever { get; set; } + + public TestEvaluatePropertiesNever() + { + listNever = new List() { 1, 2 }; + arrayNever = new int[] { 11, 22 }; + textNever = "textNever"; + nullNever = null; + valueTypeEnumNever = new(); + sampleStructNever = new(); + sampleClassNever = new(); + } + } + + public struct TestEvaluateFieldsCollapsed + { + public TestEvaluateFieldsCollapsed() {} + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public List listCollapsed = new List() { 1, 2 }; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public int[] arrayCollapsed = new int[] { 11, 22 }; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public string textCollapsed = "textCollapsed"; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public bool[] nullCollapsed = null; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleEnum valueTypeEnumCollapsed = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleStructure sampleStructCollapsed = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleClass sampleClassCollapsed = new(); + } + + public struct TestEvaluatePropertiesCollapsed + { + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public List listCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public int[] arrayCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public string textCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public bool[] nullCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleEnum valueTypeEnumCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleStructure sampleStructCollapsed { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Collapsed)] + public SampleClass sampleClassCollapsed { get; set; } + + public TestEvaluatePropertiesCollapsed() + { + listCollapsed = new List() { 1, 2 }; + arrayCollapsed = new int[] { 11, 22 }; + textCollapsed = "textCollapsed"; + nullCollapsed = null; + valueTypeEnumCollapsed = new(); + sampleStructCollapsed = new(); + sampleClassCollapsed = new(); + } + } + + public struct TestEvaluateFieldsRootHidden + { + public TestEvaluateFieldsRootHidden() {} + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public List listRootHidden = new List() { 1, 2 }; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public int[] arrayRootHidden = new int[] { 11, 22 }; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public string textRootHidden = "textRootHidden"; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public bool[] nullRootHidden = null; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleEnum valueTypeEnumRootHidden = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleStructure sampleStructRootHidden = new(); + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleClass sampleClassRootHidden = new(); + } + + public struct TestEvaluatePropertiesRootHidden + { + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public List listRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public int[] arrayRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public string textRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public bool[] nullRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleEnum valueTypeEnumRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleStructure sampleStructRootHidden { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)] + public SampleClass sampleClassRootHidden { get; set; } + + public TestEvaluatePropertiesRootHidden() + { + listRootHidden = new List() { 1, 2 }; + arrayRootHidden = new int[] { 11, 22 }; + textRootHidden = "textRootHidden"; + nullRootHidden = null; + valueTypeEnumRootHidden = new(); + sampleStructRootHidden = new(); + sampleClassRootHidden = new(); + } + } + + 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 EvaluateBrowsableStaticClass { public class TestEvaluateFieldsNone From 66606abc4e9d94a73dc2950d22702a21fff0f913 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 4 May 2022 17:53:10 +0200 Subject: [PATCH 16/18] Removed unnecessary comment. --- .../DebuggerTestSuite/EvaluateOnCallFrameTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 0640f792a17c60..fa950862ddcee9 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -861,12 +861,12 @@ await RuntimeEvaluateAndCheck( }); [ConditionalTheory(nameof(RunningOnChrome))] - // [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("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( From 6e98403a8d1ccac910a9d6cc535f3f240304c5cc Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 4 May 2022 18:00:13 +0200 Subject: [PATCH 17/18] Removed whitespaces. --- .../debugger/DebuggerTestSuite/DebuggerTestBase.cs | 2 +- .../DebuggerTestSuite/EvaluateOnCallFrameTests.cs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs index 1040e84ead2f91..8bf434755105bf 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs @@ -859,7 +859,7 @@ internal async Task GetObjectOnLocals(JToken locals, string name) return await GetProperties(objectId); } - + /* @fn_args is for use with `Runtime.callFunctionOn` only */ internal virtual async Task GetProperties(string id, JToken fn_args = null, bool? own_properties = null, bool? accessors_only = null, bool expect_ok = true) { diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index fa950862ddcee9..30a24d4c1c8ceb 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -828,12 +828,12 @@ public async Task EvaluateStaticClassInvalidField() => await CheckInspectLocalsA [ConditionalFact(nameof(RunningOnChrome))] public async Task AsyncLocalsInContinueWithBlock() => await CheckInspectLocalsAtBreakpointSite( - "DebuggerTests.AsyncTests.ContinueWithTests", "ContinueWithStaticAsync", 4, "b__3_0", - "window.setTimeout(function() { invoke_static_method('[debugger-test] DebuggerTests.AsyncTests.ContinueWithTests:RunAsync'); })", - wait_for_event_fn: async (pause_location) => - { - var frame_locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); - var id = pause_location["callFrames"][0]["callFrameId"].Value(); + "DebuggerTests.AsyncTests.ContinueWithTests", "ContinueWithStaticAsync", 4, "b__3_0", + "window.setTimeout(function() { invoke_static_method('[debugger-test] DebuggerTests.AsyncTests.ContinueWithTests:RunAsync'); })", + wait_for_event_fn: async (pause_location) => + { + var frame_locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); await EvaluateOnCallFrameAndCheck(id, ($"t.Status", TEnum("System.Threading.Tasks.TaskStatus", "RanToCompletion")), From 5d1a4e456fd15286acf602cf691e945372ec10cc Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 5 May 2022 08:03:54 +0200 Subject: [PATCH 18/18] Blocked tests failing/timeouting on Firefox. --- .../wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs | 2 +- src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 30a24d4c1c8ceb..1b693c113443b8 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -752,7 +752,7 @@ await EvaluateOnCallFrameAndCheck(id, ("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'); })", diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs index 590ee41fa53e96..717ef7b9e924e5 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs @@ -475,7 +475,7 @@ public static TheoryData, Dictionary expectedPublic, Dictionary expectedProtInter, Dictionary expectedPriv, string entryMethod) =>