Skip to content
Prev Previous commit
Next Next commit
Applied a variation of @radical's idea.
  • Loading branch information
ilonatommy committed Oct 12, 2022
commit c49e7eaadf5f2e1c0c9475888a1ec1c8107efe99
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,13 @@

using System;
using Newtonsoft.Json.Linq;
using BrowserDebugProxy;
using System.Collections.Generic;
using System.Linq;

namespace Microsoft.WebAssembly.Diagnostics;

internal static class HelperExtensions
{
private const int MaxLogMessageLineLength = 65536;
private static readonly bool TruncateLogMessages = string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WASM_DONT_TRUNCATE_LOG_MESSAGES"));
private static Dictionary<ProxyInternalUseProperty, string> proxyInternalUsePropNames = new Dictionary<ProxyInternalUseProperty, string>() {
{ ProxyInternalUseProperty.Hidden, "__hidden" },
{ ProxyInternalUseProperty.State, "__state" },
{ ProxyInternalUseProperty.Section, "__section" },
{ ProxyInternalUseProperty.Owner, "__owner" },
{ ProxyInternalUseProperty.IsStatic, "__isStatic" },
{ ProxyInternalUseProperty.IsNewSlot, "__isNewSlot" },
{ ProxyInternalUseProperty.IsBackingField, "__isBackingField" },
{ ProxyInternalUseProperty.ParentTypeId, "__parentTypeId" }
};
private static Dictionary<string, ProxyInternalUseProperty> proxyInternalUsePropNamesInverse = proxyInternalUsePropNames.ToDictionary((i) => i.Value, (i) => i.Key);

public static string Truncate(this string message, int maxLen, string suffix = "")

Expand All @@ -43,9 +29,6 @@ public static void AddRange(this JArray arr, JArray addedArr)
arr.Add(item);
}

public static string ToUnderscoredString(this ProxyInternalUseProperty key) => proxyInternalUsePropNames[key];
public static bool TryConvertToProxyInternalUseProperty(this string key) => proxyInternalUsePropNamesInverse.TryGetValue(key, out _);

public static bool IsNullValuedObject(this JObject obj)
=> obj != null && obj["type"]?.Value<string>() == "object" && obj["subtype"]?.Value<string>() == "null";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

using System.Collections.Generic;

namespace Microsoft.WebAssembly.Diagnostics;

internal sealed class InternalUseFieldName
{
public static string Hidden => "__hidden__";
public static string State => "__state__";
public static string Section => "__section__";
public static string Owner => "__owner__";
public static string IsStatic => "__isStatic__";
public static string IsNewSlot => "__isNewSlot__";
public static string IsBackingField => "__isBackingField__";
public static string ParentTypeId => "__parentTypeId__";

private static readonly HashSet<string> s_names = new()
{
Hidden,
State,
Section,
Owner,
IsStatic,
IsNewSlot,
IsBackingField,
ParentTypeId
};

public static int Count => s_names.Count;
public static bool IsKnown(string name) => !string.IsNullOrEmpty(name) && s_names.Contains(name);
}
88 changes: 42 additions & 46 deletions src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,17 @@ private static async Task<JObject> ReadFieldValue(
// for backing fields, we are getting it from the properties
typePropertiesBrowsableInfo.TryGetValue(field.Name, out state);
}
fieldValue[ProxyInternalUseProperty.State.ToUnderscoredString()] = state?.ToString();
fieldValue[ProxyInternalUseProperty.Section.ToUnderscoredString()] = field.Attributes.HasFlag(FieldAttributes.Private)
fieldValue[InternalUseFieldName.State] = state?.ToString();
fieldValue[InternalUseFieldName.Section] = field.Attributes.HasFlag(FieldAttributes.Private)
? "private" : "result";

if (field.IsBackingField)
{
fieldValue[ProxyInternalUseProperty.IsBackingField.ToUnderscoredString()] = true;
fieldValue[ProxyInternalUseProperty.ParentTypeId.ToUnderscoredString()] = parentTypeId;
fieldValue[InternalUseFieldName.IsBackingField] = true;
fieldValue[InternalUseFieldName.ParentTypeId] = parentTypeId;
}
if (field.Attributes.HasFlag(FieldAttributes.Static))
fieldValue[ProxyInternalUseProperty.IsStatic.ToUnderscoredString()] = true;
fieldValue[InternalUseFieldName.IsStatic] = true;

