[wasm][debugger] Refactor: narrow down SdbHelper responsibility (#66821)
authorIlona Tomkowicz <32700855+ilonatommy@users.noreply.github.com>
Mon, 16 May 2022 15:53:54 +0000 (17:53 +0200)
committerGitHub <noreply@github.com>
Mon, 16 May 2022 15:53:54 +0000 (17:53 +0200)
* Refactor.

* Applied @radical's suggestions about expression body.

* On refactor-createjobj-66068: Applied @radical's https://github.com/radical/runtime/commit/69f5cea4dfe30f3dbd74333b1b23fd8feb665e18.

* Revert test fix and separate it to another PR.

* Reverted whitespaces.

* Overlooked whitespaces - undo.

* Made static what can be static.

* Removed whitespaces.

* Removed whitespaces.

* Blocked tests failing on Firefox.

src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs
src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs [new file with mode: 0644]
src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs
src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs
src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs
src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs
src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs
src/mono/wasm/debugger/DebuggerTestSuite/PointerTests.cs

index 4d9369d..7dc35ac 100644 (file)
@@ -453,7 +453,7 @@ namespace Microsoft.WebAssembly.Diagnostics
                 newScript = script.ContinueWith(
                 string.Join("\n", replacer.variableDefinitions) + "\nreturn " + syntaxTree.ToString());
                 var state = await newScript.RunAsync(cancellationToken: token);
-                return JObject.FromObject(ConvertCSharpToJSType(state.ReturnValue, state.ReturnValue?.GetType()));
+                return JObject.FromObject(ConvertCLRToJSType(state.ReturnValue));
             }
             catch (CompilationErrorException cee)
             {
@@ -466,31 +466,23 @@ namespace Microsoft.WebAssembly.Diagnostics
             }
         }
 
-        private static readonly HashSet<Type> NumericTypes = new HashSet<Type>
+        private static JObject ConvertCLRToJSType(object v)
         {
-            typeof(decimal), typeof(byte), typeof(sbyte),
-            typeof(short), typeof(ushort),
-            typeof(int), typeof(uint),
-            typeof(float), typeof(double)
-        };
-
-        private static object ConvertCSharpToJSType(object v, Type type)
-        {
-            if (v == null)
-                return new { type = "object", subtype = "null", className = type?.ToString(), description = type?.ToString() };
-            if (v is string s)
-                return new { type = "string", value = s, description = s };
-            if (v is char c)
-                return new { type = "symbol", value = c, description = $"{(int)c} '{c}'" };
-            if (NumericTypes.Contains(v.GetType()))
-                return new { type = "number", value = v, description = Convert.ToDouble(v).ToString(CultureInfo.InvariantCulture) };
-            if (v is bool)
-                return new { type = "boolean", value = v, description = v.ToString().ToLowerInvariant(), className = type.ToString() };
-            if (v is JObject)
-                return v;
-            return new { type = "object", value = v, description = v.ToString(), className = type.ToString() };
+            if (v is JObject jobj)
+                return jobj;
+
+            if (v is null)
+                return JObjectValueCreator.CreateNull("<unknown>")?["value"] as JObject;
+
+            string typeName = v.GetType().ToString();
+            jobj = JObjectValueCreator.CreateFromPrimitiveType(v);
+            return jobj is not null
+                ? jobj["value"] as JObject
+                : JObjectValueCreator.Create<object>(value: null,
+                                                    type: "object",
+                                                    description: v.ToString(),
+                                                    className: typeName)?["value"] as JObject;
         }
-
     }
 
     internal sealed class ReturnAsErrorException : Exception
diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs b/src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs
new file mode 100644 (file)
index 0000000..5346b98
--- /dev/null
@@ -0,0 +1,404 @@
+// 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.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using BrowserDebugProxy;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.WebAssembly.Diagnostics;
+
+internal sealed class JObjectValueCreator
+{
+    private Dictionary<int, ValueTypeClass> _valueTypes = new();
+    private Dictionary<int, PointerValue> _pointerValues = new();
+    private readonly MonoSDBHelper _sdbAgent;
+    private readonly ILogger _logger;
+
+    public JObjectValueCreator(MonoSDBHelper sdbAgent, ILogger logger)
+    {
+        _sdbAgent = sdbAgent;
+        _logger = logger;
+    }
+
+    public static JObject Create<T>(T value,
+                             string type,
+                             string description,
+                             string className = null,
+                             string objectId = null,
+                             string subtype = null,
+                             bool writable = false,
+                             bool isValueType = false,
+                             bool isEnum = false)
+    {
+        var ret = JObject.FromObject(new
+        {
+            value = new
+            {
+                type,
+                value,
+                description
+            },
+            writable
+        });
+        if (className != null)
+            ret["value"]["className"] = className;
+        if (objectId != null)
+            ret["value"]["objectId"] = objectId;
+        if (subtype != null)
+            ret["value"]["subtype"] = subtype;
+        if (isValueType)
+            ret["value"]["isValueType"] = isValueType;
+        if (isEnum)
+            ret["value"]["isEnum"] = isEnum;
+        return ret;
+    }
+
+    public static JObject CreateFromPrimitiveType(object v)
+        => v switch
+        {
+            string s => Create(s, type: "string", description: s),
+            char c => CreateJObjectForChar(Convert.ToInt32(c)),
+            bool b => Create(b, type: "boolean", description: b ? "true" : "false", className: "System.Boolean"),
+
+            decimal or float or double or
+            byte or sbyte or
+            short or ushort or
+            int or uint or
+            long or ulong
+                => CreateJObjectForNumber(v),
+
+            _ => null
+        };
+
+    public static JObject CreateNull(string className!!)
+        => Create<object>(value: null,
+                          type: "object",
+                          description: className,
+                          className: className,
+                          subtype: "null");
+
+    public async Task<JObject> ReadAsVariableValue(
+        MonoBinaryReader retDebuggerCmdReader,
+        string name,
+        CancellationToken token,
+        bool isOwn = false,
+        int typeIdForObject = -1,
+        bool forDebuggerDisplayAttribute = false)
+    {
+        long initialPos =  /*retDebuggerCmdReader == null ? 0 : */retDebuggerCmdReader.BaseStream.Position;
+        ElementType etype = (ElementType)retDebuggerCmdReader.ReadByte();
+        JObject ret = null;
+        switch (etype)
+        {
+            case ElementType.I:
+            case ElementType.U:
+            case ElementType.Void:
+            case (ElementType)ValueTypeId.VType:
+            case (ElementType)ValueTypeId.FixedArray:
+                ret = Create(value: "void", type: "void", description: "void");
+                break;
+            case ElementType.Boolean:
+                {
+                    var value = retDebuggerCmdReader.ReadInt32();
+                    ret = CreateFromPrimitiveType(value == 1);
+                    break;
+                }
+            case ElementType.I1:
+                {
+                    var value = retDebuggerCmdReader.ReadSByte();
+                    ret = CreateJObjectForNumber<int>(value);
+                    break;
+                }
+            case ElementType.I2:
+            case ElementType.I4:
+                {
+                    var value = retDebuggerCmdReader.ReadInt32();
+                    ret = CreateJObjectForNumber<int>(value);
+                    break;
+                }
+            case ElementType.U1:
+                {
+                    var value = retDebuggerCmdReader.ReadUByte();
+                    ret = CreateJObjectForNumber<int>(value);
+                    break;
+                }
+            case ElementType.U2:
+                {
+                    var value = retDebuggerCmdReader.ReadUShort();
+                    ret = CreateJObjectForNumber<int>(value);
+                    break;
+                }
+            case ElementType.U4:
+                {
+                    var value = retDebuggerCmdReader.ReadUInt32();
+                    ret = CreateJObjectForNumber<uint>(value);
+                    break;
+                }
+            case ElementType.R4:
+                {
+                    float value = retDebuggerCmdReader.ReadSingle();
+                    ret = CreateJObjectForNumber<float>(value);
+                    break;
+                }
+            case ElementType.Char:
+                {
+                    var value = retDebuggerCmdReader.ReadInt32();
+                    ret = CreateJObjectForChar(value);
+                    break;
+                }
+            case ElementType.I8:
+                {
+                    long value = retDebuggerCmdReader.ReadInt64();
+                    ret = CreateJObjectForNumber<long>(value);
+                    break;
+                }
+            case ElementType.U8:
+                {
+                    ulong value = retDebuggerCmdReader.ReadUInt64();
+                    ret = CreateJObjectForNumber<ulong>(value);
+                    break;
+                }
+            case ElementType.R8:
+                {
+                    double value = retDebuggerCmdReader.ReadDouble();
+                    ret = CreateJObjectForNumber<double>(value);
+                    break;
+                }
+            case ElementType.FnPtr:
+            case ElementType.Ptr:
+                {
+                    ret = await ReadAsPtrValue(etype, retDebuggerCmdReader, name, token);
+                    break;
+                }
+            case ElementType.String:
+                {
+                    var stringId = retDebuggerCmdReader.ReadInt32();
+                    string value = await _sdbAgent.GetStringValue(stringId, token);
+                    ret = CreateFromPrimitiveType(value);
+                    break;
+                }
+            case ElementType.SzArray:
+            case ElementType.Array:
+                {
+                    ret = await ReadAsArray(retDebuggerCmdReader, token);
+                    break;
+                }
+            case ElementType.Class:
+            case ElementType.Object:
+                {
+                    ret = await ReadAsObjectValue(retDebuggerCmdReader, typeIdForObject, forDebuggerDisplayAttribute, token);
+                    break;
+                }
+            case ElementType.ValueType:
+                {
+                    ret = await ReadAsValueType(retDebuggerCmdReader, name, initialPos, forDebuggerDisplayAttribute, token);
+                    break;
+                }
+            case (ElementType)ValueTypeId.Null:
+                {
+                    var className = await GetNullObjectClassName();
+                    ret = CreateNull(className);
+                    break;
+                }
+            case (ElementType)ValueTypeId.Type:
+                {
+                    retDebuggerCmdReader.ReadInt32();
+                    break;
+                }
+            default:
+                {
+                    _logger.LogDebug($"Could not evaluate CreateJObjectForVariableValue invalid type {etype}");
+                    break;
+                }
+        }
+        if (ret != null)
+        {
+            if (isOwn)
+                ret["isOwn"] = true;
+            ret["name"] = name;
+        }
+        return ret;
+
+        async Task<string> GetNullObjectClassName()
+        {
+            string className;
+            ElementType variableType = (ElementType)retDebuggerCmdReader.ReadByte();
+            switch (variableType)
+            {
+                case ElementType.String:
+                case ElementType.Class:
+                    {
+                        var type_id = retDebuggerCmdReader.ReadInt32();
+                        className = await _sdbAgent.GetTypeName(type_id, token);
+                        break;
+
+                    }
+                case ElementType.SzArray:
+                case ElementType.Array:
+                    {
+                        ElementType byte_type = (ElementType)retDebuggerCmdReader.ReadByte();
+                        retDebuggerCmdReader.ReadInt32(); // rank
+                        if (byte_type == ElementType.Class)
+                        {
+                            retDebuggerCmdReader.ReadInt32(); // internal_type_id
+                        }
+                        var type_id = retDebuggerCmdReader.ReadInt32();
+                        className = await _sdbAgent.GetTypeName(type_id, token);
+                        break;
+                    }
+                default:
+                    {
+                        var type_id = retDebuggerCmdReader.ReadInt32();
+                        className = await _sdbAgent.GetTypeName(type_id, token);
+                        break;
+                    }
+            }
+            return className;
+        }
+    }
+
+    private async Task<JObject> ReadAsObjectValue(MonoBinaryReader retDebuggerCmdReader, int typeIdFromAttribute, bool forDebuggerDisplayAttribute, CancellationToken token)
+    {
+        var objectId = retDebuggerCmdReader.ReadInt32();
+        var type_id = await _sdbAgent.GetTypeIdsForObject(objectId, false, token);
+        string className = await _sdbAgent.GetTypeName(type_id[0], token);
+        string debuggerDisplayAttribute = null;
+        if (!forDebuggerDisplayAttribute)
+            debuggerDisplayAttribute = await _sdbAgent.GetValueFromDebuggerDisplayAttribute(
+                new DotnetObjectId("object", objectId), type_id[0], token);
+        var description = className.ToString();
+
+        if (debuggerDisplayAttribute != null)
+            description = debuggerDisplayAttribute;
+
+        if (await _sdbAgent.IsDelegate(objectId, token))
+        {
+            if (typeIdFromAttribute != -1)
+            {
+                className = await _sdbAgent.GetTypeName(typeIdFromAttribute, token);
+            }
+
+            description = await _sdbAgent.GetDelegateMethodDescription(objectId, token);
+            if (description == "")
+            {
+                return Create(value: className, type: "symbol", description: className);
+            }
+        }
+        return Create<object>(value: null, type: "object", description: description, className: className, objectId: $"dotnet:object:{objectId}");
+    }
+
+    public async Task<JObject> ReadAsValueType(
+        MonoBinaryReader retDebuggerCmdReader,
+        string name,
+        long initialPos,
+        bool forDebuggerDisplayAttribute,
+        CancellationToken token)
+    {
+        // FIXME: debugger proxy
+        var isEnum = retDebuggerCmdReader.ReadByte() == 1;
+        var isBoxed = retDebuggerCmdReader.ReadByte() == 1;
+        var typeId = retDebuggerCmdReader.ReadInt32();
+        var className = await _sdbAgent.GetTypeName(typeId, token);
+        var numValues = retDebuggerCmdReader.ReadInt32();
+
+        if (className.IndexOf("System.Nullable<", StringComparison.Ordinal) == 0) //should we call something on debugger-agent to check???
+        {
+            retDebuggerCmdReader.ReadByte(); //ignoring the boolean type
+            var isNull = retDebuggerCmdReader.ReadInt32();
+
+            // Read the value, even if isNull==true, to correctly advance the reader
+            var value = await ReadAsVariableValue(retDebuggerCmdReader, name, token);
+            if (isNull != 0)
+                return value;
+            else
+                return Create<object>(null, "object", className, className, subtype: "null", isValueType: true);
+        }
+        if (isBoxed && numValues == 1)
+        {
+            if (MonoSDBHelper.IsPrimitiveType(className))
+            {
+                return await ReadAsVariableValue(retDebuggerCmdReader, name: null, token);
+            }
+        }
+
+        ValueTypeClass valueType = await ValueTypeClass.CreateFromReader(
+                                                    _sdbAgent,
+                                                    retDebuggerCmdReader,
+                                                    initialPos,
+                                                    className,
+                                                    typeId,
+                                                    numValues,
+                                                    isEnum,
+                                                    token);
+        _valueTypes[valueType.Id.Value] = valueType;
+        return await valueType.ToJObject(_sdbAgent, forDebuggerDisplayAttribute, token);
+    }
+    public void ClearCache()
+    {
+        _valueTypes = new Dictionary<int, ValueTypeClass>();
+        _pointerValues = new Dictionary<int, PointerValue>();
+    }
+
+    public bool TryGetValueTypeById(int valueTypeId, out ValueTypeClass vt) => _valueTypes.TryGetValue(valueTypeId, out vt);
+    public PointerValue GetPointerValue(int pointerId) => _pointerValues.TryGetValue(pointerId, out PointerValue pv) ? pv : null;
+
+    private static JObject CreateJObjectForNumber<T>(T value) => Create(value, "number", value.ToString(), writable: true);
+
+    private static JObject CreateJObjectForChar(int value)
+    {
+        char charValue = Convert.ToChar(value);
+        var description = $"{value} '{charValue}'";
+        return Create(charValue, "symbol", description, writable: true);
+    }
+
+    private async Task<JObject> ReadAsPtrValue(ElementType etype, MonoBinaryReader retDebuggerCmdReader, string name, CancellationToken token)
+    {
+        string type;
+        string value;
+        long valueAddress = retDebuggerCmdReader.ReadInt64();
+        var typeId = retDebuggerCmdReader.ReadInt32();
+        string className;
+        if (etype == ElementType.FnPtr)
+            className = "(*())"; //to keep the old behavior
+        else
+            className = "(" + await _sdbAgent.GetTypeName(typeId, token) + ")";
+
+        int pointerId = 0;
+        if (valueAddress != 0 && className != "(void*)")
+        {
+            pointerId = MonoSDBHelper.GetNextDebuggerObjectId();
+            type = "object";
+            value = className;
+            _pointerValues[pointerId] = new PointerValue(valueAddress, typeId, name);
+        }
+        else
+        {
+            type = "symbol";
+            value = className + " " + valueAddress;
+        }
+        return Create(value: value, type: type, description: value, className: className, objectId: $"dotnet:pointer:{pointerId}", subtype: "pointer");
+    }
+
+    private async Task<JObject> ReadAsArray(MonoBinaryReader retDebuggerCmdReader, CancellationToken token)
+    {
+        var objectId = retDebuggerCmdReader.ReadInt32();
+        var className = await _sdbAgent.GetClassNameFromObject(objectId, token);
+        var arrayType = className.ToString();
+        var length = await _sdbAgent.GetArrayDimensions(objectId, token);
+        if (arrayType.LastIndexOf('[') > 0)
+            arrayType = arrayType.Insert(arrayType.LastIndexOf('[') + 1, length.ToString());
+        if (className.LastIndexOf('[') > 0)
+            className = className.Insert(arrayType.LastIndexOf('[') + 1, new string(',', length.Rank - 1));
+        return Create<object>(value: null,
+                              type: "object",
+                              description: arrayType,
+                              className: className.ToString(),
+                              objectId: "dotnet:array:" + objectId,
+                              subtype: length.Rank == 1 ? "array" : null);
+    }
+}
index 24a2eae..89a568b 100644 (file)
@@ -37,7 +37,7 @@ namespace BrowserDebugProxy
 
         private static async Task<JObject> ReadFieldValue(MonoSDBHelper sdbHelper, MonoBinaryReader reader, FieldTypeClass field, int objectId, TypeInfoWithDebugInformation typeInfo, int fieldValueType, bool isOwn, GetObjectCommandOptions getObjectOptions, CancellationToken token)
         {
-            var fieldValue = await sdbHelper.CreateJObjectForVariableValue(
+            var fieldValue = await sdbHelper.ValueCreator.ReadAsVariableValue(
                 reader,
                 field.Name,
                 token,
@@ -253,7 +253,7 @@ namespace BrowserDebugProxy
         public static Task<GetMembersResult> GetValueTypeMemberValues(
             MonoSDBHelper sdbHelper, int valueTypeId, GetObjectCommandOptions getCommandOptions, CancellationToken token, bool sortByAccessLevel = false, bool includeStatic = false)
         {
-            return sdbHelper.valueTypes.TryGetValue(valueTypeId, out ValueTypeClass valueType)
+            return sdbHelper.ValueCreator.TryGetValueTypeById(valueTypeId, out ValueTypeClass valueType)
                 ? valueType.GetMemberValues(sdbHelper, getCommandOptions, sortByAccessLevel, includeStatic, token)
                 : throw new ArgumentException($"Could not find any valuetype with id: {valueTypeId}", nameof(valueTypeId));
         }
index 724953f..af3a82f 100644 (file)
@@ -424,7 +424,18 @@ namespace Microsoft.WebAssembly.Diagnostics
             bool isExtensionMethod = false;
             try
             {
-                var typeIds = await context.SdbAgent.GetTypeIdsForObject(objectId.Value, true, token);
+                List<int> typeIds;
+                if (objectId.IsValueType)
+                {
+                    if (!context.SdbAgent.ValueCreator.TryGetValueTypeById(objectId.Value, out ValueTypeClass valueType))
+                        throw new Exception($"Could not find valuetype {objectId}");
+
+                    typeIds = new List<int>(1) { valueType.TypeId };
+                }
+                else
+                {
+                    typeIds = await context.SdbAgent.GetTypeIdsForObject(objectId.Value, true, token);
+                }
                 int methodId = await context.SdbAgent.GetMethodIdByName(typeIds[0], methodName, token);
                 var className = await context.SdbAgent.GetTypeNameOriginal(typeIds[0], token);
                 if (methodId == 0) //try to search on System.Linq.Enumerable
index 9936ca0..90cc53d 100644 (file)
@@ -714,7 +714,7 @@ namespace Microsoft.WebAssembly.Diagnostics
                 byte[] newBytes = Convert.FromBase64String(res.Value?["result"]?["value"]?["value"]?.Value<string>());
                 var retDebuggerCmdReader = new MonoBinaryReader(newBytes);
                 retDebuggerCmdReader.ReadByte(); //number of objects returned.
-                var obj = await context.SdbAgent.CreateJObjectForVariableValue(retDebuggerCmdReader, "ret", token);
+                var obj = await context.SdbAgent.ValueCreator.ReadAsVariableValue(retDebuggerCmdReader, "ret", token);
                 /*JTokenType? res_value_type = res.Value?["result"]?["value"]?.Type;*/
                 res = Result.OkFromObject(new { result = obj["value"]});
                 SendResponse(id, res, token);
index 809d310..663c022 100644 (file)
@@ -517,7 +517,10 @@ namespace Microsoft.WebAssembly.Diagnostics
             }
             else if (objectId.Scheme == "valuetype")
             {
-                Write(SdbHelper.valueTypes[objectId.Value].Buffer);
+                if (SdbHelper.ValueCreator.TryGetValueTypeById(objectId.Value, out ValueTypeClass vt))
+                    Write(vt.Buffer);
+                else
+                    throw new ArgumentException($"Could not find any valuetype with id: {objectId.Value}", nameof(objectId.Value));
             }
         }
 
@@ -734,7 +737,7 @@ namespace Microsoft.WebAssembly.Diagnostics
                 string displayVarName = varName;
                 if (int.TryParse(varName, out _))
                     displayVarName = $"[{varName}]";
-                _value = await sdbHelper.CreateJObjectForVariableValue(retDebuggerCmdReader, "*" + displayVarName, token);
+                _value = await sdbHelper.ValueCreator.ReadAsVariableValue(retDebuggerCmdReader, "*" + displayVarName, token);
             }
 
             return _value;
@@ -751,15 +754,13 @@ namespace Microsoft.WebAssembly.Diagnostics
         private Dictionary<int, AssemblyInfo> assemblies;
         private Dictionary<int, TypeInfoWithDebugInformation> types;
 
-        internal Dictionary<int, ValueTypeClass> valueTypes = new Dictionary<int, ValueTypeClass>();
-        internal Dictionary<int, PointerValue> pointerValues = new Dictionary<int, PointerValue>();
-
         private MonoProxy proxy;
         private DebugStore store;
         private SessionId sessionId;
 
         private readonly ILogger logger;
         private Regex regexForAsyncLocals = new Regex(@"\<([^)]*)\>", RegexOptions.Singleline);
+        public JObjectValueCreator ValueCreator { get; init; }
 
         public static int GetNewId() { return cmdId++; }
         public static int GetNewObjectId() => Interlocked.Increment(ref debuggerObjectId);
@@ -769,6 +770,7 @@ namespace Microsoft.WebAssembly.Diagnostics
             this.proxy = proxy;
             this.logger = logger;
             this.sessionId = sessionId;
+            ValueCreator = new(this, logger);
             ResetStore(null);
         }
 
