diff --git a/src/Components/WebAssembly/DebugProxy/src/Microsoft.AspNetCore.Components.WebAssembly.DebugProxy.csproj b/src/Components/WebAssembly/DebugProxy/src/Microsoft.AspNetCore.Components.WebAssembly.DebugProxy.csproj
index 933fcb9386..4ae030c63a 100644
--- a/src/Components/WebAssembly/DebugProxy/src/Microsoft.AspNetCore.Components.WebAssembly.DebugProxy.csproj
+++ b/src/Components/WebAssembly/DebugProxy/src/Microsoft.AspNetCore.Components.WebAssembly.DebugProxy.csproj
@@ -21,6 +21,7 @@
+
diff --git a/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DevToolsHelper.cs b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DevToolsHelper.cs
new file mode 100644
index 0000000000..441426299d
--- /dev/null
+++ b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DevToolsHelper.cs
@@ -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 (), "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 BreakpointRequests { get; } = new Dictionary ();
+
+ public TaskCompletionSource ready = null;
+ public bool IsRuntimeReady => ready != null && ready.Task.IsCompleted;
+
+ public int Id { get; set; }
+ public object AuxData { get; set; }
+
+ public List CallStack { get; set; }
+
+ internal DebugStore store;
+ public TaskCompletionSource Source { get; } = new TaskCompletionSource ();
+
+ int nextValueTypeId = 0;
+ public Dictionary ValueTypesCache = new Dictionary ();
+ public Dictionary LocalsCache = new Dictionary ();
+
+ 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);
+
+ }
+}
diff --git a/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DevToolsProxy.cs b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DevToolsProxy.cs
index 42c7e33342..22d86b20f8 100644
--- a/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DevToolsProxy.cs
+++ b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DevToolsProxy.cs
@@ -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 (), "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 ();
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);
}
diff --git a/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/EvaluateExpression.cs b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/EvaluateExpression.cs
new file mode 100644
index 0000000000..fb7e776ed9
--- /dev/null
+++ b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/EvaluateExpression.cs
@@ -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 thisExpressions = new List ();
+ 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 variables = new List ();
+ public List thisList = new List ();
+ public List methodCall = new List ();
+ public List