if (getObjectOptions.HasFlag(GetObjectCommandOptions.WithSetter))
{
Expand Down Expand Up @@ -241,7 +241,7 @@ public static async Task<JArray> ExpandFieldValues(
if (typeInfo.Info.IsNonUserCode && getCommandOptions.HasFlag(GetObjectCommandOptions.JustMyCode) && field.Attributes.HasFlag(FieldAttributes.Private))
continue;

if (!Enum.TryParse(fieldValue["__state"].Value<string>(), out DebuggerBrowsableState fieldState)
if (!Enum.TryParse(fieldValue[InternalUseFieldName.State].Value<string>(), out DebuggerBrowsableState fieldState)
|| fieldState == DebuggerBrowsableState.Collapsed)
{
fieldValues.Add(fieldValue);
Expand Down Expand Up @@ -303,11 +303,9 @@ public static async Task<JArray> GetExpandedMemberValues(

JArray GetHiddenElement()
{
return new JArray(JObject.FromObject(new
{
name = namePrefix,
__hidden = true
}));
var emptyHidden = JObject.FromObject(new { name = namePrefix });
emptyHidden.Add(InternalUseFieldName.Hidden, true);
return new JArray(emptyHidden);
}
}

Expand Down Expand Up @@ -368,14 +366,14 @@ public static async Task<Dictionary<string, JObject>> ExpandPropertyValues(
continue;
}

bool isExistingMemberABackingField = existingMember[ProxyInternalUseProperty.IsBackingField.ToUnderscoredString()]?.Value<bool>() == true;
bool isExistingMemberABackingField = existingMember[InternalUseFieldName.IsBackingField]?.Value<bool>() == true;
if (isOwn && !isExistingMemberABackingField)
{
// repeated propname on the same type! cannot happen
throw new Exception($"Internal Error: should not happen. propName: {propName}. Existing all members: {string.Join(",", allMembers.Keys)}");
}

bool isExistingMemberABackingFieldOwnedByThisType = isExistingMemberABackingField && existingMember[ProxyInternalUseProperty.Owner.ToUnderscoredString()]?.Value<string>() == typeName;
bool isExistingMemberABackingFieldOwnedByThisType = isExistingMemberABackingField && existingMember[InternalUseFieldName.Owner]?.Value<string>() == typeName;
if (isExistingMemberABackingField && (isOwn || isExistingMemberABackingFieldOwnedByThisType))
{
// this is the property corresponding to the backing field in *this* type
Expand All @@ -389,8 +387,8 @@ public static async Task<Dictionary<string, JObject>> ExpandPropertyValues(
{
// this has `new` keyword if it is newSlot but direct child was not a newSlot:
var child = allMembers.FirstOrDefault(
kvp => (kvp.Key == propName || kvp.Key.StartsWith($"{propName} (")) && kvp.Value[ProxyInternalUseProperty.ParentTypeId.ToUnderscoredString()]?.Value<int>() == typeId).Value;
bool wasOverriddenByDerivedType = child != null && child[ProxyInternalUseProperty.IsNewSlot.ToUnderscoredString()]?.Value<bool>() != true;
kvp => (kvp.Key == propName || kvp.Key.StartsWith($"{propName} (")) && kvp.Value[InternalUseFieldName.ParentTypeId]?.Value<int>() == typeId).Value;
bool wasOverriddenByDerivedType = child != null && child[InternalUseFieldName.IsNewSlot]?.Value<bool>() != true;
if (wasOverriddenByDerivedType)
{
/*
Expand All @@ -416,7 +414,7 @@ public static async Task<Dictionary<string, JObject>> ExpandPropertyValues(
*/

JObject backingFieldForHiddenProp = allMembers.GetValueOrDefault(overriddenOrHiddenPropName);
if (backingFieldForHiddenProp is null || backingFieldForHiddenProp[ProxyInternalUseProperty.IsBackingField.ToUnderscoredString()]?.Value<bool>() != true)
if (backingFieldForHiddenProp is null || backingFieldForHiddenProp[InternalUseFieldName.IsBackingField]?.Value<bool>() != true)
{
// hiding with a non-auto property, so nothing to adjust
// add the new property
Expand All @@ -431,12 +429,12 @@ public static async Task<Dictionary<string, JObject>> ExpandPropertyValues(

async Task UpdateBackingFieldWithPropertyAttributes(JObject backingField, string autoPropName, MethodAttributes getterMemberAccessAttrs, DebuggerBrowsableState? state)
{
backingField[ProxyInternalUseProperty.Section.ToUnderscoredString()] = getterMemberAccessAttrs switch
backingField[InternalUseFieldName.Section] = getterMemberAccessAttrs switch
{
MethodAttributes.Private => "private",
_ => "result"
};
backingField[ProxyInternalUseProperty.State.ToUnderscoredString()] = state?.ToString();
backingField[InternalUseFieldName.State] = state?.ToString();

if (state is null)
return;
Expand Down Expand Up @@ -479,16 +477,16 @@ async Task AddProperty(
}

propRet["isOwn"] = isOwn;
propRet[ProxyInternalUseProperty.Section.ToUnderscoredString()] = getterAttrs switch
propRet[InternalUseFieldName.Section] = getterAttrs switch
{
MethodAttributes.Private => "private",
_ => "result"
};
propRet[ProxyInternalUseProperty.State.ToUnderscoredString()] = state?.ToString();
propRet[InternalUseFieldName.State] = state?.ToString();
if (parentTypeId != -1)
{
propRet[ProxyInternalUseProperty.ParentTypeId.ToUnderscoredString()] = parentTypeId;
propRet[ProxyInternalUseProperty.IsNewSlot.ToUnderscoredString()] = isNewSlot;
propRet[InternalUseFieldName.ParentTypeId] = parentTypeId;
propRet[InternalUseFieldName.IsNewSlot] = isNewSlot;
}

string namePrefix = GetNamePrefixForValues(propNameWithSufix, typeName, isOwn, state);
Expand Down Expand Up @@ -593,7 +591,7 @@ public static async Task<GetMembersResult> GetObjectMemberValues(
if (getCommandType.HasFlag(GetObjectCommandOptions.AccessorPropertiesOnly))
{
foreach (var f in allFields)
f[ProxyInternalUseProperty.Hidden.ToUnderscoredString()] = true;
f[InternalUseFieldName.Hidden] = true;
}
AddOnlyNewFieldValuesByNameTo(allFields, allMembers, typeName, isOwn);
}
Expand Down Expand Up @@ -639,8 +637,8 @@ static void AddOnlyNewFieldValuesByNameTo(JArray namedValues, IDictionary<string
if (valuesDict.TryAdd(name, item as JObject))
{
// new member
if (item[ProxyInternalUseProperty.IsBackingField.ToUnderscoredString()]?.Value<bool>() == true)
item[ProxyInternalUseProperty.Owner.ToUnderscoredString()] = typeName;
if (item[InternalUseFieldName.IsBackingField]?.Value<bool>() == true)
item[InternalUseFieldName.Owner] = typeName;
continue;
}

Expand All @@ -657,18 +655,6 @@ static void AddOnlyNewFieldValuesByNameTo(JArray namedValues, IDictionary<string

}

public enum ProxyInternalUseProperty
{
Hidden,
State,
Section,
Owner,
IsStatic,
IsNewSlot,
IsBackingField,
ParentTypeId
}

internal sealed class GetMembersResult
{
// public / protected / internal:
Expand Down Expand Up @@ -697,17 +683,27 @@ public GetMembersResult(JArray value, bool sortByAccessLevel)

public void CleanUp()
{
JProperty[] toRemoveInObject = new JProperty[InternalUseFieldName.Count];

CleanUpJArray(Result);
CleanUpJArray(PrivateMembers);
static void CleanUpJArray(JArray arr)

void CleanUpJArray(JArray arr)
{
foreach(var item in arr)
foreach (JToken item in arr)
{
item.Children().Where(x =>
x is JProperty p &&
p.Name.TryConvertToProxyInternalUseProperty())
.ToList()
.ForEach(x => x.Remove());
if (item is not JObject jobj || jobj.Count == 0)
continue;

int removeCount = 0;
foreach (JProperty jp in jobj.Properties())
{
if (InternalUseFieldName.IsKnown(jp.Name))
toRemoveInObject[removeCount++] = jp;
}

for (int i = 0; i < removeCount; i++)
toRemoveInObject[i].Remove();
}
}
}
Expand All @@ -730,10 +726,10 @@ public static GetMembersResult FromValues(JArray values, bool splitMembersByAcce

private void Split(JToken member)
{
if (member[ProxyInternalUseProperty.Hidden.ToUnderscoredString()]?.Value<bool>() == true)
if (member[InternalUseFieldName.Hidden]?.Value<bool>() == true)
return;

if (member[ProxyInternalUseProperty.Section.ToUnderscoredString()]?.Value<string>() is not string section)
if (member[InternalUseFieldName.Section]?.Value<string>() is not string section)
{
Result.Add(member);
return;
Expand Down
8 changes: 4 additions & 4 deletions src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,15 @@ JObject GetFieldWithMetadata(FieldTypeClass field, JObject fieldValue, bool isSt
if (isStatic)
fieldValue["name"] = field.Name;
FieldAttributes attr = field.Attributes & FieldAttributes.FieldAccessMask;
fieldValue[ProxyInternalUseProperty.Section.ToUnderscoredString()] = attr == FieldAttributes.Private ? "private" : "result";
fieldValue[InternalUseFieldName.Section] = attr == FieldAttributes.Private ? "private" : "result";

if (field.IsBackingField)
{
fieldValue[ProxyInternalUseProperty.IsBackingField.ToUnderscoredString()] = true;
fieldValue[InternalUseFieldName.IsBackingField] = true;
return fieldValue;
}
typeFieldsBrowsableInfo.TryGetValue(field.Name, out DebuggerBrowsableState? state);
fieldValue[ProxyInternalUseProperty.State.ToUnderscoredString()] = state?.ToString();
fieldValue[InternalUseFieldName.State] = state?.ToString();
return fieldValue;
}
}
Expand Down Expand Up @@ -244,7 +244,7 @@ public async Task ExpandedFieldValues(MonoSDBHelper sdbHelper, bool includeStati
JArray visibleFields = new();
foreach (JObject field in fields)
{
if (!Enum.TryParse(field["__state"]?.Value<string>(), out DebuggerBrowsableState state))
if (!Enum.TryParse(field[InternalUseFieldName.State]?.Value<string>(), out DebuggerBrowsableState state))
{
visibleFields.Add(field);
continue;
Expand Down
14 changes: 14 additions & 0 deletions src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,18 @@ internal async Task<JToken> GetObjectOnLocals(JToken locals, string name)
return await GetProperties(objectId);
}

internal void AssertInternalUseFieldsAreRemoved(JToken item)
{
if (item is JObject jobj && jobj.Count != 0)
{
foreach (JProperty jp in jobj.Properties())
{
Assert.False(InternalUseFieldName.IsKnown(jp.Name),
$"Property {jp.Name} of object: {jobj} is for internal proxy use and should not be exposed externally.");
}
}
}

/* @fn_args is for use with `Runtime.callFunctionOn` only */
internal virtual async Task<JToken> GetProperties(string id, JToken fn_args = null, bool? own_properties = null, bool? accessors_only = null, bool expect_ok = true)
{
Expand Down Expand Up @@ -978,6 +990,7 @@ internal virtual async Task<JToken> GetProperties(string id, JToken fn_args = nu
{
foreach (var p in locals)
{
AssertInternalUseFieldsAreRemoved(p);
if (p["name"]?.Value<string>() == "length" && p["enumerable"]?.Value<bool>() != true)
{
p.Remove();
Expand Down Expand Up @@ -1035,6 +1048,7 @@ internal virtual async Task<JToken> GetProperties(string id, JToken fn_args = nu
{
foreach (var p in locals)
{
AssertInternalUseFieldsAreRemoved(p);
if (p["name"]?.Value<string>() == "length" && p["enumerable"]?.Value<bool>() != true)
{
p.Remove();
Expand Down