@@ -879,11 +881,7 @@ namespace Microsoft.WebAssembly.Diagnostics
             return types[typeId];
         }
 
-        public void ClearCache()
-        {
-            valueTypes = new Dictionary<int, ValueTypeClass>();
-            pointerValues = new Dictionary<int, PointerValue>();
-        }
+        public void ClearCache() => ValueCreator.ClearCache();
 
         public async Task<bool> SetProtocolVersion(CancellationToken token)
         {
@@ -1243,7 +1241,7 @@ namespace Microsoft.WebAssembly.Diagnostics
             commandParamsWriter.Write(fieldId);
 
             using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdType.GetValues, commandParamsWriter, token);
-            return await CreateJObjectForVariableValue(retDebuggerCmdReader, "", token);
+            return await ValueCreator.ReadAsVariableValue(retDebuggerCmdReader, "", token);
         }
 
         public async Task<int> TypeIsInitialized(int typeId, CancellationToken token)
@@ -1362,13 +1360,13 @@ namespace Microsoft.WebAssembly.Diagnostics
                     return retDebuggerCmdReader;
 
                 //reading buffer only to advance the reader to the next cattr
-                for (int k = 0 ; k < 2; k++)
+                for (int k = 0; k < 2; k++)
                 {
                     var parmCount = retDebuggerCmdReader.ReadInt32();
                     for (int j = 0; j < parmCount; j++)
                     {
                         //to typed_args
-                        await CreateJObjectForVariableValue(retDebuggerCmdReader, "varName", token);
+                        await ValueCreator.ReadAsVariableValue(retDebuggerCmdReader, "varName", token);
                     }
                 }
             }
