Update ws-proxy sources to match Mono commit c5149e31b4d7c (#20579)
This commit is contained in:
parent
96e70ebe0e
commit
3224092fdd
|
|
@ -21,6 +21,7 @@
|
|||
<!-- Dependencies of ws-proxy sources -->
|
||||
<Reference Include="Newtonsoft.Json" />
|
||||
<Reference Include="Mono.Cecil" />
|
||||
<Reference Include="Microsoft.CodeAnalysis.CSharp" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,256 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using System.Threading;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace WebAssembly.Net.Debugging {
|
||||
|
||||
internal struct SessionId {
|
||||
public readonly string sessionId;
|
||||
|
||||
public SessionId (string sessionId)
|
||||
{
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public override int GetHashCode ()
|
||||
=> sessionId?.GetHashCode () ?? 0;
|
||||
|
||||
public override bool Equals (object obj)
|
||||
=> (obj is SessionId) ? ((SessionId) obj).sessionId == sessionId : false;
|
||||
|
||||
public override string ToString ()
|
||||
=> $"session-{sessionId}";
|
||||
}
|
||||
|
||||
internal struct MessageId {
|
||||
public readonly string sessionId;
|
||||
public readonly int id;
|
||||
|
||||
public MessageId (string sessionId, int id)
|
||||
{
|
||||
this.sessionId = sessionId;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public static implicit operator SessionId (MessageId id)
|
||||
=> new SessionId (id.sessionId);
|
||||
|
||||
public override string ToString ()
|
||||
=> $"msg-{sessionId}:::{id}";
|
||||
|
||||
public override int GetHashCode ()
|
||||
=> (sessionId?.GetHashCode () ?? 0) ^ id.GetHashCode ();
|
||||
|
||||
public override bool Equals (object obj)
|
||||
=> (obj is MessageId) ? ((MessageId) obj).sessionId == sessionId && ((MessageId) obj).id == id : false;
|
||||
}
|
||||
|
||||
internal struct Result {
|
||||
public JObject Value { get; private set; }
|
||||
public JObject Error { get; private set; }
|
||||
|
||||
public bool IsOk => Value != null;
|
||||
public bool IsErr => Error != null;
|
||||
|
||||
Result (JObject result, JObject error)
|
||||
{
|
||||
if (result != null && error != null)
|
||||
throw new ArgumentException ($"Both {nameof(result)} and {nameof(error)} arguments cannot be non-null.");
|
||||
|
||||
bool resultHasError = String.Compare ((result? ["result"] as JObject)? ["subtype"]?. Value<string> (), "error") == 0;
|
||||
if (result != null && resultHasError) {
|
||||
this.Value = null;
|
||||
this.Error = result;
|
||||
} else {
|
||||
this.Value = result;
|
||||
this.Error = error;
|
||||
}
|
||||
}
|
||||
|
||||
public static Result FromJson (JObject obj)
|
||||
{
|
||||
//Log ("protocol", $"from result: {obj}");
|
||||
return new Result (obj ["result"] as JObject, obj ["error"] as JObject);
|
||||
}
|
||||
|
||||
public static Result Ok (JObject ok)
|
||||
=> new Result (ok, null);
|
||||
|
||||
public static Result OkFromObject (object ok)
|
||||
=> Ok (JObject.FromObject(ok));
|
||||
|
||||
public static Result Err (JObject err)
|
||||
=> new Result (null, err);
|
||||
|
||||
public static Result Exception (Exception e)
|
||||
=> new Result (null, JObject.FromObject (new { message = e.Message }));
|
||||
|
||||
public JObject ToJObject (MessageId target) {
|
||||
if (IsOk) {
|
||||
return JObject.FromObject (new {
|
||||
target.id,
|
||||
target.sessionId,
|
||||
result = Value
|
||||
});
|
||||
} else {
|
||||
return JObject.FromObject (new {
|
||||
target.id,
|
||||
target.sessionId,
|
||||
error = Error
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
return $"[Result: IsOk: {IsOk}, IsErr: {IsErr}, Value: {Value?.ToString ()}, Error: {Error?.ToString ()} ]";
|
||||
}
|
||||
}
|
||||
|
||||
internal class MonoCommands {
|
||||
public string expression { get; set; }
|
||||
public string objectGroup { get; set; } = "mono-debugger";
|
||||
public bool includeCommandLineAPI { get; set; } = false;
|
||||
public bool silent { get; set; } = false;
|
||||
public bool returnByValue { get; set; } = true;
|
||||
|
||||
public MonoCommands (string expression)
|
||||
=> this.expression = expression;
|
||||
|
||||
public static MonoCommands GetCallStack ()
|
||||
=> new MonoCommands ("MONO.mono_wasm_get_call_stack()");
|
||||
|
||||
public static MonoCommands IsRuntimeReady ()
|
||||
=> new MonoCommands ("MONO.mono_wasm_runtime_is_ready");
|
||||
|
||||
public static MonoCommands StartSingleStepping (StepKind kind)
|
||||
=> new MonoCommands ($"MONO.mono_wasm_start_single_stepping ({(int)kind})");
|
||||
|
||||
public static MonoCommands GetLoadedFiles ()
|
||||
=> new MonoCommands ("MONO.mono_wasm_get_loaded_files()");
|
||||
|
||||
public static MonoCommands ClearAllBreakpoints ()
|
||||
=> new MonoCommands ("MONO.mono_wasm_clear_all_breakpoints()");
|
||||
|
||||
public static MonoCommands GetObjectProperties (int objectId, bool expandValueTypes)
|
||||
=> new MonoCommands ($"MONO.mono_wasm_get_object_properties({objectId}, { (expandValueTypes ? "true" : "false") })");
|
||||
|
||||
public static MonoCommands GetArrayValues (int objectId)
|
||||
=> new MonoCommands ($"MONO.mono_wasm_get_array_values({objectId})");
|
||||
|
||||
public static MonoCommands GetArrayValueExpanded (int objectId, int idx)
|
||||
=> new MonoCommands ($"MONO.mono_wasm_get_array_value_expanded({objectId}, {idx})");
|
||||
|
||||
public static MonoCommands GetScopeVariables (int scopeId, params int[] vars)
|
||||
=> new MonoCommands ($"MONO.mono_wasm_get_variables({scopeId}, [ {string.Join (",", vars)} ])");
|
||||
|
||||
public static MonoCommands SetBreakpoint (string assemblyName, int methodToken, int ilOffset)
|
||||
=> new MonoCommands ($"MONO.mono_wasm_set_breakpoint (\"{assemblyName}\", {methodToken}, {ilOffset})");
|
||||
|
||||
public static MonoCommands RemoveBreakpoint (int breakpointId)
|
||||
=> new MonoCommands ($"MONO.mono_wasm_remove_breakpoint({breakpointId})");
|
||||
}
|
||||
|
||||
internal enum MonoErrorCodes {
|
||||
BpNotFound = 100000,
|
||||
}
|
||||
|
||||
internal class MonoConstants {
|
||||
public const string RUNTIME_IS_READY = "mono_wasm_runtime_ready";
|
||||
}
|
||||
|
||||
class Frame {
|
||||
public Frame (MethodInfo method, SourceLocation location, int id)
|
||||
{
|
||||
this.Method = method;
|
||||
this.Location = location;
|
||||
this.Id = id;
|
||||
}
|
||||
|
||||
public MethodInfo Method { get; private set; }
|
||||
public SourceLocation Location { get; private set; }
|
||||
public int Id { get; private set; }
|
||||
}
|
||||
|
||||
class Breakpoint {
|
||||
public SourceLocation Location { get; private set; }
|
||||
public int RemoteId { get; set; }
|
||||
public BreakpointState State { get; set; }
|
||||
public string StackId { get; private set; }
|
||||
|
||||
public static bool TryParseId (string stackId, out int id)
|
||||
{
|
||||
id = -1;
|
||||
if (stackId?.StartsWith ("dotnet:", StringComparison.Ordinal) != true)
|
||||
return false;
|
||||
|
||||
return int.TryParse (stackId.Substring ("dotnet:".Length), out id);
|
||||
}
|
||||
|
||||
public Breakpoint (string stackId, SourceLocation loc, BreakpointState state)
|
||||
{
|
||||
this.StackId = stackId;
|
||||
this.Location = loc;
|
||||
this.State = state;
|
||||
}
|
||||
}
|
||||
|
||||
enum BreakpointState {
|
||||
Active,
|
||||
Disabled,
|
||||
Pending
|
||||
}
|
||||
|
||||
enum StepKind {
|
||||
Into,
|
||||
Out,
|
||||
Over
|
||||
}
|
||||
|
||||
internal class ExecutionContext {
|
||||
public string DebuggerId { get; set; }
|
||||
public Dictionary<string,BreakpointRequest> BreakpointRequests { get; } = new Dictionary<string,BreakpointRequest> ();
|
||||
|
||||
public TaskCompletionSource<DebugStore> ready = null;
|
||||
public bool IsRuntimeReady => ready != null && ready.Task.IsCompleted;
|
||||
|
||||
public int Id { get; set; }
|
||||
public object AuxData { get; set; }
|
||||
|
||||
public List<Frame> CallStack { get; set; }
|
||||
|
||||
internal DebugStore store;
|
||||
public TaskCompletionSource<DebugStore> Source { get; } = new TaskCompletionSource<DebugStore> ();
|
||||
|
||||
int nextValueTypeId = 0;
|
||||
public Dictionary<string, JToken> ValueTypesCache = new Dictionary<string, JToken> ();
|
||||
public Dictionary<string, JToken> LocalsCache = new Dictionary<string, JToken> ();
|
||||
|
||||
public DebugStore Store {
|
||||
get {
|
||||
if (store == null || !Source.Task.IsCompleted)
|
||||
return null;
|
||||
|
||||
return store;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearState ()
|
||||
{
|
||||
CallStack = null;
|
||||
ValueTypesCache.Clear ();
|
||||
LocalsCache.Clear ();
|
||||
nextValueTypeId = 0;
|
||||
}
|
||||
|
||||
public int NextValueTypeId () => Interlocked.Increment (ref nextValueTypeId);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -11,103 +11,6 @@ using System.Collections.Generic;
|
|||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace WebAssembly.Net.Debugging {
|
||||
internal struct SessionId {
|
||||
public readonly string sessionId;
|
||||
|
||||
public SessionId (string sessionId)
|
||||
{
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public override int GetHashCode ()
|
||||
=> sessionId?.GetHashCode () ?? 0;
|
||||
|
||||
public override bool Equals (object obj)
|
||||
=> (obj is SessionId) ? ((SessionId) obj).sessionId == sessionId : false;
|
||||
|
||||
public override string ToString ()
|
||||
=> $"session-{sessionId}";
|
||||
}
|
||||
|
||||
internal struct MessageId {
|
||||
public readonly string sessionId;
|
||||
public readonly int id;
|
||||
|
||||
public MessageId (string sessionId, int id)
|
||||
{
|
||||
this.sessionId = sessionId;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public static implicit operator SessionId (MessageId id)
|
||||
=> new SessionId (id.sessionId);
|
||||
|
||||
public override string ToString ()
|
||||
=> $"msg-{sessionId}:::{id}";
|
||||
|
||||
public override int GetHashCode ()
|
||||
=> (sessionId?.GetHashCode () ?? 0) ^ id.GetHashCode ();
|
||||
|
||||
public override bool Equals (object obj)
|
||||
=> (obj is MessageId) ? ((MessageId) obj).sessionId == sessionId && ((MessageId) obj).id == id : false;
|
||||
}
|
||||
|
||||
internal struct Result {
|
||||
public JObject Value { get; private set; }
|
||||
public JObject Error { get; private set; }
|
||||
|
||||
public bool IsOk => Value != null;
|
||||
public bool IsErr => Error != null;
|
||||
|
||||
Result (JObject result, JObject error)
|
||||
{
|
||||
if (result != null && error != null)
|
||||
throw new ArgumentException ($"Both {nameof(result)} and {nameof(error)} arguments cannot be non-null.");
|
||||
|
||||
bool resultHasError = String.Compare ((result? ["result"] as JObject)? ["subtype"]?. Value<string> (), "error") == 0;
|
||||
if (result != null && resultHasError) {
|
||||
this.Value = null;
|
||||
this.Error = result;
|
||||
} else {
|
||||
this.Value = result;
|
||||
this.Error = error;
|
||||
}
|
||||
}
|
||||
|
||||
public static Result FromJson (JObject obj)
|
||||
{
|
||||
//Log ("protocol", $"from result: {obj}");
|
||||
return new Result (obj ["result"] as JObject, obj ["error"] as JObject);
|
||||
}
|
||||
|
||||
public static Result Ok (JObject ok)
|
||||
=> new Result (ok, null);
|
||||
|
||||
public static Result OkFromObject (object ok)
|
||||
=> Ok (JObject.FromObject(ok));
|
||||
|
||||
public static Result Err (JObject err)
|
||||
=> new Result (null, err);
|
||||
|
||||
public static Result Exception (Exception e)
|
||||
=> new Result (null, JObject.FromObject (new { message = e.Message }));
|
||||
|
||||
public JObject ToJObject (MessageId target) {
|
||||
if (IsOk) {
|
||||
return JObject.FromObject (new {
|
||||
target.id,
|
||||
target.sessionId,
|
||||
result = Value
|
||||
});
|
||||
} else {
|
||||
return JObject.FromObject (new {
|
||||
target.id,
|
||||
target.sessionId,
|
||||
error = Error
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DevToolsQueue {
|
||||
Task current_send;
|
||||
|
|
@ -293,11 +196,12 @@ namespace WebAssembly.Net.Debugging {
|
|||
int id = Interlocked.Increment (ref next_cmd_id);
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
sessionId.sessionId,
|
||||
id,
|
||||
method,
|
||||
@params = args
|
||||
});
|
||||
if (sessionId.sessionId != null)
|
||||
o["sessionId"] = sessionId.sessionId;
|
||||
var tcs = new TaskCompletionSource<Result> ();
|
||||
|
||||
var msgId = new MessageId (sessionId.sessionId, id);
|
||||
|
|
@ -317,10 +221,11 @@ namespace WebAssembly.Net.Debugging {
|
|||
void SendEventInternal (SessionId sessionId, string method, JObject args, CancellationToken token)
|
||||
{
|
||||
var o = JObject.FromObject (new {
|
||||
sessionId.sessionId,
|
||||
method,
|
||||
@params = args
|
||||
});
|
||||
if (sessionId.sessionId != null)
|
||||
o["sessionId"] = sessionId.sessionId;
|
||||
|
||||
Send (this.ide, o, token);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,182 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using System.Threading;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Emit;
|
||||
using System.Reflection;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace WebAssembly.Net.Debugging {
|
||||
|
||||
internal class EvaluateExpression {
|
||||
|
||||
class FindThisExpression : CSharpSyntaxWalker {
|
||||
public List<string> thisExpressions = new List<string> ();
|
||||
public SyntaxTree syntaxTree;
|
||||
public FindThisExpression (SyntaxTree syntax)
|
||||
{
|
||||
syntaxTree = syntax;
|
||||
}
|
||||
public override void Visit (SyntaxNode node)
|
||||
{
|
||||
if (node is ThisExpressionSyntax) {
|
||||
if (node.Parent is MemberAccessExpressionSyntax thisParent && thisParent.Name is IdentifierNameSyntax) {
|
||||
IdentifierNameSyntax var = thisParent.Name as IdentifierNameSyntax;
|
||||
thisExpressions.Add(var.Identifier.Text);
|
||||
var newRoot = syntaxTree.GetRoot ().ReplaceNode (node.Parent, thisParent.Name);
|
||||
syntaxTree = syntaxTree.WithRootAndOptions (newRoot, syntaxTree.Options);
|
||||
this.Visit (GetExpressionFromSyntaxTree(syntaxTree));
|
||||
}
|
||||
}
|
||||
else
|
||||
base.Visit (node);
|
||||
}
|
||||
|
||||
public async Task CheckIfIsProperty (MonoProxy proxy, MessageId msg_id, int scope_id, CancellationToken token)
|
||||
{
|
||||
foreach (var var in thisExpressions) {
|
||||
JToken value = await proxy.TryGetVariableValue (msg_id, scope_id, var, true, token);
|
||||
if (value == null)
|
||||
throw new Exception ($"The property {var} does not exist in the current context");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FindVariableNMethodCall : CSharpSyntaxWalker {
|
||||
public List<IdentifierNameSyntax> variables = new List<IdentifierNameSyntax> ();
|
||||
public List<ThisExpressionSyntax> thisList = new List<ThisExpressionSyntax> ();
|
||||
public List<InvocationExpressionSyntax> methodCall = new List<InvocationExpressionSyntax> ();
|
||||
public List<object> values = new List<Object> ();
|
||||
|
||||
public override void Visit (SyntaxNode node)
|
||||
{
|
||||
if (node is IdentifierNameSyntax identifier && !variables.Any (x => x.Identifier.Text == identifier.Identifier.Text))
|
||||
variables.Add (identifier);
|
||||
if (node is InvocationExpressionSyntax) {
|
||||
methodCall.Add (node as InvocationExpressionSyntax);
|
||||
throw new Exception ("Method Call is not implemented yet");
|
||||
}
|
||||
if (node is AssignmentExpressionSyntax)
|
||||
throw new Exception ("Assignment is not implemented yet");
|
||||
base.Visit (node);
|
||||
}
|
||||
public async Task<SyntaxTree> ReplaceVars (SyntaxTree syntaxTree, MonoProxy proxy, MessageId msg_id, int scope_id, CancellationToken token)
|
||||
{
|
||||
CompilationUnitSyntax root = syntaxTree.GetCompilationUnitRoot ();
|
||||
foreach (var var in variables) {
|
||||
ClassDeclarationSyntax classDeclaration = root.Members.ElementAt (0) as ClassDeclarationSyntax;
|
||||
MethodDeclarationSyntax method = classDeclaration.Members.ElementAt (0) as MethodDeclarationSyntax;
|
||||
|
||||
JToken value = await proxy.TryGetVariableValue (msg_id, scope_id, var.Identifier.Text, false, token);
|
||||
|
||||
if (value == null)
|
||||
throw new Exception ($"The name {var.Identifier.Text} does not exist in the current context");
|
||||
|
||||
values.Add (ConvertJSToCSharpType (value ["value"] ["value"].ToString (), value ["value"] ["type"].ToString ()));
|
||||
|
||||
var updatedMethod = method.AddParameterListParameters (
|
||||
SyntaxFactory.Parameter (
|
||||
SyntaxFactory.Identifier (var.Identifier.Text))
|
||||
.WithType (SyntaxFactory.ParseTypeName (GetTypeFullName(value["value"]["type"].ToString()))));
|
||||
root = root.ReplaceNode (method, updatedMethod);
|
||||
}
|
||||
syntaxTree = syntaxTree.WithRootAndOptions (root, syntaxTree.Options);
|
||||
return syntaxTree;
|
||||
}
|
||||
|
||||
private object ConvertJSToCSharpType (string v, string type)
|
||||
{
|
||||
switch (type) {
|
||||
case "number":
|
||||
return Convert.ChangeType (v, typeof (int));
|
||||
case "string":
|
||||
return v;
|
||||
}
|
||||
|
||||
throw new Exception ($"Evaluate of this datatype {type} not implemented yet");
|
||||
}
|
||||
|
||||
private string GetTypeFullName (string type)
|
||||
{
|
||||
switch (type) {
|
||||
case "number":
|
||||
return typeof (int).FullName;
|
||||
case "string":
|
||||
return typeof (string).FullName;
|
||||
}
|
||||
|
||||
throw new Exception ($"Evaluate of this datatype {type} not implemented yet");
|
||||
}
|
||||
}
|
||||
static SyntaxNode GetExpressionFromSyntaxTree (SyntaxTree syntaxTree)
|
||||
{
|
||||
CompilationUnitSyntax root = syntaxTree.GetCompilationUnitRoot ();
|
||||
ClassDeclarationSyntax classDeclaration = root.Members.ElementAt (0) as ClassDeclarationSyntax;
|
||||
MethodDeclarationSyntax methodDeclaration = classDeclaration.Members.ElementAt (0) as MethodDeclarationSyntax;
|
||||
BlockSyntax blockValue = methodDeclaration.Body;
|
||||
ReturnStatementSyntax returnValue = blockValue.Statements.ElementAt (0) as ReturnStatementSyntax;
|
||||
InvocationExpressionSyntax expressionInvocation = returnValue.Expression as InvocationExpressionSyntax;
|
||||
MemberAccessExpressionSyntax expressionMember = expressionInvocation.Expression as MemberAccessExpressionSyntax;
|
||||
ParenthesizedExpressionSyntax expressionParenthesized = expressionMember.Expression as ParenthesizedExpressionSyntax;
|
||||
return expressionParenthesized.Expression;
|
||||
}
|
||||
internal static async Task<string> CompileAndRunTheExpression (MonoProxy proxy, MessageId msg_id, int scope_id, string expression, CancellationToken token)
|
||||
{
|
||||
FindVariableNMethodCall findVarNMethodCall = new FindVariableNMethodCall ();
|
||||
string retString;
|
||||
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText (@"
|
||||
using System;
|
||||
public class CompileAndRunTheExpression
|
||||
{
|
||||
public string Evaluate()
|
||||
{
|
||||
return (" + expression + @").ToString();
|
||||
}
|
||||
}");
|
||||
|
||||
FindThisExpression findThisExpression = new FindThisExpression (syntaxTree);
|
||||
var expressionTree = GetExpressionFromSyntaxTree(syntaxTree);
|
||||
findThisExpression.Visit (expressionTree);
|
||||
await findThisExpression.CheckIfIsProperty (proxy, msg_id, scope_id, token);
|
||||
syntaxTree = findThisExpression.syntaxTree;
|
||||
|
||||
expressionTree = GetExpressionFromSyntaxTree (syntaxTree);
|
||||
findVarNMethodCall.Visit (expressionTree);
|
||||
|
||||
syntaxTree = await findVarNMethodCall.ReplaceVars (syntaxTree, proxy, msg_id, scope_id, token);
|
||||
|
||||
MetadataReference [] references = new MetadataReference []
|
||||
{
|
||||
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
|
||||
MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location)
|
||||
};
|
||||
|
||||
CSharpCompilation compilation = CSharpCompilation.Create (
|
||||
"compileAndRunTheExpression",
|
||||
syntaxTrees: new [] { syntaxTree },
|
||||
references: references,
|
||||
options: new CSharpCompilationOptions (OutputKind.DynamicallyLinkedLibrary));
|
||||
using (var ms = new MemoryStream ()) {
|
||||
EmitResult result = compilation.Emit (ms);
|
||||
ms.Seek (0, SeekOrigin.Begin);
|
||||
Assembly assembly = Assembly.Load (ms.ToArray ());
|
||||
Type type = assembly.GetType ("CompileAndRunTheExpression");
|
||||
object obj = Activator.CreateInstance (type);
|
||||
var ret = type.InvokeMember ("Evaluate",
|
||||
BindingFlags.Default | BindingFlags.InvokeMethod,
|
||||
null,
|
||||
obj,
|
||||
//new object [] { 10 }
|
||||
findVarNMethodCall.values.ToArray ());
|
||||
retString = ret.ToString ();
|
||||
}
|
||||
return retString;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,137 +8,17 @@ using System.IO;
|
|||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
|
||||
namespace WebAssembly.Net.Debugging {
|
||||
|
||||
internal class MonoCommands {
|
||||
public string expression { get; set; }
|
||||
public string objectGroup { get; set; } = "mono-debugger";
|
||||
public bool includeCommandLineAPI { get; set; } = false;
|
||||
public bool silent { get; set; } = false;
|
||||
public bool returnByValue { get; set; } = true;
|
||||
|
||||
public MonoCommands (string expression)
|
||||
=> this.expression = expression;
|
||||
|
||||
public static MonoCommands GetCallStack ()
|
||||
=> new MonoCommands ("MONO.mono_wasm_get_call_stack()");
|
||||
|
||||
public static MonoCommands IsRuntimeReady ()
|
||||
=> new MonoCommands ("MONO.mono_wasm_runtime_is_ready");
|
||||
|
||||
public static MonoCommands StartSingleStepping (StepKind kind)
|
||||
=> new MonoCommands ($"MONO.mono_wasm_start_single_stepping ({(int)kind})");
|
||||
|
||||
public static MonoCommands GetLoadedFiles ()
|
||||
=> new MonoCommands ("MONO.mono_wasm_get_loaded_files()");
|
||||
|
||||
public static MonoCommands ClearAllBreakpoints ()
|
||||
=> new MonoCommands ("MONO.mono_wasm_clear_all_breakpoints()");
|
||||
|
||||
public static MonoCommands GetObjectProperties (int objectId)
|
||||
=> new MonoCommands ($"MONO.mono_wasm_get_object_properties({objectId})");
|
||||
|
||||
public static MonoCommands GetArrayValues (int objectId)
|
||||
=> new MonoCommands ($"MONO.mono_wasm_get_array_values({objectId})");
|
||||
|
||||
public static MonoCommands GetScopeVariables (int scopeId, params int[] vars)
|
||||
=> new MonoCommands ($"MONO.mono_wasm_get_variables({scopeId}, [ {string.Join (",", vars)} ])");
|
||||
|
||||
public static MonoCommands SetBreakpoint (string assemblyName, int methodToken, int ilOffset)
|
||||
=> new MonoCommands ($"MONO.mono_wasm_set_breakpoint (\"{assemblyName}\", {methodToken}, {ilOffset})");
|
||||
|
||||
public static MonoCommands RemoveBreakpoint (int breakpointId)
|
||||
=> new MonoCommands ($"MONO.mono_wasm_remove_breakpoint({breakpointId})");
|
||||
}
|
||||
|
||||
internal enum MonoErrorCodes {
|
||||
BpNotFound = 100000,
|
||||
}
|
||||
|
||||
internal class MonoConstants {
|
||||
public const string RUNTIME_IS_READY = "mono_wasm_runtime_ready";
|
||||
}
|
||||
|
||||
class Frame {
|
||||
public Frame (MethodInfo method, SourceLocation location, int id)
|
||||
{
|
||||
this.Method = method;
|
||||
this.Location = location;
|
||||
this.Id = id;
|
||||
}
|
||||
|
||||
public MethodInfo Method { get; private set; }
|
||||
public SourceLocation Location { get; private set; }
|
||||
public int Id { get; private set; }
|
||||
}
|
||||
|
||||
class Breakpoint {
|
||||
public SourceLocation Location { get; private set; }
|
||||
public int RemoteId { get; set; }
|
||||
public BreakpointState State { get; set; }
|
||||
public string StackId { get; private set; }
|
||||
|
||||
public static bool TryParseId (string stackId, out int id)
|
||||
{
|
||||
id = -1;
|
||||
if (stackId?.StartsWith ("dotnet:", StringComparison.Ordinal) != true)
|
||||
return false;
|
||||
|
||||
return int.TryParse (stackId.Substring ("dotnet:".Length), out id);
|
||||
}
|
||||
|
||||
public Breakpoint (string stackId, SourceLocation loc, BreakpointState state)
|
||||
{
|
||||
this.StackId = stackId;
|
||||
this.Location = loc;
|
||||
this.State = state;
|
||||
}
|
||||
}
|
||||
|
||||
enum BreakpointState {
|
||||
Active,
|
||||
Disabled,
|
||||
Pending
|
||||
}
|
||||
|
||||
enum StepKind {
|
||||
Into,
|
||||
Out,
|
||||
Over
|
||||
}
|
||||
|
||||
internal class ExecutionContext {
|
||||
public string DebuggerId { get; set; }
|
||||
public Dictionary<string,BreakpointRequest> BreakpointRequests { get; } = new Dictionary<string,BreakpointRequest> ();
|
||||
|
||||
public TaskCompletionSource<DebugStore> ready = null;
|
||||
public bool IsRuntimeReady => ready != null && ready.Task.IsCompleted;
|
||||
|
||||
public int Id { get; set; }
|
||||
public object AuxData { get; set; }
|
||||
|
||||
public List<Frame> CallStack { get; set; }
|
||||
|
||||
internal DebugStore store;
|
||||
public TaskCompletionSource<DebugStore> Source { get; } = new TaskCompletionSource<DebugStore> ();
|
||||
|
||||
public DebugStore Store {
|
||||
get {
|
||||
if (store == null || !Source.Task.IsCompleted)
|
||||
return null;
|
||||
|
||||
return store;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class MonoProxy : DevToolsProxy {
|
||||
Dictionary<SessionId, ExecutionContext> contexts = new Dictionary<SessionId, ExecutionContext> ();
|
||||
|
||||
public MonoProxy (ILoggerFactory loggerFactory) : base(loggerFactory) { }
|
||||
|
||||
ExecutionContext GetContext (SessionId sessionId)
|
||||
internal ExecutionContext GetContext (SessionId sessionId)
|
||||
{
|
||||
if (contexts.TryGetValue (sessionId, out var context))
|
||||
return context;
|
||||
|
|
@ -221,11 +101,14 @@ namespace WebAssembly.Net.Debugging {
|
|||
|
||||
protected override async Task<bool> AcceptCommand (MessageId id, string method, JObject args, CancellationToken token)
|
||||
{
|
||||
if (!contexts.TryGetValue (id, out var context))
|
||||
return false;
|
||||
|
||||
switch (method) {
|
||||
case "Debugger.enable": {
|
||||
var resp = await SendCommand (id, method, args, token);
|
||||
|
||||
GetContext (id).DebuggerId = resp.Value ["debuggerId"]?.ToString ();
|
||||
context.DebuggerId = resp.Value ["debuggerId"]?.ToString ();
|
||||
|
||||
if (await IsRuntimeAlreadyReadyAlready (id, token))
|
||||
await RuntimeReady (id, token);
|
||||
|
|
@ -270,7 +153,6 @@ namespace WebAssembly.Net.Debugging {
|
|||
}
|
||||
|
||||
case "Debugger.setBreakpointByUrl": {
|
||||
var context = GetContext (id);
|
||||
var resp = await SendCommand (id, method, args, token);
|
||||
if (!resp.IsOk) {
|
||||
SendResponse (id, resp, token);
|
||||
|
|
@ -312,6 +194,23 @@ namespace WebAssembly.Net.Debugging {
|
|||
return await Step (id, StepKind.Over, token);
|
||||
}
|
||||
|
||||
case "Debugger.evaluateOnCallFrame": {
|
||||
var objId = args? ["callFrameId"]?.Value<string> ();
|
||||
if (objId.StartsWith ("dotnet:", StringComparison.Ordinal)) {
|
||||
var parts = objId.Split (new char [] { ':' });
|
||||
if (parts.Length < 3)
|
||||
return true;
|
||||
switch (parts [1]) {
|
||||
case "scope": {
|
||||
await GetEvaluateOnCallFrame (id, int.Parse (parts [2]), args? ["expression"]?.Value<string> (), token);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
case "Runtime.getProperties": {
|
||||
var objId = args? ["objectId"]?.Value<string> ();
|
||||
if (objId.StartsWith ("dotnet:", StringComparison.Ordinal)) {
|
||||
|
|
@ -324,14 +223,26 @@ namespace WebAssembly.Net.Debugging {
|
|||
break;
|
||||
}
|
||||
case "object": {
|
||||
await GetDetails (id, MonoCommands.GetObjectProperties (int.Parse (parts[2])), token);
|
||||
await GetDetails (id, MonoCommands.GetObjectProperties (int.Parse (parts[2]), expandValueTypes: false), token);
|
||||
break;
|
||||
}
|
||||
case "array": {
|
||||
await GetDetails (id, MonoCommands.GetArrayValues (int.Parse (parts [2])), token);
|
||||
await GetArrayDetails (id, objId, parts, token);
|
||||
break;
|
||||
}
|
||||
case "valuetype": {
|
||||
await GetDetailsForValueType (id, objId,
|
||||
get_props_cmd_fn: () => {
|
||||
if (parts.Length < 4)
|
||||
return null;
|
||||
|
||||
var containerObjId = int.Parse (parts[2]);
|
||||
return MonoCommands.GetObjectProperties (containerObjId, expandValueTypes: true);
|
||||
}, token);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
|
@ -341,6 +252,30 @@ namespace WebAssembly.Net.Debugging {
|
|||
return false;
|
||||
}
|
||||
|
||||
async Task GetArrayDetails (MessageId id, string objId, string[] objIdParts, CancellationToken token)
|
||||
{
|
||||
switch (objIdParts.Length)
|
||||
{
|
||||
case 3: {
|
||||
await GetDetails (id, MonoCommands.GetArrayValues (int.Parse (objIdParts [2])), token);
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
// This form of the id is being used only for valuetypes right now
|
||||
await GetDetailsForValueType(id, objId,
|
||||
get_props_cmd_fn: () => {
|
||||
var arrayObjectId = int.Parse (objIdParts [2]);
|
||||
var idx = int.Parse (objIdParts [3]);
|
||||
return MonoCommands.GetArrayValueExpanded (arrayObjectId, idx);
|
||||
}, token);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
SendResponse (id, Result.Exception (new ArgumentException ($"Unknown objectId format for array: {objId}")), token);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//static int frame_id=0;
|
||||
async Task<bool> OnBreakpointHit (SessionId sessionId, JObject args, CancellationToken token)
|
||||
{
|
||||
|
|
@ -381,6 +316,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
var the_mono_frames = res.Value? ["result"]? ["value"]? ["frames"]?.Values<JObject> ();
|
||||
|
||||
foreach (var mono_frame in the_mono_frames) {
|
||||
++frame_id;
|
||||
var il_pos = mono_frame ["il_pos"].Value<int> ();
|
||||
var method_token = mono_frame ["method_token"].Value<int> ();
|
||||
var assembly_name = mono_frame ["assembly_name"].Value<string> ();
|
||||
|
|
@ -411,11 +347,11 @@ namespace WebAssembly.Net.Debugging {
|
|||
|
||||
Log ("info", $"frame il offset: {il_pos} method token: {method_token} assembly name: {assembly_name}");
|
||||
Log ("info", $"\tmethod {method.Name} location: {location}");
|
||||
frames.Add (new Frame (method, location, frame_id));
|
||||
frames.Add (new Frame (method, location, frame_id-1));
|
||||
|
||||
callFrames.Add (new {
|
||||
functionName = method.Name,
|
||||
callFrameId = $"dotnet:scope:{frame_id}",
|
||||
callFrameId = $"dotnet:scope:{frame_id-1}",
|
||||
functionLocation = method.StartLocation.AsLocation (),
|
||||
|
||||
location = location.AsLocation (),
|
||||
|
|
@ -429,7 +365,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
@type = "object",
|
||||
className = "Object",
|
||||
description = "Object",
|
||||
objectId = $"dotnet:scope:{frame_id}",
|
||||
objectId = $"dotnet:scope:{frame_id-1}",
|
||||
},
|
||||
name = method.Name,
|
||||
startLocation = method.StartLocation.AsLocation (),
|
||||
|
|
@ -437,7 +373,6 @@ namespace WebAssembly.Net.Debugging {
|
|||
}}
|
||||
});
|
||||
|
||||
++frame_id;
|
||||
context.CallStack = frames;
|
||||
|
||||
}
|
||||
|
|
@ -477,7 +412,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
async Task OnResume (MessageId msd_id, CancellationToken token)
|
||||
{
|
||||
//discard managed frames
|
||||
GetContext (msd_id).CallStack = null;
|
||||
GetContext (msd_id).ClearState ();
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
|
@ -495,30 +430,20 @@ namespace WebAssembly.Net.Debugging {
|
|||
var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
|
||||
|
||||
if (ret_code.HasValue && ret_code.Value == 0) {
|
||||
context.CallStack = null;
|
||||
context.ClearState ();
|
||||
await SendCommand (msg_id, "Debugger.stepOut", new JObject (), token);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
SendResponse (msg_id, Result.Ok (new JObject ()), token);
|
||||
|
||||
context.CallStack = null;
|
||||
context.ClearState ();
|
||||
|
||||
await SendCommand (msg_id, "Debugger.resume", new JObject (), token);
|
||||
return true;
|
||||
}
|
||||
|
||||
static string FormatFieldName (string name)
|
||||
{
|
||||
if (name.Contains("k__BackingField", StringComparison.Ordinal)) {
|
||||
return name.Replace("k__BackingField", "", StringComparison.Ordinal)
|
||||
.Replace("<", "", StringComparison.Ordinal)
|
||||
.Replace(">", "", StringComparison.Ordinal);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
async Task GetDetails(MessageId msg_id, MonoCommands cmd, CancellationToken token)
|
||||
async Task GetDetails(MessageId msg_id, MonoCommands cmd, CancellationToken token, bool send_response = true)
|
||||
{
|
||||
var res = await SendMonoCommand(msg_id, cmd, token);
|
||||
|
||||
|
|
@ -529,43 +454,124 @@ namespace WebAssembly.Net.Debugging {
|
|||
}
|
||||
|
||||
try {
|
||||
var values = res.Value?["result"]?["value"]?.Values<JObject>().ToArray() ?? Array.Empty<JObject>();
|
||||
var var_list = new List<JObject>();
|
||||
var var_list = res.Value?["result"]?["value"]?.Values<JObject>().ToArray() ?? Array.Empty<JObject>();
|
||||
if (var_list.Length > 0)
|
||||
ExtractAndCacheValueTypes (GetContext (msg_id), var_list);
|
||||
|
||||
// Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously
|
||||
// results in a "Memory access out of bounds", causing 'values' to be null,
|
||||
// so skip returning variable values in that case.
|
||||
for (int i = 0; i < values.Length; i+=2)
|
||||
{
|
||||
string fieldName = FormatFieldName ((string)values[i]["name"]);
|
||||
var value = values [i + 1]? ["value"];
|
||||
if (((string)value ["description"]) == null)
|
||||
value ["description"] = value ["value"]?.ToString ();
|
||||
if (!send_response)
|
||||
return;
|
||||
|
||||
var_list.Add(JObject.FromObject(new {
|
||||
name = fieldName,
|
||||
value
|
||||
}));
|
||||
|
||||
}
|
||||
var response = JObject.FromObject(new
|
||||
{
|
||||
result = var_list
|
||||
});
|
||||
|
||||
SendResponse(msg_id, Result.Ok(response), token);
|
||||
} catch (Exception e) {
|
||||
} catch (Exception e) when (send_response) {
|
||||
Log ("verbose", $"failed to parse {res.Value} - {e.Message}");
|
||||
SendResponse(msg_id, Result.Exception(e), token);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal bool TryFindVariableValueInCache(ExecutionContext ctx, string expression, bool only_search_on_this, out JToken obj)
|
||||
{
|
||||
if (ctx.LocalsCache.TryGetValue (expression, out obj)) {
|
||||
if (only_search_on_this && obj["fromThis"] == null)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal async Task<JToken> TryGetVariableValue (MessageId msg_id, int scope_id, string expression, bool only_search_on_this, CancellationToken token)
|
||||
{
|
||||
JToken thisValue = null;
|
||||
var context = GetContext (msg_id);
|
||||
if (context.CallStack == null)
|
||||
return null;
|
||||
|
||||
if (TryFindVariableValueInCache(context, expression, only_search_on_this, out JToken obj))
|
||||
return obj;
|
||||
|
||||
var scope = context.CallStack.FirstOrDefault (s => s.Id == scope_id);
|
||||
var vars = scope.Method.GetLiveVarsAt (scope.Location.CliLocation.Offset);
|
||||
//get_this
|
||||
int [] var_ids = { };
|
||||
var res = await SendMonoCommand (msg_id, MonoCommands.GetScopeVariables (scope.Id, var_ids), token);
|
||||
var values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray ();
|
||||
thisValue = values.FirstOrDefault (v => v ["name"].Value<string> () == "this");
|
||||
|
||||
if (!only_search_on_this) {
|
||||
if (thisValue != null && expression == "this") {
|
||||
return thisValue;
|
||||
}
|
||||
//search in locals
|
||||
var var_id = vars.SingleOrDefault (v => v.Name == expression);
|
||||
if (var_id != null) {
|
||||
res = await SendMonoCommand (msg_id, MonoCommands.GetScopeVariables (scope.Id, new int [] { var_id.Index }), token);
|
||||
values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray ();
|
||||
return values [0];
|
||||
}
|
||||
}
|
||||
|
||||
//search in scope
|
||||
if (thisValue != null) {
|
||||
var objectId = thisValue ["value"] ["objectId"].Value<string> ();
|
||||
var parts = objectId.Split (new char [] { ':' });
|
||||
res = await SendMonoCommand (msg_id, MonoCommands.GetObjectProperties (int.Parse (parts [2]), expandValueTypes: false), token);
|
||||
values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray ();
|
||||
var foundValue = values.FirstOrDefault (v => v ["name"].Value<string> () == expression);
|
||||
if (foundValue != null) {
|
||||
foundValue["fromThis"] = true;
|
||||
context.LocalsCache[foundValue ["name"].Value<string> ()] = foundValue;
|
||||
return foundValue;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async Task GetEvaluateOnCallFrame (MessageId msg_id, int scope_id, string expression, CancellationToken token)
|
||||
{
|
||||
try {
|
||||
var context = GetContext (msg_id);
|
||||
if (context.CallStack == null)
|
||||
return;
|
||||
|
||||
var varValue = await TryGetVariableValue (msg_id, scope_id, expression, false, token);
|
||||
|
||||
if (varValue != null) {
|
||||
varValue ["value"] ["description"] = varValue ["value"] ["className"];
|
||||
SendResponse (msg_id, Result.OkFromObject (new {
|
||||
result = varValue ["value"]
|
||||
}), token);
|
||||
return;
|
||||
}
|
||||
|
||||
string retValue = await EvaluateExpression.CompileAndRunTheExpression (this, msg_id, scope_id, expression, token);
|
||||
SendResponse (msg_id, Result.OkFromObject (new {
|
||||
result = new {
|
||||
value = retValue
|
||||
}
|
||||
}), token);
|
||||
} catch (Exception e) {
|
||||
logger.LogTrace (e.Message, expression);
|
||||
SendResponse (msg_id, Result.OkFromObject (new {}), token);
|
||||
}
|
||||
}
|
||||
|
||||
async Task GetScopeProperties (MessageId msg_id, int scope_id, CancellationToken token)
|
||||
{
|
||||
|
||||
try {
|
||||
var scope = GetContext (msg_id).CallStack.FirstOrDefault (s => s.Id == scope_id);
|
||||
var ctx = GetContext (msg_id);
|
||||
var scope = ctx.CallStack.FirstOrDefault (s => s.Id == scope_id);
|
||||
if (scope == null) {
|
||||
SendResponse (msg_id,
|
||||
Result.Err (JObject.FromObject (new { message = $"Could not find scope with id #{scope_id}" })),
|
||||
token);
|
||||
return;
|
||||
}
|
||||
var vars = scope.Method.GetLiveVarsAt (scope.Location.CliLocation.Offset);
|
||||
|
||||
var var_ids = vars.Select (v => v.Index).ToArray ();
|
||||
|
|
@ -584,39 +590,17 @@ namespace WebAssembly.Net.Debugging {
|
|||
return;
|
||||
}
|
||||
|
||||
ExtractAndCacheValueTypes (ctx, values);
|
||||
|
||||
var var_list = new List<object> ();
|
||||
int i = 0;
|
||||
// Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously
|
||||
// results in a "Memory access out of bounds", causing 'values' to be null,
|
||||
// so skip returning variable values in that case.
|
||||
while (i < vars.Length && i < values.Length) {
|
||||
var value = values [i] ["value"];
|
||||
if (((string)value ["description"]) == null)
|
||||
value ["description"] = value ["value"]?.ToString ();
|
||||
|
||||
var_list.Add (new {
|
||||
name = vars [i].Name,
|
||||
value
|
||||
});
|
||||
i++;
|
||||
for (; i < vars.Length && i < values.Length; i ++) {
|
||||
ctx.LocalsCache[vars [i].Name] = values [i];
|
||||
var_list.Add (new { name = vars [i].Name, value = values [i]["value"] });
|
||||
}
|
||||
//Async methods are special in the way that local variables can be lifted to generated class fields
|
||||
//value of "this" comes here either
|
||||
while (i < values.Length) {
|
||||
String name = values [i] ["name"].ToString ();
|
||||
|
||||
if (name.IndexOf (">", StringComparison.Ordinal) > 0)
|
||||
name = name.Substring (1, name.IndexOf (">", StringComparison.Ordinal) - 1);
|
||||
|
||||
var value = values [i + 1] ["value"];
|
||||
if (((string)value ["description"]) == null)
|
||||
value ["description"] = value ["value"]?.ToString ();
|
||||
|
||||
var_list.Add (new {
|
||||
name,
|
||||
value
|
||||
});
|
||||
i = i + 2;
|
||||
for (; i < values.Length; i ++) {
|
||||
ctx.LocalsCache[values [i]["name"].ToString()] = values [i]["value"];
|
||||
var_list.Add (values [i]);
|
||||
}
|
||||
|
||||
SendResponse (msg_id, Result.OkFromObject (new { result = var_list }), token);
|
||||
|
|
@ -626,6 +610,68 @@ namespace WebAssembly.Net.Debugging {
|
|||
}
|
||||
}
|
||||
|
||||
IEnumerable<JObject> ExtractAndCacheValueTypes (ExecutionContext ctx, IEnumerable<JObject> var_list)
|
||||
{
|
||||
foreach (var jo in var_list) {
|
||||
var value = jo["value"]?.Value<JObject> ();
|
||||
if (value ["type"]?.Value<string> () != "object")
|
||||
continue;
|
||||
|
||||
if (!(value ["isValueType"]?.Value<bool> () ?? false) || // not a valuetype
|
||||
!(value ["expanded"]?.Value<bool> () ?? false)) // not expanded
|
||||
continue;
|
||||
|
||||
// Expanded ValueType
|
||||
var members = value ["members"]?.Values<JObject>().ToArray() ?? Array.Empty<JObject>();
|
||||
var objectId = value ["objectId"]?.Value<string> () ?? $"dotnet:valuetype:{ctx.NextValueTypeId ()}";
|
||||
|
||||
value ["objectId"] = objectId;
|
||||
|
||||
ExtractAndCacheValueTypes (ctx, members);
|
||||
|
||||
ctx.ValueTypesCache [objectId] = JArray.FromObject (members);
|
||||
value.Remove ("members");
|
||||
}
|
||||
|
||||
return var_list;
|
||||
}
|
||||
|
||||
async Task<bool> GetDetailsForValueType (MessageId msg_id, string object_id, Func<MonoCommands> get_props_cmd_fn, CancellationToken token)
|
||||
{
|
||||
var ctx = GetContext (msg_id);
|
||||
|
||||
if (!ctx.ValueTypesCache.ContainsKey (object_id)) {
|
||||
var cmd = get_props_cmd_fn ();
|
||||
if (cmd == null) {
|
||||
SendResponse (msg_id, Result.Exception (new ArgumentException (
|
||||
"Could not find a cached value for {object_id}, and cant' expand it.")),
|
||||
token);
|
||||
|
||||
return false;
|
||||
} else {
|
||||
await GetDetails (msg_id, cmd, token, send_response: false);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.ValueTypesCache.TryGetValue (object_id, out var var_list)) {
|
||||
var response = JObject.FromObject(new
|
||||
{
|
||||
result = var_list
|
||||
});
|
||||
|
||||
SendResponse(msg_id, Result.Ok(response), token);
|
||||
return true;
|
||||
} else {
|
||||
var response = JObject.FromObject(new
|
||||
{
|
||||
result = $"Unable to get details for {object_id}"
|
||||
});
|
||||
|
||||
SendResponse(msg_id, Result.Err(response), token);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async Task<Breakpoint> SetMonoBreakpoint (SessionId sessionId, BreakpointRequest req, SourceLocation location, CancellationToken token)
|
||||
{
|
||||
var bp = new Breakpoint (req.Id, location, BreakpointState.Pending);
|
||||
|
|
|
|||
Loading…
Reference in New Issue