@@ -1411,7 +1409,7 @@ namespace Microsoft.WebAssembly.Diagnostics
                     token);
                 JArray objectValues = new JArray(members.Flatten());
 
-                var thisObj = CreateJObject<string>(value: "", type: "object", description: "", writable: false, objectId: dotnetObjectId.ToString());
+                var thisObj = JObjectValueCreator.Create(value: "", type: "object", description: "", writable: false, objectId: dotnetObjectId.ToString());
                 thisObj["name"] = "this";
                 objectValues.Add(thisObj);
 
@@ -1605,14 +1603,14 @@ namespace Microsoft.WebAssembly.Diagnostics
             commandParamsWriter.Write(0);
             using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdVM.InvokeMethod, commandParamsWriter, token);
             retDebuggerCmdReader.ReadByte(); //number of objects returned.
-            return await CreateJObjectForVariableValue(retDebuggerCmdReader, name, token);
+            return await ValueCreator.ReadAsVariableValue(retDebuggerCmdReader, name, token);
         }
 
         public Task<JObject> InvokeMethod(int objectId, int methodId, bool isValueType, CancellationToken token)
         {
             if (isValueType)
             {
-                return valueTypes.TryGetValue(objectId, out var valueType)
+                return ValueCreator.TryGetValueTypeById(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));
             }
@@ -1666,393 +1664,14 @@ namespace Microsoft.WebAssembly.Diagnostics
 
         public async Task<JObject> GetPointerContent(int pointerId, CancellationToken token)
         {
-            if (!pointerValues.TryGetValue(pointerId, out PointerValue pointerValue))
+            using var commandParamsWriter = new MonoBinaryWriter();
+            PointerValue pointerValue = ValueCreator.GetPointerValue(pointerId);
+            if (pointerValue == null)
                 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) {
-            if (className == "System.DateTime" ||
-                className == "System.DateTimeOffset" ||
-                className == "System.TimeSpan")
-                return true;
-            return false;
-        }
-
-        private static bool AutoInvokeToString(string className) {
-            if (className == "System.DateTime" ||
-                className == "System.DateTimeOffset" ||
-                className == "System.TimeSpan" ||
-                className == "System.Decimal"  ||
-                className == "System.Guid")
-                return true;
-            return false;
-        }
-
-        public static JObject CreateJObject<T>(T value, string type, string description, bool writable, string className = null, string objectId = null, string __custom_type = null, string subtype = null, bool isValueType = false, bool expanded = false, bool isEnum = false)
-        {
-            var ret = JObject.FromObject(new {
-                    value = new
-                    {
-                        type,
-                        value,
-                        description
-                    },
-                    writable
-                });
-            if (__custom_type != null)
-                ret["value"]["__custom_type"] = __custom_type;
-            if (className != null)
-                ret["value"]["className"] = className;
-            if (objectId != null)
-                ret["value"]["objectId"] = objectId;
-            if (subtype != null)
-                ret["value"]["subtype"] = subtype;
-            if (isValueType)
-                ret["value"]["isValueType"] = isValueType;
-            if (expanded)
-                ret["value"]["expanded"] = expanded;
-            if (isEnum)
-                ret["value"]["isEnum"] = isEnum;
-            return ret;
-
-        }
-
-        private static JObject CreateJObjectForBoolean(int value)
-        {
-            return CreateJObject<bool>(value == 0 ? false : true, "boolean", value == 0 ? "false" : "true", true);
-        }
-
-        private static JObject CreateJObjectForNumber<T>(T value)
-        {
-            return CreateJObject<T>(value, "number", value.ToString(), true);
-        }
-
-        private static JObject CreateJObjectForChar(int value)
-        {
-            char charValue = Convert.ToChar(value);
-            var description = $"{value} '{charValue}'";
-            return CreateJObject<char>(charValue, "symbol", description, true);
-        }
-
-        public async Task<JObject> CreateJObjectForPtr(ElementType etype, MonoBinaryReader retDebuggerCmdReader, string name, CancellationToken token)
-        {
-            string type;
-            string value;
-            long valueAddress = retDebuggerCmdReader.ReadInt64();
-            var typeId = retDebuggerCmdReader.ReadInt32();
-            string className;
-            if (etype == ElementType.FnPtr)
-                className = "(*())"; //to keep the old behavior
-            else
-                className = "(" + await GetTypeName(typeId, token) + ")";
-
-            int pointerId = -1;
-            if (valueAddress != 0 && className != "(void*)")
-            {
-                pointerId = GetNewObjectId();
-                type = "object";
-                value =  className;
-                pointerValues[pointerId] = new PointerValue(valueAddress, typeId, name);
-            }
-            else
-            {
-                type = "symbol";
-                value = className + " " + valueAddress;
-            }
-            return CreateJObject<string>(value, type, value, false, className, $"dotnet:pointer:{pointerId}", "pointer");
-        }
-
-        public async Task<JObject> CreateJObjectForString(MonoBinaryReader retDebuggerCmdReader, CancellationToken token)
-        {
-            var string_id = retDebuggerCmdReader.ReadInt32();
-            var value = await GetStringValue(string_id, token);
-            return CreateJObject<string>(value, "string", value, false);
-        }
-
-        public async Task<JObject> CreateJObjectForArray(MonoBinaryReader retDebuggerCmdReader, CancellationToken token)
-        {
-            var objectId = retDebuggerCmdReader.ReadInt32();
-            var className = await GetClassNameFromObject(objectId, token);
-            var arrayType = className.ToString();
-            var length = await GetArrayDimensions(objectId, token);
-            if (arrayType.LastIndexOf('[') > 0)
-                arrayType = arrayType.Insert(arrayType.LastIndexOf('[')+1, length.ToString());
-            if (className.LastIndexOf('[') > 0)
-                className = className.Insert(arrayType.LastIndexOf('[')+1, new string(',', length.Rank-1));
-            return CreateJObject<string>(null, "object", description : arrayType, writable : false, className.ToString(), "dotnet:array:" + objectId, null, subtype : length.Rank == 1 ? "array" : null);
-        }
-
-        public async Task<JObject> CreateJObjectForObject(MonoBinaryReader retDebuggerCmdReader, int typeIdFromAttribute, bool forDebuggerDisplayAttribute, CancellationToken token)
-        {
-            var objectId = retDebuggerCmdReader.ReadInt32();
-            var type_id = await GetTypeIdsForObject(objectId, false, token);
-            string className = await GetTypeName(type_id[0], token);
-            string debuggerDisplayAttribute = null;
-            if (!forDebuggerDisplayAttribute)
-                debuggerDisplayAttribute = await GetValueFromDebuggerDisplayAttribute(new DotnetObjectId("object", objectId), type_id[0], token);
-            var description = className.ToString();
-
-            if (debuggerDisplayAttribute != null)
-                description = debuggerDisplayAttribute;
-
-            if (await IsDelegate(objectId, token))
-            {
-                if (typeIdFromAttribute != -1)
-                    className = await GetTypeName(typeIdFromAttribute, token);
-
-                description = await GetDelegateMethodDescription(objectId, token);
-                if (description == "")
-                    return CreateJObject<string>(className.ToString(), "symbol", className.ToString(), false);
-            }
-            return CreateJObject<string>(null, "object", description, false, className, $"dotnet:object:{objectId}");
-        }
-
-        private static readonly string[] s_primitiveTypeNames = new[]
-        {
-            "bool",
-            "char",
-            "string",
-            "byte",
-            "sbyte",
-            "int",
-            "uint",
-            "long",
-            "ulong",
-            "short",
-            "ushort",
-            "float",
-            "double",
-        };
-
-        public static bool IsPrimitiveType(string simplifiedClassName)
-            => s_primitiveTypeNames.Contains(simplifiedClassName);
-
-        public async Task<JObject> CreateJObjectForValueType(
-            MonoBinaryReader retDebuggerCmdReader, string name, long initialPos, bool forDebuggerDisplayAttribute, CancellationToken token)
-        {
-            // FIXME: debugger proxy
-            var isEnum = retDebuggerCmdReader.ReadByte() == 1;
-            var isBoxed = retDebuggerCmdReader.ReadByte() == 1;
-            var typeId = retDebuggerCmdReader.ReadInt32();
-            var className = await GetTypeName(typeId, token);
-            var numValues = retDebuggerCmdReader.ReadInt32();
-
-            if (className.IndexOf("System.Nullable<", StringComparison.Ordinal) == 0) //should we call something on debugger-agent to check???
-            {
-                retDebuggerCmdReader.ReadByte(); //ignoring the boolean type
-                var isNull = retDebuggerCmdReader.ReadInt32();
-
-                // Read the value, even if isNull==true, to correctly advance the reader
-                var value = await CreateJObjectForVariableValue(retDebuggerCmdReader, name, token);
-                if (isNull != 0)
-                    return value;
-                else
-                    return CreateJObject<string>(null, "object", className, false, className, null, null, "null", true);
-            }
-            if (isBoxed && numValues == 1)
-            {
-                if (IsPrimitiveType(className))
-                {
-                    var value = await CreateJObjectForVariableValue(retDebuggerCmdReader, name: null, token);
-                    return value;
-                }
-            }
-
-            ValueTypeClass valueType = await ValueTypeClass.CreateFromReader(
-                                                        this,
-                                                        retDebuggerCmdReader,
-                                                        initialPos,
-                                                        className,
-                                                        typeId,
-                                                        numValues,
-                                                        isEnum,
-                                                        token);
-            valueTypes[valueType.Id.Value] = valueType;
-            return await valueType.ToJObject(this, forDebuggerDisplayAttribute, token);
-        }
-
-        public async Task<JObject> CreateJObjectForNull(MonoBinaryReader retDebuggerCmdReader, CancellationToken token)
-        {
-            string className;
-            ElementType variableType = (ElementType)retDebuggerCmdReader.ReadByte();
-            switch (variableType)
-            {
-                case ElementType.String:
-                case ElementType.Class:
-                {
-                    var type_id = retDebuggerCmdReader.ReadInt32();
-                    className = await GetTypeName(type_id, token);
-                    break;
-
-                }
-                case ElementType.SzArray:
-                case ElementType.Array:
-                {
-                    ElementType byte_type = (ElementType)retDebuggerCmdReader.ReadByte();
-                    retDebuggerCmdReader.ReadInt32(); // rank
-                    if (byte_type == ElementType.Class) {
-                        retDebuggerCmdReader.ReadInt32(); // internal_type_id
-                    }
-                    var type_id = retDebuggerCmdReader.ReadInt32();
-                    className = await GetTypeName(type_id, token);
-                    break;
-                }
-                default:
-                {
-                    var type_id = retDebuggerCmdReader.ReadInt32();
-                    className = await GetTypeName(type_id, token);
-                    break;
-                }
-            }
-            return CreateJObject<string>(null, "object", className, false, className, null, null, "null");
-        }
-
-        public async Task<JObject> CreateJObjectForVariableValue(MonoBinaryReader retDebuggerCmdReader,
-                                                                 string name,
-                                                                 CancellationToken token,
-                                                                 bool isOwn = false,
-                                                                 int typeIdForObject = -1,
-                                                                 bool forDebuggerDisplayAttribute = false)
-        {
-            long initialPos =  /*retDebuggerCmdReader == null ? 0 : */retDebuggerCmdReader.BaseStream.Position;
-            ElementType etype = (ElementType)retDebuggerCmdReader.ReadByte();
-            JObject ret = null;
-            switch (etype) {
-                case ElementType.I:
-                case ElementType.U:
-                case ElementType.Void:
-                case (ElementType)ValueTypeId.VType:
-                case (ElementType)ValueTypeId.FixedArray:
-                    ret = JObject.FromObject(new {
-                        value = new
-                        {
-                            type = "void",
-                            value = "void",
-                            description = "void"
-                        }});
-                    break;
-                case ElementType.Boolean:
-                {
-                    var value = retDebuggerCmdReader.ReadInt32();
-                    ret = CreateJObjectForBoolean(value);
-                    break;
-                }
-                case ElementType.I1:
-                {
-                    var value = retDebuggerCmdReader.ReadSByte();
-                    ret = CreateJObjectForNumber<int>(value);
-                    break;
-                }
-                case ElementType.I2:
-                case ElementType.I4:
-                {
-                    var value = retDebuggerCmdReader.ReadInt32();
-                    ret = CreateJObjectForNumber<int>(value);
-                    break;
-                }
-                case ElementType.U1:
-                {
-                    var value = retDebuggerCmdReader.ReadUByte();
-                    ret = CreateJObjectForNumber<int>(value);
-                    break;
-                }
-                case ElementType.U2:
-                {
-                    var value = retDebuggerCmdReader.ReadUShort();
-                    ret = CreateJObjectForNumber<int>(value);
-                    break;
-                }
-                case ElementType.U4:
-                {
-                    var value = retDebuggerCmdReader.ReadUInt32();
-                    ret = CreateJObjectForNumber<uint>(value);
-                    break;
-                }
-                case ElementType.R4:
-                {
-                    float value = retDebuggerCmdReader.ReadSingle();
-                    ret = CreateJObjectForNumber<float>(value);
-                    break;
-                }
-                case ElementType.Char:
-                {
-                    var value = retDebuggerCmdReader.ReadInt32();
-                    ret = CreateJObjectForChar(value);
-                    break;
-                }
-                case ElementType.I8:
-                {
-                    long value = retDebuggerCmdReader.ReadInt64();
-                    ret = CreateJObjectForNumber<long>(value);
-                    break;
-                }
-                case ElementType.U8:
-                {
-                    ulong value = retDebuggerCmdReader.ReadUInt64();
-                    ret = CreateJObjectForNumber<ulong>(value);
-                    break;
-                }
-                case ElementType.R8:
-                {
-                    double value = retDebuggerCmdReader.ReadDouble();
-                    ret = CreateJObjectForNumber<double>(value);
-                    break;
-                }
-                case ElementType.FnPtr:
-                case ElementType.Ptr:
-                {
-                    ret = await CreateJObjectForPtr(etype, retDebuggerCmdReader, name, token);
-                    break;
-                }
-                case ElementType.String:
-                {
-                    ret = await CreateJObjectForString(retDebuggerCmdReader, token);
-                    break;
-                }
-                case ElementType.SzArray:
-                case ElementType.Array:
-                {
-                    ret = await CreateJObjectForArray(retDebuggerCmdReader, token);
-                    break;
-                }
-                case ElementType.Class:
-                case ElementType.Object:
-                {
-                    ret = await CreateJObjectForObject(retDebuggerCmdReader, typeIdForObject, forDebuggerDisplayAttribute, token);
-                    break;
-                }
-                case ElementType.ValueType:
-                {
-                    ret = await CreateJObjectForValueType(retDebuggerCmdReader, name, initialPos, forDebuggerDisplayAttribute, token);
-                    break;
-                }
-                case (ElementType)ValueTypeId.Null:
-                {
-                    ret = await CreateJObjectForNull(retDebuggerCmdReader, token);
-                    break;
-                }
-                case (ElementType)ValueTypeId.Type:
-                {
-                    retDebuggerCmdReader.ReadInt32();
-                    break;
-                }
-                default:
-                {
-                    logger.LogDebug($"Could not evaluate CreateJObjectForVariableValue invalid type {etype}");
-                    break;
-                }
-            }
-            if (ret != null)
-            {
-                if (isOwn)
-                    ret["isOwn"] = true;
-                if (!string.IsNullOrEmpty(name))
-                    ret["name"] = name;
-            }
-            return ret;
-        }
+        public static int GetNextDebuggerObjectId() => Interlocked.Increment(ref debuggerObjectId);
 
         public async Task<bool> IsAsyncMethod(int methodId, CancellationToken token)
         {
@@ -2154,7 +1773,7 @@ namespace Microsoft.WebAssembly.Diagnostics
             {
                 try
                 {
-                    var var_json = await CreateJObjectForVariableValue(localsDebuggerCmdReader, var.Name, token);
+                    var var_json = await ValueCreator.ReadAsVariableValue(localsDebuggerCmdReader, var.Name, token);
                     locals.Add(var_json);
                 }
                 catch (Exception ex)
@@ -2166,7 +1785,7 @@ namespace Microsoft.WebAssembly.Diagnostics
             if (!method.Info.IsStatic())
             {
                 using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdFrame.GetThis, commandParamsWriter, token);
-                var var_json = await CreateJObjectForVariableValue(retDebuggerCmdReader, "this", token);
+                var var_json = await ValueCreator.ReadAsVariableValue(retDebuggerCmdReader, "this", token);
                 var_json.Add("fieldOffset", -1);
                 locals.Add(var_json);
             }
@@ -2174,13 +1793,6 @@ namespace Microsoft.WebAssembly.Diagnostics
 
         }
 
-        public ValueTypeClass GetValueTypeClass(int valueTypeId)
-        {
-            if (valueTypes.TryGetValue(valueTypeId, out ValueTypeClass value))
-                return value;
-            return null;
-        }
-
         public async Task<JArray> GetArrayValues(int arrayId, CancellationToken token)
         {
             var dimensions = await GetArrayDimensions(arrayId, token);
@@ -2190,9 +1802,9 @@ namespace Microsoft.WebAssembly.Diagnostics
             commandParamsWriter.Write(dimensions.TotalLength);
             var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdArray.GetValues, commandParamsWriter, token);
             JArray array = new JArray();
-            for (int i = 0 ; i < dimensions.TotalLength; i++)
+            for (int i = 0; i < dimensions.TotalLength; i++)
             {
-                var var_json = await CreateJObjectForVariableValue(retDebuggerCmdReader, dimensions.GetArrayIndexString(i), token);
+                var var_json = await ValueCreator.ReadAsVariableValue(retDebuggerCmdReader, dimensions.GetArrayIndexString(i), token);
                 array.Add(var_json);
             }
             return array;
@@ -2262,7 +1874,7 @@ namespace Microsoft.WebAssembly.Diagnostics
                 ctorArgsWriter.Write((byte)ValueTypeId.Null);
 
                 // FIXME: move method invocation to valueTypeclass?
-                if (valueTypes.TryGetValue(objectId, out var valueType))
+                if (ValueCreator.TryGetValueTypeById(objectId, out var valueType))
                 {
                     //FIXME: Issue #68390
                     //ctorArgsWriter.Write((byte)0); //not used but needed
@@ -2359,6 +1971,13 @@ namespace Microsoft.WebAssembly.Diagnostics
             return -1;
         }
 
+        public ValueTypeClass GetValueTypeClass(int valueTypeId)
+        {
+            if (ValueCreator.TryGetValueTypeById(valueTypeId, out ValueTypeClass vt))
+                return vt;
+            throw new ArgumentException($"Could not find any valuetype with id: {valueTypeId}", nameof(valueTypeId));
+        }
+
         public Task<GetMembersResult> GetTypeMemberValues(DotnetObjectId dotnetObjectId, GetObjectCommandOptions getObjectOptions, CancellationToken token, bool sortByAccessLevel = false)
             => dotnetObjectId.IsValueType
                     ? MemberObjectsExplorer.GetValueTypeMemberValues(this, dotnetObjectId.Value, getObjectOptions, token)
@@ -2380,7 +1999,7 @@ namespace Microsoft.WebAssembly.Diagnostics
             var typeIds = await GetTypeIdsForObject(objectId, true, token);
             foreach (var typeId in typeIds)
             {
-                var retDebuggerCmdReader =  await GetTypePropertiesReader(typeId, token);
+                var retDebuggerCmdReader = await GetTypePropertiesReader(typeId, token);
                 if (retDebuggerCmdReader == null)
                     return null;
 
@@ -2500,6 +2119,27 @@ namespace Microsoft.WebAssembly.Diagnostics
             await SendDebuggerAgentCommand(CmdModule.ApplyChanges, commandParamsWriter, token);
             return true;
         }
+
+        private static readonly string[] s_primitiveTypeNames = new[]
+            {
+            "bool",
+            "char",
+            "string",
+            "byte",
+            "sbyte",
+            "int",
+            "uint",
+            "long",
+            "ulong",
+            "short",
+            "ushort",
+            "float",
+            "double",
+        };
+
+        public static bool IsPrimitiveType(string simplifiedClassName)
+            => s_primitiveTypeNames.Contains(simplifiedClassName);
+
     }
 
     internal static class HelperExtensions
index 1444e6d..aaa2f08 100644 (file)
@@ -68,7 +68,7 @@ namespace BrowserDebugProxy
             JArray fields = new();
             foreach (var field in writableFields)
             {
-                var fieldValue = await sdbAgent.CreateJObjectForVariableValue(cmdReader, field.Name, token, true, field.TypeId, false);
+                var fieldValue = await sdbAgent.ValueCreator.ReadAsVariableValue(cmdReader, field.Name, token, true, field.TypeId, false);
 
                 fieldValue["__section"] = field.Attributes switch
                 {
@@ -114,15 +114,14 @@ namespace BrowserDebugProxy
                 if (displayString != null)
                     description = displayString;
             }
-            return MonoSDBHelper.CreateJObject(
+            return JObjectValueCreator.Create(
                 IsEnum ? fields[0]["value"] : null,
                 "object",
                 description,
-                false,
                 className,
                 Id.ToString(),
-                null, null, true, true,
-                IsEnum);
+                isValueType: true,
+                isEnum: IsEnum);
         }
 
         public async Task<JArray> GetProxy(MonoSDBHelper sdbHelper, CancellationToken token)
index 7a8133b..6745663 100644 (file)
@@ -1131,9 +1131,8 @@ namespace DebuggerTests
                    );
 
                 var (_, res) = await EvaluateOnCallFrame(id, "test.GetDefaultAndRequiredParamMixedTypes(\"a\", 23, true, 1.23f)", expect_ok: false);
-               Assert.Equal(
-                    "Unable to evaluate method 'GetDefaultAndRequiredParamMixedTypes'. Too many arguments passed.", 
-                    res.Error["exceptionDetails"]["exception"]["description"]?.Value<string>());
+                AssertEqual("Unable to evaluate method 'GetDefaultAndRequiredParamMixedTypes'. Too many arguments passed.", 
+                    res.Error["result"]["description"]?.Value<string>(), "wrong error message");
             });
 
         [Fact]
index 1767f56..4a444cd 100644 (file)
@@ -68,7 +68,7 @@ namespace DebuggerTests
                await CheckPointerValue(props, "*cp", TChar('q'));
            });
 
-        [Theory]
+        [ConditionalTheory(nameof(RunningOnChrome))]
         [MemberDataAttribute(nameof(PointersTestData))]
         public async Task InspectLocalPointerArrays(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
             type, method, line_offset, bp_function_name,
@@ -99,7 +99,7 @@ namespace DebuggerTests
                });
            });
 
-        [Theory]
+        [ConditionalTheory(nameof(RunningOnChrome))]
         [MemberDataAttribute(nameof(PointersTestData))]
         public async Task InspectLocalDoublePointerToPrimitiveTypeArrays(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
             type, method, line_offset, bp_function_name,
@@ -142,7 +142,7 @@ namespace DebuggerTests
                }
            });
 
-        [Theory]
+        [ConditionalTheory(nameof(RunningOnChrome))]
         [MemberDataAttribute(nameof(PointersTestData))]
         public async Task InspectLocalPointersToValueTypes(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
             type, method, line_offset, bp_function_name,
@@ -210,7 +210,7 @@ namespace DebuggerTests
                }
            });
 
-        [Theory]
+        [ConditionalTheory(nameof(RunningOnChrome))]
         [MemberDataAttribute(nameof(PointersTestData))]
         public async Task InspectLocalPointersToValueTypeArrays(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
             type, method, line_offset, bp_function_name,
@@ -243,7 +243,7 @@ namespace DebuggerTests
                }
            });
 
-        [Theory]
+        [ConditionalTheory(nameof(RunningOnChrome))]
         [MemberDataAttribute(nameof(PointersTestData))]
         public async Task InspectLocalPointersToGenericValueTypeArrays(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
             type, method, line_offset, bp_function_name,
@@ -310,7 +310,7 @@ namespace DebuggerTests
                }
            });
 
-        [Theory]
+        [ConditionalTheory(nameof(RunningOnChrome))]
         [MemberDataAttribute(nameof(PointersTestData))]
         public async Task InspectLocalDoublePointersToValueTypeArrays(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
             type, method, line_offset, bp_function_name,
@@ -380,7 +380,7 @@ namespace DebuggerTests
                 { $"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "PointersAsArgsTest", 2, "PointersAsArgsTest", true },
             };
 
-        [Theory]
+        [ConditionalTheory(nameof(RunningOnChrome))]
         [MemberDataAttribute(nameof(PointersAsMethodArgsTestData))]
         public async Task InspectPrimitiveTypePointersAsMethodArgs(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
             type, method, line_offset, bp_function_name,
@@ -453,7 +453,7 @@ namespace DebuggerTests
                }
            });
 
-        [Theory]
+        [ConditionalTheory(nameof(RunningOnChrome))]
         [MemberDataAttribute(nameof(PointersAsMethodArgsTestData))]
         public async Task InspectValueTypePointersAsMethodArgs(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
             type, method, line_offset, bp_function_name,