Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/mono.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/sdks
diff options
context:
space:
mode:
authorThays Grazia <thaystg@gmail.com>2020-08-15 20:03:15 +0300
committerGitHub <noreply@github.com>2020-08-15 20:03:15 +0300
commit70219cc2b54ebc52a54d24f8973802370bfdcc76 (patch)
tree8bab043b6415e7bad180625b257793f8508d3d6b /sdks
parent0161714a5adbb8738f1d6227662b6b5aadf90405 (diff)
[wasm] Merge dotnet/runtime changes to mono/mono. (#20249)
* Reformatting files. Merging dotnet/runtime to mono/mono * Removing testharnessstartup from browserdebughost. Fix format of EvaluateOnCallFrameTests. * Fix package.
Diffstat (limited to 'sdks')
-rw-r--r--sdks/wasm/BrowserDebugHost/BrowserDebugHost.csproj6
-rw-r--r--sdks/wasm/BrowserDebugHost/Program.cs101
-rw-r--r--sdks/wasm/BrowserDebugHost/Startup.cs292
-rw-r--r--sdks/wasm/BrowserDebugHost/TestHarnessStartup.cs225
-rw-r--r--sdks/wasm/BrowserDebugProxy/.editorconfig69
-rw-r--r--sdks/wasm/BrowserDebugProxy/AssemblyInfo.cs3
-rw-r--r--sdks/wasm/BrowserDebugProxy/BrowserDebugProxy.csproj2
-rw-r--r--sdks/wasm/BrowserDebugProxy/DebugStore.cs1693
-rw-r--r--sdks/wasm/BrowserDebugProxy/DebuggerProxy.cs31
-rw-r--r--sdks/wasm/BrowserDebugProxy/DevToolsHelper.cs546
-rw-r--r--sdks/wasm/BrowserDebugProxy/DevToolsProxy.cs706
-rw-r--r--sdks/wasm/BrowserDebugProxy/EvaluateExpression.cs379
-rw-r--r--sdks/wasm/BrowserDebugProxy/MonoProxy.cs1894
-rw-r--r--sdks/wasm/DebuggerTestSuite/ArrayTests.cs1231
-rw-r--r--sdks/wasm/DebuggerTestSuite/CallFunctionOnTests.cs1754
-rw-r--r--sdks/wasm/DebuggerTestSuite/DateTimeTests.cs110
-rw-r--r--sdks/wasm/DebuggerTestSuite/DelegateTests.cs568
-rw-r--r--sdks/wasm/DebuggerTestSuite/DevToolsClient.cs297
-rw-r--r--sdks/wasm/DebuggerTestSuite/EvaluateOnCallFrameTests.cs396
-rw-r--r--sdks/wasm/DebuggerTestSuite/ExceptionTests.cs264
-rw-r--r--sdks/wasm/DebuggerTestSuite/InspectorClient.cs125
-rw-r--r--sdks/wasm/DebuggerTestSuite/PointerTests.cs1060
-rw-r--r--sdks/wasm/DebuggerTestSuite/Support.cs1934
-rw-r--r--sdks/wasm/DebuggerTestSuite/TestHarnessOptions.cs15
-rw-r--r--sdks/wasm/DebuggerTestSuite/TestHarnessProxy.cs59
-rw-r--r--sdks/wasm/DebuggerTestSuite/TestHarnessStartup.cs255
-rw-r--r--sdks/wasm/DebuggerTestSuite/Tests.cs2840
-rw-r--r--sdks/wasm/Makefile6
-rw-r--r--sdks/wasm/other.js37
-rw-r--r--sdks/wasm/packager.cs6
-rw-r--r--sdks/wasm/tests/debugger/debugger-array-test.cs583
-rw-r--r--sdks/wasm/tests/debugger/debugger-cfo-test.cs125
-rw-r--r--sdks/wasm/tests/debugger/debugger-datetime-test.cs43
-rw-r--r--sdks/wasm/tests/debugger/debugger-driver.html2
-rw-r--r--sdks/wasm/tests/debugger/debugger-evaluate-test.cs157
-rw-r--r--sdks/wasm/tests/debugger/debugger-exception-test.cs55
-rw-r--r--sdks/wasm/tests/debugger/debugger-pointers-test.cs211
-rw-r--r--sdks/wasm/tests/debugger/debugger-test.cs618
-rw-r--r--sdks/wasm/tests/debugger/debugger-test2.cs85
-rw-r--r--sdks/wasm/tests/debugger/debugger-valuetypes-test.cs506
-rw-r--r--sdks/wasm/tests/debugger/dependency.cs44
41 files changed, 10404 insertions, 8929 deletions
diff --git a/sdks/wasm/BrowserDebugHost/BrowserDebugHost.csproj b/sdks/wasm/BrowserDebugHost/BrowserDebugHost.csproj
index f1765749813..aee852c016e 100644
--- a/sdks/wasm/BrowserDebugHost/BrowserDebugHost.csproj
+++ b/sdks/wasm/BrowserDebugHost/BrowserDebugHost.csproj
@@ -5,11 +5,9 @@
</PropertyGroup>
<ItemGroup>
- <Folder Include="wwwroot\" />
- </ItemGroup>
-
- <ItemGroup>
<ProjectReference Include="..\BrowserDebugProxy\BrowserDebugProxy.csproj" />
</ItemGroup>
+ <Target Name="GetFilesToPackage" />
+
</Project>
diff --git a/sdks/wasm/BrowserDebugHost/Program.cs b/sdks/wasm/BrowserDebugHost/Program.cs
index faaa4960c58..c5acedd5e16 100644
--- a/sdks/wasm/BrowserDebugHost/Program.cs
+++ b/sdks/wasm/BrowserDebugHost/Program.cs
@@ -1,81 +1,40 @@
-using System;
+// 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.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
+using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
-using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
-namespace WebAssembly.Net.Debugging
+namespace Microsoft.WebAssembly.Diagnostics
{
- public class ProxyOptions {
- public Uri DevToolsUrl { get; set; } = new Uri ("http://localhost:9222");
- }
-
- public class TestHarnessOptions : ProxyOptions {
- public string ChromePath { get; set; }
- public string AppPath { get; set; }
- public string PagePath { get; set; }
- public string NodeApp { get; set; }
- }
-
- public class Program {
- public static void Main(string[] args)
- {
- var host = new WebHostBuilder()
- .UseSetting ("UseIISIntegration", false.ToString ())
- .UseKestrel ()
- .UseContentRoot (Directory.GetCurrentDirectory())
- .UseStartup<Startup> ()
- .ConfigureAppConfiguration ((hostingContext, config) =>
- {
- config.AddCommandLine(args);
- })
- .UseUrls ("http://localhost:9300")
- .Build ();
-
- host.Run ();
- }
- }
-
- public class TestHarnessProxy {
- static IWebHost host;
- static Task hostTask;
- static CancellationTokenSource cts = new CancellationTokenSource ();
- static object proxyLock = new object ();
-
- public static readonly Uri Endpoint = new Uri ("http://localhost:9400");
-
- public static Task Start (string chromePath, string appPath, string pagePath)
- {
- lock (proxyLock) {
- if (host != null)
- return hostTask;
-
- host = WebHost.CreateDefaultBuilder ()
- .UseSetting ("UseIISIntegration", false.ToString ())
- .ConfigureAppConfiguration ((hostingContext, config) => {
- config.AddEnvironmentVariables (prefix: "WASM_TESTS_");
- })
- .ConfigureServices ((ctx, services) => {
- services.Configure<TestHarnessOptions> (ctx.Configuration);
- services.Configure<TestHarnessOptions> (options => {
- options.ChromePath = options.ChromePath ?? chromePath;
- options.AppPath = appPath;
- options.PagePath = pagePath;
- options.DevToolsUrl = new Uri ("http://localhost:0");
- });
- })
- .UseStartup<TestHarnessStartup> ()
- .UseUrls (Endpoint.ToString ())
- .Build();
- hostTask = host.StartAsync (cts.Token);
- }
-
- Console.WriteLine ("WebServer Ready!");
- return hostTask;
- }
- }
+ public class ProxyOptions
+ {
+ public Uri DevToolsUrl { get; set; } = new Uri("http://localhost:9222");
+ }
+
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ var host = new WebHostBuilder()
+ .UseSetting("UseIISIntegration", false.ToString())
+ .UseKestrel()
+ .UseContentRoot(Directory.GetCurrentDirectory())
+ .UseStartup<Startup>()
+ .ConfigureAppConfiguration((hostingContext, config) =>
+ {
+ config.AddCommandLine(args);
+ })
+ .UseUrls("http://localhost:9300")
+ .Build();
+
+ host.Run();
+ }
+ }
}
diff --git a/sdks/wasm/BrowserDebugHost/Startup.cs b/sdks/wasm/BrowserDebugHost/Startup.cs
index e18df90c39c..81c70187ed0 100644
--- a/sdks/wasm/BrowserDebugHost/Startup.cs
+++ b/sdks/wasm/BrowserDebugHost/Startup.cs
@@ -1,148 +1,164 @@
+// 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.Net.Http;
+using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-using System.Net.Http;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.Json;
+using Microsoft.Extensions.Options;
-namespace WebAssembly.Net.Debugging {
- internal class Startup {
- // This method gets called by the runtime. Use this method to add services to the container.
- // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
- public void ConfigureServices (IServiceCollection services) =>
- services.AddRouting ()
- .Configure<ProxyOptions> (Configuration);
-
- public Startup (IConfiguration configuration) =>
- Configuration = configuration;
-
- public IConfiguration Configuration { get; }
-
- // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
- public void Configure (IApplicationBuilder app, IOptionsMonitor<ProxyOptions> optionsAccessor, IWebHostEnvironment env)
- {
- var options = optionsAccessor.CurrentValue;
- app.UseDeveloperExceptionPage ()
- .UseWebSockets ()
- .UseDebugProxy (options);
- }
- }
-
- static class DebugExtensions {
- public static Dictionary<string,string> MapValues (Dictionary<string,string> response, HttpContext context, Uri debuggerHost)
- {
- var filtered = new Dictionary<string, string> ();
- var request = context.Request;
-
- foreach (var key in response.Keys) {
- switch (key) {
- case "devtoolsFrontendUrl":
- var front = response [key];
- filtered[key] = $"{debuggerHost.Scheme}://{debuggerHost.Authority}{front.Replace ($"ws={debuggerHost.Authority}", $"ws={request.Host}")}";
- break;
- case "webSocketDebuggerUrl":
- var page = new Uri (response [key]);
- filtered [key] = $"{page.Scheme}://{request.Host}{page.PathAndQuery}";
- break;
- default:
- filtered [key] = response [key];
- break;
- }
- }
- return filtered;
- }
-
- public static IApplicationBuilder UseDebugProxy (this IApplicationBuilder app, ProxyOptions options) =>
- UseDebugProxy (app, options, MapValues);
-
- public static IApplicationBuilder UseDebugProxy (
- this IApplicationBuilder app,
- ProxyOptions options,
- Func<Dictionary<string,string>, HttpContext, Uri, Dictionary<string,string>> mapFunc)
- {
- var devToolsHost = options.DevToolsUrl;
- app.UseRouter (router => {
- router.MapGet ("/", Copy);
- router.MapGet ("/favicon.ico", Copy);
- router.MapGet ("json", RewriteArray);
- router.MapGet ("json/list", RewriteArray);
- router.MapGet ("json/version", RewriteSingle);
- router.MapGet ("json/new", RewriteSingle);
- router.MapGet ("devtools/page/{pageId}", ConnectProxy);
- router.MapGet ("devtools/browser/{pageId}", ConnectProxy);
-
- string GetEndpoint (HttpContext context)
- {
- var request = context.Request;
- var requestPath = request.Path;
- return $"{devToolsHost.Scheme}://{devToolsHost.Authority}{request.Path}{request.QueryString}";
- }
-
- async Task Copy (HttpContext context) {
- using (var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds (5) }) {
- var response = await httpClient.GetAsync (GetEndpoint (context));
- context.Response.ContentType = response.Content.Headers.ContentType.ToString ();
- if ((response.Content.Headers.ContentLength ?? 0) > 0)
- context.Response.ContentLength = response.Content.Headers.ContentLength;
- var bytes = await response.Content.ReadAsByteArrayAsync ();
- await context.Response.Body.WriteAsync (bytes);
-
- }
- }
-
- async Task RewriteSingle (HttpContext context)
- {
- var version = await ProxyGetJsonAsync<Dictionary<string, string>> (GetEndpoint (context));
- context.Response.ContentType = "application/json";
- await context.Response.WriteAsync (
- JsonSerializer.Serialize (mapFunc (version, context, devToolsHost)));
- }
-
- async Task RewriteArray (HttpContext context)
- {
- var tabs = await ProxyGetJsonAsync<Dictionary<string, string> []> (GetEndpoint (context));
- var alteredTabs = tabs.Select (t => mapFunc (t, context, devToolsHost)).ToArray ();
- context.Response.ContentType = "application/json";
- await context.Response.WriteAsync (JsonSerializer.Serialize (alteredTabs));
- }
-
- async Task ConnectProxy (HttpContext context)
- {
- if (!context.WebSockets.IsWebSocketRequest) {
- context.Response.StatusCode = 400;
- return;
- }
-
- var endpoint = new Uri ($"ws://{devToolsHost.Authority}{context.Request.Path.ToString ()}");
- try {
- using var loggerFactory = LoggerFactory.Create(
- builder => builder.AddConsole().AddFilter(null, LogLevel.Information));
- var proxy = new DebuggerProxy (loggerFactory);
- var ideSocket = await context.WebSockets.AcceptWebSocketAsync ();
-
- await proxy.Run (endpoint, ideSocket);
- } catch (Exception e) {
- Console.WriteLine ("got exception {0}", e);
- }
- }
- });
- return app;
- }
-
- static async Task<T> ProxyGetJsonAsync<T> (string url)
- {
- using (var httpClient = new HttpClient ()) {
- var response = await httpClient.GetAsync (url);
- return await JsonSerializer.DeserializeAsync<T> (await response.Content.ReadAsStreamAsync ());
- }
- }
- }
+namespace Microsoft.WebAssembly.Diagnostics
+{
+ internal class Startup
+ {
+ // This method gets called by the runtime. Use this method to add services to the container.
+ // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
+ public void ConfigureServices(IServiceCollection services) =>
+ services.AddRouting()
+ .Configure<ProxyOptions>(Configuration);
+
+ public Startup(IConfiguration configuration) =>
+ Configuration = configuration;
+
+ public IConfiguration Configuration { get; }
+
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ public void Configure(IApplicationBuilder app, IOptionsMonitor<ProxyOptions> optionsAccessor, IWebHostEnvironment env)
+ {
+ var options = optionsAccessor.CurrentValue;
+ app.UseDeveloperExceptionPage()
+ .UseWebSockets()
+ .UseDebugProxy(options);
+ }
+ }
+
+ static class DebugExtensions
+ {
+ public static Dictionary<string, string> MapValues(Dictionary<string, string> response, HttpContext context, Uri debuggerHost)
+ {
+ var filtered = new Dictionary<string, string>();
+ var request = context.Request;
+
+ foreach (var key in response.Keys)
+ {
+ switch (key)
+ {
+ case "devtoolsFrontendUrl":
+ var front = response[key];
+ filtered[key] = $"{debuggerHost.Scheme}://{debuggerHost.Authority}{front.Replace($"ws={debuggerHost.Authority}", $"ws={request.Host}")}";
+ break;
+ case "webSocketDebuggerUrl":
+ var page = new Uri(response[key]);
+ filtered[key] = $"{page.Scheme}://{request.Host}{page.PathAndQuery}";
+ break;
+ default:
+ filtered[key] = response[key];
+ break;
+ }
+ }
+ return filtered;
+ }
+
+ public static IApplicationBuilder UseDebugProxy(this IApplicationBuilder app, ProxyOptions options) =>
+ UseDebugProxy(app, options, MapValues);
+
+ public static IApplicationBuilder UseDebugProxy(
+ this IApplicationBuilder app,
+ ProxyOptions options,
+ Func<Dictionary<string, string>, HttpContext, Uri, Dictionary<string, string>> mapFunc)
+ {
+ var devToolsHost = options.DevToolsUrl;
+ app.UseRouter(router =>
+ {
+ router.MapGet("/", Copy);
+ router.MapGet("/favicon.ico", Copy);
+ router.MapGet("json", RewriteArray);
+ router.MapGet("json/list", RewriteArray);
+ router.MapGet("json/version", RewriteSingle);
+ router.MapGet("json/new", RewriteSingle);
+ router.MapGet("devtools/page/{pageId}", ConnectProxy);
+ router.MapGet("devtools/browser/{pageId}", ConnectProxy);
+
+ string GetEndpoint(HttpContext context)
+ {
+ var request = context.Request;
+ var requestPath = request.Path;
+ return $"{devToolsHost.Scheme}://{devToolsHost.Authority}{request.Path}{request.QueryString}";
+ }
+
+ async Task Copy(HttpContext context)
+ {
+ using (var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) })
+ {
+ var response = await httpClient.GetAsync(GetEndpoint(context));
+ context.Response.ContentType = response.Content.Headers.ContentType.ToString();
+ if ((response.Content.Headers.ContentLength ?? 0) > 0)
+ context.Response.ContentLength = response.Content.Headers.ContentLength;
+ var bytes = await response.Content.ReadAsByteArrayAsync();
+ await context.Response.Body.WriteAsync(bytes);
+
+ }
+ }
+
+ async Task RewriteSingle(HttpContext context)
+ {
+ var version = await ProxyGetJsonAsync<Dictionary<string, string>>(GetEndpoint(context));
+ context.Response.ContentType = "application/json";
+ await context.Response.WriteAsync(
+ JsonSerializer.Serialize(mapFunc(version, context, devToolsHost)));
+ }
+
+ async Task RewriteArray(HttpContext context)
+ {
+ var tabs = await ProxyGetJsonAsync<Dictionary<string, string>[]>(GetEndpoint(context));
+ var alteredTabs = tabs.Select(t => mapFunc(t, context, devToolsHost)).ToArray();
+ context.Response.ContentType = "application/json";
+ await context.Response.WriteAsync(JsonSerializer.Serialize(alteredTabs));
+ }
+
+ async Task ConnectProxy(HttpContext context)
+ {
+ if (!context.WebSockets.IsWebSocketRequest)
+ {
+ context.Response.StatusCode = 400;
+ return;
+ }
+
+ var endpoint = new Uri($"ws://{devToolsHost.Authority}{context.Request.Path.ToString()}");
+ try
+ {
+ using var loggerFactory = LoggerFactory.Create(
+ builder => builder.AddConsole().AddFilter(null, LogLevel.Information));
+ var proxy = new DebuggerProxy(loggerFactory);
+ var ideSocket = await context.WebSockets.AcceptWebSocketAsync();
+
+ await proxy.Run(endpoint, ideSocket);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("got exception {0}", e);
+ }
+ }
+ });
+ return app;
+ }
+
+ static async Task<T> ProxyGetJsonAsync<T>(string url)
+ {
+ using (var httpClient = new HttpClient())
+ {
+ var response = await httpClient.GetAsync(url);
+ return await JsonSerializer.DeserializeAsync<T>(await response.Content.ReadAsStreamAsync());
+ }
+ }
+ }
}
diff --git a/sdks/wasm/BrowserDebugHost/TestHarnessStartup.cs b/sdks/wasm/BrowserDebugHost/TestHarnessStartup.cs
deleted file mode 100644
index 6ae4ba32b76..00000000000
--- a/sdks/wasm/BrowserDebugHost/TestHarnessStartup.cs
+++ /dev/null
@@ -1,225 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.IO;
-using System.Net.Http;
-using System.Text.RegularExpressions;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Routing;
-using Microsoft.AspNetCore.StaticFiles;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.FileProviders;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
-using Newtonsoft.Json.Linq;
-
-namespace WebAssembly.Net.Debugging {
- public class TestHarnessStartup {
- static Regex parseConnection = new Regex (@"listening on (ws?s://[^\s]*)");
- public TestHarnessStartup (IConfiguration configuration)
- {
- Configuration = configuration;
- }
-
- public IConfiguration Configuration { get; set; }
-
- // This method gets called by the runtime. Use this method to add services to the container.
- // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
- public void ConfigureServices (IServiceCollection services)
- {
- services.AddRouting ()
- .Configure<TestHarnessOptions> (Configuration);
- }
-
- async Task SendNodeVersion (HttpContext context)
- {
- Console.WriteLine ("hello chrome! json/version");
- var resp_obj = new JObject ();
- resp_obj ["Browser"] = "node.js/v9.11.1";
- resp_obj ["Protocol-Version"] = "1.1";
-
- var response = resp_obj.ToString ();
- await context.Response.WriteAsync (response, new CancellationTokenSource ().Token);
- }
-
- async Task SendNodeList (HttpContext context)
- {
- Console.WriteLine ("hello chrome! json/list");
- try {
- var response = new JArray (JObject.FromObject (new {
- description = "node.js instance",
- devtoolsFrontendUrl = "chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=localhost:9300/91d87807-8a81-4f49-878c-a5604103b0a4",
- faviconUrl = "https://nodejs.org/static/favicon.ico",
- id = "91d87807-8a81-4f49-878c-a5604103b0a4",
- title = "foo.js",
- type = "node",
- webSocketDebuggerUrl = "ws://localhost:9300/91d87807-8a81-4f49-878c-a5604103b0a4"
- })).ToString ();
-
- Console.WriteLine ($"sending: {response}");
- await context.Response.WriteAsync (response, new CancellationTokenSource ().Token);
- } catch (Exception e) { Console.WriteLine (e); }
- }
-
- public async Task LaunchAndServe (ProcessStartInfo psi, HttpContext context, Func<string, Task<string>> extract_conn_url)
- {
-
- if (!context.WebSockets.IsWebSocketRequest) {
- context.Response.StatusCode = 400;
- return;
- }
-
- var tcs = new TaskCompletionSource<string> ();
-
- var proc = Process.Start (psi);
- try {
- proc.ErrorDataReceived += (sender, e) => {
- var str = e.Data;
- Console.WriteLine ($"stderr: {str}");
-
- if (tcs.Task.IsCompleted)
- return;
-
- var match = parseConnection.Match (str);
- if (match.Success) {
- tcs.TrySetResult (match.Groups[1].Captures[0].Value);
- }
- };
-
- proc.OutputDataReceived += (sender, e) => {
- Console.WriteLine ($"stdout: {e.Data}");
- };
-
- proc.BeginErrorReadLine ();
- proc.BeginOutputReadLine ();
-
- if (await Task.WhenAny (tcs.Task, Task.Delay (5000)) != tcs.Task) {
- Console.WriteLine ("Didnt get the con string after 5s.");
- throw new Exception ("node.js timedout");
- }
- var line = await tcs.Task;
- var con_str = extract_conn_url != null ? await extract_conn_url (line) : line;
-
- Console.WriteLine ($"launching proxy for {con_str}");
-
- using var loggerFactory = LoggerFactory.Create(
- builder => builder.AddConsole().AddFilter(null, LogLevel.Information));
- var proxy = new DebuggerProxy (loggerFactory);
- var browserUri = new Uri (con_str);
- var ideSocket = await context.WebSockets.AcceptWebSocketAsync ();
-
- await proxy.Run (browserUri, ideSocket);
- Console.WriteLine("Proxy done");
- } catch (Exception e) {
- Console.WriteLine ("got exception {0}", e);
- } finally {
- proc.CancelErrorRead ();
- proc.CancelOutputRead ();
- proc.Kill ();
- proc.WaitForExit ();
- proc.Close ();
- }
- }
-
- // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
- public void Configure (IApplicationBuilder app, IOptionsMonitor<TestHarnessOptions> optionsAccessor, IWebHostEnvironment env)
- {
- app.UseWebSockets ();
- app.UseStaticFiles ();
-
- TestHarnessOptions options = optionsAccessor.CurrentValue;
-
- var provider = new FileExtensionContentTypeProvider();
- provider.Mappings [".wasm"] = "application/wasm";
-
- app.UseStaticFiles (new StaticFileOptions {
- FileProvider = new PhysicalFileProvider (options.AppPath),
- ServeUnknownFileTypes = true, //Cuz .wasm is not a known file type :cry:
- RequestPath = "",
- ContentTypeProvider = provider
- });
-
- var devToolsUrl = options.DevToolsUrl;
- app.UseRouter (router => {
- router.MapGet ("launch-chrome-and-connect", async context => {
- Console.WriteLine ("New test request");
- try {
- var client = new HttpClient ();
- var psi = new ProcessStartInfo ();
-
- psi.Arguments = $"--headless --disable-gpu --lang=en-US --incognito --remote-debugging-port={devToolsUrl.Port} http://{TestHarnessProxy.Endpoint.Authority}/{options.PagePath}";
- psi.UseShellExecute = false;
- psi.FileName = options.ChromePath;
- psi.RedirectStandardError = true;
- psi.RedirectStandardOutput = true;
-
-
- await LaunchAndServe (psi, context, async (str) => {
- var start = DateTime.Now;
- JArray obj = null;
-
- while (true) {
- // Unfortunately it does look like we have to wait
- // for a bit after getting the response but before
- // making the list request. We get an empty result
- // if we make the request too soon.
- await Task.Delay (100);
-
- var res = await client.GetStringAsync (new Uri (new Uri (str), "/json/list"));
- Console.WriteLine ("res is {0}", res);
-
- if (!String.IsNullOrEmpty (res)) {
- // Sometimes we seem to get an empty array `[ ]`
- obj = JArray.Parse (res);
- if (obj != null && obj.Count >= 1)
- break;
- }
-
- var elapsed = DateTime.Now - start;
- if (elapsed.Milliseconds > 5000) {
- Console.WriteLine ($"Unable to get DevTools /json/list response in {elapsed.Seconds} seconds, stopping");
- return null;
- }
- }
-
- var wsURl = obj[0]? ["webSocketDebuggerUrl"]?.Value<string> ();
- Console.WriteLine (">>> {0}", wsURl);
-
- return wsURl;
- });
- } catch (Exception ex) {
- Console.WriteLine ($"launch-chrome-and-connect failed with {ex.ToString ()}");
- }
- });
- });
-
- if (options.NodeApp != null) {
- Console.WriteLine($"Doing the nodejs: {options.NodeApp}");
- var nodeFullPath = Path.GetFullPath (options.NodeApp);
- Console.WriteLine (nodeFullPath);
- var psi = new ProcessStartInfo ();
-
- psi.UseShellExecute = false;
- psi.RedirectStandardError = true;
- psi.RedirectStandardOutput = true;
-
- psi.Arguments = $"--inspect-brk=localhost:0 {nodeFullPath}";
- psi.FileName = "node";
-
- app.UseRouter (router => {
- //Inspector API for using chrome devtools directly
- router.MapGet ("json", SendNodeList);
- router.MapGet ("json/list", SendNodeList);
- router.MapGet ("json/version", SendNodeVersion);
- router.MapGet ("launch-done-and-connect", async context => {
- await LaunchAndServe (psi, context, null);
- });
- });
- }
- }
- }
-}
diff --git a/sdks/wasm/BrowserDebugProxy/.editorconfig b/sdks/wasm/BrowserDebugProxy/.editorconfig
deleted file mode 100644
index ca41c33264a..00000000000
--- a/sdks/wasm/BrowserDebugProxy/.editorconfig
+++ /dev/null
@@ -1,69 +0,0 @@
-root = true
-
-[*]
-end_of_line = crlf
-tab_width = 4
-trim_trailing_whitespace = true
-insert_final_newline = true
-
-[{*.cs, *.tt}]
-indent_style = tab
-csharp_indent_block_contents = true
-csharp_indent_braces = false
-csharp_indent_case_contents = true
-csharp_indent_switch_labels = false
-csharp_new_line_before_catch = false
-csharp_new_line_before_else = false
-csharp_new_line_before_finally = false
-csharp_new_line_before_members_in_anonymous_types = true
-csharp_new_line_before_members_in_object_initializers = true
-csharp_new_line_before_open_brace = methods
-csharp_prefer_braces = false
-csharp_preserve_single_line_statements = false
-csharp_space_after_comma = true
-csharp_space_after_dot = false
-csharp_space_after_keywords_in_control_flow_statements = true
-csharp_space_after_semicolon_in_for_statement = true
-csharp_space_around_binary_operators = true
-csharp_space_around_declaration_statements = do_not_ignore
-csharp_space_before_colon_in_inheritance_clause = true
-csharp_space_before_comma = false
-csharp_space_before_open_square_brackets = true
-csharp_space_before_semicolon_in_for_statement = false
-csharp_space_between_empty_square_brackets = false
-csharp_space_between_method_call_name_and_opening_parenthesis = true
-csharp_space_between_method_call_empty_parameter_list_parentheses = false
-csharp_space_between_method_call_parameter_list_parentheses = false
-csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
-csharp_space_between_method_declaration_name_and_open_parenthesis = true
-csharp_space_between_method_declaration_parameter_list_parentheses = false
-csharp_space_between_parentheses = false
-csharp_space_between_square_brackets = false
-csharp_style_conditional_delegate_call = true
-csharp_style_expression_bodied_accessors = true
-csharp_style_expression_bodied_constructors = true
-csharp_style_expression_bodied_indexers = true
-csharp_style_expression_bodied_methods = true
-csharp_style_expression_bodied_operators = true
-csharp_style_expression_bodied_properties = true
-csharp_style_inlined_variable_declaration = true
-csharp_style_pattern_matching_over_as_with_null_check = true
-csharp_style_pattern_matching_over_is_with_cast_check = true
-csharp_style_throw_expression = true
-
-[{*.ts, *.json}]
-indent_style = tab
-
-[*.csproj]
-indent_style = tab
-tab_width = 2
-indent_size = 2
-
-[*.js]
-indent_style = tab
-
-[*.yml]
-indent_style = tab
-
-[*.md]
-indent_style = tab
diff --git a/sdks/wasm/BrowserDebugProxy/AssemblyInfo.cs b/sdks/wasm/BrowserDebugProxy/AssemblyInfo.cs
deleted file mode 100644
index 8e935c7ed0d..00000000000
--- a/sdks/wasm/BrowserDebugProxy/AssemblyInfo.cs
+++ /dev/null
@@ -1,3 +0,0 @@
-using System.Runtime.CompilerServices;
-
-[assembly: InternalsVisibleTo ("DebuggerTestSuite")]
diff --git a/sdks/wasm/BrowserDebugProxy/BrowserDebugProxy.csproj b/sdks/wasm/BrowserDebugProxy/BrowserDebugProxy.csproj
index 4063416ceb0..4ac612f127a 100644
--- a/sdks/wasm/BrowserDebugProxy/BrowserDebugProxy.csproj
+++ b/sdks/wasm/BrowserDebugProxy/BrowserDebugProxy.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>netstandard2.1</TargetFramework>
+ <TargetFramework>netcoreapp3.0</TargetFramework>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
diff --git a/sdks/wasm/BrowserDebugProxy/DebugStore.cs b/sdks/wasm/BrowserDebugProxy/DebugStore.cs
index f633fe786d5..9f9c7a6bd08 100644
--- a/sdks/wasm/BrowserDebugProxy/DebugStore.cs
+++ b/sdks/wasm/BrowserDebugProxy/DebugStore.cs
@@ -1,828 +1,883 @@
+// 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.IO;
using System.Collections.Generic;
-using Mono.Cecil;
-using Mono.Cecil.Cil;
+using System.IO;
using System.Linq;
-using Newtonsoft.Json.Linq;
using System.Net.Http;
-using Mono.Cecil.Pdb;
-using Newtonsoft.Json;
+using System.Runtime.CompilerServices;
+using System.Security.Cryptography;
using System.Text.RegularExpressions;
-using System.Threading.Tasks;
using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
-using System.Runtime.CompilerServices;
-using System.Security.Cryptography;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+using Mono.Cecil.Pdb;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
-namespace WebAssembly.Net.Debugging {
- internal class BreakpointRequest {
- public string Id { get; private set; }
- public string Assembly { get; private set; }
- public string File { get; private set; }
- public int Line { get; private set; }
- public int Column { get; private set; }
- public MethodInfo Method { get; private set; }
-
- JObject request;
-
- public bool IsResolved => Assembly != null;
- public List<Breakpoint> Locations { get; } = new List<Breakpoint> ();
-
- public override string ToString ()
- => $"BreakpointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}";
-
- public object AsSetBreakpointByUrlResponse (IEnumerable<object> jsloc)
- => new { breakpointId = Id, locations = Locations.Select(l => l.Location.AsLocation ()).Concat (jsloc) };
-
- public BreakpointRequest () {
- }
-
- public BreakpointRequest (string id, MethodInfo method) {
- Id = id;
- Method = method;
- }
-
- public BreakpointRequest (string id, JObject request) {
- Id = id;
- this.request = request;
- }
-
- public static BreakpointRequest Parse (string id, JObject args)
- {
- return new BreakpointRequest (id, args);
- }
-
- public BreakpointRequest Clone ()
- => new BreakpointRequest { Id = Id, request = request };
-
- public bool IsMatch (SourceFile sourceFile)
- {
- var url = request? ["url"]?.Value<string> ();
- if (url == null) {
- var urlRegex = request?["urlRegex"].Value<string>();
- var regex = new Regex (urlRegex);
- return regex.IsMatch (sourceFile.Url.ToString ()) || regex.IsMatch (sourceFile.DocUrl);
- }
-
- return sourceFile.Url.ToString () == url || sourceFile.DotNetUrl == url;
- }
-
- public bool TryResolve (SourceFile sourceFile)
- {
- if (!IsMatch (sourceFile))
- return false;
-
- var line = request? ["lineNumber"]?.Value<int> ();
- var column = request? ["columnNumber"]?.Value<int> ();
-
- if (line == null || column == null)
- return false;
-
- Assembly = sourceFile.AssemblyName;
- File = sourceFile.DebuggerFileName;
- Line = line.Value;
- Column = column.Value;
- return true;
- }
-
- public bool TryResolve (DebugStore store)
- {
- if (request == null || store == null)
- return false;
-
- return store.AllSources().FirstOrDefault (source => TryResolve (source)) != null;
- }
- }
-
- internal class VarInfo {
- public VarInfo (VariableDebugInformation v)
- {
- this.Name = v.Name;
- this.Index = v.Index;
- }
-
- public VarInfo (ParameterDefinition p)
- {
- this.Name = p.Name;
- this.Index = (p.Index + 1) * -1;
- }
-
- public string Name { get; }
- public int Index { get; }
-
- public override string ToString ()
- => $"(var-info [{Index}] '{Name}')";
- }
-
- internal class CliLocation {
- public CliLocation (MethodInfo method, int offset)
- {
- Method = method;
- Offset = offset;
- }
-
- public MethodInfo Method { get; }
- public int Offset { get; }
- }
-
- internal class SourceLocation {
- SourceId id;
- int line;
- int column;
- CliLocation cliLoc;
-
- public SourceLocation (SourceId id, int line, int column)
- {
- this.id = id;
- this.line = line;
- this.column = column;
- }
-
- public SourceLocation (MethodInfo mi, SequencePoint sp)
- {
- this.id = mi.SourceId;
- this.line = sp.StartLine - 1;
- this.column = sp.StartColumn - 1;
- this.cliLoc = new CliLocation (mi, sp.Offset);
- }
-
- public SourceId Id { get => id; }
- public int Line { get => line; }
- public int Column { get => column; }
- public CliLocation CliLocation => this.cliLoc;
-
- public override string ToString ()
- => $"{id}:{Line}:{Column}";
-
- public static SourceLocation Parse (JObject obj)
- {
- if (obj == null)
- return null;
-
- if (!SourceId.TryParse (obj ["scriptId"]?.Value<string> (), out var id))
- return null;
-
- var line = obj ["lineNumber"]?.Value<int> ();
- var column = obj ["columnNumber"]?.Value<int> ();
- if (id == null || line == null || column == null)
- return null;
-
- return new SourceLocation (id, line.Value, column.Value);
- }
-
-
- internal class LocationComparer : EqualityComparer<SourceLocation>
- {
- public override bool Equals (SourceLocation l1, SourceLocation l2)
- {
- if (l1 == null && l2 == null)
- return true;
- else if (l1 == null || l2 == null)
- return false;
-
- return (l1.Line == l2.Line &&
- l1.Column == l2.Column &&
- l1.Id == l2.Id);
- }
-
- public override int GetHashCode (SourceLocation loc)
- {
- int hCode = loc.Line ^ loc.Column;
- return loc.Id.GetHashCode () ^ hCode.GetHashCode ();
- }
- }
-
- internal object AsLocation ()
- => new {
- scriptId = id.ToString (),
- lineNumber = line,
- columnNumber = column
- };
- }
-
- internal class SourceId {
- const string Scheme = "dotnet://";
-
- readonly int assembly, document;
-
- public int Assembly => assembly;
- public int Document => document;
-
- internal SourceId (int assembly, int document)
- {
- this.assembly = assembly;
- this.document = document;
- }
-
- public SourceId (string id)
- {
- if (!TryParse (id, out assembly, out document))
- throw new ArgumentException ("invalid source identifier", nameof (id));
- }
-
- public static bool TryParse (string id, out SourceId source)
- {
- source = null;
- if (!TryParse (id, out var assembly, out var document))
- return false;
-
- source = new SourceId (assembly, document);
- return true;
- }
-
- static bool TryParse (string id, out int assembly, out int document)
- {
- assembly = document = 0;
- if (id == null || !id.StartsWith (Scheme, StringComparison.Ordinal))
- return false;
-
- var sp = id.Substring (Scheme.Length).Split ('_');
- if (sp.Length != 2)
- return false;
-
- if (!int.TryParse (sp [0], out assembly))
- return false;
-
- if (!int.TryParse (sp [1], out document))
- return false;
-
- return true;
- }
-
- public override string ToString ()
- => $"{Scheme}{assembly}_{document}";
-
- public override bool Equals (object obj)
- {
- if (obj == null)
- return false;
- SourceId that = obj as SourceId;
- return that.assembly == this.assembly && that.document == this.document;
- }
-
- public override int GetHashCode ()
- => assembly.GetHashCode () ^ document.GetHashCode ();
-
- public static bool operator == (SourceId a, SourceId b)
- => ((object)a == null) ? (object)b == null : a.Equals (b);
-
- public static bool operator != (SourceId a, SourceId b)
- => !a.Equals (b);
- }
-
- internal class MethodInfo {
- MethodDefinition methodDef;
- SourceFile source;
-
- public SourceId SourceId => source.SourceId;
-
- public string Name => methodDef.Name;
- public MethodDebugInformation DebugInformation => methodDef.DebugInformation;
-
- public SourceLocation StartLocation { get; }
- public SourceLocation EndLocation { get; }
- public AssemblyInfo Assembly { get; }
- public uint Token => methodDef.MetadataToken.RID;
-
- public MethodInfo (AssemblyInfo assembly, MethodDefinition methodDef, SourceFile source)
- {
- this.Assembly = assembly;
- this.methodDef = methodDef;
- this.source = source;
-
- var sps = DebugInformation.SequencePoints;
- if (sps == null || sps.Count() < 1)
- return;
-
- SequencePoint start = sps [0];
- SequencePoint end = sps [0];
-
- foreach (var sp in sps) {
- if (sp.StartLine < start.StartLine)
- start = sp;
- else if (sp.StartLine == start.StartLine && sp.StartColumn < start.StartColumn)
- start = sp;
-
- if (sp.EndLine > end.EndLine)
- end = sp;
- else if (sp.EndLine == end.EndLine && sp.EndColumn > end.EndColumn)
- end = sp;
- }
-
- StartLocation = new SourceLocation (this, start);
- EndLocation = new SourceLocation (this, end);
- }
-
- public SourceLocation GetLocationByIl (int pos)
- {
- SequencePoint prev = null;
- foreach (var sp in DebugInformation.SequencePoints) {
- if (sp.Offset > pos)
- break;
- prev = sp;
- }
-
- if (prev != null)
- return new SourceLocation (this, prev);
-
- return null;
- }
-
- public VarInfo [] GetLiveVarsAt (int offset)
- {
- var res = new List<VarInfo> ();
-
- res.AddRange (methodDef.Parameters.Select (p => new VarInfo (p)));
- res.AddRange (methodDef.DebugInformation.GetScopes ()
- .Where (s => s.Start.Offset <= offset && (s.End.IsEndOfMethod || s.End.Offset > offset))
- .SelectMany (s => s.Variables)
- .Where (v => !v.IsDebuggerHidden)
- .Select (v => new VarInfo (v)));
-
- return res.ToArray ();
- }
-
- public override string ToString () => "MethodInfo(" + methodDef.FullName + ")";
- }
-
- internal class TypeInfo {
- AssemblyInfo assembly;
- TypeDefinition type;
- List<MethodInfo> methods;
-
- public TypeInfo (AssemblyInfo assembly, TypeDefinition type) {
- this.assembly = assembly;
- this.type = type;
- methods = new List<MethodInfo> ();
- }
-
- public string Name => type.Name;
- public string FullName => type.FullName;
- public List<MethodInfo> Methods => methods;
-
- public override string ToString () => "TypeInfo('" + FullName + "')";
- }
-
- class AssemblyInfo {
- static int next_id;
- ModuleDefinition image;
- readonly int id;
- readonly ILogger logger;
- Dictionary<uint, MethodInfo> methods = new Dictionary<uint, MethodInfo> ();
- Dictionary<string, string> sourceLinkMappings = new Dictionary<string, string>();
- Dictionary<string, TypeInfo> typesByName = new Dictionary<string, TypeInfo> ();
- readonly List<SourceFile> sources = new List<SourceFile>();
- internal string Url { get; }
-
- public AssemblyInfo (IAssemblyResolver resolver, string url, byte[] assembly, byte[] pdb)
- {
- this.id = Interlocked.Increment (ref next_id);
-
- try {
- Url = url;
- ReaderParameters rp = new ReaderParameters (/*ReadingMode.Immediate*/);
- rp.AssemblyResolver = resolver;
- // set ReadSymbols = true unconditionally in case there
- // is an embedded pdb then handle ArgumentException
- // and assume that if pdb == null that is the cause
- rp.ReadSymbols = true;
- rp.SymbolReaderProvider = new PdbReaderProvider ();
- if (pdb != null)
- rp.SymbolStream = new MemoryStream (pdb);
- rp.ReadingMode = ReadingMode.Immediate;
-
- this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp);
- } catch (BadImageFormatException ex) {
- logger.LogWarning ($"Failed to read assembly as portable PDB: {ex.Message}");
- } catch (ArgumentException) {
- // if pdb == null this is expected and we
- // read the assembly without symbols below
- if (pdb != null)
- throw;
- }
-
- if (this.image == null) {
- ReaderParameters rp = new ReaderParameters (/*ReadingMode.Immediate*/);
- rp.AssemblyResolver = resolver;
- if (pdb != null) {
- rp.ReadSymbols = true;
- rp.SymbolReaderProvider = new PdbReaderProvider ();
- rp.SymbolStream = new MemoryStream (pdb);
- }
-
- rp.ReadingMode = ReadingMode.Immediate;
-
- this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp);
- }
-
- Populate ();
- }
-
- public AssemblyInfo (ILogger logger)
- {
- this.logger = logger;
- }
-
- void Populate ()
- {
- ProcessSourceLink();
-
- var d2s = new Dictionary<Document, SourceFile> ();
-
- SourceFile FindSource (Document doc)
- {
- if (doc == null)
- return null;
-
- if (d2s.TryGetValue (doc, out var source))
- return source;
-
- var src = new SourceFile (this, sources.Count, doc, GetSourceLinkUrl (doc.Url));
- sources.Add (src);
- d2s [doc] = src;
- return src;
- };
-
- foreach (var type in image.GetTypes()) {
- var typeInfo = new TypeInfo (this, type);
- typesByName [type.FullName] = typeInfo;
-
- foreach (var method in type.Methods) {
- foreach (var sp in method.DebugInformation.SequencePoints) {
- var source = FindSource (sp.Document);
- var methodInfo = new MethodInfo (this, method, source);
- methods [method.MetadataToken.RID] = methodInfo;
- if (source != null)
- source.AddMethod (methodInfo);
-
- typeInfo.Methods.Add (methodInfo);
- }
- }
- }
- }
-
- private void ProcessSourceLink ()
- {
- var sourceLinkDebugInfo = image.CustomDebugInformations.FirstOrDefault (i => i.Kind == CustomDebugInformationKind.SourceLink);
-
- if (sourceLinkDebugInfo != null) {
- var sourceLinkContent = ((SourceLinkDebugInformation)sourceLinkDebugInfo).Content;
-
- if (sourceLinkContent != null) {
- var jObject = JObject.Parse (sourceLinkContent) ["documents"];
- sourceLinkMappings = JsonConvert.DeserializeObject<Dictionary<string, string>> (jObject.ToString ());
- }
- }
- }
-
- private Uri GetSourceLinkUrl (string document)
- {
- if (sourceLinkMappings.TryGetValue (document, out string url))
- return new Uri (url);
-
- foreach (var sourceLinkDocument in sourceLinkMappings) {
- string key = sourceLinkDocument.Key;
-
- if (Path.GetFileName (key) != "*") {
- continue;
- }
-
- var keyTrim = key.TrimEnd ('*');
-
- if (document.StartsWith(keyTrim, StringComparison.OrdinalIgnoreCase)) {
- var docUrlPart = document.Replace (keyTrim, "");
- return new Uri (sourceLinkDocument.Value.TrimEnd ('*') + docUrlPart);
- }
- }
-
- return null;
- }
-
- public IEnumerable<SourceFile> Sources
- => this.sources;
-
- public Dictionary<string, TypeInfo> TypesByName => this.typesByName;
- public int Id => id;
- public string Name => image.Name;
-
- public SourceFile GetDocById (int document)
- {
- return sources.FirstOrDefault (s => s.SourceId.Document == document);
- }
-
- public MethodInfo GetMethodByToken (uint token)
- {
- methods.TryGetValue (token, out var value);
- return value;
- }
-
- public TypeInfo GetTypeByName (string name) {
- typesByName.TryGetValue (name, out var res);
- return res;
- }
- }
-
- internal class SourceFile {
- Dictionary<uint, MethodInfo> methods;
- AssemblyInfo assembly;
- int id;
- Document doc;
-
- internal SourceFile (AssemblyInfo assembly, int id, Document doc, Uri sourceLinkUri)
- {
- this.methods = new Dictionary<uint, MethodInfo> ();
- this.SourceLinkUri = sourceLinkUri;
- this.assembly = assembly;
- this.id = id;
- this.doc = doc;
- this.DebuggerFileName = doc.Url.Replace ("\\", "/").Replace (":", "");
-
- this.SourceUri = new Uri ((Path.IsPathRooted (doc.Url) ? "file://" : "") + doc.Url, UriKind.RelativeOrAbsolute);
- if (SourceUri.IsFile && File.Exists (SourceUri.LocalPath)) {
- this.Url = this.SourceUri.ToString ();
- } else {
- this.Url = DotNetUrl;
- }
- }
-
- internal void AddMethod (MethodInfo mi)
- {
- if (!this.methods.ContainsKey (mi.Token))
- this.methods [mi.Token] = mi;
- }
-
- public string DebuggerFileName { get; }
- public string Url { get; }
- public string AssemblyName => assembly.Name;
- public string DotNetUrl => $"dotnet://{assembly.Name}/{DebuggerFileName}";
-
- public SourceId SourceId => new SourceId (assembly.Id, this.id);
- public Uri SourceLinkUri { get; }
- public Uri SourceUri { get; }
-
- public IEnumerable<MethodInfo> Methods => this.methods.Values;
-
- public string DocUrl => doc.Url;
-
- public (int startLine, int startColumn, int endLine, int endColumn) GetExtents ()
- {
- var start = Methods.OrderBy (m => m.StartLocation.Line).ThenBy (m => m.StartLocation.Column).First ();
- var end = Methods.OrderByDescending (m => m.EndLocation.Line).ThenByDescending (m => m.EndLocation.Column).First ();
- return (start.StartLocation.Line, start.StartLocation.Column, end.EndLocation.Line, end.EndLocation.Column);
- }
-
- async Task<MemoryStream> GetDataAsync (Uri uri, CancellationToken token)
- {
- var mem = new MemoryStream ();
- try {
- if (uri.IsFile && File.Exists (uri.LocalPath)) {
- using (var file = File.Open (SourceUri.LocalPath, FileMode.Open)) {
- await file.CopyToAsync (mem, token).ConfigureAwait (false);
- mem.Position = 0;
- }
- } else if (uri.Scheme == "http" || uri.Scheme == "https") {
- var client = new HttpClient ();
- using (var stream = await client.GetStreamAsync (uri)) {
- await stream.CopyToAsync (mem, token).ConfigureAwait (false);
- mem.Position = 0;
- }
- }
- } catch (Exception) {
- return null;
- }
- return mem;
- }
-
- static HashAlgorithm GetHashAlgorithm (DocumentHashAlgorithm algorithm)
- {
- switch (algorithm) {
- case DocumentHashAlgorithm.SHA1: return SHA1.Create ();
- case DocumentHashAlgorithm.SHA256: return SHA256.Create ();
- case DocumentHashAlgorithm.MD5: return MD5.Create ();
- }
- return null;
- }
-
- bool CheckPdbHash (byte [] computedHash)
- {
- if (computedHash.Length != doc.Hash.Length)
- return false;
-
- for (var i = 0; i < computedHash.Length; i++)
- if (computedHash[i] != doc.Hash[i])
- return false;
-
- return true;
- }
-
- byte[] ComputePdbHash (Stream sourceStream)
- {
- var algorithm = GetHashAlgorithm (doc.HashAlgorithm);
- if (algorithm != null)
- using (algorithm)
- return algorithm.ComputeHash (sourceStream);
-
- return Array.Empty<byte> ();
- }
-
- public async Task<Stream> GetSourceAsync (bool checkHash, CancellationToken token = default(CancellationToken))
- {
- if (doc.EmbeddedSource.Length > 0)
- return new MemoryStream (doc.EmbeddedSource, false);
-
- foreach (var url in new [] { SourceUri, SourceLinkUri }) {
- var mem = await GetDataAsync (url, token).ConfigureAwait (false);
- if (mem != null && (!checkHash || CheckPdbHash (ComputePdbHash (mem)))) {
- mem.Position = 0;
- return mem;
- }
- }
-
- return MemoryStream.Null;
- }
-
- public object ToScriptSource (int executionContextId, object executionContextAuxData)
- {
- return new {
- scriptId = SourceId.ToString (),
- url = Url,
- executionContextId,
- executionContextAuxData,
- //hash: should be the v8 hash algo, managed implementation is pending
- dotNetUrl = DotNetUrl,
- };
- }
- }
-
- internal class DebugStore {
- List<AssemblyInfo> assemblies = new List<AssemblyInfo> ();
- readonly HttpClient client;
- readonly ILogger logger;
-
- public DebugStore (ILogger logger, HttpClient client) {
- this.client = client;
- this.logger = logger;
- }
-
- public DebugStore (ILogger logger) : this (logger, new HttpClient ())
- {
- }
-
- class DebugItem {
- public string Url { get; set; }
- public Task<byte[][]> Data { get; set; }
- }
-
- public async IAsyncEnumerable<SourceFile> Load (SessionId sessionId, string [] loaded_files, [EnumeratorCancellation] CancellationToken token)
- {
- static bool MatchPdb (string asm, string pdb)
- => Path.ChangeExtension (asm, "pdb") == pdb;
-
- var asm_files = new List<string> ();
- var pdb_files = new List<string> ();
- foreach (var file_name in loaded_files) {
- if (file_name.EndsWith (".pdb", StringComparison.OrdinalIgnoreCase))
- pdb_files.Add (file_name);
- else
- asm_files.Add (file_name);
- }
-
- List<DebugItem> steps = new List<DebugItem> ();
- foreach (var url in asm_files) {
- try {
- var pdb = pdb_files.FirstOrDefault (n => MatchPdb (url, n));
- steps.Add (
- new DebugItem {
- Url = url,
- Data = Task.WhenAll (client.GetByteArrayAsync (url), pdb != null ? client.GetByteArrayAsync (pdb) : Task.FromResult<byte []> (null))
- });
- } catch (Exception e) {
- logger.LogDebug ($"Failed to read {url} ({e.Message})");
- }
- }
-
- var resolver = new DefaultAssemblyResolver ();
- foreach (var step in steps) {
- AssemblyInfo assembly = null;
- try {
- var bytes = await step.Data.ConfigureAwait (false);
- assembly = new AssemblyInfo (resolver, step.Url, bytes [0], bytes [1]);
- } catch (Exception e) {
- logger.LogDebug ($"Failed to load {step.Url} ({e.Message})");
- }
- if (assembly == null)
- continue;
-
- assemblies.Add (assembly);
- foreach (var source in assembly.Sources)
- yield return source;
- }
- }
-
- public IEnumerable<SourceFile> AllSources ()
- => assemblies.SelectMany (a => a.Sources);
-
- public SourceFile GetFileById (SourceId id)
- => AllSources ().SingleOrDefault (f => f.SourceId.Equals (id));
-
- public AssemblyInfo GetAssemblyByName (string name)
- => assemblies.FirstOrDefault (a => a.Name.Equals (name, StringComparison.InvariantCultureIgnoreCase));
-
- /*
- V8 uses zero based indexing for both line and column.
- PPDBs uses one based indexing for both line and column.
- */
- static bool Match (SequencePoint sp, SourceLocation start, SourceLocation end)
- {
- var spStart = (Line: sp.StartLine - 1, Column: sp.StartColumn - 1);
- var spEnd = (Line: sp.EndLine - 1, Column: sp.EndColumn - 1);
-
- if (start.Line > spEnd.Line)
- return false;
-
- if (start.Column > spEnd.Column && start.Line == spEnd.Line)
- return false;
-
- if (end.Line < spStart.Line)
- return false;
-
- if (end.Column < spStart.Column && end.Line == spStart.Line)
- return false;
-
- return true;
- }
-
- public List<SourceLocation> FindPossibleBreakpoints (SourceLocation start, SourceLocation end)
- {
- //XXX FIXME no idea what todo with locations on different files
- if (start.Id != end.Id) {
- logger.LogDebug ($"FindPossibleBreakpoints: documents differ (start: {start.Id}) (end {end.Id}");
- return null;
- }
-
- var sourceId = start.Id;
-
- var doc = GetFileById (sourceId);
-
- var res = new List<SourceLocation> ();
- if (doc == null) {
- logger.LogDebug ($"Could not find document {sourceId}");
- return res;
- }
-
- foreach (var method in doc.Methods) {
- foreach (var sequencePoint in method.DebugInformation.SequencePoints) {
- if (!sequencePoint.IsHidden && Match (sequencePoint, start, end))
- res.Add (new SourceLocation (method, sequencePoint));
- }
- }
- return res;
- }
-
- /*
- V8 uses zero based indexing for both line and column.
- PPDBs uses one based indexing for both line and column.
- */
- static bool Match (SequencePoint sp, int line, int column)
- {
- var bp = (line: line + 1, column: column + 1);
-
- if (sp.StartLine > bp.line || sp.EndLine < bp.line)
- return false;
-
- //Chrome sends a zero column even if getPossibleBreakpoints say something else
- if (column == 0)
- return true;
-
- if (sp.StartColumn > bp.column && sp.StartLine == bp.line)
- return false;
-
- if (sp.EndColumn < bp.column && sp.EndLine == bp.line)
- return false;
-
- return true;
- }
-
- public IEnumerable<SourceLocation> FindBreakpointLocations (BreakpointRequest request)
- {
- request.TryResolve (this);
-
- var asm = assemblies.FirstOrDefault (a => a.Name.Equals (request.Assembly, StringComparison.OrdinalIgnoreCase));
- var sourceFile = asm?.Sources?.SingleOrDefault (s => s.DebuggerFileName.Equals (request.File, StringComparison.OrdinalIgnoreCase));
-
- if (sourceFile == null)
- yield break;
-
- foreach (var method in sourceFile.Methods) {
- foreach (var sequencePoint in method.DebugInformation.SequencePoints) {
- if (!sequencePoint.IsHidden && Match (sequencePoint, request.Line, request.Column))
- yield return new SourceLocation (method, sequencePoint);
- }
- }
- }
-
- public string ToUrl (SourceLocation location)
- => location != null ? GetFileById (location.Id).Url : "";
- }
+namespace Microsoft.WebAssembly.Diagnostics
+{
+ internal class BreakpointRequest
+ {
+ public string Id { get; private set; }
+ public string Assembly { get; private set; }
+ public string File { get; private set; }
+ public int Line { get; private set; }
+ public int Column { get; private set; }
+ public MethodInfo Method { get; private set; }
+
+ JObject request;
+
+ public bool IsResolved => Assembly != null;
+ public List<Breakpoint> Locations { get; } = new List<Breakpoint>();
+
+ public override string ToString() => $"BreakpointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}";
+
+ public object AsSetBreakpointByUrlResponse(IEnumerable<object> jsloc) => new { breakpointId = Id, locations = Locations.Select(l => l.Location.AsLocation()).Concat(jsloc) };
+
+ public BreakpointRequest()
+ { }
+
+ public BreakpointRequest(string id, MethodInfo method)
+ {
+ Id = id;
+ Method = method;
+ }
+
+ public BreakpointRequest(string id, JObject request)
+ {
+ Id = id;
+ this.request = request;
+ }
+
+ public static BreakpointRequest Parse(string id, JObject args)
+ {
+ return new BreakpointRequest(id, args);
+ }
+
+ public BreakpointRequest Clone() => new BreakpointRequest { Id = Id, request = request };
+
+ public bool IsMatch(SourceFile sourceFile)
+ {
+ var url = request?["url"]?.Value<string>();
+ if (url == null)
+ {
+ var urlRegex = request?["urlRegex"].Value<string>();
+ var regex = new Regex(urlRegex);
+ return regex.IsMatch(sourceFile.Url.ToString()) || regex.IsMatch(sourceFile.DocUrl);
+ }
+
+ return sourceFile.Url.ToString() == url || sourceFile.DotNetUrl == url;
+ }
+
+ public bool TryResolve(SourceFile sourceFile)
+ {
+ if (!IsMatch(sourceFile))
+ return false;
+
+ var line = request?["lineNumber"]?.Value<int>();
+ var column = request?["columnNumber"]?.Value<int>();
+
+ if (line == null || column == null)
+ return false;
+
+ Assembly = sourceFile.AssemblyName;
+ File = sourceFile.DebuggerFileName;
+ Line = line.Value;
+ Column = column.Value;
+ return true;
+ }
+
+ public bool TryResolve(DebugStore store)
+ {
+ if (request == null || store == null)
+ return false;
+
+ return store.AllSources().FirstOrDefault(source => TryResolve(source)) != null;
+ }
+ }
+
+ internal class VarInfo
+ {
+ public VarInfo(VariableDebugInformation v)
+ {
+ this.Name = v.Name;
+ this.Index = v.Index;
+ }
+
+ public VarInfo(ParameterDefinition p)
+ {
+ this.Name = p.Name;
+ this.Index = (p.Index + 1) * -1;
+ }
+
+ public string Name { get; }
+ public int Index { get; }
+
+ public override string ToString() => $"(var-info [{Index}] '{Name}')";
+ }
+
+ internal class CliLocation
+ {
+ public CliLocation(MethodInfo method, int offset)
+ {
+ Method = method;
+ Offset = offset;
+ }
+
+ public MethodInfo Method { get; }
+ public int Offset { get; }
+ }
+
+ internal class SourceLocation
+ {
+ SourceId id;
+ int line;
+ int column;
+ CliLocation cliLoc;
+
+ public SourceLocation(SourceId id, int line, int column)
+ {
+ this.id = id;
+ this.line = line;
+ this.column = column;
+ }
+
+ public SourceLocation(MethodInfo mi, SequencePoint sp)
+ {
+ this.id = mi.SourceId;
+ this.line = sp.StartLine - 1;
+ this.column = sp.StartColumn - 1;
+ this.cliLoc = new CliLocation(mi, sp.Offset);
+ }
+
+ public SourceId Id { get => id; }
+ public int Line { get => line; }
+ public int Column { get => column; }
+ public CliLocation CliLocation => this.cliLoc;
+
+ public override string ToString() => $"{id}:{Line}:{Column}";
+
+ public static SourceLocation Parse(JObject obj)
+ {
+ if (obj == null)
+ return null;
+
+ if (!SourceId.TryParse(obj["scriptId"]?.Value<string>(), out var id))
+ return null;
+
+ var line = obj["lineNumber"]?.Value<int>();
+ var column = obj["columnNumber"]?.Value<int>();
+ if (id == null || line == null || column == null)
+ return null;
+
+ return new SourceLocation(id, line.Value, column.Value);
+ }
+
+ internal class LocationComparer : EqualityComparer<SourceLocation>
+ {
+ public override bool Equals(SourceLocation l1, SourceLocation l2)
+ {
+ if (l1 == null && l2 == null)
+ return true;
+ else if (l1 == null || l2 == null)
+ return false;
+
+ return (l1.Line == l2.Line &&
+ l1.Column == l2.Column &&
+ l1.Id == l2.Id);
+ }
+
+ public override int GetHashCode(SourceLocation loc)
+ {
+ int hCode = loc.Line ^ loc.Column;
+ return loc.Id.GetHashCode() ^ hCode.GetHashCode();
+ }
+ }
+
+ internal object AsLocation() => new
+ {
+ scriptId = id.ToString(),
+ lineNumber = line,
+ columnNumber = column
+ };
+ }
+
+ internal class SourceId
+ {
+ const string Scheme = "dotnet://";
+
+ readonly int assembly, document;
+
+ public int Assembly => assembly;
+ public int Document => document;
+
+ internal SourceId(int assembly, int document)
+ {
+ this.assembly = assembly;
+ this.document = document;
+ }
+
+ public SourceId(string id)
+ {
+ if (!TryParse(id, out assembly, out document))
+ throw new ArgumentException("invalid source identifier", nameof(id));
+ }
+
+ public static bool TryParse(string id, out SourceId source)
+ {
+ source = null;
+ if (!TryParse(id, out var assembly, out var document))
+ return false;
+
+ source = new SourceId(assembly, document);
+ return true;
+ }
+
+ static bool TryParse(string id, out int assembly, out int document)
+ {
+ assembly = document = 0;
+ if (id == null || !id.StartsWith(Scheme, StringComparison.Ordinal))
+ return false;
+
+ var sp = id.Substring(Scheme.Length).Split('_');
+ if (sp.Length != 2)
+ return false;
+
+ if (!int.TryParse(sp[0], out assembly))
+ return false;
+
+ if (!int.TryParse(sp[1], out document))
+ return false;
+
+ return true;
+ }
+
+ public override string ToString() => $"{Scheme}{assembly}_{document}";
+
+ public override bool Equals(object obj)
+ {
+ if (obj == null)
+ return false;
+ SourceId that = obj as SourceId;
+ return that.assembly == this.assembly && that.document == this.document;
+ }
+
+ public override int GetHashCode() => assembly.GetHashCode() ^ document.GetHashCode();
+
+ public static bool operator ==(SourceId a, SourceId b) => ((object)a == null) ? (object)b == null : a.Equals(b);
+
+ public static bool operator !=(SourceId a, SourceId b) => !a.Equals(b);
+ }
+
+ internal class MethodInfo
+ {
+ MethodDefinition methodDef;
+ SourceFile source;
+
+ public SourceId SourceId => source.SourceId;
+
+ public string Name => methodDef.Name;
+ public MethodDebugInformation DebugInformation => methodDef.DebugInformation;
+
+ public SourceLocation StartLocation { get; }
+ public SourceLocation EndLocation { get; }
+ public AssemblyInfo Assembly { get; }
+ public uint Token => methodDef.MetadataToken.RID;
+
+ public MethodInfo(AssemblyInfo assembly, MethodDefinition methodDef, SourceFile source)
+ {
+ this.Assembly = assembly;
+ this.methodDef = methodDef;
+ this.source = source;
+
+ var sps = DebugInformation.SequencePoints;
+ if (sps == null || sps.Count() < 1)
+ return;
+
+ SequencePoint start = sps[0];
+ SequencePoint end = sps[0];
+
+ foreach (var sp in sps)
+ {
+ if (sp.StartLine < start.StartLine)
+ start = sp;
+ else if (sp.StartLine == start.StartLine && sp.StartColumn < start.StartColumn)
+ start = sp;
+
+ if (sp.EndLine > end.EndLine)
+ end = sp;
+ else if (sp.EndLine == end.EndLine && sp.EndColumn > end.EndColumn)
+ end = sp;
+ }
+
+ StartLocation = new SourceLocation(this, start);
+ EndLocation = new SourceLocation(this, end);
+ }
+
+ public SourceLocation GetLocationByIl(int pos)
+ {
+ SequencePoint prev = null;
+ foreach (var sp in DebugInformation.SequencePoints)
+ {
+ if (sp.Offset > pos)
+ break;
+ prev = sp;
+ }
+
+ if (prev != null)
+ return new SourceLocation(this, prev);
+
+ return null;
+ }
+
+ public VarInfo[] GetLiveVarsAt(int offset)
+ {
+ var res = new List<VarInfo>();
+
+ res.AddRange(methodDef.Parameters.Select(p => new VarInfo(p)));
+ res.AddRange(methodDef.DebugInformation.GetScopes()
+ .Where(s => s.Start.Offset <= offset && (s.End.IsEndOfMethod || s.End.Offset > offset))
+ .SelectMany(s => s.Variables)
+ .Where(v => !v.IsDebuggerHidden)
+ .Select(v => new VarInfo(v)));
+
+ return res.ToArray();
+ }
+
+ public override string ToString() => "MethodInfo(" + methodDef.FullName + ")";
+ }
+
+ internal class TypeInfo
+ {
+ AssemblyInfo assembly;
+ TypeDefinition type;
+ List<MethodInfo> methods;
+
+ public TypeInfo(AssemblyInfo assembly, TypeDefinition type)
+ {
+ this.assembly = assembly;
+ this.type = type;
+ methods = new List<MethodInfo>();
+ }
+
+ public string Name => type.Name;
+ public string FullName => type.FullName;
+ public List<MethodInfo> Methods => methods;
+
+ public override string ToString() => "TypeInfo('" + FullName + "')";
+ }
+
+ class AssemblyInfo
+ {
+ static int next_id;
+ ModuleDefinition image;
+ readonly int id;
+ readonly ILogger logger;
+ Dictionary<uint, MethodInfo> methods = new Dictionary<uint, MethodInfo>();
+ Dictionary<string, string> sourceLinkMappings = new Dictionary<string, string>();
+ Dictionary<string, TypeInfo> typesByName = new Dictionary<string, TypeInfo>();
+ readonly List<SourceFile> sources = new List<SourceFile>();
+ internal string Url { get; }
+
+ public AssemblyInfo(IAssemblyResolver resolver, string url, byte[] assembly, byte[] pdb)
+ {
+ this.id = Interlocked.Increment(ref next_id);
+
+ try
+ {
+ Url = url;
+ ReaderParameters rp = new ReaderParameters( /*ReadingMode.Immediate*/ );
+ rp.AssemblyResolver = resolver;
+ // set ReadSymbols = true unconditionally in case there
+ // is an embedded pdb then handle ArgumentException
+ // and assume that if pdb == null that is the cause
+ rp.ReadSymbols = true;
+ rp.SymbolReaderProvider = new PdbReaderProvider();
+ if (pdb != null)
+ rp.SymbolStream = new MemoryStream(pdb);
+ rp.ReadingMode = ReadingMode.Immediate;
+
+ this.image = ModuleDefinition.ReadModule(new MemoryStream(assembly), rp);
+ }
+ catch (BadImageFormatException ex)
+ {
+ logger.LogWarning($"Failed to read assembly as portable PDB: {ex.Message}");
+ }
+ catch (ArgumentException)
+ {
+ // if pdb == null this is expected and we
+ // read the assembly without symbols below
+ if (pdb != null)
+ throw;
+ }
+
+ if (this.image == null)
+ {
+ ReaderParameters rp = new ReaderParameters( /*ReadingMode.Immediate*/ );
+ rp.AssemblyResolver = resolver;
+ if (pdb != null)
+ {
+ rp.ReadSymbols = true;
+ rp.SymbolReaderProvider = new PdbReaderProvider();
+ rp.SymbolStream = new MemoryStream(pdb);
+ }
+
+ rp.ReadingMode = ReadingMode.Immediate;
+
+ this.image = ModuleDefinition.ReadModule(new MemoryStream(assembly), rp);
+ }
+
+ Populate();
+ }
+
+ public AssemblyInfo(ILogger logger)
+ {
+ this.logger = logger;
+ }
+
+ void Populate()
+ {
+ ProcessSourceLink();
+
+ var d2s = new Dictionary<Document, SourceFile>();
+
+ SourceFile FindSource(Document doc)
+ {
+ if (doc == null)
+ return null;
+
+ if (d2s.TryGetValue(doc, out var source))
+ return source;
+
+ var src = new SourceFile(this, sources.Count, doc, GetSourceLinkUrl(doc.Url));
+ sources.Add(src);
+ d2s[doc] = src;
+ return src;
+ };
+
+ foreach (var type in image.GetTypes())
+ {
+ var typeInfo = new TypeInfo(this, type);
+ typesByName[type.FullName] = typeInfo;
+
+ foreach (var method in type.Methods)
+ {
+ foreach (var sp in method.DebugInformation.SequencePoints)
+ {
+ var source = FindSource(sp.Document);
+ var methodInfo = new MethodInfo(this, method, source);
+ methods[method.MetadataToken.RID] = methodInfo;
+ if (source != null)
+ source.AddMethod(methodInfo);
+
+ typeInfo.Methods.Add(methodInfo);
+ }
+ }
+ }
+ }
+
+ private void ProcessSourceLink()
+ {
+ var sourceLinkDebugInfo = image.CustomDebugInformations.FirstOrDefault(i => i.Kind == CustomDebugInformationKind.SourceLink);
+
+ if (sourceLinkDebugInfo != null)
+ {
+ var sourceLinkContent = ((SourceLinkDebugInformation)sourceLinkDebugInfo).Content;
+
+ if (sourceLinkContent != null)
+ {
+ var jObject = JObject.Parse(sourceLinkContent)["documents"];
+ sourceLinkMappings = JsonConvert.DeserializeObject<Dictionary<string, string>>(jObject.ToString());
+ }
+ }
+ }
+
+ private Uri GetSourceLinkUrl(string document)
+ {
+ if (sourceLinkMappings.TryGetValue(document, out string url))
+ return new Uri(url);
+
+ foreach (var sourceLinkDocument in sourceLinkMappings)
+ {
+ string key = sourceLinkDocument.Key;
+
+ if (Path.GetFileName(key) != "*")
+ {
+ continue;
+ }
+
+ var keyTrim = key.TrimEnd('*');
+
+ if (document.StartsWith(keyTrim, StringComparison.OrdinalIgnoreCase))
+ {
+ var docUrlPart = document.Replace(keyTrim, "");
+ return new Uri(sourceLinkDocument.Value.TrimEnd('*') + docUrlPart);
+ }
+ }
+
+ return null;
+ }
+
+ public IEnumerable<SourceFile> Sources => this.sources;
+
+ public Dictionary<string, TypeInfo> TypesByName => this.typesByName;
+ public int Id => id;
+ public string Name => image.Name;
+
+ public SourceFile GetDocById(int document)
+ {
+ return sources.FirstOrDefault(s => s.SourceId.Document == document);
+ }
+
+ public MethodInfo GetMethodByToken(uint token)
+ {
+ methods.TryGetValue(token, out var value);
+ return value;
+ }
+
+ public TypeInfo GetTypeByName(string name)
+ {
+ typesByName.TryGetValue(name, out var res);
+ return res;
+ }
+ }
+
+ internal class SourceFile
+ {
+ Dictionary<uint, MethodInfo> methods;
+ AssemblyInfo assembly;
+ int id;
+ Document doc;
+
+ internal SourceFile(AssemblyInfo assembly, int id, Document doc, Uri sourceLinkUri)
+ {
+ this.methods = new Dictionary<uint, MethodInfo>();
+ this.SourceLinkUri = sourceLinkUri;
+ this.assembly = assembly;
+ this.id = id;
+ this.doc = doc;
+ this.DebuggerFileName = doc.Url.Replace("\\", "/").Replace(":", "");
+
+ this.SourceUri = new Uri((Path.IsPathRooted(doc.Url) ? "file://" : "") + doc.Url, UriKind.RelativeOrAbsolute);
+ if (SourceUri.IsFile && File.Exists(SourceUri.LocalPath))
+ {
+ this.Url = this.SourceUri.ToString();
+ }
+ else
+ {
+ this.Url = DotNetUrl;
+ }
+ }
+
+ internal void AddMethod(MethodInfo mi)
+ {
+ if (!this.methods.ContainsKey(mi.Token))
+ this.methods[mi.Token] = mi;
+ }
+
+ public string DebuggerFileName { get; }
+ public string Url { get; }
+ public string AssemblyName => assembly.Name;
+ public string DotNetUrl => $"dotnet://{assembly.Name}/{DebuggerFileName}";
+
+ public SourceId SourceId => new SourceId(assembly.Id, this.id);
+ public Uri SourceLinkUri { get; }
+ public Uri SourceUri { get; }
+
+ public IEnumerable<MethodInfo> Methods => this.methods.Values;
+
+ public string DocUrl => doc.Url;
+
+ public (int startLine, int startColumn, int endLine, int endColumn) GetExtents()
+ {
+ var start = Methods.OrderBy(m => m.StartLocation.Line).ThenBy(m => m.StartLocation.Column).First();
+ var end = Methods.OrderByDescending(m => m.EndLocation.Line).ThenByDescending(m => m.EndLocation.Column).First();
+ return (start.StartLocation.Line, start.StartLocation.Column, end.EndLocation.Line, end.EndLocation.Column);
+ }
+
+ async Task<MemoryStream> GetDataAsync(Uri uri, CancellationToken token)
+ {
+ var mem = new MemoryStream();
+ try
+ {
+ if (uri.IsFile && File.Exists(uri.LocalPath))
+ {
+ using (var file = File.Open(SourceUri.LocalPath, FileMode.Open))
+ {
+ await file.CopyToAsync(mem, token).ConfigureAwait(false);
+ mem.Position = 0;
+ }
+ }
+ else if (uri.Scheme == "http" || uri.Scheme == "https")
+ {
+ var client = new HttpClient();
+ using (var stream = await client.GetStreamAsync(uri))
+ {
+ await stream.CopyToAsync(mem, token).ConfigureAwait(false);
+ mem.Position = 0;
+ }
+ }
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+ return mem;
+ }
+
+ static HashAlgorithm GetHashAlgorithm(DocumentHashAlgorithm algorithm)
+ {
+ switch (algorithm)
+ {
+ case DocumentHashAlgorithm.SHA1:
+ return SHA1.Create();
+ case DocumentHashAlgorithm.SHA256:
+ return SHA256.Create();
+ case DocumentHashAlgorithm.MD5:
+ return MD5.Create();
+ }
+ return null;
+ }
+
+ bool CheckPdbHash(byte[] computedHash)
+ {
+ if (computedHash.Length != doc.Hash.Length)
+ return false;
+
+ for (var i = 0; i < computedHash.Length; i++)
+ if (computedHash[i] != doc.Hash[i])
+ return false;
+
+ return true;
+ }
+
+ byte[] ComputePdbHash(Stream sourceStream)
+ {
+ var algorithm = GetHashAlgorithm(doc.HashAlgorithm);
+ if (algorithm != null)
+ using (algorithm)
+ return algorithm.ComputeHash(sourceStream);
+
+ return Array.Empty<byte>();
+ }
+
+ public async Task<Stream> GetSourceAsync(bool checkHash, CancellationToken token = default(CancellationToken))
+ {
+ if (doc.EmbeddedSource.Length > 0)
+ return new MemoryStream(doc.EmbeddedSource, false);
+
+ foreach (var url in new[] { SourceUri, SourceLinkUri })
+ {
+ var mem = await GetDataAsync(url, token).ConfigureAwait(false);
+ if (mem != null && (!checkHash || CheckPdbHash(ComputePdbHash(mem))))
+ {
+ mem.Position = 0;
+ return mem;
+ }
+ }
+
+ return MemoryStream.Null;
+ }
+
+ public object ToScriptSource(int executionContextId, object executionContextAuxData)
+ {
+ return new
+ {
+ scriptId = SourceId.ToString(),
+ url = Url,
+ executionContextId,
+ executionContextAuxData,
+ //hash: should be the v8 hash algo, managed implementation is pending
+ dotNetUrl = DotNetUrl,
+ };
+ }
+ }
+
+ internal class DebugStore
+ {
+ List<AssemblyInfo> assemblies = new List<AssemblyInfo>();
+ readonly HttpClient client;
+ readonly ILogger logger;
+
+ public DebugStore(ILogger logger, HttpClient client)
+ {
+ this.client = client;
+ this.logger = logger;
+ }
+
+ public DebugStore(ILogger logger) : this(logger, new HttpClient())
+ { }
+
+ class DebugItem
+ {
+ public string Url { get; set; }
+ public Task<byte[][]> Data { get; set; }
+ }
+
+ public async IAsyncEnumerable<SourceFile> Load(SessionId sessionId, string[] loaded_files, [EnumeratorCancellation] CancellationToken token)
+ {
+ static bool MatchPdb(string asm, string pdb) => Path.ChangeExtension(asm, "pdb") == pdb;
+
+ var asm_files = new List<string>();
+ var pdb_files = new List<string>();
+ foreach (var file_name in loaded_files)
+ {
+ if (file_name.EndsWith(".pdb", StringComparison.OrdinalIgnoreCase))
+ pdb_files.Add(file_name);
+ else
+ asm_files.Add(file_name);
+ }
+
+ List<DebugItem> steps = new List<DebugItem>();
+ foreach (var url in asm_files)
+ {
+ try
+ {
+ var pdb = pdb_files.FirstOrDefault(n => MatchPdb(url, n));
+ steps.Add(
+ new DebugItem
+ {
+ Url = url,
+ Data = Task.WhenAll(client.GetByteArrayAsync(url), pdb != null ? client.GetByteArrayAsync(pdb) : Task.FromResult<byte[]>(null))
+ });
+ }
+ catch (Exception e)
+ {
+ logger.LogDebug($"Failed to read {url} ({e.Message})");
+ }
+ }
+
+ var resolver = new DefaultAssemblyResolver();
+ foreach (var step in steps)
+ {
+ AssemblyInfo assembly = null;
+ try
+ {
+ var bytes = await step.Data.ConfigureAwait(false);
+ assembly = new AssemblyInfo(resolver, step.Url, bytes[0], bytes[1]);
+ }
+ catch (Exception e)
+ {
+ logger.LogDebug($"Failed to load {step.Url} ({e.Message})");
+ }
+ if (assembly == null)
+ continue;
+
+ assemblies.Add(assembly);
+ foreach (var source in assembly.Sources)
+ yield return source;
+ }
+ }
+
+ public IEnumerable<SourceFile> AllSources() => assemblies.SelectMany(a => a.Sources);
+
+ public SourceFile GetFileById(SourceId id) => AllSources().SingleOrDefault(f => f.SourceId.Equals(id));
+
+ public AssemblyInfo GetAssemblyByName(string name) => assemblies.FirstOrDefault(a => a.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
+
+ /*
+ V8 uses zero based indexing for both line and column.
+ PPDBs uses one based indexing for both line and column.
+ */
+ static bool Match(SequencePoint sp, SourceLocation start, SourceLocation end)
+ {
+ var spStart = (Line: sp.StartLine - 1, Column: sp.StartColumn - 1);
+ var spEnd = (Line: sp.EndLine - 1, Column: sp.EndColumn - 1);
+
+ if (start.Line > spEnd.Line)
+ return false;
+
+ if (start.Column > spEnd.Column && start.Line == spEnd.Line)
+ return false;
+
+ if (end.Line < spStart.Line)
+ return false;
+
+ if (end.Column < spStart.Column && end.Line == spStart.Line)
+ return false;
+
+ return true;
+ }
+
+ public List<SourceLocation> FindPossibleBreakpoints(SourceLocation start, SourceLocation end)
+ {
+ //XXX FIXME no idea what todo with locations on different files
+ if (start.Id != end.Id)
+ {
+ logger.LogDebug($"FindPossibleBreakpoints: documents differ (start: {start.Id}) (end {end.Id}");
+ return null;
+ }
+
+ var sourceId = start.Id;
+
+ var doc = GetFileById(sourceId);
+
+ var res = new List<SourceLocation>();
+ if (doc == null)
+ {
+ logger.LogDebug($"Could not find document {sourceId}");
+ return res;
+ }
+
+ foreach (var method in doc.Methods)
+ {
+ foreach (var sequencePoint in method.DebugInformation.SequencePoints)
+ {
+ if (!sequencePoint.IsHidden && Match(sequencePoint, start, end))
+ res.Add(new SourceLocation(method, sequencePoint));
+ }
+ }
+ return res;
+ }
+
+ /*
+ V8 uses zero based indexing for both line and column.
+ PPDBs uses one based indexing for both line and column.
+ */
+ static bool Match(SequencePoint sp, int line, int column)
+ {
+ var bp = (line: line + 1, column: column + 1);
+
+ if (sp.StartLine > bp.line || sp.EndLine < bp.line)
+ return false;
+
+ //Chrome sends a zero column even if getPossibleBreakpoints say something else
+ if (column == 0)
+ return true;
+
+ if (sp.StartColumn > bp.column && sp.StartLine == bp.line)
+ return false;
+
+ if (sp.EndColumn < bp.column && sp.EndLine == bp.line)
+ return false;
+
+ return true;
+ }
+
+ public IEnumerable<SourceLocation> FindBreakpointLocations(BreakpointRequest request)
+ {
+ request.TryResolve(this);
+
+ var asm = assemblies.FirstOrDefault(a => a.Name.Equals(request.Assembly, StringComparison.OrdinalIgnoreCase));
+ var sourceFile = asm?.Sources?.SingleOrDefault(s => s.DebuggerFileName.Equals(request.File, StringComparison.OrdinalIgnoreCase));
+
+ if (sourceFile == null)
+ yield break;
+
+ foreach (var method in sourceFile.Methods)
+ {
+ foreach (var sequencePoint in method.DebugInformation.SequencePoints)
+ {
+ if (!sequencePoint.IsHidden && Match(sequencePoint, request.Line, request.Column))
+ yield return new SourceLocation(method, sequencePoint);
+ }
+ }
+ }
+
+ public string ToUrl(SourceLocation location) => location != null ? GetFileById(location.Id).Url : "";
+ }
}
diff --git a/sdks/wasm/BrowserDebugProxy/DebuggerProxy.cs b/sdks/wasm/BrowserDebugProxy/DebuggerProxy.cs
index 74566c56024..08fe836200d 100644
--- a/sdks/wasm/BrowserDebugProxy/DebuggerProxy.cs
+++ b/sdks/wasm/BrowserDebugProxy/DebuggerProxy.cs
@@ -1,22 +1,29 @@
+// 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.Net.WebSockets;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
-namespace WebAssembly.Net.Debugging {
+namespace Microsoft.WebAssembly.Diagnostics
+{
- // This type is the public entrypoint that allows external code to attach the debugger proxy
- // to a given websocket listener. Everything else in this package can be internal.
+ // This type is the public entrypoint that allows external code to attach the debugger proxy
+ // to a given websocket listener. Everything else in this package can be internal.
- public class DebuggerProxy {
- private readonly MonoProxy proxy;
+ public class DebuggerProxy
+ {
+ private readonly MonoProxy proxy;
- public DebuggerProxy (ILoggerFactory loggerFactory) {
- proxy = new MonoProxy(loggerFactory);
- }
+ public DebuggerProxy(ILoggerFactory loggerFactory)
+ {
+ proxy = new MonoProxy(loggerFactory);
+ }
- public Task Run (Uri browserUri, WebSocket ideSocket) {
- return proxy.Run (browserUri, ideSocket);
- }
- }
+ public Task Run(Uri browserUri, WebSocket ideSocket)
+ {
+ return proxy.Run(browserUri, ideSocket);
+ }
+ }
}
diff --git a/sdks/wasm/BrowserDebugProxy/DevToolsHelper.cs b/sdks/wasm/BrowserDebugProxy/DevToolsHelper.cs
index a615540e450..a08a62512de 100644
--- a/sdks/wasm/BrowserDebugProxy/DevToolsHelper.cs
+++ b/sdks/wasm/BrowserDebugProxy/DevToolsHelper.cs
@@ -1,306 +1,308 @@
+// 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.IO;
using System.Linq;
+using System.Net;
+using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
-using System.Threading;
-using System.IO;
-using System.Collections.Generic;
-using System.Net;
-using Microsoft.Extensions.Logging;
+namespace Microsoft.WebAssembly.Diagnostics
+{
+
+ public struct SessionId
+ {
+ public readonly string sessionId;
+
+ public SessionId(string sessionId)
+ {
+ this.sessionId = sessionId;
+ }
+
+ // hashset treats 0 as unset
+ public override int GetHashCode() => sessionId?.GetHashCode() ?? -1;
+
+ public override bool Equals(object obj) => (obj is SessionId) ? ((SessionId)obj).sessionId == sessionId : false;
+
+ public static bool operator ==(SessionId a, SessionId b) => a.sessionId == b.sessionId;
+
+ public static bool operator !=(SessionId a, SessionId b) => a.sessionId != b.sessionId;
+
+ public static SessionId Null { get; } = new SessionId();
+
+ public override string ToString() => $"session-{sessionId}";
+ }
+
+ public 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}";
-namespace WebAssembly.Net.Debugging {
+ public override int GetHashCode() => (sessionId?.GetHashCode() ?? 0) ^ id.GetHashCode();
- internal struct SessionId {
- public readonly string sessionId;
+ public override bool Equals(object obj) => (obj is MessageId) ? ((MessageId)obj).sessionId == sessionId && ((MessageId)obj).id == id : false;
+ }
- public SessionId (string sessionId)
- {
- this.sessionId = sessionId;
- }
+ internal class DotnetObjectId
+ {
+ public string Scheme { get; }
+ public string Value { get; }
- // hashset treats 0 as unset
- public override int GetHashCode ()
- => sessionId?.GetHashCode () ?? -1;
+ public static bool TryParse(JToken jToken, out DotnetObjectId objectId) => TryParse(jToken?.Value<string>(), out objectId);
- public override bool Equals (object obj)
- => (obj is SessionId) ? ((SessionId) obj).sessionId == sessionId : false;
+ public static bool TryParse(string id, out DotnetObjectId objectId)
+ {
+ objectId = null;
+ if (id == null)
+ return false;
+
+ if (!id.StartsWith("dotnet:"))
+ return false;
- public static bool operator == (SessionId a, SessionId b)
- => a.sessionId == b.sessionId;
+ var parts = id.Split(":", 3);
- public static bool operator != (SessionId a, SessionId b)
- => a.sessionId != b.sessionId;
+ if (parts.Length < 3)
+ return false;
- public static SessionId Null { get; } = new SessionId ();
+ objectId = new DotnetObjectId(parts[1], parts[2]);
- public override string ToString ()
- => $"session-{sessionId}";
- }
+ return true;
+ }
- internal struct MessageId {
- public readonly string sessionId;
- public readonly int id;
+ public DotnetObjectId(string scheme, string value)
+ {
+ Scheme = scheme;
+ Value = value;
+ }
- public MessageId (string sessionId, int id)
- {
- this.sessionId = sessionId;
- this.id = id;
- }
+ public override string ToString() => $"dotnet:{Scheme}:{Value}";
+ }
- public static implicit operator SessionId (MessageId id)
- => new SessionId (id.sessionId);
+ public struct Result
+ {
+ public JObject Value { get; private set; }
+ public JObject Error { get; private set; }
- public override string ToString ()
- => $"msg-{sessionId}:::{id}";
+ public bool IsOk => Value != null;
+ public bool IsErr => Error != null;
- public override int GetHashCode ()
- => (sessionId?.GetHashCode () ?? 0) ^ id.GetHashCode ();
+ Result(JObject result, JObject error)
+ {
+ if (result != null && error != null)
+ throw new ArgumentException($"Both {nameof(result)} and {nameof(error)} arguments cannot be non-null.");
- public override bool Equals (object obj)
- => (obj is MessageId) ? ((MessageId) obj).sessionId == sessionId && ((MessageId) obj).id == id : false;
- }
-
- internal class DotnetObjectId {
- public string Scheme { get; }
- public string Value { get; }
-
- public static bool TryParse (JToken jToken, out DotnetObjectId objectId)
- => TryParse (jToken?.Value<string>(), out objectId);
-
- public static bool TryParse (string id, out DotnetObjectId objectId)
- {
- objectId = null;
- if (id == null)
- return false;
-
- if (!id.StartsWith ("dotnet:"))
- return false;
-
- var parts = id.Split (":", 3);
+ 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;
+ }
+ }
- if (parts.Length < 3)
- return false;
+ public static Result FromJson(JObject obj)
+ {
+ //Log ("protocol", $"from result: {obj}");
+ return new Result(obj["result"] as JObject, obj["error"] as JObject);
+ }
- objectId = new DotnetObjectId (parts[1], parts[2]);
+ public static Result Ok(JObject ok) => new Result(ok, null);
- return true;
- }
+ public static Result OkFromObject(object ok) => Ok(JObject.FromObject(ok));
- public DotnetObjectId (string scheme, string value)
- {
- Scheme = scheme;
- Value = value;
- }
+ public static Result Err(JObject err) => new Result(null, err);
- public override string ToString ()
- => $"dotnet:{Scheme}:{Value}";
- }
+ public static Result Err(string msg) => new Result(null, JObject.FromObject(new { message = msg }));
- internal struct Result {
- public JObject Value { get; private set; }
- public JObject Error { get; private set; }
+ public static Result Exception(Exception e) => new Result(null, JObject.FromObject(new { message = e.Message }));
- public bool IsOk => Value != null;
- public bool IsErr => Error != null;
+ 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 GetExceptionObject() => new MonoCommands("MONO.mono_wasm_get_exception_object()");
+
+ 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 GetDetails(DotnetObjectId objectId, JToken args = null) => new MonoCommands($"MONO.mono_wasm_get_details ('{objectId}', {(args ?? "{ }")})");
+
+ public static MonoCommands GetScopeVariables(int scopeId, params VarInfo[] vars)
+ {
+ var var_ids = vars.Select(v => new { index = v.Index, name = v.Name }).ToArray();
+ return new MonoCommands($"MONO.mono_wasm_get_variables({scopeId}, {JsonConvert.SerializeObject(var_ids)})");
+ }
+
+ public static MonoCommands SetBreakpoint(string assemblyName, uint 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})");
+
+ public static MonoCommands ReleaseObject(DotnetObjectId objectId) => new MonoCommands($"MONO.mono_wasm_release_object('{objectId}')");
+
+ public static MonoCommands CallFunctionOn(JToken args) => new MonoCommands($"MONO.mono_wasm_call_function_on ({args.ToString()})");
+
+ public static MonoCommands Resume() => new MonoCommands($"MONO.mono_wasm_debugger_resume ()");
+
+ public static MonoCommands SetPauseOnExceptions(string state) => new MonoCommands($"MONO.mono_wasm_set_pause_on_exceptions(\"{state}\")");
+ }
+
+ 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
+ }
- Result (JObject result, JObject error)
- {
- if (result != null && error != null)
- throw new ArgumentException ($"Both {nameof(result)} and {nameof(error)} arguments cannot be non-null.");
+ enum StepKind
+ {
+ Into,
+ Out,
+ Over
+ }
- 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;
- }
- }
+ internal class ExecutionContext
+ {
+ public string DebuggerId { get; set; }
+ public Dictionary<string, BreakpointRequest> BreakpointRequests { get; } = new Dictionary<string, BreakpointRequest>();
- 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 Err (string msg)
- => new Result (null, JObject.FromObject (new { message = msg }));
-
- 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 GetDetails (DotnetObjectId objectId, JToken args = null)
- => new MonoCommands ($"MONO.mono_wasm_get_details ('{objectId}', {(args ?? "{}")})");
-
- public static MonoCommands GetScopeVariables (int scopeId, params VarInfo[] vars)
- {
- var var_ids = vars.Select (v => new { index = v.Index, name = v.Name }).ToArray ();
- return new MonoCommands ($"MONO.mono_wasm_get_variables({scopeId}, {JsonConvert.SerializeObject (var_ids)})");
- }
-
- public static MonoCommands SetBreakpoint (string assemblyName, uint 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})");
-
- public static MonoCommands ReleaseObject (DotnetObjectId objectId)
- => new MonoCommands ($"MONO.mono_wasm_release_object('{objectId}')");
-
- public static MonoCommands CallFunctionOn (JToken args)
- => new MonoCommands ($"MONO.mono_wasm_call_function_on ({args.ToString ()})");
-
- public static MonoCommands Resume ()
- => new MonoCommands ($"MONO.mono_wasm_debugger_resume ()");
- }
-
- 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
- }
+ public TaskCompletionSource<DebugStore> ready = null;
+ public bool IsRuntimeReady => ready != null && ready.Task.IsCompleted;
- internal class ExecutionContext {
- public string DebuggerId { get; set; }
- public Dictionary<string,BreakpointRequest> BreakpointRequests { get; } = new Dictionary<string,BreakpointRequest> ();
+ public int Id { get; set; }
+ public object AuxData { get; set; }
- 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; }
-
- public string[] LoadedFiles { get; set; }
- internal DebugStore store;
- public TaskCompletionSource<DebugStore> Source { get; } = new TaskCompletionSource<DebugStore> ();
-
- 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;
- LocalsCache.Clear ();
- }
-
- }
+ public List<Frame> CallStack { get; set; }
+
+ public string[] LoadedFiles { get; set; }
+ internal DebugStore store;
+ public TaskCompletionSource<DebugStore> Source { get; } = new TaskCompletionSource<DebugStore>();
+
+ 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;
+ LocalsCache.Clear();
+ }
+
+ }
}
diff --git a/sdks/wasm/BrowserDebugProxy/DevToolsProxy.cs b/sdks/wasm/BrowserDebugProxy/DevToolsProxy.cs
index 5eac86d124a..93d18438d32 100644
--- a/sdks/wasm/BrowserDebugProxy/DevToolsProxy.cs
+++ b/sdks/wasm/BrowserDebugProxy/DevToolsProxy.cs
@@ -1,337 +1,381 @@
-using System;
-using System.Linq;
-using System.Threading.Tasks;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
-using System.Net.WebSockets;
-using System.Threading;
+using System;
+using System.Collections.Generic;
using System.IO;
+using System.Linq;
+using System.Net.WebSockets;
using System.Text;
-using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
-namespace WebAssembly.Net.Debugging {
-
- class DevToolsQueue {
- Task current_send;
- List<byte []> pending;
-
- public WebSocket Ws { get; private set; }
- public Task CurrentSend { get { return current_send; } }
- public DevToolsQueue (WebSocket sock)
- {
- this.Ws = sock;
- pending = new List<byte []> ();
- }
-
- public Task Send (byte [] bytes, CancellationToken token)
- {
- pending.Add (bytes);
- if (pending.Count == 1) {
- if (current_send != null)
- throw new Exception ("current_send MUST BE NULL IF THERE'S no pending send");
- //logger.LogTrace ("sending {0} bytes", bytes.Length);
- current_send = Ws.SendAsync (new ArraySegment<byte> (bytes), WebSocketMessageType.Text, true, token);
- return current_send;
- }
- return null;
- }
-
- public Task Pump (CancellationToken token)
- {
- current_send = null;
- pending.RemoveAt (0);
-
- if (pending.Count > 0) {
- if (current_send != null)
- throw new Exception ("current_send MUST BE NULL IF THERE'S no pending send");
-
- current_send = Ws.SendAsync (new ArraySegment<byte> (pending [0]), WebSocketMessageType.Text, true, token);
- return current_send;
- }
- return null;
- }
- }
-
- internal class DevToolsProxy {
- TaskCompletionSource<bool> side_exception = new TaskCompletionSource<bool> ();
- TaskCompletionSource<bool> client_initiated_close = new TaskCompletionSource<bool> ();
- Dictionary<MessageId, TaskCompletionSource<Result>> pending_cmds = new Dictionary<MessageId, TaskCompletionSource<Result>> ();
- ClientWebSocket browser;
- WebSocket ide;
- int next_cmd_id;
- List<Task> pending_ops = new List<Task> ();
- List<DevToolsQueue> queues = new List<DevToolsQueue> ();
-
- protected readonly ILogger logger;
-
- public DevToolsProxy (ILoggerFactory loggerFactory)
- {
- logger = loggerFactory.CreateLogger<DevToolsProxy>();
- }
-
- protected virtual Task<bool> AcceptEvent (SessionId sessionId, string method, JObject args, CancellationToken token)
- {
- return Task.FromResult (false);
- }
-
- protected virtual Task<bool> AcceptCommand (MessageId id, string method, JObject args, CancellationToken token)
- {
- return Task.FromResult (false);
- }
-
- async Task<string> ReadOne (WebSocket socket, CancellationToken token)
- {
- byte [] buff = new byte [4000];
- var mem = new MemoryStream ();
- while (true) {
-
- if (socket.State != WebSocketState.Open) {
- Log ("error", $"DevToolsProxy: Socket is no longer open.");
- client_initiated_close.TrySetResult (true);
- return null;
- }
-
- var result = await socket.ReceiveAsync (new ArraySegment<byte> (buff), token);
- if (result.MessageType == WebSocketMessageType.Close) {
- client_initiated_close.TrySetResult (true);
- return null;
- }
-
- mem.Write (buff, 0, result.Count);
-
- if (result.EndOfMessage)
- return Encoding.UTF8.GetString (mem.GetBuffer (), 0, (int)mem.Length);
- }
- }
-
- DevToolsQueue GetQueueForSocket (WebSocket ws)
- {
- return queues.FirstOrDefault (q => q.Ws == ws);
- }
-
- DevToolsQueue GetQueueForTask (Task task)
- {
- return queues.FirstOrDefault (q => q.CurrentSend == task);
- }
-
- void Send (WebSocket to, JObject o, CancellationToken token)
- {
- var sender = browser == to ? "Send-browser" : "Send-ide";
-
- var method = o ["method"]?.ToString ();
- //if (method != "Debugger.scriptParsed" && method != "Runtime.consoleAPICalled")
- Log ("protocol", $"{sender}: " + JsonConvert.SerializeObject (o));
- var bytes = Encoding.UTF8.GetBytes (o.ToString ());
-
- var queue = GetQueueForSocket (to);
-
- var task = queue.Send (bytes, token);
- if (task != null)
- pending_ops.Add (task);
- }
-
- async Task OnEvent (SessionId sessionId, string method, JObject args, CancellationToken token)
- {
- try {
- if (!await AcceptEvent (sessionId, method, args, token)) {
- //logger.LogDebug ("proxy browser: {0}::{1}",method, args);
- SendEventInternal (sessionId, method, args, token);
- }
- } catch (Exception e) {
- side_exception.TrySetException (e);
- }
- }
-
- async Task OnCommand (MessageId id, string method, JObject args, CancellationToken token)
- {
- try {
- if (!await AcceptCommand (id, method, args, token)) {
- var res = await SendCommandInternal (id, method, args, token);
- SendResponseInternal (id, res, token);
- }
- } catch (Exception e) {
- side_exception.TrySetException (e);
- }
- }
-
- void OnResponse (MessageId id, Result result)
- {
- //logger.LogTrace ("got id {0} res {1}", id, result);
- // Fixme
- if (pending_cmds.Remove (id, out var task)) {
- task.SetResult (result);
- return;
- }
- logger.LogError ("Cannot respond to command: {id} with result: {result} - command is not pending", id, result);
- }
-
- void ProcessBrowserMessage (string msg, CancellationToken token)
- {
- var res = JObject.Parse (msg);
-
- var method = res ["method"]?.ToString ();
- //if (method != "Debugger.scriptParsed" && method != "Runtime.consoleAPICalled")
- Log ("protocol", $"browser: {msg}");
-
- if (res ["id"] == null)
- pending_ops.Add (OnEvent (new SessionId (res ["sessionId"]?.Value<string> ()), res ["method"].Value<string> (), res ["params"] as JObject, token));
- else
- OnResponse (new MessageId (res ["sessionId"]?.Value<string> (), res ["id"].Value<int> ()), Result.FromJson (res));
- }
-
- void ProcessIdeMessage (string msg, CancellationToken token)
- {
- Log ("protocol", $"ide: {msg}");
- if (!string.IsNullOrEmpty (msg)) {
- var res = JObject.Parse (msg);
- pending_ops.Add (OnCommand (
- new MessageId (res ["sessionId"]?.Value<string> (), res ["id"].Value<int> ()),
- res ["method"].Value<string> (),
- res ["params"] as JObject, token));
- }
- }
-
- internal async Task<Result> SendCommand (SessionId id, string method, JObject args, CancellationToken token) {
- //Log ("verbose", $"sending command {method}: {args}");
- return await SendCommandInternal (id, method, args, token);
- }
-
- Task<Result> SendCommandInternal (SessionId sessionId, string method, JObject args, CancellationToken token)
- {
- int id = Interlocked.Increment (ref next_cmd_id);
-
- var o = JObject.FromObject (new {
- id,
- method,
- @params = args
- });
- if (sessionId.sessionId != null)
- o["sessionId"] = sessionId.sessionId;
- var tcs = new TaskCompletionSource<Result> ();
-
- var msgId = new MessageId (sessionId.sessionId, id);
- //Log ("verbose", $"add cmd id {sessionId}-{id}");
- pending_cmds[msgId] = tcs;
-
- Send (this.browser, o, token);
- return tcs.Task;
- }
-
- public void SendEvent (SessionId sessionId, string method, JObject args, CancellationToken token)
- {
- //Log ("verbose", $"sending event {method}: {args}");
- SendEventInternal (sessionId, method, args, token);
- }
-
- void SendEventInternal (SessionId sessionId, string method, JObject args, CancellationToken token)
- {
- var o = JObject.FromObject (new {
- method,
- @params = args
- });
- if (sessionId.sessionId != null)
- o["sessionId"] = sessionId.sessionId;
-
- Send (this.ide, o, token);
- }
-
- internal void SendResponse (MessageId id, Result result, CancellationToken token)
- {
- SendResponseInternal (id, result, token);
- }
-
- void SendResponseInternal (MessageId id, Result result, CancellationToken token)
- {
- JObject o = result.ToJObject (id);
- if (result.IsErr)
- logger.LogError ($"sending error response for id: {id} -> {result}");
-
- Send (this.ide, o, token);
- }
-
- // , HttpContext context)
- public async Task Run (Uri browserUri, WebSocket ideSocket)
- {
- Log ("info", $"DevToolsProxy: Starting on {browserUri}");
- using (this.ide = ideSocket) {
- Log ("verbose", $"DevToolsProxy: IDE waiting for connection on {browserUri}");
- queues.Add (new DevToolsQueue (this.ide));
- using (this.browser = new ClientWebSocket ()) {
- this.browser.Options.KeepAliveInterval = Timeout.InfiniteTimeSpan;
- await this.browser.ConnectAsync (browserUri, CancellationToken.None);
- queues.Add (new DevToolsQueue (this.browser));
-
- Log ("verbose", $"DevToolsProxy: Client connected on {browserUri}");
- var x = new CancellationTokenSource ();
-
- pending_ops.Add (ReadOne (browser, x.Token));
- pending_ops.Add (ReadOne (ide, x.Token));
- pending_ops.Add (side_exception.Task);
- pending_ops.Add (client_initiated_close.Task);
-
- try {
- while (!x.IsCancellationRequested) {
- var task = await Task.WhenAny (pending_ops.ToArray ());
- //logger.LogTrace ("pump {0} {1}", task, pending_ops.IndexOf (task));
- if (task == pending_ops [0]) {
- var msg = ((Task<string>)task).Result;
- if (msg != null) {
- pending_ops [0] = ReadOne (browser, x.Token); //queue next read
- ProcessBrowserMessage (msg, x.Token);
- }
- } else if (task == pending_ops [1]) {
- var msg = ((Task<string>)task).Result;
- if (msg != null) {
- pending_ops [1] = ReadOne (ide, x.Token); //queue next read
- ProcessIdeMessage (msg, x.Token);
- }
- } else if (task == pending_ops [2]) {
- var res = ((Task<bool>)task).Result;
- throw new Exception ("side task must always complete with an exception, what's going on???");
- } else if (task == pending_ops [3]) {
- var res = ((Task<bool>)task).Result;
- Log ("verbose", $"DevToolsProxy: Client initiated close from {browserUri}");
- x.Cancel ();
- } else {
- //must be a background task
- pending_ops.Remove (task);
- var queue = GetQueueForTask (task);
- if (queue != null) {
- var tsk = queue.Pump (x.Token);
- if (tsk != null)
- pending_ops.Add (tsk);
- }
- }
- }
- } catch (Exception e) {
- Log ("error", $"DevToolsProxy::Run: Exception {e}");
- //throw;
- } finally {
- if (!x.IsCancellationRequested)
- x.Cancel ();
- }
- }
- }
- }
-
- protected void Log (string priority, string msg)
- {
- switch (priority) {
- case "protocol":
- logger.LogTrace (msg);
- break;
- case "verbose":
- logger.LogDebug (msg);
- break;
- case "info":
- case "warning":
- case "error":
- default:
- logger.LogDebug (msg);
- break;
- }
- }
- }
+namespace Microsoft.WebAssembly.Diagnostics
+{
+
+ class DevToolsQueue
+ {
+ Task current_send;
+ List<byte[]> pending;
+
+ public WebSocket Ws { get; private set; }
+ public Task CurrentSend { get { return current_send; } }
+ public DevToolsQueue(WebSocket sock)
+ {
+ this.Ws = sock;
+ pending = new List<byte[]>();
+ }
+
+ public Task Send(byte[] bytes, CancellationToken token)
+ {
+ pending.Add(bytes);
+ if (pending.Count == 1)
+ {
+ if (current_send != null)
+ throw new Exception("current_send MUST BE NULL IF THERE'S no pending send");
+ //logger.LogTrace ("sending {0} bytes", bytes.Length);
+ current_send = Ws.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, token);
+ return current_send;
+ }
+ return null;
+ }
+
+ public Task Pump(CancellationToken token)
+ {
+ current_send = null;
+ pending.RemoveAt(0);
+
+ if (pending.Count > 0)
+ {
+ if (current_send != null)
+ throw new Exception("current_send MUST BE NULL IF THERE'S no pending send");
+
+ current_send = Ws.SendAsync(new ArraySegment<byte>(pending[0]), WebSocketMessageType.Text, true, token);
+ return current_send;
+ }
+ return null;
+ }
+ }
+
+ internal class DevToolsProxy
+ {
+ TaskCompletionSource<bool> side_exception = new TaskCompletionSource<bool>();
+ TaskCompletionSource<bool> client_initiated_close = new TaskCompletionSource<bool>();
+ Dictionary<MessageId, TaskCompletionSource<Result>> pending_cmds = new Dictionary<MessageId, TaskCompletionSource<Result>>();
+ ClientWebSocket browser;
+ WebSocket ide;
+ int next_cmd_id;
+ List<Task> pending_ops = new List<Task>();
+ List<DevToolsQueue> queues = new List<DevToolsQueue>();
+
+ protected readonly ILogger logger;
+
+ public DevToolsProxy(ILoggerFactory loggerFactory)
+ {
+ logger = loggerFactory.CreateLogger<DevToolsProxy>();
+ }
+
+ protected virtual Task<bool> AcceptEvent(SessionId sessionId, string method, JObject args, CancellationToken token)
+ {
+ return Task.FromResult(false);
+ }
+
+ protected virtual Task<bool> AcceptCommand(MessageId id, string method, JObject args, CancellationToken token)
+ {
+ return Task.FromResult(false);
+ }
+
+ async Task<string> ReadOne(WebSocket socket, CancellationToken token)
+ {
+ byte[] buff = new byte[4000];
+ var mem = new MemoryStream();
+ while (true)
+ {
+
+ if (socket.State != WebSocketState.Open)
+ {
+ Log("error", $"DevToolsProxy: Socket is no longer open.");
+ client_initiated_close.TrySetResult(true);
+ return null;
+ }
+
+ var result = await socket.ReceiveAsync(new ArraySegment<byte>(buff), token);
+ if (result.MessageType == WebSocketMessageType.Close)
+ {
+ client_initiated_close.TrySetResult(true);
+ return null;
+ }
+
+ mem.Write(buff, 0, result.Count);
+
+ if (result.EndOfMessage)
+ return Encoding.UTF8.GetString(mem.GetBuffer(), 0, (int)mem.Length);
+ }
+ }
+
+ DevToolsQueue GetQueueForSocket(WebSocket ws)
+ {
+ return queues.FirstOrDefault(q => q.Ws == ws);
+ }
+
+ DevToolsQueue GetQueueForTask(Task task)
+ {
+ return queues.FirstOrDefault(q => q.CurrentSend == task);
+ }
+
+ void Send(WebSocket to, JObject o, CancellationToken token)
+ {
+ var sender = browser == to ? "Send-browser" : "Send-ide";
+
+ var method = o["method"]?.ToString();
+ //if (method != "Debugger.scriptParsed" && method != "Runtime.consoleAPICalled")
+ Log("protocol", $"{sender}: " + JsonConvert.SerializeObject(o));
+ var bytes = Encoding.UTF8.GetBytes(o.ToString());
+
+ var queue = GetQueueForSocket(to);
+
+ var task = queue.Send(bytes, token);
+ if (task != null)
+ pending_ops.Add(task);
+ }
+
+ async Task OnEvent(SessionId sessionId, string method, JObject args, CancellationToken token)
+ {
+ try
+ {
+ if (!await AcceptEvent(sessionId, method, args, token))
+ {
+ //logger.LogDebug ("proxy browser: {0}::{1}",method, args);
+ SendEventInternal(sessionId, method, args, token);
+ }
+ }
+ catch (Exception e)
+ {
+ side_exception.TrySetException(e);
+ }
+ }
+
+ async Task OnCommand(MessageId id, string method, JObject args, CancellationToken token)
+ {
+ try
+ {
+ if (!await AcceptCommand(id, method, args, token))
+ {
+ var res = await SendCommandInternal(id, method, args, token);
+ SendResponseInternal(id, res, token);
+ }
+ }
+ catch (Exception e)
+ {
+ side_exception.TrySetException(e);
+ }
+ }
+
+ void OnResponse(MessageId id, Result result)
+ {
+ //logger.LogTrace ("got id {0} res {1}", id, result);
+ // Fixme
+ if (pending_cmds.Remove(id, out var task))
+ {
+ task.SetResult(result);
+ return;
+ }
+ logger.LogError("Cannot respond to command: {id} with result: {result} - command is not pending", id, result);
+ }
+
+ void ProcessBrowserMessage(string msg, CancellationToken token)
+ {
+ var res = JObject.Parse(msg);
+
+ var method = res["method"]?.ToString();
+ //if (method != "Debugger.scriptParsed" && method != "Runtime.consoleAPICalled")
+ Log("protocol", $"browser: {msg}");
+
+ if (res["id"] == null)
+ pending_ops.Add(OnEvent(new SessionId(res["sessionId"]?.Value<string>()), res["method"].Value<string>(), res["params"] as JObject, token));
+ else
+ OnResponse(new MessageId(res["sessionId"]?.Value<string>(), res["id"].Value<int>()), Result.FromJson(res));
+ }
+
+ void ProcessIdeMessage(string msg, CancellationToken token)
+ {
+ Log("protocol", $"ide: {msg}");
+ if (!string.IsNullOrEmpty(msg))
+ {
+ var res = JObject.Parse(msg);
+ pending_ops.Add(OnCommand(
+ new MessageId(res["sessionId"]?.Value<string>(), res["id"].Value<int>()),
+ res["method"].Value<string>(),
+ res["params"] as JObject, token));
+ }
+ }
+
+ internal async Task<Result> SendCommand(SessionId id, string method, JObject args, CancellationToken token)
+ {
+ //Log ("verbose", $"sending command {method}: {args}");
+ return await SendCommandInternal(id, method, args, token);
+ }
+
+ Task<Result> SendCommandInternal(SessionId sessionId, string method, JObject args, CancellationToken token)
+ {
+ int id = Interlocked.Increment(ref next_cmd_id);
+
+ var o = JObject.FromObject(new
+ {
+ id,
+ method,
+ @params = args
+ });
+ if (sessionId.sessionId != null)
+ o["sessionId"] = sessionId.sessionId;
+ var tcs = new TaskCompletionSource<Result>();
+
+ var msgId = new MessageId(sessionId.sessionId, id);
+ //Log ("verbose", $"add cmd id {sessionId}-{id}");
+ pending_cmds[msgId] = tcs;
+
+ Send(this.browser, o, token);
+ return tcs.Task;
+ }
+
+ public void SendEvent(SessionId sessionId, string method, JObject args, CancellationToken token)
+ {
+ //Log ("verbose", $"sending event {method}: {args}");
+ SendEventInternal(sessionId, method, args, token);
+ }
+
+ void SendEventInternal(SessionId sessionId, string method, JObject args, CancellationToken token)
+ {
+ var o = JObject.FromObject(new
+ {
+ method,
+ @params = args
+ });
+ if (sessionId.sessionId != null)
+ o["sessionId"] = sessionId.sessionId;
+
+ Send(this.ide, o, token);
+ }
+
+ internal void SendResponse(MessageId id, Result result, CancellationToken token)
+ {
+ SendResponseInternal(id, result, token);
+ }
+
+ void SendResponseInternal(MessageId id, Result result, CancellationToken token)
+ {
+ JObject o = result.ToJObject(id);
+ if (result.IsErr)
+ logger.LogError($"sending error response for id: {id} -> {result}");
+
+ Send(this.ide, o, token);
+ }
+
+ // , HttpContext context)
+ public async Task Run(Uri browserUri, WebSocket ideSocket)
+ {
+ Log("info", $"DevToolsProxy: Starting on {browserUri}");
+ using (this.ide = ideSocket)
+ {
+ Log("verbose", $"DevToolsProxy: IDE waiting for connection on {browserUri}");
+ queues.Add(new DevToolsQueue(this.ide));
+ using (this.browser = new ClientWebSocket())
+ {
+ this.browser.Options.KeepAliveInterval = Timeout.InfiniteTimeSpan;
+ await this.browser.ConnectAsync(browserUri, CancellationToken.None);
+ queues.Add(new DevToolsQueue(this.browser));
+
+ Log("verbose", $"DevToolsProxy: Client connected on {browserUri}");
+ var x = new CancellationTokenSource();
+
+ pending_ops.Add(ReadOne(browser, x.Token));
+ pending_ops.Add(ReadOne(ide, x.Token));
+ pending_ops.Add(side_exception.Task);
+ pending_ops.Add(client_initiated_close.Task);
+
+ try
+ {
+ while (!x.IsCancellationRequested)
+ {
+ var task = await Task.WhenAny(pending_ops.ToArray());
+ //logger.LogTrace ("pump {0} {1}", task, pending_ops.IndexOf (task));
+ if (task == pending_ops[0])
+ {
+ var msg = ((Task<string>)task).Result;
+ if (msg != null)
+ {
+ pending_ops[0] = ReadOne(browser, x.Token); //queue next read
+ ProcessBrowserMessage(msg, x.Token);
+ }
+ }
+ else if (task == pending_ops[1])
+ {
+ var msg = ((Task<string>)task).Result;
+ if (msg != null)
+ {
+ pending_ops[1] = ReadOne(ide, x.Token); //queue next read
+ ProcessIdeMessage(msg, x.Token);
+ }
+ }
+ else if (task == pending_ops[2])
+ {
+ var res = ((Task<bool>)task).Result;
+ throw new Exception("side task must always complete with an exception, what's going on???");
+ }
+ else if (task == pending_ops[3])
+ {
+ var res = ((Task<bool>)task).Result;
+ Log("verbose", $"DevToolsProxy: Client initiated close from {browserUri}");
+ x.Cancel();
+ }
+ else
+ {
+ //must be a background task
+ pending_ops.Remove(task);
+ var queue = GetQueueForTask(task);
+ if (queue != null)
+ {
+ var tsk = queue.Pump(x.Token);
+ if (tsk != null)
+ pending_ops.Add(tsk);
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Log("error", $"DevToolsProxy::Run: Exception {e}");
+ //throw;
+ }
+ finally
+ {
+ if (!x.IsCancellationRequested)
+ x.Cancel();
+ }
+ }
+ }
+ }
+
+ protected void Log(string priority, string msg)
+ {
+ switch (priority)
+ {
+ case "protocol":
+ logger.LogTrace(msg);
+ break;
+ case "verbose":
+ logger.LogDebug(msg);
+ break;
+ case "info":
+ case "warning":
+ case "error":
+ default:
+ logger.LogDebug(msg);
+ break;
+ }
+ }
+ }
}
diff --git a/sdks/wasm/BrowserDebugProxy/EvaluateExpression.cs b/sdks/wasm/BrowserDebugProxy/EvaluateExpression.cs
index 7bcf6f81174..d66d6d07f09 100644
--- a/sdks/wasm/BrowserDebugProxy/EvaluateExpression.cs
+++ b/sdks/wasm/BrowserDebugProxy/EvaluateExpression.cs
@@ -1,153 +1,167 @@
-using System;
-using System.Linq;
-using System.Threading.Tasks;
-using Newtonsoft.Json.Linq;
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
-using System.Threading;
-using System.IO;
+using System;
using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.Emit;
-using System.Reflection;
using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Emit;
+using Newtonsoft.Json.Linq;
-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"]));
-
- var updatedMethod = method.AddParameterListParameters (
- SyntaxFactory.Parameter (
- SyntaxFactory.Identifier (var.Identifier.Text))
- .WithType (SyntaxFactory.ParseTypeName (GetTypeFullName(value["value"]))));
- root = root.ReplaceNode (method, updatedMethod);
- }
- syntaxTree = syntaxTree.WithRootAndOptions (root, syntaxTree.Options);
- return syntaxTree;
- }
-
- private object ConvertJSToCSharpType (JToken variable)
- {
- var value = variable["value"];
- var type = variable["type"].Value<string>();
- var subType = variable["subtype"]?.Value<string>();
-
- switch (type) {
- case "string":
- return value?.Value<string> ();
- case "number":
- return value?.Value<double> ();
- case "boolean":
- return value?.Value<bool> ();
- case "object":
- if (subType == "null")
- return null;
- break;
- }
- throw new Exception ($"Evaluate of this datatype {type} not implemented yet");
- }
-
- private string GetTypeFullName (JToken variable)
- {
- var type = variable["type"].ToString ();
- var subType = variable["subtype"]?.Value<string>();
- object value = ConvertJSToCSharpType (variable);
-
- switch (type) {
- case "object": {
- if (subType == "null")
- return variable["className"].Value<string>();
- break;
- }
- default:
- return value.GetType ().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 (@"
+namespace Microsoft.WebAssembly.Diagnostics
+{
+
+ 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"]));
+
+ var updatedMethod = method.AddParameterListParameters(
+ SyntaxFactory.Parameter(
+ SyntaxFactory.Identifier(var.Identifier.Text))
+ .WithType(SyntaxFactory.ParseTypeName(GetTypeFullName(value["value"]))));
+ root = root.ReplaceNode(method, updatedMethod);
+ }
+ syntaxTree = syntaxTree.WithRootAndOptions(root, syntaxTree.Options);
+ return syntaxTree;
+ }
+
+ private object ConvertJSToCSharpType(JToken variable)
+ {
+ var value = variable["value"];
+ var type = variable["type"].Value<string>();
+ var subType = variable["subtype"]?.Value<string>();
+
+ switch (type)
+ {
+ case "string":
+ return value?.Value<string>();
+ case "number":
+ return value?.Value<double>();
+ case "boolean":
+ return value?.Value<bool>();
+ case "object":
+ if (subType == "null")
+ return null;
+ break;
+ }
+ throw new Exception($"Evaluate of this datatype {type} not implemented yet");
+ }
+
+ private string GetTypeFullName(JToken variable)
+ {
+ var type = variable["type"].ToString();
+ var subType = variable["subtype"]?.Value<string>();
+ object value = ConvertJSToCSharpType(variable);
+
+ switch (type)
+ {
+ case "object":
+ {
+ if (subType == "null")
+ return variable["className"].Value<string>();
+ break;
+ }
+ default:
+ return value.GetType().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
{
@@ -157,42 +171,43 @@ namespace WebAssembly.Net.Debugging {
}
}");
- 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,
- findVarNMethodCall.values.ToArray ());
- retString = ret.ToString ();
- }
- return retString;
- }
- }
+ 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,
+ findVarNMethodCall.values.ToArray());
+ retString = ret.ToString();
+ }
+ return retString;
+ }
+ }
}
diff --git a/sdks/wasm/BrowserDebugProxy/MonoProxy.cs b/sdks/wasm/BrowserDebugProxy/MonoProxy.cs
index 5169d1ccfff..4166c7ef236 100644
--- a/sdks/wasm/BrowserDebugProxy/MonoProxy.cs
+++ b/sdks/wasm/BrowserDebugProxy/MonoProxy.cs
@@ -1,881 +1,1025 @@
-using System;
-using System.Linq;
-using System.Threading.Tasks;
-using Newtonsoft.Json.Linq;
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
-using System.Threading;
-using System.IO;
+using System;
using System.Collections.Generic;
+using System.IO;
+using System.Linq;
using System.Net;
-using Microsoft.Extensions.Logging;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json.Linq;
-
-namespace WebAssembly.Net.Debugging {
-
- internal class MonoProxy : DevToolsProxy {
- HashSet<SessionId> sessions = new HashSet<SessionId> ();
- Dictionary<SessionId, ExecutionContext> contexts = new Dictionary<SessionId, ExecutionContext> ();
-
- public MonoProxy (ILoggerFactory loggerFactory, bool hideWebDriver = true) : base(loggerFactory) { hideWebDriver = true; }
-
- readonly bool hideWebDriver;
-
- internal ExecutionContext GetContext (SessionId sessionId)
- {
- if (contexts.TryGetValue (sessionId, out var context))
- return context;
-
- throw new ArgumentException ($"Invalid Session: \"{sessionId}\"", nameof (sessionId));
- }
-
- bool UpdateContext (SessionId sessionId, ExecutionContext executionContext, out ExecutionContext previousExecutionContext)
- {
- var previous = contexts.TryGetValue (sessionId, out previousExecutionContext);
- contexts[sessionId] = executionContext;
- return previous;
- }
-
- internal Task<Result> SendMonoCommand (SessionId id, MonoCommands cmd, CancellationToken token)
- => SendCommand (id, "Runtime.evaluate", JObject.FromObject (cmd), token);
-
- protected override async Task<bool> AcceptEvent (SessionId sessionId, string method, JObject args, CancellationToken token)
- {
- switch (method) {
- case "Runtime.consoleAPICalled": {
- var type = args["type"]?.ToString ();
- if (type == "debug") {
- var a = args ["args"];
- if (a? [0]? ["value"]?.ToString () == MonoConstants.RUNTIME_IS_READY &&
- a? [1]? ["value"]?.ToString () == "fe00e07a-5519-4dfe-b35a-f867dbaf2e28") {
- if (a.Count () > 2) {
- try {
- // The optional 3rd argument is the stringified assembly
- // list so that we don't have to make more round trips
- var context = GetContext (sessionId);
- var loaded = a? [2]? ["value"]?.ToString ();
- if (loaded != null)
- context.LoadedFiles = JToken.Parse (loaded).ToObject<string []> ();
- } catch (InvalidCastException ice) {
- Log ("verbose", ice.ToString ());
- }
- }
- await RuntimeReady (sessionId, token);
- }
-
- }
- break;
- }
-
- case "Runtime.executionContextCreated": {
- SendEvent (sessionId, method, args, token);
- var ctx = args? ["context"];
- var aux_data = ctx? ["auxData"] as JObject;
- var id = ctx ["id"].Value<int> ();
- if (aux_data != null) {
- var is_default = aux_data ["isDefault"]?.Value<bool> ();
- if (is_default == true) {
- await OnDefaultContext (sessionId, new ExecutionContext { Id = id, AuxData = aux_data }, token);
- }
- }
- return true;
- }
-
- case "Debugger.paused": {
- //TODO figure out how to stich out more frames and, in particular what happens when real wasm is on the stack
- var top_func = args? ["callFrames"]? [0]? ["functionName"]?.Value<string> ();
-
- if (top_func == "mono_wasm_fire_bp" || top_func == "_mono_wasm_fire_bp") {
- return await OnBreakpointHit (sessionId, args, token);
- }
- break;
- }
-
- case "Debugger.breakpointResolved": {
- break;
- }
-
- case "Debugger.scriptParsed": {
- var url = args? ["url"]?.Value<string> () ?? "";
-
- switch (url) {
- case var _ when url == "":
- case var _ when url.StartsWith ("wasm://", StringComparison.Ordinal): {
- Log ("verbose", $"ignoring wasm: Debugger.scriptParsed {url}");
- return true;
- }
- }
- Log ("verbose", $"proxying Debugger.scriptParsed ({sessionId.sessionId}) {url} {args}");
- break;
- }
-
- case "Target.attachedToTarget": {
- if (args["targetInfo"]["type"]?.ToString() == "page")
- await DeleteWebDriver (new SessionId (args["sessionId"]?.ToString ()), token);
- break;
- }
-
- }
-
- return false;
- }
-
- async Task<bool> IsRuntimeAlreadyReadyAlready (SessionId sessionId, CancellationToken token)
- {
- if (contexts.TryGetValue (sessionId, out var context) && context.IsRuntimeReady)
- return true;
-
- var res = await SendMonoCommand (sessionId, MonoCommands.IsRuntimeReady (), token);
- return res.Value? ["result"]? ["value"]?.Value<bool> () ?? false;
- }
-
- static int bpIdGenerator;
-
- protected override async Task<bool> AcceptCommand (MessageId id, string method, JObject args, CancellationToken token)
- {
- // Inspector doesn't use the Target domain or sessions
- // so we try to init immediately
- if (hideWebDriver && id == SessionId.Null)
- await DeleteWebDriver (id, token);
-
- if (!contexts.TryGetValue (id, out var context))
- return false;
-
- switch (method) {
- case "Target.attachToTarget": {
- var resp = await SendCommand (id, method, args, token);
- await DeleteWebDriver (new SessionId (resp.Value ["sessionId"]?.ToString ()), token);
- break;
- }
-
- case "Debugger.enable": {
- var resp = await SendCommand (id, method, args, token);
-
- context.DebuggerId = resp.Value ["debuggerId"]?.ToString ();
-
- if (await IsRuntimeAlreadyReadyAlready (id, token))
- await RuntimeReady (id, token);
-
- SendResponse (id,resp,token);
- return true;
- }
-
- case "Debugger.getScriptSource": {
- var script = args? ["scriptId"]?.Value<string> ();
- return await OnGetScriptSource (id, script, token);
- }
-
- case "Runtime.compileScript": {
- var exp = args? ["expression"]?.Value<string> ();
- if (exp.StartsWith ("//dotnet:", StringComparison.Ordinal)) {
- OnCompileDotnetScript (id, token);
- return true;
- }
- break;
- }
-
- case "Debugger.getPossibleBreakpoints": {
- var resp = await SendCommand (id, method, args, token);
- if (resp.IsOk && resp.Value["locations"].HasValues) {
- SendResponse (id, resp, token);
- return true;
- }
-
- var start = SourceLocation.Parse (args? ["start"] as JObject);
- //FIXME support variant where restrictToFunction=true and end is omitted
- var end = SourceLocation.Parse (args? ["end"] as JObject);
- if (start != null && end != null && await GetPossibleBreakpoints (id, start, end, token))
- return true;
-
- SendResponse (id, resp, token);
- return true;
- }
-
- case "Debugger.setBreakpoint": {
- break;
- }
-
- case "Debugger.setBreakpointByUrl": {
- var resp = await SendCommand (id, method, args, token);
- if (!resp.IsOk) {
- SendResponse (id, resp, token);
- return true;
- }
-
- var bpid = resp.Value["breakpointId"]?.ToString ();
- var locations = resp.Value["locations"]?.Values<object>();
- var request = BreakpointRequest.Parse (bpid, args);
-
- // is the store done loading?
- var loaded = context.Source.Task.IsCompleted;
- if (!loaded) {
- // Send and empty response immediately if not
- // and register the breakpoint for resolution
- context.BreakpointRequests [bpid] = request;
- SendResponse (id, resp, token);
- }
-
- if (await IsRuntimeAlreadyReadyAlready (id, token)) {
- var store = await RuntimeReady (id, token);
-
- Log ("verbose", $"BP req {args}");
- await SetBreakpoint (id, store, request, !loaded, token);
- }
-
- if (loaded) {
- // we were already loaded so we should send a response
- // with the locations included and register the request
- context.BreakpointRequests [bpid] = request;
- var result = Result.OkFromObject (request.AsSetBreakpointByUrlResponse (locations));
- SendResponse (id, result, token);
-
- }
- return true;
- }
-
- case "Debugger.removeBreakpoint": {
- await RemoveBreakpoint (id, args, token);
- break;
- }
-
- case "Debugger.resume": {
- await OnResume (id, token);
- break;
- }
-
- case "Debugger.stepInto": {
- return await Step (id, StepKind.Into, token);
- }
-
- case "Debugger.stepOut": {
- return await Step (id, StepKind.Out, token);
- }
-
- case "Debugger.stepOver": {
- return await Step (id, StepKind.Over, token);
- }
-
- case "Debugger.evaluateOnCallFrame": {
- if (!DotnetObjectId.TryParse (args? ["callFrameId"], out var objectId))
- return false;
-
- switch (objectId.Scheme) {
- case "scope":
- return await OnEvaluateOnCallFrame (id,
- int.Parse (objectId.Value),
- args? ["expression"]?.Value<string> (), token);
- default:
- return false;
- }
- }
-
- case "Runtime.getProperties": {
- if (!DotnetObjectId.TryParse (args? ["objectId"], out var objectId))
- break;
-
- var result = await RuntimeGetProperties (id, objectId, args, token);
- SendResponse (id, result, token);
- return true;
- }
-
- case "Runtime.releaseObject": {
- if (!(DotnetObjectId.TryParse (args ["objectId"], out var objectId) && objectId.Scheme == "cfo_res"))
- break;
-
- await SendMonoCommand (id, MonoCommands.ReleaseObject (objectId), token);
- SendResponse (id, Result.OkFromObject (new{}), token);
- return true;
- }
-
- // Protocol extensions
- case "DotnetDebugger.getMethodLocation": {
- Console.WriteLine ("set-breakpoint-by-method: " + id + " " + args);
-
- var store = await RuntimeReady (id, token);
- string aname = args ["assemblyName"]?.Value<string> ();
- string typeName = args ["typeName"]?.Value<string> ();
- string methodName = args ["methodName"]?.Value<string> ();
- if (aname == null || typeName == null || methodName == null) {
- SendResponse (id, Result.Err ("Invalid protocol message '" + args + "'."), token);
- return true;
- }
-
- // GetAssemblyByName seems to work on file names
- var assembly = store.GetAssemblyByName (aname);
- if (assembly == null)
- assembly = store.GetAssemblyByName (aname + ".exe");
- if (assembly == null)
- assembly = store.GetAssemblyByName (aname + ".dll");
- if (assembly == null) {
- SendResponse (id, Result.Err ("Assembly '" + aname + "' not found."), token);
- return true;
- }
-
- var type = assembly.GetTypeByName (typeName);
- if (type == null) {
- SendResponse (id, Result.Err ($"Type '{typeName}' not found."), token);
- return true;
- }
-
- var methodInfo = type.Methods.FirstOrDefault (m => m.Name == methodName);
- if (methodInfo == null) {
- // Maybe this is an async method, in which case the debug info is attached
- // to the async method implementation, in class named:
- // `{type_name}/<method_name>::MoveNext`
- methodInfo = assembly.TypesByName.Values.SingleOrDefault (t => t.FullName.StartsWith ($"{typeName}/<{methodName}>"))
- ?.Methods.FirstOrDefault (mi => mi.Name == "MoveNext");
- }
-
- if (methodInfo == null) {
- SendResponse (id, Result.Err ($"Method '{typeName}:{methodName}' not found."), token);
- return true;
- }
-
- var src_url = methodInfo.Assembly.Sources.Single (sf => sf.SourceId == methodInfo.SourceId).Url;
- SendResponse (id, Result.OkFromObject (new {
- result = new { line = methodInfo.StartLocation.Line, column = methodInfo.StartLocation.Column, url = src_url }
- }), token);
-
- return true;
- }
- case "Runtime.callFunctionOn": {
- if (!DotnetObjectId.TryParse (args ["objectId"], out var objectId))
- return false;
-
- if (objectId.Scheme == "scope") {
- SendResponse (id,
- Result.Exception (new ArgumentException (
- $"Runtime.callFunctionOn not supported with scope ({objectId}).")),
- token);
- return true;
- }
-
- var res = await SendMonoCommand (id, MonoCommands.CallFunctionOn (args), token);
- var res_value_type = res.Value? ["result"]? ["value"]?.Type;
-
- if (res.IsOk && res_value_type == JTokenType.Object || res_value_type == JTokenType.Object)
- res = Result.OkFromObject (new { result = res.Value ["result"]["value"] });
-
- SendResponse (id, res, token);
- return true;
- }
- }
-
- return false;
- }
-
- async Task<Result> RuntimeGetProperties (MessageId id, DotnetObjectId objectId, JToken args, CancellationToken token)
- {
- if (objectId.Scheme == "scope")
- return await GetScopeProperties (id, int.Parse (objectId.Value), token);
-
- var res = await SendMonoCommand (id, MonoCommands.GetDetails (objectId, args), token);
- if (res.IsErr)
- return res;
-
- if (objectId.Scheme == "cfo_res") {
- // Runtime.callFunctionOn result object
- var value_json_str = res.Value ["result"]?["value"]?["__value_as_json_string__"]?.Value<string> ();
- if (value_json_str != null) {
- res = Result.OkFromObject (new {
- result = JArray.Parse (value_json_str)
- });
- } else {
- res = Result.OkFromObject (new { result = new {} });
- }
- } else {
- res = Result.Ok (JObject.FromObject (new { result = res.Value ["result"] ["value"] }));
- }
-
- return res;
- }
-
- //static int frame_id=0;
- async Task<bool> OnBreakpointHit (SessionId sessionId, JObject args, CancellationToken token)
- {
- //FIXME we should send release objects every now and then? Or intercept those we inject and deal in the runtime
- var res = await SendMonoCommand (sessionId, MonoCommands.GetCallStack(), token);
- var orig_callframes = args? ["callFrames"]?.Values<JObject> ();
- var context = GetContext (sessionId);
-
- if (res.IsErr) {
- //Give up and send the original call stack
- return false;
- }
-
- //step one, figure out where did we hit
- var res_value = res.Value? ["result"]? ["value"];
- if (res_value == null || res_value is JValue) {
- //Give up and send the original call stack
- return false;
- }
-
- Log ("verbose", $"call stack (err is {res.Error} value is:\n{res.Value}");
- var bp_id = res_value? ["breakpoint_id"]?.Value<int> ();
- Log ("verbose", $"We just hit bp {bp_id}");
- if (!bp_id.HasValue) {
- //Give up and send the original call stack
- return false;
- }
-
- var bp = context.BreakpointRequests.Values.SelectMany (v => v.Locations).FirstOrDefault (b => b.RemoteId == bp_id.Value);
-
- var callFrames = new List<object> ();
- foreach (var frame in orig_callframes) {
- var function_name = frame ["functionName"]?.Value<string> ();
- var url = frame ["url"]?.Value<string> ();
- if ("mono_wasm_fire_bp" == function_name || "_mono_wasm_fire_bp" == function_name) {
- var frames = new List<Frame> ();
- int frame_id = 0;
- 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<uint> ();
- var assembly_name = mono_frame ["assembly_name"].Value<string> ();
-
- // This can be different than `method.Name`, like in case of generic methods
- var method_name = mono_frame ["method_name"]?.Value<string> ();
-
- var store = await LoadStore (sessionId, token);
- var asm = store.GetAssemblyByName (assembly_name);
- if (asm == null) {
- Log ("info",$"Unable to find assembly: {assembly_name}");
- continue;
- }
-
- var method = asm.GetMethodByToken (method_token);
-
- if (method == null) {
- Log ("info", $"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name}");
- continue;
- }
-
- var location = method?.GetLocationByIl (il_pos);
-
- // When hitting a breakpoint on the "IncrementCount" method in the standard
- // Blazor project template, one of the stack frames is inside mscorlib.dll
- // and we get location==null for it. It will trigger a NullReferenceException
- // if we don't skip over that stack frame.
- if (location == null) {
- continue;
- }
-
- 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-1));
-
- callFrames.Add (new {
- functionName = method_name,
- callFrameId = $"dotnet:scope:{frame_id-1}",
- functionLocation = method.StartLocation.AsLocation (),
-
- location = location.AsLocation (),
-
- url = store.ToUrl (location),
-
- scopeChain = new [] {
- new {
- type = "local",
- @object = new {
- @type = "object",
- className = "Object",
- description = "Object",
- objectId = $"dotnet:scope:{frame_id-1}",
- },
- name = method_name,
- startLocation = method.StartLocation.AsLocation (),
- endLocation = method.EndLocation.AsLocation (),
- }}
- });
-
- context.CallStack = frames;
-
- }
- } else if (!(function_name.StartsWith ("wasm-function", StringComparison.Ordinal)
- || url.StartsWith ("wasm://wasm/", StringComparison.Ordinal))) {
- callFrames.Add (frame);
- }
- }
-
- var bp_list = new string [bp == null ? 0 : 1];
- if (bp != null)
- bp_list [0] = bp.StackId;
-
- var o = JObject.FromObject (new {
- callFrames,
- reason = "other", //other means breakpoint
- hitBreakpoints = bp_list,
- });
-
- SendEvent (sessionId, "Debugger.paused", o, token);
- return true;
- }
-
- async Task OnDefaultContext (SessionId sessionId, ExecutionContext context, CancellationToken token)
- {
- Log ("verbose", "Default context created, clearing state and sending events");
- if (UpdateContext (sessionId, context, out var previousContext)) {
- foreach (var kvp in previousContext.BreakpointRequests) {
- context.BreakpointRequests[kvp.Key] = kvp.Value.Clone();
- }
- }
-
- if (await IsRuntimeAlreadyReadyAlready (sessionId, token))
- await RuntimeReady (sessionId, token);
- }
-
- async Task OnResume (MessageId msg_id, CancellationToken token)
- {
- var ctx = GetContext (msg_id);
- if (ctx.CallStack != null) {
- // Stopped on managed code
- await SendMonoCommand (msg_id, MonoCommands.Resume (), token);
- }
-
- //discard managed frames
- GetContext (msg_id).ClearState ();
- }
-
- async Task<bool> Step (MessageId msg_id, StepKind kind, CancellationToken token)
- {
- var context = GetContext (msg_id);
- if (context.CallStack == null)
- return false;
-
- if (context.CallStack.Count <= 1 && kind == StepKind.Out)
- return false;
-
- var res = await SendMonoCommand (msg_id, MonoCommands.StartSingleStepping (kind), token);
-
- var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
-
- if (ret_code.HasValue && ret_code.Value == 0) {
- context.ClearState ();
- await SendCommand (msg_id, "Debugger.stepOut", new JObject (), token);
- return false;
- }
-
- SendResponse (msg_id, Result.Ok (new JObject ()), token);
-
- context.ClearState ();
-
- await SendCommand (msg_id, "Debugger.resume", new JObject (), token);
- return true;
- }
-
- 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 live_vars = scope.Method.GetLiveVarsAt (scope.Location.CliLocation.Offset);
- //get_this
- var res = await SendMonoCommand (msg_id, MonoCommands.GetScopeVariables (scope.Id, live_vars), token);
-
- var scope_values = res.Value? ["result"]? ["value"]?.Values<JObject> ()?.ToArray ();
- thisValue = scope_values?.FirstOrDefault (v => v ["name"]?.Value<string> () == "this");
-
- if (!only_search_on_this) {
- if (thisValue != null && expression == "this")
- return thisValue;
-
- var value = scope_values.SingleOrDefault (sv => sv ["name"]?.Value<string> () == expression);
- if (value != null)
- return value;
- }
-
- //search in scope
- if (thisValue != null) {
- if (!DotnetObjectId.TryParse (thisValue ["value"] ["objectId"], out var objectId))
- return null;
-
- res = await SendMonoCommand (msg_id, MonoCommands.GetDetails (objectId), token);
- scope_values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray ();
- var foundValue = scope_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<bool> OnEvaluateOnCallFrame (MessageId msg_id, int scope_id, string expression, CancellationToken token)
- {
- try {
- var context = GetContext (msg_id);
- if (context.CallStack == null)
- return false;
-
- var varValue = await TryGetVariableValue (msg_id, scope_id, expression, false, token);
-
- if (varValue != null) {
- SendResponse (msg_id, Result.OkFromObject (new {
- result = varValue ["value"]
- }), token);
- return true;
- }
-
- string retValue = await EvaluateExpression.CompileAndRunTheExpression (this, msg_id, scope_id, expression, token);
- SendResponse (msg_id, Result.OkFromObject (new {
- result = new {
- value = retValue
- }
- }), token);
- return true;
- } catch (Exception e) {
- logger.LogDebug (e, $"Error in EvaluateOnCallFrame for expression '{expression}.");
- }
- return false;
- }
-
- async Task<Result> GetScopeProperties (MessageId msg_id, int scope_id, CancellationToken token)
- {
- try {
- var ctx = GetContext (msg_id);
- var scope = ctx.CallStack.FirstOrDefault (s => s.Id == scope_id);
- if (scope == null)
- return Result.Err (JObject.FromObject (new { message = $"Could not find scope with id #{scope_id}" }));
-
- var var_ids = scope.Method.GetLiveVarsAt (scope.Location.CliLocation.Offset);
- var res = await SendMonoCommand (msg_id, MonoCommands.GetScopeVariables (scope.Id, var_ids), token);
-
- //if we fail we just buble that to the IDE (and let it panic over it)
- if (res.IsErr)
- return res;
-
- var values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray ();
-
- if(values == null || values.Length == 0)
- return Result.OkFromObject (new { result = Array.Empty<object> () });
-
- foreach (var value in values)
- ctx.LocalsCache [value ["name"]?.Value<string> ()] = value;
-
- return Result.OkFromObject (new { result = values });
- } catch (Exception exception) {
- Log ("verbose", $"Error resolving scope properties {exception.Message}");
- return Result.Exception (exception);
- }
- }
-
- async Task<Breakpoint> SetMonoBreakpoint (SessionId sessionId, string reqId, SourceLocation location, CancellationToken token)
- {
- var bp = new Breakpoint (reqId, location, BreakpointState.Pending);
- var asm_name = bp.Location.CliLocation.Method.Assembly.Name;
- var method_token = bp.Location.CliLocation.Method.Token;
- var il_offset = bp.Location.CliLocation.Offset;
-
- var res = await SendMonoCommand (sessionId, MonoCommands.SetBreakpoint (asm_name, method_token, il_offset), token);
- var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
-
- if (ret_code.HasValue) {
- bp.RemoteId = ret_code.Value;
- bp.State = BreakpointState.Active;
- //Log ("verbose", $"BP local id {bp.LocalId} enabled with remote id {bp.RemoteId}");
- }
-
- return bp;
- }
-
- async Task<DebugStore> LoadStore (SessionId sessionId, CancellationToken token)
- {
- var context = GetContext (sessionId);
-
- if (Interlocked.CompareExchange (ref context.store, new DebugStore (logger), null) != null)
- return await context.Source.Task;
-
- try {
- var loaded_files = context.LoadedFiles;
-
- if (loaded_files == null) {
- var loaded = await SendMonoCommand (sessionId, MonoCommands.GetLoadedFiles (), token);
- loaded_files = loaded.Value? ["result"]? ["value"]?.ToObject<string []> ();
- }
-
- await foreach (var source in context.store.Load(sessionId, loaded_files, token).WithCancellation (token)) {
- var scriptSource = JObject.FromObject (source.ToScriptSource (context.Id, context.AuxData));
- Log ("verbose", $"\tsending {source.Url} {context.Id} {sessionId.sessionId}");
-
- SendEvent (sessionId, "Debugger.scriptParsed", scriptSource, token);
-
- foreach (var req in context.BreakpointRequests.Values) {
- if (req.TryResolve (source)) {
- await SetBreakpoint (sessionId, context.store, req, true, token);
- }
- }
- }
- } catch (Exception e) {
- context.Source.SetException (e);
- }
-
- if (!context.Source.Task.IsCompleted)
- context.Source.SetResult (context.store);
- return context.store;
- }
-
- async Task<DebugStore> RuntimeReady (SessionId sessionId, CancellationToken token)
- {
- var context = GetContext (sessionId);
- if (Interlocked.CompareExchange (ref context.ready, new TaskCompletionSource<DebugStore> (), null) != null)
- return await context.ready.Task;
-
- var clear_result = await SendMonoCommand (sessionId, MonoCommands.ClearAllBreakpoints (), token);
- if (clear_result.IsErr) {
- Log ("verbose", $"Failed to clear breakpoints due to {clear_result}");
- }
-
- var store = await LoadStore (sessionId, token);
-
- context.ready.SetResult (store);
- SendEvent (sessionId, "Mono.runtimeReady", new JObject (), token);
- return store;
- }
-
- async Task RemoveBreakpoint(MessageId msg_id, JObject args, CancellationToken token) {
- var bpid = args? ["breakpointId"]?.Value<string> ();
-
- var context = GetContext (msg_id);
- if (!context.BreakpointRequests.TryGetValue (bpid, out var breakpointRequest))
- return;
-
- foreach (var bp in breakpointRequest.Locations) {
- var res = await SendMonoCommand (msg_id, MonoCommands.RemoveBreakpoint (bp.RemoteId), token);
- var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
-
- if (ret_code.HasValue) {
- bp.RemoteId = -1;
- bp.State = BreakpointState.Disabled;
- }
- }
- breakpointRequest.Locations.Clear ();
- }
-
- async Task SetBreakpoint (SessionId sessionId, DebugStore store, BreakpointRequest req, bool sendResolvedEvent, CancellationToken token)
- {
- var context = GetContext (sessionId);
- if (req.Locations.Any ()) {
- Log ("debug", $"locations already loaded for {req.Id}");
- return;
- }
-
- var comparer = new SourceLocation.LocationComparer ();
- // if column is specified the frontend wants the exact matches
- // and will clear the bp if it isn't close enoug
- var locations = store.FindBreakpointLocations (req)
- .Distinct (comparer)
- .Where (l => l.Line == req.Line && (req.Column == 0 || l.Column == req.Column))
- .OrderBy (l => l.Column)
- .GroupBy (l => l.Id);
-
- logger.LogDebug ("BP request for '{req}' runtime ready {context.RuntimeReady}", req, GetContext (sessionId).IsRuntimeReady);
-
- var breakpoints = new List<Breakpoint> ();
-
- foreach (var sourceId in locations) {
- var loc = sourceId.First ();
- var bp = await SetMonoBreakpoint (sessionId, req.Id, loc, token);
-
- // If we didn't successfully enable the breakpoint
- // don't add it to the list of locations for this id
- if (bp.State != BreakpointState.Active)
- continue;
-
- breakpoints.Add (bp);
-
- var resolvedLocation = new {
- breakpointId = req.Id,
- location = loc.AsLocation ()
- };
-
- if (sendResolvedEvent)
- SendEvent (sessionId, "Debugger.breakpointResolved", JObject.FromObject (resolvedLocation), token);
- }
-
- req.Locations.AddRange (breakpoints);
- return;
- }
-
- async Task<bool> GetPossibleBreakpoints (MessageId msg, SourceLocation start, SourceLocation end, CancellationToken token)
- {
- var bps = (await RuntimeReady (msg, token)).FindPossibleBreakpoints (start, end);
-
- if (bps == null)
- return false;
-
- var response = new { locations = bps.Select (b => b.AsLocation ()) };
-
- SendResponse (msg, Result.OkFromObject (response), token);
- return true;
- }
-
- void OnCompileDotnetScript (MessageId msg_id, CancellationToken token)
- {
- SendResponse (msg_id, Result.OkFromObject (new { }), token);
- }
-
- async Task<bool> OnGetScriptSource (MessageId msg_id, string script_id, CancellationToken token)
- {
- if (!SourceId.TryParse (script_id, out var id))
- return false;
-
- var src_file = (await LoadStore (msg_id, token)).GetFileById (id);
-
- try {
- var uri = new Uri (src_file.Url);
- string source = $"// Unable to find document {src_file.SourceUri}";
-
- using (var data = await src_file.GetSourceAsync (checkHash: false, token: token)) {
- if (data.Length == 0)
- return false;
-
- using (var reader = new StreamReader (data))
- source = await reader.ReadToEndAsync ();
- }
- SendResponse (msg_id, Result.OkFromObject (new { scriptSource = source }), token);
- } catch (Exception e) {
- var o = new {
- scriptSource = $"// Unable to read document ({e.Message})\n" +
- $"Local path: {src_file?.SourceUri}\n" +
- $"SourceLink path: {src_file?.SourceLinkUri}\n"
- };
-
- SendResponse (msg_id, Result.OkFromObject (o), token);
- }
- return true;
- }
-
- async Task DeleteWebDriver (SessionId sessionId, CancellationToken token)
- {
- // see https://github.com/mono/mono/issues/19549 for background
- if (hideWebDriver && sessions.Add (sessionId)) {
- var res = await SendCommand (sessionId,
- "Page.addScriptToEvaluateOnNewDocument",
- JObject.FromObject (new { source = "delete navigator.constructor.prototype.webdriver"}),
- token);
-
- if (sessionId != SessionId.Null && !res.IsOk)
- sessions.Remove (sessionId);
- }
- }
- }
+namespace Microsoft.WebAssembly.Diagnostics
+{
+
+ internal class MonoProxy : DevToolsProxy
+ {
+ HashSet<SessionId> sessions = new HashSet<SessionId>();
+ Dictionary<SessionId, ExecutionContext> contexts = new Dictionary<SessionId, ExecutionContext>();
+
+ public MonoProxy(ILoggerFactory loggerFactory, bool hideWebDriver = true) : base(loggerFactory) { this.hideWebDriver = hideWebDriver; }
+
+ readonly bool hideWebDriver;
+
+ internal ExecutionContext GetContext(SessionId sessionId)
+ {
+ if (contexts.TryGetValue(sessionId, out var context))
+ return context;
+
+ throw new ArgumentException($"Invalid Session: \"{sessionId}\"", nameof(sessionId));
+ }
+
+ bool UpdateContext(SessionId sessionId, ExecutionContext executionContext, out ExecutionContext previousExecutionContext)
+ {
+ var previous = contexts.TryGetValue(sessionId, out previousExecutionContext);
+ contexts[sessionId] = executionContext;
+ return previous;
+ }
+
+ internal Task<Result> SendMonoCommand(SessionId id, MonoCommands cmd, CancellationToken token) => SendCommand(id, "Runtime.evaluate", JObject.FromObject(cmd), token);
+
+ protected override async Task<bool> AcceptEvent(SessionId sessionId, string method, JObject args, CancellationToken token)
+ {
+ switch (method)
+ {
+ case "Runtime.consoleAPICalled":
+ {
+ var type = args["type"]?.ToString();
+ if (type == "debug")
+ {
+ var a = args["args"];
+ if (a?[0]?["value"]?.ToString() == MonoConstants.RUNTIME_IS_READY &&
+ a?[1]?["value"]?.ToString() == "fe00e07a-5519-4dfe-b35a-f867dbaf2e28")
+ {
+ if (a.Count() > 2)
+ {
+ try
+ {
+ // The optional 3rd argument is the stringified assembly
+ // list so that we don't have to make more round trips
+ var context = GetContext(sessionId);
+ var loaded = a?[2]?["value"]?.ToString();
+ if (loaded != null)
+ context.LoadedFiles = JToken.Parse(loaded).ToObject<string[]>();
+ }
+ catch (InvalidCastException ice)
+ {
+ Log("verbose", ice.ToString());
+ }
+ }
+ await RuntimeReady(sessionId, token);
+ }
+
+ }
+ break;
+ }
+
+ case "Runtime.executionContextCreated":
+ {
+ SendEvent(sessionId, method, args, token);
+ var ctx = args?["context"];
+ var aux_data = ctx?["auxData"] as JObject;
+ var id = ctx["id"].Value<int>();
+ if (aux_data != null)
+ {
+ var is_default = aux_data["isDefault"]?.Value<bool>();
+ if (is_default == true)
+ {
+ await OnDefaultContext(sessionId, new ExecutionContext { Id = id, AuxData = aux_data }, token);
+ }
+ }
+ return true;
+ }
+
+ case "Debugger.paused":
+ {
+ //TODO figure out how to stich out more frames and, in particular what happens when real wasm is on the stack
+ var top_func = args?["callFrames"]?[0]?["functionName"]?.Value<string>();
+
+ if (top_func == "mono_wasm_fire_bp" || top_func == "_mono_wasm_fire_bp" || top_func == "_mono_wasm_fire_exception")
+ {
+ return await OnPause(sessionId, args, token);
+ }
+ break;
+ }
+
+ case "Debugger.breakpointResolved":
+ {
+ break;
+ }
+
+ case "Debugger.scriptParsed":
+ {
+ var url = args?["url"]?.Value<string>() ?? "";
+
+ switch (url)
+ {
+ case var _ when url == "":
+ case var _ when url.StartsWith("wasm://", StringComparison.Ordinal):
+ {
+ Log("verbose", $"ignoring wasm: Debugger.scriptParsed {url}");
+ return true;
+ }
+ }
+ Log("verbose", $"proxying Debugger.scriptParsed ({sessionId.sessionId}) {url} {args}");
+ break;
+ }
+
+ case "Target.attachedToTarget":
+ {
+ if (args["targetInfo"]["type"]?.ToString() == "page")
+ await DeleteWebDriver(new SessionId(args["sessionId"]?.ToString()), token);
+ break;
+ }
+
+ }
+
+ return false;
+ }
+
+ async Task<bool> IsRuntimeAlreadyReadyAlready(SessionId sessionId, CancellationToken token)
+ {
+ if (contexts.TryGetValue(sessionId, out var context) && context.IsRuntimeReady)
+ return true;
+
+ var res = await SendMonoCommand(sessionId, MonoCommands.IsRuntimeReady(), token);
+ return res.Value?["result"]?["value"]?.Value<bool>() ?? false;
+ }
+
+ protected override async Task<bool> AcceptCommand(MessageId id, string method, JObject args, CancellationToken token)
+ {
+ // Inspector doesn't use the Target domain or sessions
+ // so we try to init immediately
+ if (hideWebDriver && id == SessionId.Null)
+ await DeleteWebDriver(id, token);
+
+ if (!contexts.TryGetValue(id, out var context))
+ return false;
+
+ switch (method)
+ {
+ case "Target.attachToTarget":
+ {
+ var resp = await SendCommand(id, method, args, token);
+ await DeleteWebDriver(new SessionId(resp.Value["sessionId"]?.ToString()), token);
+ break;
+ }
+
+ case "Debugger.enable":
+ {
+ System.Console.WriteLine("recebi o Debugger.enable");
+ var resp = await SendCommand(id, method, args, token);
+
+ context.DebuggerId = resp.Value["debuggerId"]?.ToString();
+
+ if (await IsRuntimeAlreadyReadyAlready(id, token))
+ await RuntimeReady(id, token);
+
+ SendResponse(id, resp, token);
+ return true;
+ }
+
+ case "Debugger.getScriptSource":
+ {
+ var script = args?["scriptId"]?.Value<string>();
+ return await OnGetScriptSource(id, script, token);
+ }
+
+ case "Runtime.compileScript":
+ {
+ var exp = args?["expression"]?.Value<string>();
+ if (exp.StartsWith("//dotnet:", StringComparison.Ordinal))
+ {
+ OnCompileDotnetScript(id, token);
+ return true;
+ }
+ break;
+ }
+
+ case "Debugger.getPossibleBreakpoints":
+ {
+ var resp = await SendCommand(id, method, args, token);
+ if (resp.IsOk && resp.Value["locations"].HasValues)
+ {
+ SendResponse(id, resp, token);
+ return true;
+ }
+
+ var start = SourceLocation.Parse(args?["start"] as JObject);
+ //FIXME support variant where restrictToFunction=true and end is omitted
+ var end = SourceLocation.Parse(args?["end"] as JObject);
+ if (start != null && end != null && await GetPossibleBreakpoints(id, start, end, token))
+ return true;
+
+ SendResponse(id, resp, token);
+ return true;
+ }
+
+ case "Debugger.setBreakpoint":
+ {
+ break;
+ }
+
+ case "Debugger.setBreakpointByUrl":
+ {
+ var resp = await SendCommand(id, method, args, token);
+ if (!resp.IsOk)
+ {
+ SendResponse(id, resp, token);
+ return true;
+ }
+
+ var bpid = resp.Value["breakpointId"]?.ToString();
+ var locations = resp.Value["locations"]?.Values<object>();
+ var request = BreakpointRequest.Parse(bpid, args);
+
+ // is the store done loading?
+ var loaded = context.Source.Task.IsCompleted;
+ if (!loaded)
+ {
+ // Send and empty response immediately if not
+ // and register the breakpoint for resolution
+ context.BreakpointRequests[bpid] = request;
+ SendResponse(id, resp, token);
+ }
+
+ if (await IsRuntimeAlreadyReadyAlready(id, token))
+ {
+ var store = await RuntimeReady(id, token);
+
+ Log("verbose", $"BP req {args}");
+ await SetBreakpoint(id, store, request, !loaded, token);
+ }
+
+ if (loaded)
+ {
+ // we were already loaded so we should send a response
+ // with the locations included and register the request
+ context.BreakpointRequests[bpid] = request;
+ var result = Result.OkFromObject(request.AsSetBreakpointByUrlResponse(locations));
+ SendResponse(id, result, token);
+
+ }
+ return true;
+ }
+
+ case "Debugger.removeBreakpoint":
+ {
+ await RemoveBreakpoint(id, args, token);
+ break;
+ }
+
+ case "Debugger.resume":
+ {
+ await OnResume(id, token);
+ break;
+ }
+
+ case "Debugger.stepInto":
+ {
+ return await Step(id, StepKind.Into, token);
+ }
+
+ case "Debugger.stepOut":
+ {
+ return await Step(id, StepKind.Out, token);
+ }
+
+ case "Debugger.stepOver":
+ {
+ return await Step(id, StepKind.Over, token);
+ }
+
+ case "Debugger.evaluateOnCallFrame":
+ {
+ if (!DotnetObjectId.TryParse(args?["callFrameId"], out var objectId))
+ return false;
+
+ switch (objectId.Scheme)
+ {
+ case "scope":
+ return await OnEvaluateOnCallFrame(id,
+ int.Parse(objectId.Value),
+ args?["expression"]?.Value<string>(), token);
+ default:
+ return false;
+ }
+ }
+
+ case "Runtime.getProperties":
+ {
+ if (!DotnetObjectId.TryParse(args?["objectId"], out var objectId))
+ break;
+
+ var result = await RuntimeGetProperties(id, objectId, args, token);
+ SendResponse(id, result, token);
+ return true;
+ }
+
+ case "Runtime.releaseObject":
+ {
+ if (!(DotnetObjectId.TryParse(args["objectId"], out var objectId) && objectId.Scheme == "cfo_res"))
+ break;
+
+ await SendMonoCommand(id, MonoCommands.ReleaseObject(objectId), token);
+ SendResponse(id, Result.OkFromObject(new { }), token);
+ return true;
+ }
+
+ case "Debugger.setPauseOnExceptions":
+ {
+ string state = args["state"].Value<string>();
+ await SendMonoCommand(id, MonoCommands.SetPauseOnExceptions(state), token);
+ // Pass this on to JS too
+ return false;
+ }
+
+ // Protocol extensions
+ case "DotnetDebugger.getMethodLocation":
+ {
+ Console.WriteLine("set-breakpoint-by-method: " + id + " " + args);
+
+ var store = await RuntimeReady(id, token);
+ string aname = args["assemblyName"]?.Value<string>();
+ string typeName = args["typeName"]?.Value<string>();
+ string methodName = args["methodName"]?.Value<string>();
+ if (aname == null || typeName == null || methodName == null)
+ {
+ SendResponse(id, Result.Err("Invalid protocol message '" + args + "'."), token);
+ return true;
+ }
+
+ // GetAssemblyByName seems to work on file names
+ var assembly = store.GetAssemblyByName(aname);
+ if (assembly == null)
+ assembly = store.GetAssemblyByName(aname + ".exe");
+ if (assembly == null)
+ assembly = store.GetAssemblyByName(aname + ".dll");
+ if (assembly == null)
+ {
+ SendResponse(id, Result.Err("Assembly '" + aname + "' not found."), token);
+ return true;
+ }
+
+ var type = assembly.GetTypeByName(typeName);
+ if (type == null)
+ {
+ SendResponse(id, Result.Err($"Type '{typeName}' not found."), token);
+ return true;
+ }
+
+ var methodInfo = type.Methods.FirstOrDefault(m => m.Name == methodName);
+ if (methodInfo == null)
+ {
+ // Maybe this is an async method, in which case the debug info is attached
+ // to the async method implementation, in class named:
+ // `{type_name}/<method_name>::MoveNext`
+ methodInfo = assembly.TypesByName.Values.SingleOrDefault(t => t.FullName.StartsWith($"{typeName}/<{methodName}>"))?
+ .Methods.FirstOrDefault(mi => mi.Name == "MoveNext");
+ }
+
+ if (methodInfo == null)
+ {
+ SendResponse(id, Result.Err($"Method '{typeName}:{methodName}' not found."), token);
+ return true;
+ }
+
+ var src_url = methodInfo.Assembly.Sources.Single(sf => sf.SourceId == methodInfo.SourceId).Url;
+ SendResponse(id, Result.OkFromObject(new
+ {
+ result = new { line = methodInfo.StartLocation.Line, column = methodInfo.StartLocation.Column, url = src_url }
+ }), token);
+
+ return true;
+ }
+ case "Runtime.callFunctionOn":
+ {
+ if (!DotnetObjectId.TryParse(args["objectId"], out var objectId))
+ return false;
+
+ if (objectId.Scheme == "scope")
+ {
+ SendResponse(id,
+ Result.Exception(new ArgumentException(
+ $"Runtime.callFunctionOn not supported with scope ({objectId}).")),
+ token);
+ return true;
+ }
+
+ var res = await SendMonoCommand(id, MonoCommands.CallFunctionOn(args), token);
+ var res_value_type = res.Value?["result"]?["value"]?.Type;
+
+ if (res.IsOk && res_value_type == JTokenType.Object || res_value_type == JTokenType.Object)
+ res = Result.OkFromObject(new { result = res.Value["result"]["value"] });
+
+ SendResponse(id, res, token);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ async Task<Result> RuntimeGetProperties(MessageId id, DotnetObjectId objectId, JToken args, CancellationToken token)
+ {
+ if (objectId.Scheme == "scope")
+ return await GetScopeProperties(id, int.Parse(objectId.Value), token);
+
+ var res = await SendMonoCommand(id, MonoCommands.GetDetails(objectId, args), token);
+ if (res.IsErr)
+ return res;
+
+ if (objectId.Scheme == "cfo_res")
+ {
+ // Runtime.callFunctionOn result object
+ var value_json_str = res.Value["result"]?["value"]?["__value_as_json_string__"]?.Value<string>();
+ if (value_json_str != null)
+ {
+ res = Result.OkFromObject(new
+ {
+ result = JArray.Parse(value_json_str)
+ });
+ }
+ else
+ {
+ res = Result.OkFromObject(new { result = new { } });
+ }
+ }
+ else
+ {
+ res = Result.Ok(JObject.FromObject(new { result = res.Value["result"]["value"] }));
+ }
+
+ return res;
+ }
+
+ //static int frame_id=0;
+ async Task<bool> OnPause(SessionId sessionId, JObject args, CancellationToken token)
+ {
+ //FIXME we should send release objects every now and then? Or intercept those we inject and deal in the runtime
+ var res = await SendMonoCommand(sessionId, MonoCommands.GetCallStack(), token);
+ var orig_callframes = args?["callFrames"]?.Values<JObject>();
+ var context = GetContext(sessionId);
+ JObject data = null;
+ var reason = "other";//other means breakpoint
+
+ if (res.IsErr)
+ {
+ //Give up and send the original call stack
+ return false;
+ }
+
+ //step one, figure out where did we hit
+ var res_value = res.Value?["result"]?["value"];
+ if (res_value == null || res_value is JValue)
+ {
+ //Give up and send the original call stack
+ return false;
+ }
+
+ Log("verbose", $"call stack (err is {res.Error} value is:\n{res.Value}");
+ var bp_id = res_value?["breakpoint_id"]?.Value<int>();
+ Log("verbose", $"We just hit bp {bp_id}");
+ if (!bp_id.HasValue)
+ {
+ //Give up and send the original call stack
+ return false;
+ }
+
+ var bp = context.BreakpointRequests.Values.SelectMany(v => v.Locations).FirstOrDefault(b => b.RemoteId == bp_id.Value);
+
+ var callFrames = new List<object>();
+ foreach (var frame in orig_callframes)
+ {
+ var function_name = frame["functionName"]?.Value<string>();
+ var url = frame["url"]?.Value<string>();
+ if ("mono_wasm_fire_bp" == function_name || "_mono_wasm_fire_bp" == function_name ||
+ "_mono_wasm_fire_exception" == function_name)
+ {
+ if ("_mono_wasm_fire_exception" == function_name)
+ {
+ var exception_obj_id = await SendMonoCommand(sessionId, MonoCommands.GetExceptionObject(), token);
+ var res_val = exception_obj_id.Value?["result"]?["value"];
+ var exception_dotnet_obj_id = new DotnetObjectId("object", res_val?["exception_id"]?.Value<string>());
+ data = JObject.FromObject(new
+ {
+ type = "object",
+ subtype = "error",
+ className = res_val?["class_name"]?.Value<string>(),
+ uncaught = res_val?["uncaught"]?.Value<bool>(),
+ description = res_val?["message"]?.Value<string>() + "\n",
+ objectId = exception_dotnet_obj_id.ToString()
+ });
+ reason = "exception";
+ }
+
+ var frames = new List<Frame>();
+ int frame_id = 0;
+ 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<uint>();
+ var assembly_name = mono_frame["assembly_name"].Value<string>();
+
+ // This can be different than `method.Name`, like in case of generic methods
+ var method_name = mono_frame["method_name"]?.Value<string>();
+
+ var store = await LoadStore(sessionId, token);
+ var asm = store.GetAssemblyByName(assembly_name);
+ if (asm == null)
+ {
+ Log("info", $"Unable to find assembly: {assembly_name}");
+ continue;
+ }
+
+ var method = asm.GetMethodByToken(method_token);
+
+ if (method == null)
+ {
+ Log("info", $"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name}");
+ continue;
+ }
+
+ var location = method?.GetLocationByIl(il_pos);
+
+ // When hitting a breakpoint on the "IncrementCount" method in the standard
+ // Blazor project template, one of the stack frames is inside mscorlib.dll
+ // and we get location==null for it. It will trigger a NullReferenceException
+ // if we don't skip over that stack frame.
+ if (location == null)
+ {
+ continue;
+ }
+
+ 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 - 1));
+
+ callFrames.Add(new
+ {
+ functionName = method_name,
+ callFrameId = $"dotnet:scope:{frame_id - 1}",
+ functionLocation = method.StartLocation.AsLocation(),
+
+ location = location.AsLocation(),
+
+ url = store.ToUrl(location),
+
+ scopeChain = new[]
+ {
+ new
+ {
+ type = "local",
+ @object = new
+ {
+ @type = "object",
+ className = "Object",
+ description = "Object",
+ objectId = $"dotnet:scope:{frame_id-1}",
+ },
+ name = method_name,
+ startLocation = method.StartLocation.AsLocation(),
+ endLocation = method.EndLocation.AsLocation(),
+ }
+ }
+ });
+
+ context.CallStack = frames;
+
+ }
+ }
+ else if (!(function_name.StartsWith("wasm-function", StringComparison.Ordinal) ||
+ url.StartsWith("wasm://wasm/", StringComparison.Ordinal)))
+ {
+ callFrames.Add(frame);
+ }
+ }
+
+ var bp_list = new string[bp == null ? 0 : 1];
+ if (bp != null)
+ bp_list[0] = bp.StackId;
+
+ var o = JObject.FromObject(new
+ {
+ callFrames,
+ reason,
+ data,
+ hitBreakpoints = bp_list,
+ });
+
+ SendEvent(sessionId, "Debugger.paused", o, token);
+ return true;
+ }
+
+ async Task OnDefaultContext(SessionId sessionId, ExecutionContext context, CancellationToken token)
+ {
+ Log("verbose", "Default context created, clearing state and sending events");
+ if (UpdateContext(sessionId, context, out var previousContext))
+ {
+ foreach (var kvp in previousContext.BreakpointRequests)
+ {
+ context.BreakpointRequests[kvp.Key] = kvp.Value.Clone();
+ }
+ }
+
+ if (await IsRuntimeAlreadyReadyAlready(sessionId, token))
+ await RuntimeReady(sessionId, token);
+ }
+
+ async Task OnResume(MessageId msg_id, CancellationToken token)
+ {
+ var ctx = GetContext(msg_id);
+ if (ctx.CallStack != null)
+ {
+ // Stopped on managed code
+ await SendMonoCommand(msg_id, MonoCommands.Resume(), token);
+ }
+
+ //discard managed frames
+ GetContext(msg_id).ClearState();
+ }
+
+ async Task<bool> Step(MessageId msg_id, StepKind kind, CancellationToken token)
+ {
+ var context = GetContext(msg_id);
+ if (context.CallStack == null)
+ return false;
+
+ if (context.CallStack.Count <= 1 && kind == StepKind.Out)
+ return false;
+
+ var res = await SendMonoCommand(msg_id, MonoCommands.StartSingleStepping(kind), token);
+
+ var ret_code = res.Value?["result"]?["value"]?.Value<int>();
+
+ if (ret_code.HasValue && ret_code.Value == 0)
+ {
+ context.ClearState();
+ await SendCommand(msg_id, "Debugger.stepOut", new JObject(), token);
+ return false;
+ }
+
+ SendResponse(msg_id, Result.Ok(new JObject()), token);
+
+ context.ClearState();
+
+ await SendCommand(msg_id, "Debugger.resume", new JObject(), token);
+ return true;
+ }
+
+ 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 live_vars = scope.Method.GetLiveVarsAt(scope.Location.CliLocation.Offset);
+ //get_this
+ var res = await SendMonoCommand(msg_id, MonoCommands.GetScopeVariables(scope.Id, live_vars), token);
+
+ var scope_values = res.Value?["result"]?["value"]?.Values<JObject>()?.ToArray();
+ thisValue = scope_values?.FirstOrDefault(v => v["name"]?.Value<string>() == "this");
+
+ if (!only_search_on_this)
+ {
+ if (thisValue != null && expression == "this")
+ return thisValue;
+
+ var value = scope_values.SingleOrDefault(sv => sv["name"]?.Value<string>() == expression);
+ if (value != null)
+ return value;
+ }
+
+ //search in scope
+ if (thisValue != null)
+ {
+ if (!DotnetObjectId.TryParse(thisValue["value"]["objectId"], out var objectId))
+ return null;
+
+ res = await SendMonoCommand(msg_id, MonoCommands.GetDetails(objectId), token);
+ scope_values = res.Value?["result"]?["value"]?.Values<JObject>().ToArray();
+ var foundValue = scope_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<bool> OnEvaluateOnCallFrame(MessageId msg_id, int scope_id, string expression, CancellationToken token)
+ {
+ try
+ {
+ var context = GetContext(msg_id);
+ if (context.CallStack == null)
+ return false;
+
+ var varValue = await TryGetVariableValue(msg_id, scope_id, expression, false, token);
+
+ if (varValue != null)
+ {
+ SendResponse(msg_id, Result.OkFromObject(new
+ {
+ result = varValue["value"]
+ }), token);
+ return true;
+ }
+
+ string retValue = await EvaluateExpression.CompileAndRunTheExpression(this, msg_id, scope_id, expression, token);
+ SendResponse(msg_id, Result.OkFromObject(new
+ {
+ result = new
+ {
+ value = retValue
+ }
+ }), token);
+ return true;
+ }
+ catch (Exception e)
+ {
+ logger.LogDebug(e, $"Error in EvaluateOnCallFrame for expression '{expression}.");
+ }
+ return false;
+ }
+
+ async Task<Result> GetScopeProperties(MessageId msg_id, int scope_id, CancellationToken token)
+ {
+ try
+ {
+ var ctx = GetContext(msg_id);
+ var scope = ctx.CallStack.FirstOrDefault(s => s.Id == scope_id);
+ if (scope == null)
+ return Result.Err(JObject.FromObject(new { message = $"Could not find scope with id #{scope_id}" }));
+
+ var var_ids = scope.Method.GetLiveVarsAt(scope.Location.CliLocation.Offset);
+ var res = await SendMonoCommand(msg_id, MonoCommands.GetScopeVariables(scope.Id, var_ids), token);
+
+ //if we fail we just buble that to the IDE (and let it panic over it)
+ if (res.IsErr)
+ return res;
+
+ var values = res.Value?["result"]?["value"]?.Values<JObject>().ToArray();
+
+ if (values == null || values.Length == 0)
+ return Result.OkFromObject(new { result = Array.Empty<object>() });
+
+ foreach (var value in values)
+ ctx.LocalsCache[value["name"]?.Value<string>()] = value;
+
+ return Result.OkFromObject(new { result = values });
+ }
+ catch (Exception exception)
+ {
+ Log("verbose", $"Error resolving scope properties {exception.Message}");
+ return Result.Exception(exception);
+ }
+ }
+
+ async Task<Breakpoint> SetMonoBreakpoint(SessionId sessionId, string reqId, SourceLocation location, CancellationToken token)
+ {
+ var bp = new Breakpoint(reqId, location, BreakpointState.Pending);
+ var asm_name = bp.Location.CliLocation.Method.Assembly.Name;
+ var method_token = bp.Location.CliLocation.Method.Token;
+ var il_offset = bp.Location.CliLocation.Offset;
+
+ var res = await SendMonoCommand(sessionId, MonoCommands.SetBreakpoint(asm_name, method_token, il_offset), token);
+ var ret_code = res.Value?["result"]?["value"]?.Value<int>();
+
+ if (ret_code.HasValue)
+ {
+ bp.RemoteId = ret_code.Value;
+ bp.State = BreakpointState.Active;
+ //Log ("verbose", $"BP local id {bp.LocalId} enabled with remote id {bp.RemoteId}");
+ }
+
+ return bp;
+ }
+
+ async Task<DebugStore> LoadStore(SessionId sessionId, CancellationToken token)
+ {
+ var context = GetContext(sessionId);
+
+ if (Interlocked.CompareExchange(ref context.store, new DebugStore(logger), null) != null)
+ return await context.Source.Task;
+
+ try
+ {
+ var loaded_files = context.LoadedFiles;
+
+ if (loaded_files == null)
+ {
+ var loaded = await SendMonoCommand(sessionId, MonoCommands.GetLoadedFiles(), token);
+ loaded_files = loaded.Value?["result"]?["value"]?.ToObject<string[]>();
+ }
+
+ await
+ foreach (var source in context.store.Load(sessionId, loaded_files, token).WithCancellation(token))
+ {
+ var scriptSource = JObject.FromObject(source.ToScriptSource(context.Id, context.AuxData));
+ Log("verbose", $"\tsending {source.Url} {context.Id} {sessionId.sessionId}");
+
+ SendEvent(sessionId, "Debugger.scriptParsed", scriptSource, token);
+
+ foreach (var req in context.BreakpointRequests.Values)
+ {
+ if (req.TryResolve(source))
+ {
+ await SetBreakpoint(sessionId, context.store, req, true, token);
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ context.Source.SetException(e);
+ }
+
+ if (!context.Source.Task.IsCompleted)
+ context.Source.SetResult(context.store);
+ return context.store;
+ }
+
+ async Task<DebugStore> RuntimeReady(SessionId sessionId, CancellationToken token)
+ {
+ var context = GetContext(sessionId);
+ if (Interlocked.CompareExchange(ref context.ready, new TaskCompletionSource<DebugStore>(), null) != null)
+ return await context.ready.Task;
+
+ var clear_result = await SendMonoCommand(sessionId, MonoCommands.ClearAllBreakpoints(), token);
+ if (clear_result.IsErr)
+ {
+ Log("verbose", $"Failed to clear breakpoints due to {clear_result}");
+ }
+
+ var store = await LoadStore(sessionId, token);
+
+ context.ready.SetResult(store);
+ SendEvent(sessionId, "Mono.runtimeReady", new JObject(), token);
+ return store;
+ }
+
+ async Task RemoveBreakpoint(MessageId msg_id, JObject args, CancellationToken token)
+ {
+ var bpid = args?["breakpointId"]?.Value<string>();
+
+ var context = GetContext(msg_id);
+ if (!context.BreakpointRequests.TryGetValue(bpid, out var breakpointRequest))
+ return;
+
+ foreach (var bp in breakpointRequest.Locations)
+ {
+ var res = await SendMonoCommand(msg_id, MonoCommands.RemoveBreakpoint(bp.RemoteId), token);
+ var ret_code = res.Value?["result"]?["value"]?.Value<int>();
+
+ if (ret_code.HasValue)
+ {
+ bp.RemoteId = -1;
+ bp.State = BreakpointState.Disabled;
+ }
+ }
+ breakpointRequest.Locations.Clear();
+ }
+
+ async Task SetBreakpoint(SessionId sessionId, DebugStore store, BreakpointRequest req, bool sendResolvedEvent, CancellationToken token)
+ {
+ var context = GetContext(sessionId);
+ if (req.Locations.Any())
+ {
+ Log("debug", $"locations already loaded for {req.Id}");
+ return;
+ }
+
+ var comparer = new SourceLocation.LocationComparer();
+ // if column is specified the frontend wants the exact matches
+ // and will clear the bp if it isn't close enoug
+ var locations = store.FindBreakpointLocations(req)
+ .Distinct(comparer)
+ .Where(l => l.Line == req.Line && (req.Column == 0 || l.Column == req.Column))
+ .OrderBy(l => l.Column)
+ .GroupBy(l => l.Id);
+
+ logger.LogDebug("BP request for '{req}' runtime ready {context.RuntimeReady}", req, GetContext(sessionId).IsRuntimeReady);
+
+ var breakpoints = new List<Breakpoint>();
+
+ foreach (var sourceId in locations)
+ {
+ var loc = sourceId.First();
+ var bp = await SetMonoBreakpoint(sessionId, req.Id, loc, token);
+
+ // If we didn't successfully enable the breakpoint
+ // don't add it to the list of locations for this id
+ if (bp.State != BreakpointState.Active)
+ continue;
+
+ breakpoints.Add(bp);
+
+ var resolvedLocation = new
+ {
+ breakpointId = req.Id,
+ location = loc.AsLocation()
+ };
+
+ if (sendResolvedEvent)
+ SendEvent(sessionId, "Debugger.breakpointResolved", JObject.FromObject(resolvedLocation), token);
+ }
+
+ req.Locations.AddRange(breakpoints);
+ return;
+ }
+
+ async Task<bool> GetPossibleBreakpoints(MessageId msg, SourceLocation start, SourceLocation end, CancellationToken token)
+ {
+ var bps = (await RuntimeReady(msg, token)).FindPossibleBreakpoints(start, end);
+
+ if (bps == null)
+ return false;
+
+ var response = new { locations = bps.Select(b => b.AsLocation()) };
+
+ SendResponse(msg, Result.OkFromObject(response), token);
+ return true;
+ }
+
+ void OnCompileDotnetScript(MessageId msg_id, CancellationToken token)
+ {
+ SendResponse(msg_id, Result.OkFromObject(new { }), token);
+ }
+
+ async Task<bool> OnGetScriptSource(MessageId msg_id, string script_id, CancellationToken token)
+ {
+ if (!SourceId.TryParse(script_id, out var id))
+ return false;
+
+ var src_file = (await LoadStore(msg_id, token)).GetFileById(id);
+
+ try
+ {
+ var uri = new Uri(src_file.Url);
+ string source = $"// Unable to find document {src_file.SourceUri}";
+
+ using (var data = await src_file.GetSourceAsync(checkHash: false, token: token))
+ {
+ if (data.Length == 0)
+ return false;
+
+ using (var reader = new StreamReader(data))
+ source = await reader.ReadToEndAsync();
+ }
+ SendResponse(msg_id, Result.OkFromObject(new { scriptSource = source }), token);
+ }
+ catch (Exception e)
+ {
+ var o = new
+ {
+ scriptSource = $"// Unable to read document ({e.Message})\n" +
+ $"Local path: {src_file?.SourceUri}\n" +
+ $"SourceLink path: {src_file?.SourceLinkUri}\n"
+ };
+
+ SendResponse(msg_id, Result.OkFromObject(o), token);
+ }
+ return true;
+ }
+
+ async Task DeleteWebDriver(SessionId sessionId, CancellationToken token)
+ {
+ // see https://github.com/mono/mono/issues/19549 for background
+ if (hideWebDriver && sessions.Add(sessionId))
+ {
+ var res = await SendCommand(sessionId,
+ "Page.addScriptToEvaluateOnNewDocument",
+ JObject.FromObject(new { source = "delete navigator.constructor.prototype.webdriver" }),
+ token);
+
+ if (sessionId != SessionId.Null && !res.IsOk)
+ sessions.Remove(sessionId);
+ }
+ }
+ }
}
diff --git a/sdks/wasm/DebuggerTestSuite/ArrayTests.cs b/sdks/wasm/DebuggerTestSuite/ArrayTests.cs
index 0e010d4805a..6e5e4a948dd 100644
--- a/sdks/wasm/DebuggerTestSuite/ArrayTests.cs
+++ b/sdks/wasm/DebuggerTestSuite/ArrayTests.cs
@@ -1,556 +1,695 @@
+// 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.Linq;
using System.Threading.Tasks;
+using Microsoft.WebAssembly.Diagnostics;
using Newtonsoft.Json.Linq;
using Xunit;
-using WebAssembly.Net.Debugging;
namespace DebuggerTests
{
- public class ArrayTests : DebuggerTestBase {
-
- [Theory]
- [InlineData (16, 2, "PrimitiveTypeLocals", false, 0, false)]
- [InlineData (16, 2, "PrimitiveTypeLocals", false, 0, true)]
- [InlineData (93, 2, "YetAnotherMethod", true, 2, false)]
- [InlineData (93, 2, "YetAnotherMethod", true, 2, true)]
- public async Task InspectPrimitiveTypeArrayLocals (int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo)
- => await TestSimpleArrayLocals (
- line, col,
- entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:PrimitiveTypeLocals",
- method_name: method_name,
- etype_name: "int",
- local_var_name_prefix: "int",
- array: new [] { TNumber (4), TNumber (70), TNumber (1) },
- array_elements: null,
- test_prev_frame: test_prev_frame,
- frame_idx: frame_idx,
- use_cfo: use_cfo);
-
- [Theory]
- [InlineData (32, 2, "ValueTypeLocals", false, 0, false)]
- [InlineData (32, 2, "ValueTypeLocals", false, 0, true)]
- [InlineData (93, 2, "YetAnotherMethod", true, 2, false)]
- [InlineData (93, 2, "YetAnotherMethod", true, 2, true)]
- public async Task InspectValueTypeArrayLocals (int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo)
- => await TestSimpleArrayLocals (
- line, col,
- entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:ValueTypeLocals",
- method_name: method_name,
- etype_name: "DebuggerTests.Point",
- local_var_name_prefix: "point",
- array: new [] {
- TValueType ("DebuggerTests.Point"),
- TValueType ("DebuggerTests.Point"),
- },
- array_elements: new [] {
- TPoint (5, -2, "point_arr#Id#0", "Green"),
- TPoint (123, 0, "point_arr#Id#1", "Blue")
- },
- test_prev_frame: test_prev_frame,
- frame_idx: frame_idx,
- use_cfo: use_cfo);
-
- [Theory]
- [InlineData (49, 2, "ObjectTypeLocals", false, 0, false)]
- [InlineData (49, 2, "ObjectTypeLocals", false, 0, true)]
- [InlineData (93, 2, "YetAnotherMethod", true, 2, false)]
- [InlineData (93, 2, "YetAnotherMethod", true, 2, true)]
- public async Task InspectObjectArrayLocals (int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo)
- => await TestSimpleArrayLocals (
- line, col,
- entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:ObjectTypeLocals",
- method_name: method_name,
- etype_name: "DebuggerTests.SimpleClass",
- local_var_name_prefix: "class",
- array: new [] {
- TObject ("DebuggerTests.SimpleClass"),
- TObject ("DebuggerTests.SimpleClass", is_null: true),
- TObject ("DebuggerTests.SimpleClass")
- },
- array_elements: new [] {
- TSimpleClass (5, -2, "class_arr#Id#0", "Green"),
- null, // Element is null
- TSimpleClass (123, 0, "class_arr#Id#2", "Blue") },
- test_prev_frame: test_prev_frame,
- frame_idx: frame_idx,
- use_cfo: use_cfo);
-
- [Theory]
- [InlineData (66, 2, "GenericTypeLocals", false, 0, false)]
- [InlineData (66, 2, "GenericTypeLocals", false, 0, true)]
- [InlineData (93, 2, "YetAnotherMethod", true, 2, false)]
- [InlineData (93, 2, "YetAnotherMethod", true, 2, true)]
- public async Task InspectGenericTypeArrayLocals (int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo)
- => await TestSimpleArrayLocals (
- line, col,
- entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:GenericTypeLocals",
- method_name: method_name,
- etype_name: "DebuggerTests.GenericClass<int>",
- local_var_name_prefix: "gclass",
- array: new [] {
- TObject ("DebuggerTests.GenericClass<int>", is_null: true),
- TObject ("DebuggerTests.GenericClass<int>"),
- TObject ("DebuggerTests.GenericClass<int>")
- },
- array_elements: new [] {
- null, // Element is null
- new {
- Id = TString ("gclass_arr#1#Id"),
- Color = TEnum ("DebuggerTests.RGB", "Red"),
- Value = TNumber (5)
- },
- new {
- Id = TString ("gclass_arr#2#Id"),
- Color = TEnum ("DebuggerTests.RGB", "Blue"),
- Value = TNumber (-12)
- }
- },
- test_prev_frame: test_prev_frame,
- frame_idx: frame_idx,
- use_cfo: use_cfo);
-
- [Theory]
- [InlineData (82, 2, "GenericValueTypeLocals", false, 0, false)]
- [InlineData (82, 2, "GenericValueTypeLocals", false, 0, true)]
- [InlineData (93, 2, "YetAnotherMethod", true, 2, false)]
- [InlineData (93, 2, "YetAnotherMethod", true, 2, true)]
- public async Task InspectGenericValueTypeArrayLocals (int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo)
- => await TestSimpleArrayLocals (
- line, col,
- entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:GenericValueTypeLocals",
- method_name: method_name,
- etype_name: "DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>",
- local_var_name_prefix: "gvclass",
- array: new [] {
- TValueType ("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>"),
- TValueType ("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>")
- },
- array_elements: new [] {
- new {
- Id = TString ("gvclass_arr#1#Id"),
- Color = TEnum ("DebuggerTests.RGB", "Red"),
- Value = TPoint (100, 200, "gvclass_arr#1#Value#Id", "Red")
- },
- new {
- Id = TString ("gvclass_arr#2#Id"),
- Color = TEnum ("DebuggerTests.RGB", "Blue"),
- Value = TPoint (10, 20, "gvclass_arr#2#Value#Id", "Green")
- }
- },
- test_prev_frame: test_prev_frame,
- frame_idx: frame_idx,
- use_cfo: use_cfo);
-
- [Theory]
- [InlineData (191, 2, "GenericValueTypeLocals2", false, 0, false)]
- [InlineData (191, 2, "GenericValueTypeLocals2", false, 0, true)]
- [InlineData (93, 2, "YetAnotherMethod", true, 2, false)]
- [InlineData (93, 2, "YetAnotherMethod", true, 2, true)]
- public async Task InspectGenericValueTypeArrayLocals2 (int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo)
- => await TestSimpleArrayLocals (
- line, col,
- entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:GenericValueTypeLocals2",
- method_name: method_name,
- etype_name: "DebuggerTests.SimpleGenericStruct<DebuggerTests.Point[]>",
- local_var_name_prefix: "gvclass",
- array: new [] {
- TValueType ("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point[]>"),
- TValueType ("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point[]>")
- },
- array_elements: new [] {
- new {
- Id = TString ("gvclass_arr#0#Id"),
- Color = TEnum ("DebuggerTests.RGB", "Red"),
- Value = new [] {
- TPoint (100, 200, "gvclass_arr#0#0#Value#Id", "Red"),
- TPoint (100, 200, "gvclass_arr#0#1#Value#Id", "Green")
- }
- },
- new {
- Id = TString ("gvclass_arr#1#Id"),
- Color = TEnum ("DebuggerTests.RGB", "Blue"),
- Value = new [] {
- TPoint (100, 200, "gvclass_arr#1#0#Value#Id", "Green"),
- TPoint (100, 200, "gvclass_arr#1#1#Value#Id", "Blue")
- }
- }
- },
- test_prev_frame: test_prev_frame,
- frame_idx: frame_idx,
- use_cfo: use_cfo);
-
- async Task TestSimpleArrayLocals (int line, int col, string entry_method_name, string method_name, string etype_name,
- string local_var_name_prefix, object[] array, object[] array_elements,
- bool test_prev_frame=false, int frame_idx=0, bool use_cfo = false)
- {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
- ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
-
- await SetBreakpoint (debugger_test_loc, line, col);
-
- var eval_expr = "window.setTimeout(function() { invoke_static_method ("
- + $"'{entry_method_name}', { (test_prev_frame ? "true" : "false") }"
- + "); }, 1);";
-
- var pause_location = await EvaluateAndCheck (eval_expr, debugger_test_loc, line, col, method_name);
-
- var locals = await GetProperties (pause_location ["callFrames"][frame_idx]["callFrameId"].Value<string> ());
- Assert.Equal (4, locals.Count ());
- CheckArray (locals, $"{local_var_name_prefix}_arr", $"{etype_name}[]");
- CheckArray (locals, $"{local_var_name_prefix}_arr_empty", $"{etype_name}[]");
- CheckObject (locals, $"{local_var_name_prefix}_arr_null", $"{etype_name}[]", is_null: true);
- CheckBool (locals, "call_other", test_prev_frame);
-
- var local_arr_name = $"{local_var_name_prefix}_arr";
-
- JToken prefix_arr;
- if (use_cfo) { // Use `Runtime.callFunctionOn` to get the properties
- var frame = pause_location ["callFrames"][frame_idx];
- var name = local_arr_name;
- var fl = await GetProperties (frame ["callFrameId"].Value<string> ());
- var l_obj = GetAndAssertObjectWithName (locals, name);
- var l_objectId = l_obj ["value"]["objectId"]?.Value<string> ();
-
- Assert.True (!String.IsNullOrEmpty (l_objectId), $"No objectId found for {name}");
-
- prefix_arr = await GetObjectWithCFO (l_objectId);
- } else {
- prefix_arr = await GetObjectOnFrame (pause_location ["callFrames"][frame_idx], local_arr_name);
- }
-
- await CheckProps (prefix_arr, array, local_arr_name);
-
- if (array_elements?.Length > 0) {
- for (int i = 0; i < array_elements.Length; i ++) {
- var i_str = i.ToString ();
- var label = $"{local_var_name_prefix}_arr[{i}]";
- if (array_elements [i] == null) {
- var act_i = prefix_arr.FirstOrDefault (jt => jt ["name"]?.Value<string> () == i_str);
- Assert.True (act_i != null, $"[{label}] Couldn't find array element [{i_str}]");
-
- await CheckValue (act_i ["value"], TObject (etype_name, is_null: true), label);
- } else {
- await CompareObjectPropertiesFor (prefix_arr, i_str, array_elements [i], label: label);
- }
- }
- }
-
- var props = await GetObjectOnFrame (pause_location ["callFrames"][frame_idx], $"{local_var_name_prefix}_arr_empty");
- await CheckProps (props, new object[0], "${local_var_name_prefix}_arr_empty");
- });
-
- async Task<JToken> GetObjectWithCFO (string objectId, JObject fn_args = null)
- {
- var fn_decl = "function () { return this; }";
- var cfo_args = JObject.FromObject (new {
- functionDeclaration = fn_decl,
- objectId = objectId
- });
-
- if (fn_args != null)
- cfo_args ["arguments"] = fn_args;
-
- // callFunctionOn
- var result = await ctx.cli.SendCommand ("Runtime.callFunctionOn", cfo_args, ctx.token);
-
- return await GetProperties (result.Value ["result"]["objectId"]?.Value<string> (), fn_args);
- }
- }
-
- [Theory]
- [InlineData (false)]
- [InlineData (true)]
- public async Task InspectObjectArrayMembers (bool use_cfo)
- {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
- int line = 205;
- int col = 3;
- string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:ObjectArrayMembers";
- string method_name = "PlaceholderMethod";
- int frame_idx = 1;
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
- var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
-
- await SetBreakpoint (debugger_test_loc, line, col);
-
- var eval_expr = "window.setTimeout(function() { invoke_static_method ("
- + $"'{entry_method_name}'"
- + "); }, 1);";
-
- var pause_location = await EvaluateAndCheck (eval_expr, debugger_test_loc, line, col, method_name);
- var locals = await GetProperties (pause_location ["callFrames"][frame_idx]["callFrameId"].Value<string> ());
- Assert.Single (locals);
- CheckObject (locals, "c", "DebuggerTests.Container");
-
- var c_props = await GetObjectOnFrame (pause_location ["callFrames"][frame_idx], "c");
- await CheckProps (c_props, new {
- id = TString ("c#id"),
- ClassArrayProperty = TArray ("DebuggerTests.SimpleClass[]", 3),
- ClassArrayField = TArray ("DebuggerTests.SimpleClass[]", 3),
- PointsProperty = TArray ("DebuggerTests.Point[]", 2),
- PointsField = TArray ("DebuggerTests.Point[]", 2)
- },
- "c"
- );
-
- await CompareObjectPropertiesFor (c_props, "ClassArrayProperty",
- new [] {
- TSimpleClass (5, -2, "ClassArrayProperty#Id#0", "Green"),
- TSimpleClass (30, 1293, "ClassArrayProperty#Id#1", "Green"),
- TObject ("DebuggerTests.SimpleClass", is_null: true)
- },
- label: "InspectLocalsWithStructsStaticAsync");
-
- await CompareObjectPropertiesFor (c_props, "ClassArrayField",
- new [] {
- TObject ("DebuggerTests.SimpleClass", is_null: true),
- TSimpleClass (5, -2, "ClassArrayField#Id#1", "Blue"),
- TSimpleClass (30, 1293, "ClassArrayField#Id#2", "Green")
- },
- label: "c#ClassArrayField");
-
- await CompareObjectPropertiesFor (c_props, "PointsProperty",
- new [] {
- TPoint (5, -2, "PointsProperty#Id#0", "Green"),
- TPoint (123, 0, "PointsProperty#Id#1", "Blue"),
- },
- label: "c#PointsProperty");
-
- await CompareObjectPropertiesFor (c_props, "PointsField",
- new [] {
- TPoint (5, -2, "PointsField#Id#0", "Green"),
- TPoint (123, 0, "PointsField#Id#1", "Blue"),
- },
- label: "c#PointsField");
- });
- }
-
- [Theory]
- [InlineData (false)]
- [InlineData (true)]
- public async Task InspectValueTypeArrayLocalsStaticAsync (bool use_cfo)
- {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
- int line = 143;
- int col = 3;
- string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:ValueTypeLocalsAsync";
- string method_name = "MoveNext"; // BUG: this should be ValueTypeLocalsAsync
- int frame_idx = 0;
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
- var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
-
- await SetBreakpoint (debugger_test_loc, line, col);
-
- var eval_expr = "window.setTimeout(function() { invoke_static_method_async ("
- + $"'{entry_method_name}', false" // *false* here keeps us only in the static method
- + "); }, 1);";
-
- var pause_location = await EvaluateAndCheck (eval_expr, debugger_test_loc, line, col, method_name);
- var frame_locals = await GetProperties (pause_location ["callFrames"][frame_idx]["callFrameId"].Value<string> ());
- await CheckProps (frame_locals, new {
- call_other = TBool (false),
- gvclass_arr = TArray ("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[]", 2),
- gvclass_arr_empty = TArray ("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[]"),
- gvclass_arr_null = TObject ("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[]", is_null: true),
- gvclass = TValueType ("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>"),
- // BUG: this shouldn't be null!
- points = TObject ("DebuggerTests.Point[]", is_null: true)
- }, "ValueTypeLocalsAsync#locals");
-
- var local_var_name_prefix = "gvclass";
- await CompareObjectPropertiesFor (frame_locals, local_var_name_prefix, new {
- Id = TString (null),
- Color = TEnum ("DebuggerTests.RGB", "Red"),
- Value = TPoint (0, 0, null, "Red")
- });
-
- await CompareObjectPropertiesFor (frame_locals, $"{local_var_name_prefix}_arr",
- new [] {
- new {
- Id = TString ("gvclass_arr#1#Id"),
- Color = TEnum ("DebuggerTests.RGB", "Red"),
- Value = TPoint (100, 200, "gvclass_arr#1#Value#Id", "Red")
- },
- new {
- Id = TString ("gvclass_arr#2#Id"),
- Color = TEnum ("DebuggerTests.RGB", "Blue"),
- Value = TPoint (10, 20, "gvclass_arr#2#Value#Id", "Green")
- }
- }
- );
- await CompareObjectPropertiesFor (frame_locals, $"{local_var_name_prefix}_arr_empty",
- new object[0]);
- });
- }
-
- // TODO: Check previous frame too
- [Theory]
- [InlineData (false)]
- [InlineData (true)]
- public async Task InspectValueTypeArrayLocalsInstanceAsync (bool use_cfo)
- {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
- int line = 155;
- int col = 3;
- string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:ValueTypeLocalsAsync";
- int frame_idx = 0;
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
- var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
-
- await SetBreakpoint (debugger_test_loc, line, col);
-
- var eval_expr = "window.setTimeout(function() { invoke_static_method_async ("
- + $"'{entry_method_name}', true"
- + "); }, 1);";
-
- // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync
- var pause_location = await EvaluateAndCheck (eval_expr, debugger_test_loc, line, col, "MoveNext");
-
- var frame_locals = await GetProperties (pause_location ["callFrames"][frame_idx]["callFrameId"].Value<string> ());
- await CheckProps (frame_locals, new {
- t1 = TObject ("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>"),
- @this = TObject ("DebuggerTests.ArrayTestsClass"),
- point_arr = TArray ("DebuggerTests.Point[]", 2),
- point = TValueType ("DebuggerTests.Point")
- }, "InspectValueTypeArrayLocalsInstanceAsync#locals");
-
- await CompareObjectPropertiesFor (frame_locals, "t1",
- new {
- Id = TString ("gvclass_arr#1#Id"),
- Color = TEnum ("DebuggerTests.RGB", "Red"),
- Value = TPoint (100, 200, "gvclass_arr#1#Value#Id", "Red")
- });
-
- await CompareObjectPropertiesFor (frame_locals, "point_arr",
- new [] {
- TPoint (5, -2, "point_arr#Id#0", "Red"),
- TPoint (123, 0, "point_arr#Id#1", "Blue"),
- }
- );
-
- await CompareObjectPropertiesFor (frame_locals, "point",
- TPoint (45, 51, "point#Id", "Green"));
- });
- }
-
- [Theory]
- [InlineData (false)]
- [InlineData (true)]
- public async Task InspectValueTypeArrayLocalsInAsyncStaticStructMethod (bool use_cfo)
- {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
- int line = 222;
- int col = 3;
- string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:EntryPointForStructMethod";
- int frame_idx = 0;
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
- var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
-
- await SetBreakpoint (debugger_test_loc, line, col);
- //await SetBreakpoint (debugger_test_loc, 143, 3);
-
- var eval_expr = "window.setTimeout(function() { invoke_static_method_async ("
- + $"'{entry_method_name}', false"
- + "); }, 1);";
-
- // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync
- var pause_location = await EvaluateAndCheck (eval_expr, debugger_test_loc, line, col, "MoveNext");
-
- var frame_locals = await GetProperties (pause_location ["callFrames"][frame_idx]["callFrameId"].Value<string> ());
- await CheckProps (frame_locals, new {
- call_other = TBool (false),
- local_i = TNumber (5),
- sc = TSimpleClass (10, 45, "sc#Id", "Blue")
- }, "InspectValueTypeArrayLocalsInAsyncStaticStructMethod#locals");
- });
- }
-
- [Theory]
- [InlineData (false)]
- [InlineData (true)]
- public async Task InspectValueTypeArrayLocalsInAsyncInstanceStructMethod (bool use_cfo)
- {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
- int line = 229;
- int col = 3;
- string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:EntryPointForStructMethod";
- int frame_idx = 0;
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
- var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
-
- await SetBreakpoint (debugger_test_loc, line, col);
-
- var eval_expr = "window.setTimeout(function() { invoke_static_method_async ("
- + $"'{entry_method_name}', true"
- + "); }, 1);";
-
- // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync
- var pause_location = await EvaluateAndCheck (eval_expr, debugger_test_loc, line, col, "MoveNext");
-
- var frame_locals = await GetProperties (pause_location ["callFrames"][frame_idx]["callFrameId"].Value<string> ());
- await CheckProps (frame_locals, new {
- sc_arg = TObject ("DebuggerTests.SimpleClass"),
- @this = TValueType ("DebuggerTests.Point"),
- local_gs = TValueType ("DebuggerTests.SimpleGenericStruct<int>")
- },
- "locals#0");
-
- await CompareObjectPropertiesFor (frame_locals, "local_gs",
- new {
- Id = TString ("local_gs#Id"),
- Color = TEnum ("DebuggerTests.RGB", "Green"),
- Value = TNumber (4)
- },
- label: "local_gs#0");
-
- await CompareObjectPropertiesFor (frame_locals, "sc_arg",
- TSimpleClass (10, 45, "sc_arg#Id", "Blue"),
- label: "sc_arg#0");
-
- await CompareObjectPropertiesFor (frame_locals, "this",
- TPoint (90, -4, "point#Id", "Green"),
- label: "this#0");
- });
- }
-
- }
+ public class ArrayTests : DebuggerTestBase
+ {
+
+ [Theory]
+ [InlineData(19, 8, "PrimitiveTypeLocals", false, 0, false)]
+ [InlineData(19, 8, "PrimitiveTypeLocals", false, 0, true)]
+ [InlineData(100, 8, "YetAnotherMethod", true, 2, false)]
+ [InlineData(100, 8, "YetAnotherMethod", true, 2, true)]
+ public async Task InspectPrimitiveTypeArrayLocals(int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo) => await TestSimpleArrayLocals(
+ line, col,
+ entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:PrimitiveTypeLocals",
+ method_name: method_name,
+ etype_name: "int",
+ local_var_name_prefix: "int",
+ array: new[] { TNumber(4), TNumber(70), TNumber(1) },
+ array_elem_props: null,
+ test_prev_frame: test_prev_frame,
+ frame_idx: frame_idx,
+ use_cfo: use_cfo);
+
+ [Theory]
+ [InlineData(36, 8, "ValueTypeLocals", false, 0, false)]
+ [InlineData(36, 8, "ValueTypeLocals", false, 0, true)]
+ [InlineData(100, 8, "YetAnotherMethod", true, 2, false)]
+ [InlineData(100, 8, "YetAnotherMethod", true, 2, true)]
+ public async Task InspectValueTypeArrayLocals(int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo) => await TestSimpleArrayLocals(
+ line, col,
+ entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:ValueTypeLocals",
+ method_name: method_name,
+ etype_name: "DebuggerTests.Point",
+ local_var_name_prefix: "point",
+ array: new[]
+ {
+ TValueType("DebuggerTests.Point"),
+ TValueType("DebuggerTests.Point"),
+ },
+ array_elem_props: new[]
+ {
+ TPoint(5, -2, "point_arr#Id#0", "Green"),
+ TPoint(123, 0, "point_arr#Id#1", "Blue")
+ },
+ test_prev_frame: test_prev_frame,
+ frame_idx: frame_idx,
+ use_cfo: use_cfo);
+
+ [Theory]
+ [InlineData(54, 8, "ObjectTypeLocals", false, 0, false)]
+ [InlineData(54, 8, "ObjectTypeLocals", false, 0, true)]
+ [InlineData(100, 8, "YetAnotherMethod", true, 2, false)]
+ [InlineData(100, 8, "YetAnotherMethod", true, 2, true)]
+ public async Task InspectObjectArrayLocals(int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo) => await TestSimpleArrayLocals(
+ line, col,
+ entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:ObjectTypeLocals",
+ method_name: method_name,
+ etype_name: "DebuggerTests.SimpleClass",
+ local_var_name_prefix: "class",
+ array: new[]
+ {
+ TObject("DebuggerTests.SimpleClass"),
+ TObject("DebuggerTests.SimpleClass", is_null : true),
+ TObject("DebuggerTests.SimpleClass")
+ },
+ array_elem_props: new[]
+ {
+ TSimpleClass(5, -2, "class_arr#Id#0", "Green"),
+ null, // Element is null
+ TSimpleClass(123, 0, "class_arr#Id#2", "Blue")
+ },
+ test_prev_frame: test_prev_frame,
+ frame_idx: frame_idx,
+ use_cfo: use_cfo);
+
+ [Theory]
+ [InlineData(72, 8, "GenericTypeLocals", false, 0, false)]
+ [InlineData(72, 8, "GenericTypeLocals", false, 0, true)]
+ [InlineData(100, 8, "YetAnotherMethod", true, 2, false)]
+ [InlineData(100, 8, "YetAnotherMethod", true, 2, true)]
+ public async Task InspectGenericTypeArrayLocals(int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo) => await TestSimpleArrayLocals(
+ line, col,
+ entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:GenericTypeLocals",
+ method_name: method_name,
+ etype_name: "DebuggerTests.GenericClass<int>",
+ local_var_name_prefix: "gclass",
+ array: new[]
+ {
+ TObject("DebuggerTests.GenericClass<int>", is_null : true),
+ TObject("DebuggerTests.GenericClass<int>"),
+ TObject("DebuggerTests.GenericClass<int>")
+ },
+ array_elem_props: new[]
+ {
+ null, // Element is null
+ new
+ {
+ Id = TString("gclass_arr#1#Id"),
+ Color = TEnum("DebuggerTests.RGB", "Red"),
+ Value = TNumber(5)
+ },
+ new
+ {
+ Id = TString("gclass_arr#2#Id"),
+ Color = TEnum("DebuggerTests.RGB", "Blue"),
+ Value = TNumber(-12)
+ }
+ },
+ test_prev_frame: test_prev_frame,
+ frame_idx: frame_idx,
+ use_cfo: use_cfo);
+
+ [Theory]
+ [InlineData(89, 8, "GenericValueTypeLocals", false, 0, false)]
+ [InlineData(89, 8, "GenericValueTypeLocals", false, 0, true)]
+ [InlineData(100, 8, "YetAnotherMethod", true, 2, false)]
+ [InlineData(100, 8, "YetAnotherMethod", true, 2, true)]
+ public async Task InspectGenericValueTypeArrayLocals(int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo) => await TestSimpleArrayLocals(
+ line, col,
+ entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:GenericValueTypeLocals",
+ method_name: method_name,
+ etype_name: "DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>",
+ local_var_name_prefix: "gvclass",
+ array: new[]
+ {
+ TValueType("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>"),
+ TValueType("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>")
+ },
+ array_elem_props: new[]
+ {
+ new
+ {
+ Id = TString("gvclass_arr#1#Id"),
+ Color = TEnum("DebuggerTests.RGB", "Red"),
+ Value = TPoint(100, 200, "gvclass_arr#1#Value#Id", "Red")
+ },
+ new
+ {
+ Id = TString("gvclass_arr#2#Id"),
+ Color = TEnum("DebuggerTests.RGB", "Blue"),
+ Value = TPoint(10, 20, "gvclass_arr#2#Value#Id", "Green")
+ }
+ },
+ test_prev_frame: test_prev_frame,
+ frame_idx: frame_idx,
+ use_cfo: use_cfo);
+
+ [Theory]
+ [InlineData(213, 8, "GenericValueTypeLocals2", false, 0, false)]
+ [InlineData(213, 8, "GenericValueTypeLocals2", false, 0, true)]
+ [InlineData(100, 8, "YetAnotherMethod", true, 2, false)]
+ [InlineData(100, 8, "YetAnotherMethod", true, 2, true)]
+ public async Task InspectGenericValueTypeArrayLocals2(int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo) => await TestSimpleArrayLocals(
+ line, col,
+ entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:GenericValueTypeLocals2",
+ method_name: method_name,
+ etype_name: "DebuggerTests.SimpleGenericStruct<DebuggerTests.Point[]>",
+ local_var_name_prefix: "gvclass",
+ array: new[]
+ {
+ TValueType("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point[]>"),
+ TValueType("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point[]>")
+ },
+ array_elem_props: new[]
+ {
+ new
+ {
+ Id = TString("gvclass_arr#0#Id"),
+ Color = TEnum("DebuggerTests.RGB", "Red"),
+ Value = new []
+ {
+ TPoint(100, 200, "gvclass_arr#0#0#Value#Id", "Red"),
+ TPoint(100, 200, "gvclass_arr#0#1#Value#Id", "Green")
+ }
+ },
+ new
+ {
+ Id = TString("gvclass_arr#1#Id"),
+ Color = TEnum("DebuggerTests.RGB", "Blue"),
+ Value = new []
+ {
+ TPoint(100, 200, "gvclass_arr#1#0#Value#Id", "Green"),
+ TPoint(100, 200, "gvclass_arr#1#1#Value#Id", "Blue")
+ }
+ }
+ },
+ test_prev_frame: test_prev_frame,
+ frame_idx: frame_idx,
+ use_cfo: use_cfo);
+
+ async Task TestSimpleArrayLocals(int line, int col, string entry_method_name, string method_name, string etype_name,
+ string local_var_name_prefix, object[] array, object[] array_elem_props,
+ bool test_prev_frame = false, int frame_idx = 0, bool use_cfo = false)
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
+ ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
+
+ await SetBreakpoint(debugger_test_loc, line, col);
+
+ var eval_expr = "window.setTimeout(function() { invoke_static_method (" +
+ $"'{entry_method_name}', { (test_prev_frame ? "true" : "false") }" +
+ "); }, 1);";
+
+ var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, method_name);
+
+ var locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value<string>());
+ Assert.Equal(4, locals.Count());
+ CheckArray(locals, $"{local_var_name_prefix}_arr", $"{etype_name}[]", array?.Length ?? 0);
+ CheckArray(locals, $"{local_var_name_prefix}_arr_empty", $"{etype_name}[]", 0);
+ CheckObject(locals, $"{local_var_name_prefix}_arr_null", $"{etype_name}[]", is_null: true);
+ CheckBool(locals, "call_other", test_prev_frame);
+
+ var local_arr_name = $"{local_var_name_prefix}_arr";
+
+ JToken prefix_arr;
+ if (use_cfo)
+ { // Use `Runtime.callFunctionOn` to get the properties
+ var frame = pause_location["callFrames"][frame_idx];
+ var name = local_arr_name;
+ var fl = await GetProperties(frame["callFrameId"].Value<string>());
+ var l_obj = GetAndAssertObjectWithName(locals, name);
+ var l_objectId = l_obj["value"]["objectId"]?.Value<string>();
+
+ Assert.True(!String.IsNullOrEmpty(l_objectId), $"No objectId found for {name}");
+
+ prefix_arr = await GetObjectWithCFO(l_objectId);
+ }
+ else
+ {
+ prefix_arr = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], local_arr_name);
+ }
+
+ await CheckProps(prefix_arr, array, local_arr_name);
+
+ if (array_elem_props?.Length > 0)
+ {
+ for (int i = 0; i < array_elem_props.Length; i++)
+ {
+ var i_str = i.ToString();
+ var label = $"{local_var_name_prefix}_arr[{i}]";
+ if (array_elem_props[i] == null)
+ {
+ var act_i = prefix_arr.FirstOrDefault(jt => jt["name"]?.Value<string>() == i_str);
+ Assert.True(act_i != null, $"[{label}] Couldn't find array element [{i_str}]");
+
+ await CheckValue(act_i["value"], TObject(etype_name, is_null: true), label);
+ }
+ else
+ {
+ await CompareObjectPropertiesFor(prefix_arr, i_str, array_elem_props[i], label: label);
+ }
+ }
+ }
+
+ var props = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], $"{local_var_name_prefix}_arr_empty");
+ await CheckProps(props, new object[0], "${local_var_name_prefix}_arr_empty");
+ });
+
+ async Task<JToken> GetObjectWithCFO(string objectId, JObject fn_args = null)
+ {
+ var fn_decl = "function () { return this; }";
+ var cfo_args = JObject.FromObject(new
+ {
+ functionDeclaration = fn_decl,
+ objectId = objectId
+ });
+
+ if (fn_args != null)
+ cfo_args["arguments"] = fn_args;
+
+ // callFunctionOn
+ var result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token);
+
+ return await GetProperties(result.Value["result"]["objectId"]?.Value<string>(), fn_args);
+ }
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task InspectObjectArrayMembers(bool use_cfo)
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+ int line = 227;
+ int col = 12;
+ string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:ObjectArrayMembers";
+ string method_name = "PlaceholderMethod";
+ int frame_idx = 1;
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
+ var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
+
+ await SetBreakpoint(debugger_test_loc, line, col);
+
+ var eval_expr = "window.setTimeout(function() { invoke_static_method (" +
+ $"'{entry_method_name}'" +
+ "); }, 1);";
+
+ var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, method_name);
+ var locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value<string>());
+ Assert.Single(locals);
+ CheckObject(locals, "c", "DebuggerTests.Container");
+
+ var c_props = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], "c");
+ await CheckProps(c_props, new
+ {
+ id = TString("c#id"),
+ ClassArrayProperty = TArray("DebuggerTests.SimpleClass[]", 3),
+ ClassArrayField = TArray("DebuggerTests.SimpleClass[]", 3),
+ PointsProperty = TArray("DebuggerTests.Point[]", 2),
+ PointsField = TArray("DebuggerTests.Point[]", 2)
+ },
+ "c"
+ );
+
+ await CompareObjectPropertiesFor(c_props, "ClassArrayProperty",
+ new[]
+ {
+ TSimpleClass(5, -2, "ClassArrayProperty#Id#0", "Green"),
+ TSimpleClass(30, 1293, "ClassArrayProperty#Id#1", "Green"),
+ TObject("DebuggerTests.SimpleClass", is_null : true)
+ },
+ label: "InspectLocalsWithStructsStaticAsync");
+
+ await CompareObjectPropertiesFor(c_props, "ClassArrayField",
+ new[]
+ {
+ TObject("DebuggerTests.SimpleClass", is_null : true),
+ TSimpleClass(5, -2, "ClassArrayField#Id#1", "Blue"),
+ TSimpleClass(30, 1293, "ClassArrayField#Id#2", "Green")
+ },
+ label: "c#ClassArrayField");
+
+ await CompareObjectPropertiesFor(c_props, "PointsProperty",
+ new[]
+ {
+ TPoint(5, -2, "PointsProperty#Id#0", "Green"),
+ TPoint(123, 0, "PointsProperty#Id#1", "Blue"),
+ },
+ label: "c#PointsProperty");
+
+ await CompareObjectPropertiesFor(c_props, "PointsField",
+ new[]
+ {
+ TPoint(5, -2, "PointsField#Id#0", "Green"),
+ TPoint(123, 0, "PointsField#Id#1", "Blue"),
+ },
+ label: "c#PointsField");
+ });
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task InspectValueTypeArrayLocalsStaticAsync(bool use_cfo)
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+ int line = 157;
+ int col = 12;
+ string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:ValueTypeLocalsAsync";
+ string method_name = "MoveNext"; // BUG: this should be ValueTypeLocalsAsync
+ int frame_idx = 0;
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
+ var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
+
+ await SetBreakpoint(debugger_test_loc, line, col);
+
+ var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" +
+ $"'{entry_method_name}', false" // *false* here keeps us only in the static method
+ +
+ "); }, 1);";
+
+ var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, method_name);
+ var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value<string>());
+ await CheckProps(frame_locals, new
+ {
+ call_other = TBool(false),
+ gvclass_arr = TArray("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[]", 2),
+ gvclass_arr_empty = TArray("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[]"),
+ gvclass_arr_null = TObject("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[]", is_null: true),
+ gvclass = TValueType("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>"),
+ // BUG: this shouldn't be null!
+ points = TObject("DebuggerTests.Point[]", is_null: true)
+ }, "ValueTypeLocalsAsync#locals");
+
+ var local_var_name_prefix = "gvclass";
+ await CompareObjectPropertiesFor(frame_locals, local_var_name_prefix, new
+ {
+ Id = TString(null),
+ Color = TEnum("DebuggerTests.RGB", "Red"),
+ Value = TPoint(0, 0, null, "Red")
+ });
+
+ await CompareObjectPropertiesFor(frame_locals, $"{local_var_name_prefix}_arr",
+ new[]
+ {
+ new
+ {
+ Id = TString("gvclass_arr#1#Id"),
+ Color = TEnum("DebuggerTests.RGB", "Red"),
+ Value = TPoint(100, 200, "gvclass_arr#1#Value#Id", "Red")
+ },
+ new
+ {
+ Id = TString("gvclass_arr#2#Id"),
+ Color = TEnum("DebuggerTests.RGB", "Blue"),
+ Value = TPoint(10, 20, "gvclass_arr#2#Value#Id", "Green")
+ }
+ }
+ );
+ await CompareObjectPropertiesFor(frame_locals, $"{local_var_name_prefix}_arr_empty",
+ new object[0]);
+ });
+ }
+
+ // TODO: Check previous frame too
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task InspectValueTypeArrayLocalsInstanceAsync(bool use_cfo)
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+ int line = 170;
+ int col = 12;
+ string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:ValueTypeLocalsAsync";
+ int frame_idx = 0;
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
+ var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
+
+ await SetBreakpoint(debugger_test_loc, line, col);
+
+ var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" +
+ $"'{entry_method_name}', true" +
+ "); }, 1);";
+
+ // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync
+ var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, "MoveNext");
+
+ var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value<string>());
+ await CheckProps(frame_locals, new
+ {
+ t1 = TObject("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>"),
+ @this = TObject("DebuggerTests.ArrayTestsClass"),
+ point_arr = TArray("DebuggerTests.Point[]", 2),
+ point = TValueType("DebuggerTests.Point")
+ }, "InspectValueTypeArrayLocalsInstanceAsync#locals");
+
+ await CompareObjectPropertiesFor(frame_locals, "t1",
+ new
+ {
+ Id = TString("gvclass_arr#1#Id"),
+ Color = TEnum("DebuggerTests.RGB", "Red"),
+ Value = TPoint(100, 200, "gvclass_arr#1#Value#Id", "Red")
+ });
+
+ await CompareObjectPropertiesFor(frame_locals, "point_arr",
+ new[]
+ {
+ TPoint(5, -2, "point_arr#Id#0", "Red"),
+ TPoint(123, 0, "point_arr#Id#1", "Blue"),
+ }
+ );
+
+ await CompareObjectPropertiesFor(frame_locals, "point",
+ TPoint(45, 51, "point#Id", "Green"));
+ });
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task InspectValueTypeArrayLocalsInAsyncStaticStructMethod(bool use_cfo)
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+ int line = 244;
+ int col = 12;
+ string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:EntryPointForStructMethod";
+ int frame_idx = 0;
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
+ var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
+
+ await SetBreakpoint(debugger_test_loc, line, col);
+ //await SetBreakpoint (debugger_test_loc, 143, 3);
+
+ var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" +
+ $"'{entry_method_name}', false" +
+ "); }, 1);";
+
+ // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync
+ var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, "MoveNext");
+
+ var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value<string>());
+ await CheckProps(frame_locals, new
+ {
+ call_other = TBool(false),
+ local_i = TNumber(5),
+ sc = TSimpleClass(10, 45, "sc#Id", "Blue")
+ }, "InspectValueTypeArrayLocalsInAsyncStaticStructMethod#locals");
+ });
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task InspectValueTypeArrayLocalsInAsyncInstanceStructMethod(bool use_cfo)
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+ int line = 251;
+ int col = 12;
+ string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:EntryPointForStructMethod";
+ int frame_idx = 0;
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
+ var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
+
+ await SetBreakpoint(debugger_test_loc, line, col);
+
+ var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" +
+ $"'{entry_method_name}', true" +
+ "); }, 1);";
+
+ // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync
+ var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, "MoveNext");
+
+ var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value<string>());
+ await CheckProps(frame_locals, new
+ {
+ sc_arg = TObject("DebuggerTests.SimpleClass"),
+ @this = TValueType("DebuggerTests.Point"),
+ local_gs = TValueType("DebuggerTests.SimpleGenericStruct<int>")
+ },
+ "locals#0");
+
+ await CompareObjectPropertiesFor(frame_locals, "local_gs",
+ new
+ {
+ Id = TString("local_gs#Id"),
+ Color = TEnum("DebuggerTests.RGB", "Green"),
+ Value = TNumber(4)
+ },
+ label: "local_gs#0");
+
+ await CompareObjectPropertiesFor(frame_locals, "sc_arg",
+ TSimpleClass(10, 45, "sc_arg#Id", "Blue"),
+ label: "sc_arg#0");
+
+ await CompareObjectPropertiesFor(frame_locals, "this",
+ TPoint(90, -4, "point#Id", "Green"),
+ label: "this#0");
+ });
+ }
+
+ [Fact]
+ public async Task InvalidArrayId() => await CheckInspectLocalsAtBreakpointSite(
+ "DebuggerTests.Container", "PlaceholderMethod", 1, "PlaceholderMethod",
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ArrayTestsClass:ObjectArrayMembers'); }, 1);",
+ wait_for_event_fn: async (pause_location) =>
+ {
+
+ int frame_idx = 1;
+ var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value<string>());
+ var c_obj = GetAndAssertObjectWithName(frame_locals, "c");
+ var c_obj_id = c_obj["value"]?["objectId"]?.Value<string>();
+ Assert.NotNull(c_obj_id);
+
+ // Invalid format
+ await GetProperties("dotnet:array:4123", expect_ok: false);
+
+ // Invalid object id
+ await GetProperties("dotnet:array:{ \"arrayId\": 234980 }", expect_ok: false);
+
+ // Trying to access object as an array
+ if (!DotnetObjectId.TryParse(c_obj_id, out var id) || id.Scheme != "object")
+ Assert.True(false, "Unexpected object id format. Maybe this test is out of sync with the object id format in library_mono.js?");
+
+ if (!int.TryParse(id.Value, out var idNum))
+ Assert.True(false, "Expected a numeric value part of the object id: {c_obj_id}");
+ await GetProperties($"dotnet:array:{{\"arrayId\":{idNum}}}", expect_ok: false);
+ });
+
+ [Fact]
+ public async Task InvalidValueTypeArrayIndex() => await CheckInspectLocalsAtBreakpointSite(
+ "DebuggerTests.Container", "PlaceholderMethod", 1, "PlaceholderMethod",
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ArrayTestsClass:ObjectArrayMembers'); }, 1);",
+ locals_fn: async (locals) =>
+ {
+ var this_obj = GetAndAssertObjectWithName(locals, "this");
+ var c_obj = GetAndAssertObjectWithName(await GetProperties(this_obj["value"]["objectId"].Value<string>()), "c");
+ var c_obj_id = c_obj["value"]?["objectId"]?.Value<string>();
+ Assert.NotNull(c_obj_id);
+
+ var c_props = await GetProperties(c_obj_id);
+
+ var pf_arr = GetAndAssertObjectWithName(c_props, "PointsField");
+ var pf_arr_elems = await GetProperties(pf_arr["value"]["objectId"].Value<string>());
+
+ if (!DotnetObjectId.TryParse(pf_arr_elems[0]["value"]?["objectId"]?.Value<string>(), out var id))
+ Assert.True(false, "Couldn't parse objectId for PointsFields' elements");
+
+ AssertEqual("valuetype", id.Scheme, "Expected a valuetype id");
+ var id_args = id.ValueAsJson;
+ Assert.True(id_args["arrayId"] != null, "ObjectId format for array seems to have changed. Expected to find 'arrayId' in the value. Update this test");
+ Assert.True(id_args != null, "Expected to get a json as the value part of {id}");
+
+ // Try one valid query, to confirm that the id format hasn't changed!
+ id_args["arrayIdx"] = 0;
+ await GetProperties($"dotnet:valuetype:{id_args.ToString(Newtonsoft.Json.Formatting.None)}", expect_ok: true);
+
+ id_args["arrayIdx"] = 12399;
+ await GetProperties($"dotnet:valuetype:{id_args.ToString(Newtonsoft.Json.Formatting.None)}", expect_ok: false);
+
+ id_args["arrayIdx"] = -1;
+ await GetProperties($"dotnet:valuetype:{id_args.ToString(Newtonsoft.Json.Formatting.None)}", expect_ok: false);
+
+ id_args["arrayIdx"] = "qwe";
+ await GetProperties($"dotnet:valuetype:{id_args.ToString(Newtonsoft.Json.Formatting.None)}", expect_ok: false);
+ });
+
+ [Fact]
+ public async Task InvalidAccessors() => await CheckInspectLocalsAtBreakpointSite(
+ "DebuggerTests.Container", "PlaceholderMethod", 1, "PlaceholderMethod",
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ArrayTestsClass:ObjectArrayMembers'); }, 1);",
+ locals_fn: async (locals) =>
+ {
+ var this_obj = GetAndAssertObjectWithName(locals, "this");
+ var c_obj = GetAndAssertObjectWithName(await GetProperties(this_obj["value"]["objectId"].Value<string>()), "c");
+ var c_obj_id = c_obj["value"]?["objectId"]?.Value<string>();
+ Assert.NotNull(c_obj_id);
+
+ var c_props = await GetProperties(c_obj_id);
+
+ var pf_arr = GetAndAssertObjectWithName(c_props, "PointsField");
+
+ var invalid_accessors = new object[] { "NonExistant", "10000", "-2", 10000, -2, null, String.Empty };
+ foreach (var invalid_accessor in invalid_accessors)
+ {
+ // var res = await InvokeGetter (JObject.FromObject (new { value = new { objectId = obj_id } }), invalid_accessor, expect_ok: true);
+ var res = await InvokeGetter(pf_arr, invalid_accessor, expect_ok: true);
+ AssertEqual("undefined", res.Value["result"]?["type"]?.ToString(), "Expected to get undefined result for non-existant accessor");
+ }
+ });
+
+ }
}
diff --git a/sdks/wasm/DebuggerTestSuite/CallFunctionOnTests.cs b/sdks/wasm/DebuggerTestSuite/CallFunctionOnTests.cs
index f51be601225..d6169102bb1 100644
--- a/sdks/wasm/DebuggerTestSuite/CallFunctionOnTests.cs
+++ b/sdks/wasm/DebuggerTestSuite/CallFunctionOnTests.cs
@@ -1,795 +1,981 @@
+// 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.Linq;
using System.Threading.Tasks;
+using Microsoft.WebAssembly.Diagnostics;
using Newtonsoft.Json.Linq;
using Xunit;
-using WebAssembly.Net.Debugging;
namespace DebuggerTests
{
- public class CallFunctionOnTests : DebuggerTestBase {
-
- // This tests `callFunctionOn` with a function that the vscode-js-debug extension uses
- // Using this here as a non-trivial test case
- [Theory]
- [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, 10, false)]
- [InlineData ("big_array_js_test (0);", "/other.js", 5, 1, 0, true)]
- [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, 10, false)]
- [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 0);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, 0, true)]
- public async Task CheckVSCodeTestFunction1 (string eval_fn, string bp_loc, int line, int col, int len, bool roundtrip)
- {
- string vscode_fn0 = "function(){const e={__proto__:this.__proto__},t=Object.getOwnPropertyNames(this);for(let r=0;r<t.length;++r){const n=t[r],i=n>>>0;if(String(i>>>0)===n&&i>>>0!=4294967295)continue;const a=Object.getOwnPropertyDescriptor(this,n);a&&Object.defineProperty(e,n,a)}return e}";
-
- await RunCallFunctionOn (eval_fn, vscode_fn0, "big", bp_loc, line, col, res_array_len: len, roundtrip: roundtrip,
- test_fn: async (result) => {
-
- var is_js = bp_loc.EndsWith (".js", StringComparison.Ordinal);
- var obj_accessors = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new {
- objectId = result.Value ["result"]["objectId"].Value<string> (),
- accessorPropertiesOnly = true,
- ownProperties = false
- }), ctx.token);
- if (is_js)
- await CheckProps (obj_accessors.Value ["result"], new { __proto__ = TIgnore () }, "obj_accessors");
- else
- AssertEqual (0, obj_accessors.Value ["result"]?.Count (), "obj_accessors-count");
-
- // Check for a __proto__ object
- // isOwn = true, accessorPropertiesOnly = false
- var obj_own = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new {
- objectId = result.Value ["result"]["objectId"].Value<string> (),
- accessorPropertiesOnly = false,
- ownProperties = true
- }), ctx.token);
-
- await CheckProps (obj_own.Value ["result"], new {
- length = TNumber (len),
- // __proto__ = TArray (type, 0) // Is this one really required?
- }, $"obj_own", num_fields: is_js ? 2 : 1);
-
- });
- }
-
- void CheckJFunction (JToken actual, string className, string label)
- {
- AssertEqual ("function", actual ["type"]?.Value<string> (), $"{label}-type");
- AssertEqual (className, actual ["className"]?.Value<string> (), $"{label}-className");
- }
-
- // This tests `callFunctionOn` with a function that the vscode-js-debug extension uses
- // Using this here as a non-trivial test case
- [Theory]
- [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, 10)]
- [InlineData ("big_array_js_test (0);", "/other.js", 5, 1, 0)]
- [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, 10)]
- [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 0);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, 0)]
- public async Task CheckVSCodeTestFunction2 (string eval_fn, string bp_loc, int line, int col, int len)
- {
- var fetch_start_idx = 2;
- var num_elems_fetch = 3;
- string vscode_fn1 = "function(e,t){const r={},n=-1===e?0:e,i=-1===t?this.length:e+t;for(let e=n;e<i&&e<this.length;++e){const t=Object.getOwnPropertyDescriptor(this,e);t&&Object.defineProperty(r,e,t)}return r}";
-
- await RunCallFunctionOn (eval_fn, vscode_fn1, "big", bp_loc, line, col,
- fn_args: JArray.FromObject (new [] {
- new { @value = fetch_start_idx },
- new { @value = num_elems_fetch }
- }),
- test_fn: async (result) => {
-
- var is_js = bp_loc.EndsWith (".js", StringComparison.Ordinal);
-
- // isOwn = false, accessorPropertiesOnly = true
- var obj_accessors = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new {
- objectId = result.Value ["result"]["objectId"].Value<string> (),
- accessorPropertiesOnly = true,
- ownProperties = false
- }), ctx.token);
- if (is_js)
- await CheckProps (obj_accessors.Value ["result"], new { __proto__ = TIgnore () }, "obj_accessors");
- else
- AssertEqual (0, obj_accessors.Value ["result"]?.Count (), "obj_accessors-count");
-
- // Ignoring the __proto__ property
-
- // isOwn = true, accessorPropertiesOnly = false
- var obj_own = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new {
- objectId = result.Value ["result"]["objectId"].Value<string> (),
- accessorPropertiesOnly = false,
- ownProperties = true
- }), ctx.token);
-
- var obj_own_val = obj_own.Value ["result"];
- var num_elems_recd = len == 0 ? 0 : num_elems_fetch;
- AssertEqual (is_js ? num_elems_recd + 1 : num_elems_recd, obj_own_val.Count (), $"obj_own-count");
-
- if (is_js)
- CheckObject (obj_own_val, "__proto__", "Object");
-
- for (int i = fetch_start_idx; i < fetch_start_idx + num_elems_recd; i ++)
- CheckNumber (obj_own_val, i.ToString (), 1000 + i);
- });
- }
-
- [Theory]
- [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, false)]
- [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, true)]
- [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, false)]
- [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, true)]
- public async Task RunOnArrayReturnEmptyArray (string eval_fn, string bp_loc, int line, int col, bool roundtrip)
- {
- var ret_len = 0;
-
- await RunCallFunctionOn (eval_fn,
- "function () { return []; }",
- "big", bp_loc, line, col,
- res_array_len: ret_len,
- roundtrip: roundtrip,
- test_fn: async (result) => {
- var is_js = bp_loc.EndsWith (".js", StringComparison.Ordinal);
-
- // getProperties (isOwn = false, accessorPropertiesOnly = true)
- var obj_accessors = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new {
- objectId = result.Value ["result"]["objectId"].Value<string> (),
- accessorPropertiesOnly = true,
- ownProperties = false
- }), ctx.token);
- if (is_js)
- await CheckProps (obj_accessors.Value ["result"], new { __proto__ = TIgnore () }, "obj_accessors");
- else
- AssertEqual (0, obj_accessors.Value ["result"]?.Count (), "obj_accessors-count");
-
- // getProperties (isOwn = true, accessorPropertiesOnly = false)
- var obj_own = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new {
- objectId = result.Value ["result"]["objectId"].Value<string> (),
- accessorPropertiesOnly = false,
- ownProperties = true
- }), ctx.token);
-
- await CheckProps (obj_own.Value ["result"], new {
- length = TNumber (ret_len),
- // __proto__ returned by js
- }, $"obj_own", num_fields: is_js ? 2 : 1);
- });
- }
-
- [Theory]
- [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, false)]
- [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, true)]
- [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, false)]
- [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, true)]
- public async Task RunOnArrayReturnArray (string eval_fn, string bp_loc, int line, int col, bool roundtrip)
- {
- var ret_len = 5;
- await RunCallFunctionOn (eval_fn,
- "function (m) { return Object.values (this).filter ((k, i) => i%m == 0); }",
- "big", bp_loc, line, col,
- fn_args: JArray.FromObject (new [] { new { value = 2 } }),
- res_array_len: ret_len,
- roundtrip: roundtrip,
- test_fn: async (result) => {
- var is_js = bp_loc.EndsWith (".js");
-
- // getProperties (own=false)
- var obj_accessors = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new {
- objectId = result.Value ["result"]["objectId"].Value<string> (),
- accessorPropertiesOnly = true,
- ownProperties = false
- }), ctx.token);
-
- if (is_js)
- await CheckProps (obj_accessors.Value ["result"], new { __proto__ = TIgnore () }, "obj_accessors");
- else
- AssertEqual (0, obj_accessors.Value ["result"]?.Count (), "obj_accessors-count");
-
- // getProperties (own=true)
- // isOwn = true, accessorPropertiesOnly = false
- var obj_own = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new {
- objectId = result.Value ["result"]["objectId"].Value<string> (),
- accessorPropertiesOnly = false,
- ownProperties = true
- }), ctx.token);
-
- // AssertEqual (2, obj_own.Value ["result"].Count (), $"{label}-obj_own.count");
-
- var obj_own_val = obj_own.Value ["result"];
- await CheckProps (obj_own_val, new {
- length = TNumber (ret_len),
- // __proto__ returned by JS
- }, $"obj_own", num_fields: (is_js ? ret_len + 2 : ret_len + 1));
-
- for (int i = 0; i < ret_len; i ++)
- CheckNumber (obj_own_val, i.ToString (), i*2 + 1000);
- });
- }
-
- [Theory]
- [InlineData (false)]
- [InlineData (true)]
- public async Task RunOnVTArray (bool roundtrip)
- => await RunCallFunctionOn (
- "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);",
- "function (m) { return Object.values (this).filter ((k, i) => i%m == 0); }",
- "ss_arr",
- "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3,
- fn_args: JArray.FromObject (new [] { new { value = 2 } }),
- res_array_len: 5,
- roundtrip: roundtrip,
- test_fn: async (result) => {
- var ret_len = 5;
-
- // getProperties (own=false)
- var obj_accessors = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new {
- objectId = result.Value ["result"]["objectId"].Value<string> (),
- accessorPropertiesOnly = true,
- ownProperties = false
- }), ctx.token);
-
- AssertEqual (0, obj_accessors.Value ["result"]?.Count (), "obj_accessors-count");
-
- // getProperties (own=true)
- // isOwn = true, accessorPropertiesOnly = false
- var obj_own = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new {
- objectId = result.Value ["result"]["objectId"].Value<string> (),
- accessorPropertiesOnly = false,
- ownProperties = true
- }), ctx.token);
-
- var obj_own_val = obj_own.Value ["result"];
- await CheckProps (obj_own_val, new {
- length = TNumber (ret_len),
- // __proto__ returned by JS
- }, "obj_own", num_fields: ret_len + 1);
-
- for (int i = 0; i < ret_len; i ++) {
- var act_i = CheckValueType (obj_own_val, i.ToString (), "Math.SimpleStruct");
-
- // Valuetypes can get sent as part of the container's getProperties, so ensure that we can access it
- var act_i_props = await GetProperties (act_i ["value"]["objectId"]?.Value<string> ());
- await CheckProps (act_i_props, new {
- dt = TValueType ("System.DateTime", new DateTime (2020 + (i*2), 1, 2, 3, 4, 5).ToString ()),
- gs = TValueType ("Math.GenericStruct<System.DateTime>")
- }, "obj_own ss_arr[{i}]");
-
- var gs_props = await GetObjectOnLocals (act_i_props, "gs");
- await CheckProps (gs_props, new {
- List = TObject ("System.Collections.Generic.List<System.DateTime>", is_null: true),
- StringField = TString ($"ss_arr # {i*2} # gs # StringField")
- }, "obj_own ss_arr[{i}].gs");
-
- }
- });
-
- [Theory]
- [InlineData (false)]
- [InlineData (true)]
- public async Task RunOnCFOValueTypeResult (bool roundtrip)
- => await RunCallFunctionOn (
- eval_fn: "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);",
- fn_decl: "function () { return this; }",
- local_name: "simple_struct",
- bp_loc: "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3,
- roundtrip: roundtrip,
- test_fn: async (result) => {
-
- // getProperties (own=false)
- var obj_accessors = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new {
- objectId = result.Value ["result"]["objectId"].Value<string> (),
- accessorPropertiesOnly = true,
- ownProperties = false
- }), ctx.token);
- AssertEqual (0, obj_accessors.Value ["result"].Count (), "obj_accessors-count");
-
- // getProperties (own=true)
- // isOwn = true, accessorPropertiesOnly = false
- var obj_own = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new {
- objectId = result.Value ["result"]["objectId"].Value<string> (),
- accessorPropertiesOnly = false,
- ownProperties = true
- }), ctx.token);
-
- var obj_own_val = obj_own.Value ["result"];
- var dt = new DateTime (2020, 1, 2, 3, 4, 5);
- await CheckProps (obj_own_val, new {
- dt = TValueType ("System.DateTime", dt.ToString ()),
- gs = TValueType ("Math.GenericStruct<System.DateTime>")
- }, $"obj_own-props");
-
- await CheckDateTime (obj_own_val, "dt", dt);
-
- var gs_props = await GetObjectOnLocals (obj_own_val, "gs");
- await CheckProps (gs_props, new {
- List = TObject ("System.Collections.Generic.List<System.DateTime>", is_null: true),
- StringField = TString ($"simple_struct # gs # StringField")
- }, "simple_struct.gs-props");
- });
-
- [Theory]
- [InlineData (false)]
- [InlineData (true)]
- public async Task RunOnJSObject (bool roundtrip)
- => await RunCallFunctionOn (
- "object_js_test ();",
- "function () { return this; }",
- "obj", "/other.js", 14, 1,
- fn_args: JArray.FromObject (new [] { new { value = 2 } }),
- roundtrip: roundtrip,
- test_fn: async (result) => {
-
- // getProperties (own=false)
- var obj_accessors = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new {
- objectId = result.Value ["result"]["objectId"].Value<string> (),
- accessorPropertiesOnly = true,
- ownProperties = false
- }), ctx.token);
-
- await CheckProps (obj_accessors.Value ["result"], new { __proto__ = TIgnore () }, "obj_accessors");
-
- // getProperties (own=true)
- // isOwn = true, accessorPropertiesOnly = false
- var obj_own = await ctx.cli.SendCommand ("Runtime.getProperties", JObject.FromObject (new {
- objectId = result.Value ["result"]["objectId"].Value<string> (),
- accessorPropertiesOnly = false,
- ownProperties = true
- }), ctx.token);
-
- var obj_own_val = obj_own.Value ["result"];
- await CheckProps (obj_own_val, new {
- a_obj = TObject ("Object"),
- b_arr = TArray ("Array", 2)
- }, "obj_own", num_fields: 3);
- });
-
- [Theory]
- [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, false)]
- [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, true)]
- [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, false)]
- [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, true)]
- public async Task RunOnArrayReturnObjectArrayByValue (string eval_fn, string bp_loc, int line, int col, bool roundtrip)
- {
- var ret_len = 5;
- await RunCallFunctionOn (eval_fn,
- "function () { return Object.values (this).filter ((k, i) => i%2 == 0); }",
- "big", bp_loc, line, col, returnByValue: true, roundtrip: roundtrip,
- test_fn: async (result) => {
- // Check cfo result
- AssertEqual (JTokenType.Object, result.Value ["result"].Type, "cfo-result-jsontype");
- AssertEqual ("object", result.Value ["result"]["type"]?.Value<string> (), "cfo-res-type");
-
- AssertEqual (JTokenType.Array, result.Value ["result"] ["value"].Type, "cfo-res-value-jsontype");
- var actual = result.Value ["result"]?["value"].Values<JToken> ().ToArray ();
- AssertEqual (ret_len, actual.Length, "cfo-res-value-length");
-
- for (int i = 0; i < ret_len; i ++) {
- var exp_num = i*2 + 1000;
- if (bp_loc.EndsWith (".js", StringComparison.Ordinal))
- AssertEqual (exp_num, actual [i].Value<int> (), $"[{i}]");
- else {
- AssertEqual ("number", actual [i]?["type"]?.Value<string> (), $"[{i}]-type");
- AssertEqual (exp_num.ToString (), actual [i]?["description"]?.Value<string> (), $"[{i}]-description");
- AssertEqual (exp_num, actual [i]?["value"]?.Value<int> (), $"[{i}]-value");
- }
- }
- await Task.CompletedTask;
- });
- }
-
- [Theory]
- [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, false)]
- [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, true)]
- [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, false)]
- [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, true)]
- public async Task RunOnArrayReturnArrayByValue (string eval_fn, string bp_loc, int line, int col, bool roundtrip)
- => await RunCallFunctionOn (eval_fn,
- "function () { return Object.getOwnPropertyNames (this); }",
- "big", bp_loc, line, col, returnByValue: true,
- roundtrip: roundtrip,
- test_fn: async (result) => {
- // Check cfo result
- AssertEqual ("object", result.Value ["result"]["type"]?.Value<string> (), "cfo-res-type");
-
- var exp = new JArray ();
- for (int i = 0; i < 10; i ++)
- exp.Add (i.ToString ());
- exp.Add ("length");
-
- var actual = result.Value ["result"]?["value"];
- if (!JObject.DeepEquals (exp, actual)) {
- Assert.True (false, $"Results don't match.\nExpected: {exp}\nActual: {actual}");
- }
- await Task.CompletedTask;
- });
-
- [Theory]
- [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, false)]
- [InlineData ("big_array_js_test (10);", "/other.js", 5, 1, true)]
- [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, false)]
- [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, true)]
- public async Task RunOnArrayReturnPrimitive (string eval_fn, string bp_loc, int line, int col, bool return_by_val)
- {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- await SetBreakpoint (bp_loc, line, col);
-
- // callFunctionOn
- var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);";
- var result = await ctx.cli.SendCommand ("Runtime.evaluate", JObject.FromObject (new { expression = eval_expr }), ctx.token);
- var pause_location = await ctx.insp.WaitFor (Inspector.PAUSE);
-
- // Um for js we get "scriptId": "6"
- // CheckLocation (bp_loc, line, col, ctx.scripts, pause_location ["callFrames"][0]["location"]);
-
- // Check the object at the bp
- var frame_locals = await GetProperties (pause_location ["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value<string> ());
- var obj = GetAndAssertObjectWithName (frame_locals, "big");
- var obj_id = obj ["value"]["objectId"].Value<string> ();
-
- var cfo_args = JObject.FromObject (new {
- functionDeclaration = "function () { return 5; }",
- objectId = obj_id
- });
-
- // value of @returnByValue doesn't matter, as the returned value
- // is a primitive
- if (return_by_val)
- cfo_args ["returnByValue"] = return_by_val;
-
- // callFunctionOn
- result = await ctx.cli.SendCommand ("Runtime.callFunctionOn", cfo_args, ctx.token);
- await CheckValue (result.Value ["result"], TNumber (5), "cfo-res");
- });
- }
-
- public static TheoryData<string, string, int, int, bool?> SilentErrorsTestData (bool? silent)
- => new TheoryData<string, string, int, int, bool?> {
- { "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 19, 3, silent },
- { "big_array_js_test (10);", "/other.js", 5, 1, silent }
- };
-
- [Theory]
- [MemberData (nameof (SilentErrorsTestData), null)]
- [MemberData (nameof (SilentErrorsTestData), false)]
- [MemberData (nameof (SilentErrorsTestData), true)]
- public async Task CFOWithSilentReturnsErrors (string eval_fn, string bp_loc, int line, int col, bool? silent)
- {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- await SetBreakpoint (bp_loc, line, col);
-
- // callFunctionOn
- var eval_expr = "window.setTimeout(function() { " + eval_fn + " }, 1);";
- var result = await ctx.cli.SendCommand ("Runtime.evaluate", JObject.FromObject (new { expression = eval_expr }), ctx.token);
- var pause_location = await ctx.insp.WaitFor (Inspector.PAUSE);
-
- var frame_locals = await GetProperties (pause_location ["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value<string> ());
- var obj = GetAndAssertObjectWithName (frame_locals, "big");
- var big_obj_id = obj ["value"]["objectId"].Value<string> ();
- var error_msg = "#This is an error message#";
-
- // Check the object at the bp
- var cfo_args = JObject.FromObject (new {
- functionDeclaration = $"function () {{ throw Error ('{error_msg}'); }}",
- objectId = big_obj_id
- });
-
- if (silent.HasValue)
- cfo_args ["silent"] = silent;
-
- // callFunctionOn, Silent does not change the result, except that the error
- // doesn't get reported, and the execution is NOT paused even with setPauseOnException=true
- result = await ctx.cli.SendCommand ("Runtime.callFunctionOn", cfo_args, ctx.token);
- Assert.False (result.IsOk, "result.IsOk");
- Assert.True (result.IsErr, "result.IsErr");
-
- var hasErrorMessage = result.Error ["exceptionDetails"]?["exception"]?["description"]?.Value<string> ()?.Contains (error_msg);
- Assert.True ((hasErrorMessage ?? false), "Exception message not found");
- });
- }
-
- public static TheoryData<string, string, int, int, string, Func<string[], object>, bool> GettersTestData (bool use_cfo)
- => new TheoryData<string, string, int, int, string, Func<string[], object>, bool> {
- // Chrome sends this one
- {
- "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');",
- "PropertyGettersTest", 26, 3,
- "function invokeGetter(arrayStr){ let result=this; const properties=JSON.parse(arrayStr); for(let i=0,n=properties.length;i<n;++i){ result=result[properties[i]]; } return result; }",
- (arg_strs) => JArray.FromObject (arg_strs).ToString (),
- use_cfo
- },
- {
- "invoke_static_method_async ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTestAsync');",
- "MoveNext", 34, 3,
- "function invokeGetter(arrayStr){ let result=this; const properties=JSON.parse(arrayStr); for(let i=0,n=properties.length;i<n;++i){ result=result[properties[i]]; } return result; }",
- (arg_strs) => JArray.FromObject (arg_strs).ToString (),
- use_cfo
- },
-
- // VSCode sends this one
- {
- "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');",
- "PropertyGettersTest", 26, 3,
- "function(e){return this[e]}",
- (args_str) => args_str?.Length > 0 ? args_str [0] : String.Empty,
- use_cfo
- },
- {
- "invoke_static_method_async ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTestAsync');",
- "MoveNext", 34, 3,
- "function(e){return this[e]}",
- (args_str) => args_str?.Length > 0 ? args_str [0] : String.Empty,
- use_cfo
- }
- };
-
- [Theory]
- [MemberData (nameof (GettersTestData), parameters: false)]
- [MemberData (nameof (GettersTestData), parameters: true)]
- public async Task PropertyGettersOnObjectsTest (string eval_fn, string method_name, int line, int col, string cfo_fn, Func<string[], object> get_args_fn, bool use_cfo)
- => await CheckInspectLocalsAtBreakpointSite (
- "dotnet://debugger-test.dll/debugger-cfo-test.cs", line, col,
- method_name,
- $"window.setTimeout(function() {{ {eval_fn} }}, 1);",
- use_cfo: use_cfo,
- wait_for_event_fn: async (pause_location) => {
- var frame_locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
- var dt = new DateTime (10, 9, 8, 7, 6, 5);
-
- await CheckProps (frame_locals, new {
- ptd = TObject ("DebuggerTests.ClassWithProperties"),
- swp = TObject ("DebuggerTests.StructWithProperties"),
- }, "locals#0");
-
- var ptd = GetAndAssertObjectWithName (frame_locals, "ptd");
-
- var ptd_props = await GetProperties (ptd? ["value"]?["objectId"]?.Value<string> ());
- await CheckProps (ptd_props, new {
- Int = TGetter ("Int"),
- String = TGetter ("String"),
- DT = TGetter ("DT"),
- IntArray = TGetter ("IntArray"),
- DTArray = TGetter ("DTArray")
- }, "ptd", num_fields: 7);
-
- // Automatic properties don't have invokable getters, because we can get their
- // value from the backing field directly
- {
- dt = new DateTime (4, 5, 6, 7, 8, 9);
- var dt_auto_props = await GetObjectOnLocals (ptd_props, "DTAutoProperty");
- await CheckDateTime (ptd_props, "DTAutoProperty", dt);
- }
-
- // Invoke getters, and check values
-
- var res = await InvokeGetter (ptd, cfo_fn, get_args_fn (new[] {"Int"}));
- Assert.True (res.IsOk, $"InvokeGetter failed with : {res}");
- await CheckValue (res.Value ["result"], JObject.FromObject (new { type = "number", value = 5 }), "ptd.Int");
-
- res = await InvokeGetter (ptd, cfo_fn, get_args_fn (new[] {"String"}));
- Assert.True (res.IsOk, $"InvokeGetter failed with : {res}");
- await CheckValue (res.Value ["result"], JObject.FromObject (new { type = "string", value = "foobar" }), "ptd.String");
-
- dt = new DateTime (3, 4, 5, 6, 7, 8);
- res = await InvokeGetter (ptd, cfo_fn, get_args_fn (new[] {"DT"}));
- Assert.True (res.IsOk, $"InvokeGetter failed with : {res}");
- await CheckValue (res.Value ["result"], TValueType ("System.DateTime", dt.ToString ()), "ptd.DT");
- await CheckDateTimeValue (res.Value ["result"], dt);
-
- // Check arrays through getters
-
- res = await InvokeGetter (ptd, cfo_fn, get_args_fn (new[] {"IntArray"}));
- Assert.True (res.IsOk, $"InvokeGetter failed with : {res}");
- await CheckValue (res.Value ["result"], TArray ("int[]", 2), "ptd.IntArray");
- {
- var arr_elems = await GetProperties (res.Value ["result"]?["objectId"]?.Value<string> ());
- var exp_elems = new [] {
- TNumber (10),
- TNumber (20)
- };
-
- await CheckProps (arr_elems, exp_elems, "ptd.IntArray");
- }
-
- res = await InvokeGetter (ptd, cfo_fn, get_args_fn (new[] {"DTArray"}));
- Assert.True (res.IsOk, $"InvokeGetter failed with : {res}");
- await CheckValue (res.Value ["result"], TArray ("System.DateTime[]", 2), "ptd.DTArray");
- {
- var dt0 = new DateTime (6, 7, 8, 9, 10, 11);
- var dt1 = new DateTime (1, 2, 3, 4, 5, 6);
-
- var arr_elems = await GetProperties (res.Value ["result"]?["objectId"]?.Value<string> ());
- var exp_elems = new [] {
- TValueType ("System.DateTime", dt0.ToString ()),
- TValueType ("System.DateTime", dt1.ToString ()),
- };
-
- await CheckProps (arr_elems, exp_elems, "ptd.DTArray");
- }
- });
-
- [Theory]
- [InlineData ("invoke_static_method_async ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTestAsync');", "MoveNext", 34, 3)]
- [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');", "PropertyGettersTest", 26, 3)]
- public async Task PropertyGettersOnStructsTest (string eval_fn, string method_name, int line, int col)
- => await CheckInspectLocalsAtBreakpointSite (
- "dotnet://debugger-test.dll/debugger-cfo-test.cs", line, col,
- method_name,
- $"window.setTimeout(function() {{ {eval_fn} }}, 1);",
- wait_for_event_fn: async (pause_location) => {
- var frame_locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
- await CheckProps (frame_locals, new {
- ptd = TObject ("DebuggerTests.ClassWithProperties"),
- swp = TObject ("DebuggerTests.StructWithProperties"),
- }, "locals#0");
-
- var swp = GetAndAssertObjectWithName (frame_locals, "swp");
-
- var swp_props = await GetProperties (swp? ["value"]?["objectId"]?.Value<string> ());
- await CheckProps (swp_props, new {
- Int = TSymbol ("int { get; }"),
- String = TSymbol ("string { get; }"),
- DT = TSymbol ("System.DateTime { get; }"),
- IntArray = TSymbol ("int[] { get; }"),
- DTArray = TSymbol ("System.DateTime[] { get; }")
- }, "swp");
- });
-
- [Theory]
- [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 26, 3, false)]
- [InlineData ("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 26, 3, true)]
- [InlineData ("invoke_getters_js_test ();", "/other.js", 26, 1, false)]
- [InlineData ("invoke_getters_js_test ();", "/other.js", 26, 1, true)]
- public async Task CheckAccessorsOnObjectsWithCFO (string eval_fn, string bp_loc, int line, int col, bool roundtrip)
- {
- await RunCallFunctionOn (
- eval_fn, "function() { return this; }", "ptd",
- bp_loc, line, col,
- roundtrip: roundtrip,
- test_fn: async (result) => {
-
- var is_js = bp_loc.EndsWith (".js");
-
- // Check with `accessorPropertiesOnly=true`
-
- var id = result.Value? ["result"]?["objectId"]?.Value<string> ();
- var get_prop_req = JObject.FromObject (new {
- objectId = id,
- accessorPropertiesOnly = true
- });
-
- var res = await GetPropertiesAndCheckAccessors (get_prop_req, is_js ? 6 : 5); // js returns extra `__proto__` member also
- Assert.False (res.Value ["result"].Any (jt => jt ["name"]?.Value<string> () == "StringField"), "StringField shouldn't be returned for `accessorPropertiesOnly`");
-
- // Check with `accessorPropertiesOnly` unset, == false
- get_prop_req = JObject.FromObject (new {
- objectId = id,
- });
-
- res = await GetPropertiesAndCheckAccessors (get_prop_req, is_js ? 8 : 7); // js returns a `__proto__` member also
- Assert.True (res.Value ["result"].Any (jt => jt ["name"]?.Value<string> () == "StringField"), "StringField should be returned for `accessorPropertiesOnly=false`");
- });
-
- async Task<Result> GetPropertiesAndCheckAccessors (JObject get_prop_req, int num_fields)
- {
- var res = await ctx.cli.SendCommand ("Runtime.getProperties", get_prop_req, ctx.token);
- if (!res.IsOk)
- Assert.True (false, $"Runtime.getProperties failed for {get_prop_req.ToString ()}, with Result: {res}");
-
- var accessors = new string[] { "Int", "String", "DT", "IntArray", "DTArray" };
- foreach (var name in accessors) {
- var prop = GetAndAssertObjectWithName (res.Value ["result"], name);
- Assert.True (prop ["value"] == null, $"{name} shouldn't have a `value`");
-
- await CheckValue (prop, TGetter (name), $"{name}");
- }
-
- return res;
- }
- }
-
- async Task<Result> InvokeGetter (JToken obj, string fn, object arguments)
- => await ctx.cli.SendCommand (
- "Runtime.callFunctionOn",
- JObject.FromObject (new {
- functionDeclaration = fn,
- objectId = obj ["value"]?["objectId"]?.Value<string> (),
- arguments = new [] { new { value = arguments } }
- }), ctx.token);
-
- /*
- * 1. runs `Runtime.callFunctionOn` on the objectId,
- * if @roundtrip == false, then
- * -> calls @test_fn for that result (new objectId)
- * else
- * -> runs it again on the *result's* objectId.
- * -> calls @test_fn on the *new* result objectId
- *
- * Returns: result of `Runtime.callFunctionOn`
- */
- async Task RunCallFunctionOn (string eval_fn, string fn_decl, string local_name, string bp_loc, int line, int col, int res_array_len = -1,
- Func<Result, Task> test_fn = null, bool returnByValue = false, JArray fn_args = null, bool roundtrip = false)
- {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- await SetBreakpoint (bp_loc, line, col);
-
- // callFunctionOn
- var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);";
- var result = await ctx.cli.SendCommand ("Runtime.evaluate", JObject.FromObject (new { expression = eval_expr }), ctx.token);
- var pause_location = await ctx.insp.WaitFor (Inspector.PAUSE);
-
- // Um for js we get "scriptId": "6"
- // CheckLocation (bp_loc, line, col, ctx.scripts, pause_location ["callFrames"][0]["location"]);
-
- // Check the object at the bp
- var frame_locals = await GetProperties (pause_location ["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value<string> ());
- var obj = GetAndAssertObjectWithName (frame_locals, local_name);
- var obj_id = obj ["value"]["objectId"].Value<string> ();
-
- var cfo_args = JObject.FromObject (new {
- functionDeclaration = fn_decl,
- objectId = obj_id
- });
-
- if (fn_args != null)
- cfo_args ["arguments"] = fn_args;
-
- if (returnByValue)
- cfo_args ["returnByValue"] = returnByValue;
-
- // callFunctionOn
- result = await ctx.cli.SendCommand ("Runtime.callFunctionOn", cfo_args, ctx.token);
- await CheckCFOResult (result);
-
- // If it wasn't `returnByValue`, then try to run a new function
- // on that *returned* object
- // This second function, just returns the object as-is, so the same
- // test_fn is re-usable.
- if (!returnByValue && roundtrip) {
- cfo_args = JObject.FromObject (new {
- functionDeclaration = "function () { return this; }",
- objectId = result.Value ["result"]["objectId"]?.Value<string> ()
- });
-
- if (fn_args != null)
- cfo_args ["arguments"] = fn_args;
-
- result = await ctx.cli.SendCommand ("Runtime.callFunctionOn", cfo_args, ctx.token);
-
- await CheckCFOResult (result);
- }
-
- if (test_fn != null)
- await test_fn (result);
-
- return;
-
- async Task CheckCFOResult (Result result)
- {
- if (returnByValue)
- return;
-
- if (res_array_len < 0)
- await CheckValue (result.Value ["result"], TObject ("Object"), $"cfo-res");
- else
- await CheckValue (result.Value ["result"], TArray ("Array", res_array_len), $"cfo-res");
- }
- });
- }
- }
+ public class CallFunctionOnTests : DebuggerTestBase
+ {
+
+ // This tests `callFunctionOn` with a function that the vscode-js-debug extension uses
+ // Using this here as a non-trivial test case
+ [Theory]
+ [InlineData("big_array_js_test (10);", "/other.js", 8, 1, 10, false)]
+ [InlineData("big_array_js_test (0);", "/other.js", 8, 1, 0, true)]
+ [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, 10, false)]
+ [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 0);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, 0, true)]
+ public async Task CheckVSCodeTestFunction1(string eval_fn, string bp_loc, int line, int col, int len, bool roundtrip)
+ {
+ string vscode_fn0 = "function(){const e={__proto__:this.__proto__},t=Object.getOwnPropertyNames(this);for(let r=0;r<t.length;++r){const n=t[r],i=n>>>0;if(String(i>>>0)===n&&i>>>0!=4294967295)continue;const a=Object.getOwnPropertyDescriptor(this,n);a&&Object.defineProperty(e,n,a)}return e}";
+
+ await RunCallFunctionOn(eval_fn, vscode_fn0, "big", bp_loc, line, col, res_array_len: len, roundtrip: roundtrip,
+ test_fn: async (result) =>
+ {
+
+ var is_js = bp_loc.EndsWith(".js", StringComparison.Ordinal);
+ var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
+ {
+ objectId = result.Value["result"]["objectId"].Value<string>(),
+ accessorPropertiesOnly = true,
+ ownProperties = false
+ }), ctx.token);
+ if (is_js)
+ await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors");
+ else
+ AssertEqual(0, obj_accessors.Value["result"]?.Count(), "obj_accessors-count");
+
+ // Check for a __proto__ object
+ // isOwn = true, accessorPropertiesOnly = false
+ var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
+ {
+ objectId = result.Value["result"]["objectId"].Value<string>(),
+ accessorPropertiesOnly = false,
+ ownProperties = true
+ }), ctx.token);
+
+ await CheckProps(obj_own.Value["result"], new
+ {
+ length = TNumber(len),
+ // __proto__ = TArray (type, 0) // Is this one really required?
+ }, $"obj_own", num_fields: is_js ? 2 : 1);
+
+ });
+ }
+
+ void CheckJFunction(JToken actual, string className, string label)
+ {
+ AssertEqual("function", actual["type"]?.Value<string>(), $"{label}-type");
+ AssertEqual(className, actual["className"]?.Value<string>(), $"{label}-className");
+ }
+
+ // This tests `callFunctionOn` with a function that the vscode-js-debug extension uses
+ // Using this here as a non-trivial test case
+ [Theory]
+ [InlineData("big_array_js_test (10);", "/other.js", 8, 1, 10)]
+ [InlineData("big_array_js_test (0);", "/other.js", 8, 1, 0)]
+ [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, 10)]
+ [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 0);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, 0)]
+ public async Task CheckVSCodeTestFunction2(string eval_fn, string bp_loc, int line, int col, int len)
+ {
+ var fetch_start_idx = 2;
+ var num_elems_fetch = 3;
+ string vscode_fn1 = "function(e,t){const r={},n=-1===e?0:e,i=-1===t?this.length:e+t;for(let e=n;e<i&&e<this.length;++e){const t=Object.getOwnPropertyDescriptor(this,e);t&&Object.defineProperty(r,e,t)}return r}";
+
+ await RunCallFunctionOn(eval_fn, vscode_fn1, "big", bp_loc, line, col,
+ fn_args: JArray.FromObject(new[]
+ {
+ new { @value = fetch_start_idx },
+ new { @value = num_elems_fetch }
+ }),
+ test_fn: async (result) =>
+ {
+
+ var is_js = bp_loc.EndsWith(".js", StringComparison.Ordinal);
+
+ // isOwn = false, accessorPropertiesOnly = true
+ var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
+ {
+ objectId = result.Value["result"]["objectId"].Value<string>(),
+ accessorPropertiesOnly = true,
+ ownProperties = false
+ }), ctx.token);
+ if (is_js)
+ await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors");
+ else
+ AssertEqual(0, obj_accessors.Value["result"]?.Count(), "obj_accessors-count");
+
+ // Ignoring the __proto__ property
+
+ // isOwn = true, accessorPropertiesOnly = false
+ var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
+ {
+ objectId = result.Value["result"]["objectId"].Value<string>(),
+ accessorPropertiesOnly = false,
+ ownProperties = true
+ }), ctx.token);
+
+ var obj_own_val = obj_own.Value["result"];
+ var num_elems_recd = len == 0 ? 0 : num_elems_fetch;
+ AssertEqual(is_js ? num_elems_recd + 1 : num_elems_recd, obj_own_val.Count(), $"obj_own-count");
+
+ if (is_js)
+ CheckObject(obj_own_val, "__proto__", "Object");
+
+ for (int i = fetch_start_idx; i < fetch_start_idx + num_elems_recd; i++)
+ CheckNumber(obj_own_val, i.ToString(), 1000 + i);
+ });
+ }
+
+ [Theory]
+ [InlineData("big_array_js_test (10);", "/other.js", 8, 1, false)]
+ [InlineData("big_array_js_test (10);", "/other.js", 8, 1, true)]
+ [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, false)]
+ [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, true)]
+ public async Task RunOnArrayReturnEmptyArray(string eval_fn, string bp_loc, int line, int col, bool roundtrip)
+ {
+ var ret_len = 0;
+
+ await RunCallFunctionOn(eval_fn,
+ "function () { return []; }",
+ "big", bp_loc, line, col,
+ res_array_len: ret_len,
+ roundtrip: roundtrip,
+ test_fn: async (result) =>
+ {
+ var is_js = bp_loc.EndsWith(".js", StringComparison.Ordinal);
+
+ // getProperties (isOwn = false, accessorPropertiesOnly = true)
+ var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
+ {
+ objectId = result.Value["result"]["objectId"].Value<string>(),
+ accessorPropertiesOnly = true,
+ ownProperties = false
+ }), ctx.token);
+ if (is_js)
+ await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors");
+ else
+ AssertEqual(0, obj_accessors.Value["result"]?.Count(), "obj_accessors-count");
+
+ // getProperties (isOwn = true, accessorPropertiesOnly = false)
+ var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
+ {
+ objectId = result.Value["result"]["objectId"].Value<string>(),
+ accessorPropertiesOnly = false,
+ ownProperties = true
+ }), ctx.token);
+
+ await CheckProps(obj_own.Value["result"], new
+ {
+ length = TNumber(ret_len),
+ // __proto__ returned by js
+ }, $"obj_own", num_fields: is_js ? 2 : 1);
+ });
+ }
+
+ [Theory]
+ [InlineData("big_array_js_test (10);", "/other.js", 8, 1, false)]
+ [InlineData("big_array_js_test (10);", "/other.js", 8, 1, true)]
+ [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, false)]
+ [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, true)]
+ public async Task RunOnArrayReturnArray(string eval_fn, string bp_loc, int line, int col, bool roundtrip)
+ {
+ var ret_len = 5;
+ await RunCallFunctionOn(eval_fn,
+ "function (m) { return Object.values (this).filter ((k, i) => i%m == 0); }",
+ "big", bp_loc, line, col,
+ fn_args: JArray.FromObject(new[] { new { value = 2 } }),
+ res_array_len: ret_len,
+ roundtrip: roundtrip,
+ test_fn: async (result) =>
+ {
+ var is_js = bp_loc.EndsWith(".js");
+
+ // getProperties (own=false)
+ var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
+ {
+ objectId = result.Value["result"]["objectId"].Value<string>(),
+ accessorPropertiesOnly = true,
+ ownProperties = false
+ }), ctx.token);
+
+ if (is_js)
+ await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors");
+ else
+ AssertEqual(0, obj_accessors.Value["result"]?.Count(), "obj_accessors-count");
+
+ // getProperties (own=true)
+ // isOwn = true, accessorPropertiesOnly = false
+ var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
+ {
+ objectId = result.Value["result"]["objectId"].Value<string>(),
+ accessorPropertiesOnly = false,
+ ownProperties = true
+ }), ctx.token);
+
+ // AssertEqual (2, obj_own.Value ["result"].Count (), $"{label}-obj_own.count");
+
+ var obj_own_val = obj_own.Value["result"];
+ await CheckProps(obj_own_val, new
+ {
+ length = TNumber(ret_len),
+ // __proto__ returned by JS
+ }, $"obj_own", num_fields: (is_js ? ret_len + 2 : ret_len + 1));
+
+ for (int i = 0; i < ret_len; i++)
+ CheckNumber(obj_own_val, i.ToString(), i * 2 + 1000);
+ });
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task RunOnVTArray(bool roundtrip) => await RunCallFunctionOn(
+ "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);",
+ "function (m) { return Object.values (this).filter ((k, i) => i%m == 0); }",
+ "ss_arr",
+ "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12,
+ fn_args: JArray.FromObject(new[] { new { value = 2 } }),
+ res_array_len: 5,
+ roundtrip: roundtrip,
+ test_fn: async (result) =>
+ {
+ var ret_len = 5;
+
+ // getProperties (own=false)
+ var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
+ {
+ objectId = result.Value["result"]["objectId"].Value<string>(),
+ accessorPropertiesOnly = true,
+ ownProperties = false
+ }), ctx.token);
+
+ AssertEqual(0, obj_accessors.Value["result"]?.Count(), "obj_accessors-count");
+
+ // getProperties (own=true)
+ // isOwn = true, accessorPropertiesOnly = false
+ var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
+ {
+ objectId = result.Value["result"]["objectId"].Value<string>(),
+ accessorPropertiesOnly = false,
+ ownProperties = true
+ }), ctx.token);
+
+ var obj_own_val = obj_own.Value["result"];
+ await CheckProps(obj_own_val, new
+ {
+ length = TNumber(ret_len),
+ // __proto__ returned by JS
+ }, "obj_own", num_fields: ret_len + 1);
+
+ for (int i = 0; i < ret_len; i++)
+ {
+ var act_i = CheckValueType(obj_own_val, i.ToString(), "Math.SimpleStruct");
+
+ // Valuetypes can get sent as part of the container's getProperties, so ensure that we can access it
+ var act_i_props = await GetProperties(act_i["value"]["objectId"]?.Value<string>());
+ await CheckProps(act_i_props, new
+ {
+ dt = TValueType("System.DateTime", new DateTime(2020 + (i * 2), 1, 2, 3, 4, 5).ToString()),
+ gs = TValueType("Math.GenericStruct<System.DateTime>")
+ }, "obj_own ss_arr[{i}]");
+
+ var gs_props = await GetObjectOnLocals(act_i_props, "gs");
+ await CheckProps(gs_props, new
+ {
+ List = TObject("System.Collections.Generic.List<System.DateTime>", is_null: true),
+ StringField = TString($"ss_arr # {i * 2} # gs # StringField")
+ }, "obj_own ss_arr[{i}].gs");
+
+ }
+ });
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task RunOnCFOValueTypeResult(bool roundtrip) => await RunCallFunctionOn(
+ eval_fn: "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);",
+ fn_decl: "function () { return this; }",
+ local_name: "simple_struct",
+ bp_loc: "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12,
+ roundtrip: roundtrip,
+ test_fn: async (result) =>
+ {
+
+ // getProperties (own=false)
+ var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
+ {
+ objectId = result.Value["result"]["objectId"].Value<string>(),
+ accessorPropertiesOnly = true,
+ ownProperties = false
+ }), ctx.token);
+ AssertEqual(0, obj_accessors.Value["result"].Count(), "obj_accessors-count");
+
+ // getProperties (own=true)
+ // isOwn = true, accessorPropertiesOnly = false
+ var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
+ {
+ objectId = result.Value["result"]["objectId"].Value<string>(),
+ accessorPropertiesOnly = false,
+ ownProperties = true
+ }), ctx.token);
+
+ var obj_own_val = obj_own.Value["result"];
+ var dt = new DateTime(2020, 1, 2, 3, 4, 5);
+ await CheckProps(obj_own_val, new
+ {
+ dt = TValueType("System.DateTime", dt.ToString()),
+ gs = TValueType("Math.GenericStruct<System.DateTime>")
+ }, $"obj_own-props");
+
+ await CheckDateTime(obj_own_val, "dt", dt);
+
+ var gs_props = await GetObjectOnLocals(obj_own_val, "gs");
+ await CheckProps(gs_props, new
+ {
+ List = TObject("System.Collections.Generic.List<System.DateTime>", is_null: true),
+ StringField = TString($"simple_struct # gs # StringField")
+ }, "simple_struct.gs-props");
+ });
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task RunOnJSObject(bool roundtrip) => await RunCallFunctionOn(
+ "object_js_test ();",
+ "function () { return this; }",
+ "obj", "/other.js", 17, 1,
+ fn_args: JArray.FromObject(new[] { new { value = 2 } }),
+ roundtrip: roundtrip,
+ test_fn: async (result) =>
+ {
+
+ // getProperties (own=false)
+ var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
+ {
+ objectId = result.Value["result"]["objectId"].Value<string>(),
+ accessorPropertiesOnly = true,
+ ownProperties = false
+ }), ctx.token);
+
+ await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors");
+
+ // getProperties (own=true)
+ // isOwn = true, accessorPropertiesOnly = false
+ var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
+ {
+ objectId = result.Value["result"]["objectId"].Value<string>(),
+ accessorPropertiesOnly = false,
+ ownProperties = true
+ }), ctx.token);
+
+ var obj_own_val = obj_own.Value["result"];
+ await CheckProps(obj_own_val, new
+ {
+ a_obj = TObject("Object"),
+ b_arr = TArray("Array", 2)
+ }, "obj_own", num_fields: 3);
+ });
+
+ [Theory]
+ [InlineData("big_array_js_test (10);", "/other.js", 8, 1, false)]
+ [InlineData("big_array_js_test (10);", "/other.js", 8, 1, true)]
+ [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, false)]
+ [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, true)]
+ public async Task RunOnArrayReturnObjectArrayByValue(string eval_fn, string bp_loc, int line, int col, bool roundtrip)
+ {
+ var ret_len = 5;
+ await RunCallFunctionOn(eval_fn,
+ "function () { return Object.values (this).filter ((k, i) => i%2 == 0); }",
+ "big", bp_loc, line, col, returnByValue: true, roundtrip: roundtrip,
+ test_fn: async (result) =>
+ {
+ // Check cfo result
+ AssertEqual(JTokenType.Object, result.Value["result"].Type, "cfo-result-jsontype");
+ AssertEqual("object", result.Value["result"]["type"]?.Value<string>(), "cfo-res-type");
+
+ AssertEqual(JTokenType.Array, result.Value["result"]["value"].Type, "cfo-res-value-jsontype");
+ var actual = result.Value["result"]?["value"].Values<JToken>().ToArray();
+ AssertEqual(ret_len, actual.Length, "cfo-res-value-length");
+
+ for (int i = 0; i < ret_len; i++)
+ {
+ var exp_num = i * 2 + 1000;
+ if (bp_loc.EndsWith(".js", StringComparison.Ordinal))
+ AssertEqual(exp_num, actual[i].Value<int>(), $"[{i}]");
+ else
+ {
+ AssertEqual("number", actual[i]?["type"]?.Value<string>(), $"[{i}]-type");
+ AssertEqual(exp_num.ToString(), actual[i]?["description"]?.Value<string>(), $"[{i}]-description");
+ AssertEqual(exp_num, actual[i]?["value"]?.Value<int>(), $"[{i}]-value");
+ }
+ }
+ await Task.CompletedTask;
+ });
+ }
+
+ [Theory]
+ [InlineData("big_array_js_test (10);", "/other.js", 8, 1, false)]
+ [InlineData("big_array_js_test (10);", "/other.js", 8, 1, true)]
+ [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, false)]
+ [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, true)]
+ public async Task RunOnArrayReturnArrayByValue(string eval_fn, string bp_loc, int line, int col, bool roundtrip) => await RunCallFunctionOn(eval_fn,
+ "function () { return Object.getOwnPropertyNames (this); }",
+ "big", bp_loc, line, col, returnByValue: true,
+ roundtrip: roundtrip,
+ test_fn: async (result) =>
+ {
+ // Check cfo result
+ AssertEqual("object", result.Value["result"]["type"]?.Value<string>(), "cfo-res-type");
+
+ var exp = new JArray();
+ for (int i = 0; i < 10; i++)
+ exp.Add(i.ToString());
+ exp.Add("length");
+
+ var actual = result.Value["result"]?["value"];
+ if (!JObject.DeepEquals(exp, actual))
+ {
+ Assert.True(false, $"Results don't match.\nExpected: {exp}\nActual: {actual}");
+ }
+ await Task.CompletedTask;
+ });
+
+ [Theory]
+ [InlineData("big_array_js_test (10);", "/other.js", 8, 1, false)]
+ [InlineData("big_array_js_test (10);", "/other.js", 8, 1, true)]
+ [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, false)]
+ [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, true)]
+ public async Task RunOnArrayReturnPrimitive(string eval_fn, string bp_loc, int line, int col, bool return_by_val)
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ await SetBreakpoint(bp_loc, line, col);
+
+ // callFunctionOn
+ var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);";
+ var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token);
+ var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE);
+
+ // Um for js we get "scriptId": "6"
+ // CheckLocation (bp_loc, line, col, ctx.scripts, pause_location ["callFrames"][0]["location"]);
+
+ // Check the object at the bp
+ var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value<string>());
+ var obj = GetAndAssertObjectWithName(frame_locals, "big");
+ var obj_id = obj["value"]["objectId"].Value<string>();
+
+ var cfo_args = JObject.FromObject(new
+ {
+ functionDeclaration = "function () { return 5; }",
+ objectId = obj_id
+ });
+
+ // value of @returnByValue doesn't matter, as the returned value
+ // is a primitive
+ if (return_by_val)
+ cfo_args["returnByValue"] = return_by_val;
+
+ // callFunctionOn
+ result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token);
+ await CheckValue(result.Value["result"], TNumber(5), "cfo-res");
+
+ cfo_args = JObject.FromObject(new
+ {
+ functionDeclaration = "function () { return 'test value'; }",
+ objectId = obj_id
+ });
+
+ // value of @returnByValue doesn't matter, as the returned value
+ // is a primitive
+ if (return_by_val)
+ cfo_args["returnByValue"] = return_by_val;
+
+ // callFunctionOn
+ result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token);
+ await CheckValue(result.Value["result"], JObject.FromObject(new { type = "string", value = "test value" }), "cfo-res");
+
+ cfo_args = JObject.FromObject(new
+ {
+ functionDeclaration = "function () { return null; }",
+ objectId = obj_id
+ });
+
+ // value of @returnByValue doesn't matter, as the returned value
+ // is a primitive
+ if (return_by_val)
+ cfo_args["returnByValue"] = return_by_val;
+
+ // callFunctionOn
+ result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token);
+ await CheckValue(result.Value["result"], JObject.Parse("{ type: 'object', subtype: 'null', value: null }"), "cfo-res");
+ });
+ }
+
+ public static TheoryData<string, string, int, int, bool?> SilentErrorsTestData(bool? silent) => new TheoryData<string, string, int, int, bool?>
+ { { "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, silent },
+ { "big_array_js_test (10);", "/other.js", 8, 1, silent }
+ };
+
+ [Theory]
+ [MemberData(nameof(SilentErrorsTestData), null)]
+ [MemberData(nameof(SilentErrorsTestData), false)]
+ [MemberData(nameof(SilentErrorsTestData), true)]
+ public async Task CFOWithSilentReturnsErrors(string eval_fn, string bp_loc, int line, int col, bool? silent)
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ await SetBreakpoint(bp_loc, line, col);
+
+ // callFunctionOn
+ var eval_expr = "window.setTimeout(function() { " + eval_fn + " }, 1);";
+ var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token);
+ var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE);
+
+ var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value<string>());
+ var obj = GetAndAssertObjectWithName(frame_locals, "big");
+ var big_obj_id = obj["value"]["objectId"].Value<string>();
+ var error_msg = "#This is an error message#";
+
+ // Check the object at the bp
+ var cfo_args = JObject.FromObject(new
+ {
+ functionDeclaration = $"function () {{ throw Error ('{error_msg}'); }}",
+ objectId = big_obj_id
+ });
+
+ if (silent.HasValue)
+ cfo_args["silent"] = silent;
+
+ // callFunctionOn, Silent does not change the result, except that the error
+ // doesn't get reported, and the execution is NOT paused even with setPauseOnException=true
+ result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token);
+ Assert.False(result.IsOk, "result.IsOk");
+ Assert.True(result.IsErr, "result.IsErr");
+
+ var hasErrorMessage = result.Error["exceptionDetails"]?["exception"]?["description"]?.Value<string>()?.Contains(error_msg);
+ Assert.True((hasErrorMessage ?? false), "Exception message not found");
+ });
+ }
+
+ public static TheoryData<string, string, int, int, string, Func<string[], object>, string, bool> GettersTestData(string local_name, bool use_cfo) => new TheoryData<string, string, int, int, string, Func<string[], object>, string, bool>
+ {
+ // Chrome sends this one
+ {
+ "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');",
+ "PropertyGettersTest",
+ 30,
+ 12,
+ "function invokeGetter(arrayStr){ let result=this; const properties=JSON.parse(arrayStr); for(let i=0,n=properties.length;i<n;++i){ result=result[properties[i]]; } return result; }",
+ (arg_strs) => JArray.FromObject(arg_strs).ToString(),
+ local_name,
+ use_cfo
+ },
+ {
+ "invoke_static_method_async ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTestAsync');",
+ "MoveNext",
+ 38,
+ 12,
+ "function invokeGetter(arrayStr){ let result=this; const properties=JSON.parse(arrayStr); for(let i=0,n=properties.length;i<n;++i){ result=result[properties[i]]; } return result; }",
+ (arg_strs) => JArray.FromObject(arg_strs).ToString(),
+ local_name,
+ use_cfo
+ },
+
+ // VSCode sends this one
+ {
+ "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');",
+ "PropertyGettersTest",
+ 30,
+ 12,
+ "function(e){return this[e]}",
+ (args_str) => args_str?.Length > 0 ? args_str[0] : String.Empty,
+ local_name,
+ use_cfo
+ },
+ {
+ "invoke_static_method_async ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTestAsync');",
+ "MoveNext",
+ 38,
+ 12,
+ "function(e){return this[e]}",
+ (args_str) => args_str?.Length > 0 ? args_str[0] : String.Empty,
+ local_name,
+ use_cfo
+ }
+ };
+
+ [Theory]
+ [MemberData(nameof(GettersTestData), "ptd", false)]
+ [MemberData(nameof(GettersTestData), "ptd", true)]
+ [MemberData(nameof(GettersTestData), "swp", false)]
+ [MemberData(nameof(GettersTestData), "swp", true)]
+ public async Task PropertyGettersTest(string eval_fn, string method_name, int line, int col, string cfo_fn, Func<string[], object> get_args_fn, string local_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
+ "dotnet://debugger-test.dll/debugger-cfo-test.cs", line, col,
+ method_name,
+ $"window.setTimeout(function() {{ {eval_fn} }}, 1);",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var frame_locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+
+ await CheckProps(frame_locals, new
+ {
+ ptd = TObject("DebuggerTests.ClassWithProperties"),
+ swp = TObject("DebuggerTests.StructWithProperties")
+ }, "locals#0");
+
+ var obj = GetAndAssertObjectWithName(frame_locals, local_name);
+
+ var dt = new DateTime(4, 5, 6, 7, 8, 9);
+ var obj_props = await GetProperties(obj?["value"]?["objectId"]?.Value<string>());
+ await CheckProps(obj_props, new
+ {
+ V = TNumber(0xDEADBEEF),
+ Int = TGetter("Int"),
+ String = TGetter("String"),
+ DT = TGetter("DT"),
+ IntArray = TGetter("IntArray"),
+ DTArray = TGetter("DTArray"),
+ StringField = TString(null),
+
+ // Auto properties show w/o getters, because they have
+ // a backing field
+ DTAutoProperty = TValueType("System.DateTime", dt.ToString())
+ }, local_name);
+
+ // Automatic properties don't have invokable getters, because we can get their
+ // value from the backing field directly
+ {
+ var dt_auto_props = await GetObjectOnLocals(obj_props, "DTAutoProperty");
+ await CheckDateTime(obj_props, "DTAutoProperty", dt);
+ }
+
+ // Invoke getters, and check values
+
+ dt = new DateTime(3, 4, 5, 6, 7, 8);
+ var res = await InvokeGetter(obj, get_args_fn(new[] { "Int" }), cfo_fn);
+ await CheckValue(res.Value["result"], JObject.FromObject(new { type = "number", value = (0xDEADBEEF + (uint)dt.Month) }), $"{local_name}.Int");
+
+ res = await InvokeGetter(obj, get_args_fn(new[] { "String" }), cfo_fn);
+ await CheckValue(res.Value["result"], JObject.FromObject(new { type = "string", value = $"String property, V: 0xDEADBEEF" }), $"{local_name}.String");
+
+ res = await InvokeGetter(obj, get_args_fn(new[] { "DT" }), cfo_fn);
+ await CheckValue(res.Value["result"], TValueType("System.DateTime", dt.ToString()), $"{local_name}.DT");
+ await CheckDateTimeValue(res.Value["result"], dt);
+
+ // Check arrays through getters
+
+ res = await InvokeGetter(obj, get_args_fn(new[] { "IntArray" }), cfo_fn);
+ await CheckValue(res.Value["result"], TArray("int[]", 2), $"{local_name}.IntArray");
+ {
+ var arr_elems = await GetProperties(res.Value["result"]?["objectId"]?.Value<string>());
+ var exp_elems = new[]
+ {
+ TNumber(10),
+ TNumber(20)
+ };
+
+ await CheckProps(arr_elems, exp_elems, $"{local_name}.IntArray");
+ }
+
+ res = await InvokeGetter(obj, get_args_fn(new[] { "DTArray" }), cfo_fn);
+ await CheckValue(res.Value["result"], TArray("System.DateTime[]", 2), $"{local_name}.DTArray");
+ {
+ var dt0 = new DateTime(6, 7, 8, 9, 10, 11);
+ var dt1 = new DateTime(1, 2, 3, 4, 5, 6);
+
+ var arr_elems = await GetProperties(res.Value["result"]?["objectId"]?.Value<string>());
+ var exp_elems = new[]
+ {
+ TValueType("System.DateTime", dt0.ToString()),
+ TValueType("System.DateTime", dt1.ToString()),
+ };
+
+ await CheckProps(arr_elems, exp_elems, $"{local_name}.DTArray");
+
+ res = await InvokeGetter(arr_elems[0], "Date");
+ await CheckDateTimeValue(res.Value["result"], dt0.Date);
+ }
+ });
+
+ [Theory]
+ [InlineData("invoke_static_method_async ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTestAsync');", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 38, 12, true)]
+ [InlineData("invoke_static_method_async ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTestAsync');", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 38, 12, false)]
+ [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 30, 12, true)]
+ [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 30, 12, false)]
+ [InlineData("invoke_getters_js_test ();", "/other.js", 30, 1, false)]
+ [InlineData("invoke_getters_js_test ();", "/other.js", 30, 1, true)]
+ public async Task CheckAccessorsOnObjectsWithCFO(string eval_fn, string bp_loc, int line, int col, bool roundtrip)
+ {
+ await RunCallFunctionOn(
+ eval_fn, "function() { return this; }", "ptd",
+ bp_loc, line, col,
+ roundtrip: roundtrip,
+ test_fn: async (result) =>
+ {
+
+ var is_js = bp_loc.EndsWith(".js");
+
+ // Check with `accessorPropertiesOnly=true`
+
+ var id = result.Value?["result"]?["objectId"]?.Value<string>();
+ var get_prop_req = JObject.FromObject(new
+ {
+ objectId = id,
+ accessorPropertiesOnly = true
+ });
+
+ var res = await GetPropertiesAndCheckAccessors(get_prop_req, is_js ? 6 : 5); // js returns extra `__proto__` member also
+ Assert.False(res.Value["result"].Any(jt => jt["name"]?.Value<string>() == "StringField"), "StringField shouldn't be returned for `accessorPropertiesOnly`");
+
+ // Check with `accessorPropertiesOnly` unset, == false
+ get_prop_req = JObject.FromObject(new
+ {
+ objectId = id,
+ });
+
+ res = await GetPropertiesAndCheckAccessors(get_prop_req, is_js ? 8 : 7); // js returns a `__proto__` member also
+ Assert.True(res.Value["result"].Any(jt => jt["name"]?.Value<string>() == "StringField"), "StringField should be returned for `accessorPropertiesOnly=false`");
+ });
+
+ async Task<Result> GetPropertiesAndCheckAccessors(JObject get_prop_req, int num_fields)
+ {
+ var res = await ctx.cli.SendCommand("Runtime.getProperties", get_prop_req, ctx.token);
+ if (!res.IsOk)
+ Assert.True(false, $"Runtime.getProperties failed for {get_prop_req.ToString()}, with Result: {res}");
+
+ var accessors = new string[] { "Int", "String", "DT", "IntArray", "DTArray" };
+ foreach (var name in accessors)
+ {
+ var prop = GetAndAssertObjectWithName(res.Value["result"], name);
+ Assert.True(prop["value"] == null, $"{name} shouldn't have a `value`");
+
+ await CheckValue(prop, TGetter(name), $"{name}");
+ }
+
+ return res;
+ }
+ }
+
+ public static TheoryData<string, string, int, int, bool> NegativeTestsData(bool use_cfo = false) => new TheoryData<string, string, int, int, bool>
+ { { "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:MethodForNegativeTests', null);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 45, 12, use_cfo },
+ { "negative_cfo_test ();", "/other.js", 62, 1, use_cfo }
+ };
+
+ [Theory]
+ [MemberData(nameof(NegativeTestsData), false)]
+ public async Task RunOnInvalidCfoId(string eval_fn, string bp_loc, int line, int col, bool use_cfo) => await RunCallFunctionOn(
+ eval_fn, "function() { return this; }", "ptd",
+ bp_loc, line, col,
+ test_fn: async (cfo_result) =>
+ {
+ var ptd_id = cfo_result.Value?["result"]?["objectId"]?.Value<string>();
+
+ var cfo_args = JObject.FromObject(new
+ {
+ functionDeclaration = "function () { return 0; }",
+ objectId = ptd_id + "_invalid"
+ });
+
+ var res = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token);
+ Assert.True(res.IsErr);
+ });
+
+ [Theory]
+ [MemberData(nameof(NegativeTestsData), false)]
+ public async Task RunOnInvalidThirdSegmentOfObjectId(string eval_fn, string bp_loc, int line, int col, bool use_cfo)
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
+ await SetBreakpoint(bp_loc, line, col);
+
+ // callFunctionOn
+ var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);";
+ var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token);
+ var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE);
+
+ var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value<string>());
+ var ptd = GetAndAssertObjectWithName(frame_locals, "ptd");
+ var ptd_id = ptd["value"]["objectId"].Value<string>();
+
+ var cfo_args = JObject.FromObject(new
+ {
+ functionDeclaration = "function () { return 0; }",
+ objectId = ptd_id + "_invalid"
+ });
+
+ var res = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token);
+ Assert.True(res.IsErr);
+ });
+ }
+
+ [Theory]
+ [MemberData(nameof(NegativeTestsData), false)]
+ [MemberData(nameof(NegativeTestsData), true)]
+ public async Task InvalidPropertyGetters(string eval_fn, string bp_loc, int line, int col, bool use_cfo)
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ await SetBreakpoint(bp_loc, line, col);
+ ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
+
+ // callFunctionOn
+ var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);";
+ await SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }));
+ var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE);
+
+ var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value<string>());
+ var ptd = GetAndAssertObjectWithName(frame_locals, "ptd");
+ var ptd_id = ptd["value"]["objectId"].Value<string>();
+
+ var invalid_args = new object[] { "NonExistant", String.Empty, null, 12310 };
+ foreach (var invalid_arg in invalid_args)
+ {
+ var getter_res = await InvokeGetter(JObject.FromObject(new { value = new { objectId = ptd_id } }), invalid_arg);
+ AssertEqual("undefined", getter_res.Value["result"]?["type"]?.ToString(), $"Expected to get undefined result for non-existant accessor - {invalid_arg}");
+ }
+ });
+ }
+
+ [Theory]
+ [MemberData(nameof(NegativeTestsData), false)]
+ public async Task ReturnNullFromCFO(string eval_fn, string bp_loc, int line, int col, bool use_cfo) => await RunCallFunctionOn(
+ eval_fn, "function() { return this; }", "ptd",
+ bp_loc, line, col,
+ test_fn: async (result) =>
+ {
+ var is_js = bp_loc.EndsWith(".js");
+ var ptd = JObject.FromObject(new { value = new { objectId = result.Value?["result"]?["objectId"]?.Value<string>() } });
+
+ var null_value_json = JObject.Parse("{ 'type': 'object', 'subtype': 'null', 'value': null }");
+ foreach (var returnByValue in new bool?[] { null, false, true })
+ {
+ var res = await InvokeGetter(ptd, "StringField", returnByValue: returnByValue);
+ if (is_js)
+ {
+ // In js case, it doesn't know the className, so the result looks slightly different
+ Assert.True(
+ JObject.DeepEquals(res.Value["result"], null_value_json),
+ $"[StringField#returnByValue = {returnByValue}] Json didn't match. Actual: {res.Value["result"]} vs {null_value_json}");
+ }
+ else
+ {
+ await CheckValue(res.Value["result"], TString(null), "StringField");
+ }
+ }
+ });
+
+ /*
+ * 1. runs `Runtime.callFunctionOn` on the objectId,
+ * if @roundtrip == false, then
+ * -> calls @test_fn for that result (new objectId)
+ * else
+ * -> runs it again on the *result's* objectId.
+ * -> calls @test_fn on the *new* result objectId
+ *
+ * Returns: result of `Runtime.callFunctionOn`
+ */
+ async Task RunCallFunctionOn(string eval_fn, string fn_decl, string local_name, string bp_loc, int line, int col, int res_array_len = -1,
+ Func<Result, Task> test_fn = null, bool returnByValue = false, JArray fn_args = null, bool roundtrip = false)
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ await SetBreakpoint(bp_loc, line, col);
+
+ // callFunctionOn
+ var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);";
+ var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token);
+ var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE);
+
+ // Um for js we get "scriptId": "6"
+ // CheckLocation (bp_loc, line, col, ctx.scripts, pause_location ["callFrames"][0]["location"]);
+
+ // Check the object at the bp
+ var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value<string>());
+ var obj = GetAndAssertObjectWithName(frame_locals, local_name);
+ var obj_id = obj["value"]["objectId"].Value<string>();
+
+ var cfo_args = JObject.FromObject(new
+ {
+ functionDeclaration = fn_decl,
+ objectId = obj_id
+ });
+
+ if (fn_args != null)
+ cfo_args["arguments"] = fn_args;
+
+ if (returnByValue)
+ cfo_args["returnByValue"] = returnByValue;
+
+ // callFunctionOn
+ result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token);
+ await CheckCFOResult(result);
+
+ // If it wasn't `returnByValue`, then try to run a new function
+ // on that *returned* object
+ // This second function, just returns the object as-is, so the same
+ // test_fn is re-usable.
+ if (!returnByValue && roundtrip)
+ {
+ cfo_args = JObject.FromObject(new
+ {
+ functionDeclaration = "function () { return this; }",
+ objectId = result.Value["result"]["objectId"]?.Value<string>()
+ });
+
+ if (fn_args != null)
+ cfo_args["arguments"] = fn_args;
+
+ result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token);
+
+ await CheckCFOResult(result);
+ }
+
+ if (test_fn != null)
+ await test_fn(result);
+
+ return;
+
+ async Task CheckCFOResult(Result result)
+ {
+ if (returnByValue)
+ return;
+
+ if (res_array_len < 0)
+ await CheckValue(result.Value["result"], TObject("Object"), $"cfo-res");
+ else
+ await CheckValue(result.Value["result"], TArray("Array", res_array_len), $"cfo-res");
+ }
+ });
+ }
+ }
}
diff --git a/sdks/wasm/DebuggerTestSuite/DateTimeTests.cs b/sdks/wasm/DebuggerTestSuite/DateTimeTests.cs
index 1b2ff400da3..034b0613997 100644
--- a/sdks/wasm/DebuggerTestSuite/DateTimeTests.cs
+++ b/sdks/wasm/DebuggerTestSuite/DateTimeTests.cs
@@ -1,61 +1,67 @@
+// 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.Globalization;
using System.Threading.Tasks;
using Xunit;
-using System.Globalization;
namespace DebuggerTests
{
- public class DateTimeList : DebuggerTestBase {
-
- [Theory]
- [InlineData ("en-US")]
- [InlineData ("de-DE")]
- [InlineData ("ka-GE")]
- [InlineData ("hu-HU")]
-
+ public class DateTimeList : DebuggerTestBase
+ {
+
+ [Theory]
+ [InlineData("en-US")]
+
// Currently not passing tests. Issue #19743
// [InlineData ("ja-JP")]
// [InlineData ("es-ES")]
- public async Task CheckDateTimeLocale (string locale) {
- var insp = new Inspector ();
- var scripts = SubscribeToScripts(insp);
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- var debugger_test_loc = "dotnet://debugger-test.dll/debugger-datetime-test.cs";
-
- await SetBreakpointInMethod ("debugger-test", "DebuggerTests.DateTimeTest", "LocaleTest", 15);
-
- var pause_location = await EvaluateAndCheck (
- "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.DateTimeTest:LocaleTest',"
- + $"'{locale}'); }}, 1);",
- debugger_test_loc, 20, 3, "LocaleTest",
- locals_fn: async (locals) => {
- DateTimeFormatInfo dtfi = CultureInfo.GetCultureInfo(locale).DateTimeFormat;
- CultureInfo.CurrentCulture = new CultureInfo (locale, false);
- DateTime dt = new DateTime (2020, 1, 2, 3, 4, 5);
- string dt_str = dt.ToString();
-
- var fdtp = dtfi.FullDateTimePattern;
- var ldp = dtfi.LongDatePattern;
- var ltp = dtfi.LongTimePattern;
- var sdp = dtfi.ShortDatePattern;
- var stp = dtfi.ShortTimePattern;
-
- CheckString(locals, "fdtp", fdtp);
- CheckString(locals, "ldp", ldp);
- CheckString(locals, "ltp", ltp);
- CheckString(locals, "sdp", sdp);
- CheckString(locals, "stp", stp);
- await CheckDateTime(locals, "dt", dt);
- CheckString(locals, "dt_str", dt_str);
- }
- );
-
-
- });
- }
-
- }
-} \ No newline at end of file
+ //[InlineData ("de-DE")]
+ //[InlineData ("ka-GE")]
+ //[InlineData ("hu-HU")]
+ public async Task CheckDateTimeLocale(string locale)
+ {
+ var insp = new Inspector();
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ var debugger_test_loc = "dotnet://debugger-test.dll/debugger-datetime-test.cs";
+
+ await SetBreakpointInMethod("debugger-test", "DebuggerTests.DateTimeTest", "LocaleTest", 15);
+
+ var pause_location = await EvaluateAndCheck(
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.DateTimeTest:LocaleTest'," +
+ $"'{locale}'); }}, 1);",
+ debugger_test_loc, 25, 12, "LocaleTest",
+ locals_fn: async (locals) =>
+ {
+ DateTimeFormatInfo dtfi = CultureInfo.GetCultureInfo(locale).DateTimeFormat;
+ CultureInfo.CurrentCulture = new CultureInfo(locale, false);
+ DateTime dt = new DateTime(2020, 1, 2, 3, 4, 5);
+ string dt_str = dt.ToString();
+
+ var fdtp = dtfi.FullDateTimePattern;
+ var ldp = dtfi.LongDatePattern;
+ var ltp = dtfi.LongTimePattern;
+ var sdp = dtfi.ShortDatePattern;
+ var stp = dtfi.ShortTimePattern;
+
+ CheckString(locals, "fdtp", fdtp);
+ CheckString(locals, "ldp", ldp);
+ CheckString(locals, "ltp", ltp);
+ CheckString(locals, "sdp", sdp);
+ CheckString(locals, "stp", stp);
+ await CheckDateTime(locals, "dt", dt);
+ CheckString(locals, "dt_str", dt_str);
+ }
+ );
+
+ });
+ }
+
+ }
+}
diff --git a/sdks/wasm/DebuggerTestSuite/DelegateTests.cs b/sdks/wasm/DebuggerTestSuite/DelegateTests.cs
index 2b4c514cdee..2fc0fb52ce9 100644
--- a/sdks/wasm/DebuggerTestSuite/DelegateTests.cs
+++ b/sdks/wasm/DebuggerTestSuite/DelegateTests.cs
@@ -1,284 +1,306 @@
+// 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.Linq;
using System.Threading.Tasks;
+using Microsoft.WebAssembly.Diagnostics;
using Newtonsoft.Json.Linq;
using Xunit;
-using WebAssembly.Net.Debugging;
namespace DebuggerTests
{
- public class DelegateTests : DebuggerTestBase {
-
- [Theory]
- [InlineData (0, 45, 2, "DelegatesTest", false)]
- [InlineData (0, 45, 2, "DelegatesTest", true)]
- [InlineData (2, 90, 2, "InnerMethod2", false)]
- [InlineData (2, 90, 2, "InnerMethod2", true)]
- public async Task InspectLocalsWithDelegatesAtBreakpointSite (int frame, int line, int col, string method_name, bool use_cfo) =>
- await CheckInspectLocalsAtBreakpointSite (
- "dotnet://debugger-test.dll/debugger-test.cs", line, col, method_name,
- "window.setTimeout(function() { invoke_delegates_test (); }, 1);",
- use_cfo: use_cfo,
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties(pause_location["callFrames"][frame]["callFrameId"].Value<string>());
-
- await CheckProps (locals, new {
- fn_func = TDelegate ("System.Func<Math, bool>", "bool <DelegatesTest>|(Math)"),
- fn_func_null = TObject ("System.Func<Math, bool>", is_null: true),
- fn_func_arr = TArray ("System.Func<Math, bool>[]", 1),
- fn_del = TDelegate ("Math.IsMathNull", "bool IsMathNullDelegateTarget (Math)"),
- fn_del_null = TObject ("Math.IsMathNull", is_null: true),
- fn_del_arr = TArray ("Math.IsMathNull[]", 1),
-
- // Unused locals
- fn_func_unused = TDelegate ("System.Func<Math, bool>", "bool <DelegatesTest>|(Math)"),
- fn_func_null_unused = TObject ("System.Func<Math, bool>", is_null: true),
- fn_func_arr_unused = TArray ("System.Func<Math, bool>[]", 1),
-
- fn_del_unused = TDelegate ("Math.IsMathNull", "bool IsMathNullDelegateTarget (Math)"),
- fn_del_null_unused = TObject ("Math.IsMathNull", is_null: true),
- fn_del_arr_unused = TArray ("Math.IsMathNull[]", 1),
-
- res = TBool (false),
- m_obj = TObject ("Math")
- }, "locals");
-
- await CompareObjectPropertiesFor (locals, "fn_func_arr", new [] {
- TDelegate (
- "System.Func<Math, bool>",
- "bool <DelegatesTest>|(Math)")
- }, "locals#fn_func_arr");
-
- await CompareObjectPropertiesFor (locals, "fn_del_arr", new [] {
- TDelegate (
- "Math.IsMathNull",
- "bool IsMathNullDelegateTarget (Math)")
- }, "locals#fn_del_arr");
-
- await CompareObjectPropertiesFor (locals, "fn_func_arr_unused", new [] {
- TDelegate (
- "System.Func<Math, bool>",
- "bool <DelegatesTest>|(Math)")
- }, "locals#fn_func_arr_unused");
-
- await CompareObjectPropertiesFor (locals, "fn_del_arr_unused", new [] {
- TDelegate (
- "Math.IsMathNull",
- "bool IsMathNullDelegateTarget (Math)")
- }, "locals#fn_del_arr_unused");
- }
- );
-
- [Theory]
- [InlineData (0, 190, 2, "DelegatesSignatureTest", false)]
- [InlineData (0, 190, 2, "DelegatesSignatureTest", true)]
- [InlineData (2, 90, 2, "InnerMethod2", false)]
- [InlineData (2, 90, 2, "InnerMethod2", true)]
- public async Task InspectDelegateSignaturesWithFunc (int frame, int line, int col, string bp_method, bool use_cfo)
- => await CheckInspectLocalsAtBreakpointSite (
- "dotnet://debugger-test.dll/debugger-test.cs",
- line, col,
- bp_method,
- "window.setTimeout (function () { invoke_static_method ('[debugger-test] Math:DelegatesSignatureTest'); }, 1)",
- use_cfo: use_cfo,
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties (pause_location ["callFrames"][frame]["callFrameId"].Value<string>());
-
- await CheckProps (locals, new {
- fn_func = TDelegate ("System.Func<Math, Math.GenericStruct<Math.GenericStruct<int[]>>, Math.GenericStruct<bool[]>>",
- "Math.GenericStruct<bool[]> <DelegatesSignatureTest>|(Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
-
- fn_func_del = TDelegate ("System.Func<Math, Math.GenericStruct<Math.GenericStruct<int[]>>, Math.GenericStruct<bool[]>>",
- "Math.GenericStruct<bool[]> DelegateTargetForSignatureTest (Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
-
- fn_func_null = TObject ("System.Func<Math, Math.GenericStruct<Math.GenericStruct<int[]>>, Math.GenericStruct<bool[]>>", is_null: true),
- fn_func_only_ret= TDelegate ("System.Func<bool>", "bool <DelegatesSignatureTest>|()"),
- fn_func_arr = TArray ("System.Func<Math, Math.GenericStruct<Math.GenericStruct<int[]>>, Math.GenericStruct<bool[]>>[]", 1),
-
- fn_del = TDelegate ("Math.DelegateForSignatureTest",
- "Math.GenericStruct<bool[]> DelegateTargetForSignatureTest (Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
-
- fn_del_l = TDelegate ("Math.DelegateForSignatureTest",
- "Math.GenericStruct<bool[]> <DelegatesSignatureTest>|(Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
-
- fn_del_null = TObject ("Math.DelegateForSignatureTest", is_null: true),
- fn_del_arr = TArray ("Math.DelegateForSignatureTest[]", 2),
- m_obj = TObject ("Math"),
- gs_gs = TValueType ("Math.GenericStruct<Math.GenericStruct<int[]>>"),
- fn_void_del = TDelegate ("Math.DelegateWithVoidReturn",
- "void DelegateTargetWithVoidReturn (Math.GenericStruct<int[]>)"),
-
- fn_void_del_arr = TArray ("Math.DelegateWithVoidReturn[]", 1),
- fn_void_del_null= TObject ("Math.DelegateWithVoidReturn", is_null: true),
- gs = TValueType ("Math.GenericStruct<int[]>"),
- rets = TArray ("Math.GenericStruct<bool[]>[]", 6)
- }, "locals");
-
- await CompareObjectPropertiesFor (locals, "fn_func_arr", new [] {
- TDelegate (
- "System.Func<Math, Math.GenericStruct<Math.GenericStruct<int[]>>, Math.GenericStruct<bool[]>>",
- "Math.GenericStruct<bool[]> <DelegatesSignatureTest>|(Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
- }, "locals#fn_func_arr");
-
- await CompareObjectPropertiesFor (locals, "fn_del_arr", new [] {
- TDelegate (
- "Math.DelegateForSignatureTest",
- "Math.GenericStruct<bool[]> DelegateTargetForSignatureTest (Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
- TDelegate (
- "Math.DelegateForSignatureTest",
- "Math.GenericStruct<bool[]> <DelegatesSignatureTest>|(Math,Math.GenericStruct<Math.GenericStruct<int[]>>)")
- }, "locals#fn_del_arr");
-
- await CompareObjectPropertiesFor (locals, "fn_void_del_arr", new [] {
- TDelegate (
- "Math.DelegateWithVoidReturn",
- "void DelegateTargetWithVoidReturn (Math.GenericStruct<int[]>)")
- }, "locals#fn_void_del_arr");
- });
-
- [Theory]
- [InlineData (0, 211, 2, "ActionTSignatureTest", false)]
- [InlineData (0, 211, 2, "ActionTSignatureTest", true)]
- [InlineData (2, 90, 2, "InnerMethod2", false)]
- [InlineData (2, 90, 2, "InnerMethod2", true)]
- public async Task ActionTSignatureTest (int frame, int line, int col, string bp_method, bool use_cfo)
- => await CheckInspectLocalsAtBreakpointSite (
- "dotnet://debugger-test.dll/debugger-test.cs", line, col,
- bp_method,
- "window.setTimeout (function () { invoke_static_method ('[debugger-test] Math:ActionTSignatureTest'); }, 1)",
- use_cfo: use_cfo,
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties (pause_location ["callFrames"][frame]["callFrameId"].Value<string>());
-
- await CheckProps (locals, new
- {
- fn_action = TDelegate ("System.Action<Math.GenericStruct<int[]>>",
- "void <ActionTSignatureTest>|(Math.GenericStruct<int[]>)"),
- fn_action_del = TDelegate ("System.Action<Math.GenericStruct<int[]>>",
- "void DelegateTargetWithVoidReturn (Math.GenericStruct<int[]>)"),
- fn_action_bare = TDelegate ("System.Action",
- "void|()"),
-
- fn_action_null = TObject ("System.Action<Math.GenericStruct<int[]>>", is_null: true),
-
- fn_action_arr = TArray ("System.Action<Math.GenericStruct<int[]>>[]", 3),
-
- gs = TValueType ("Math.GenericStruct<int[]>"),
- }, "locals");
-
- await CompareObjectPropertiesFor (locals, "fn_action_arr", new [] {
- TDelegate (
- "System.Action<Math.GenericStruct<int[]>>",
- "void <ActionTSignatureTest>|(Math.GenericStruct<int[]>)"),
- TDelegate (
- "System.Action<Math.GenericStruct<int[]>>",
- "void DelegateTargetWithVoidReturn (Math.GenericStruct<int[]>)"),
- TObject ("System.Action<Math.GenericStruct<int[]>>", is_null: true)
- }, "locals#fn_action_arr");
- });
-
- [Theory]
- [InlineData (0, 228, 2, "NestedDelegatesTest", false)]
- [InlineData (0, 228, 2, "NestedDelegatesTest", true)]
- [InlineData (2, 90, 2, "InnerMethod2", false)]
- [InlineData (2, 90, 2, "InnerMethod2", true)]
- public async Task NestedDelegatesTest (int frame, int line, int col, string bp_method, bool use_cfo)
- => await CheckInspectLocalsAtBreakpointSite (
- "dotnet://debugger-test.dll/debugger-test.cs", line, col,
- bp_method,
- "window.setTimeout (function () { invoke_static_method ('[debugger-test] Math:NestedDelegatesTest'); }, 1)",
- use_cfo: use_cfo,
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties (pause_location ["callFrames"][frame]["callFrameId"].Value<string>());
-
- await CheckProps (locals, new {
- fn_func = TDelegate ("System.Func<System.Func<int, bool>, bool>",
- "bool <NestedDelegatesTest>|(Func<int, bool>)"),
- fn_func_null = TObject ("System.Func<System.Func<int, bool>, bool>", is_null: true),
- fn_func_arr = TArray ("System.Func<System.Func<int, bool>, bool>[]", 1),
- fn_del_arr = TArray ("System.Func<System.Func<int, bool>, bool>[]", 1),
-
- m_obj = TObject ("Math"),
- fn_del_null = TObject ("System.Func<System.Func<int, bool>, bool>", is_null: true),
- fs = TDelegate ("System.Func<int, bool>",
- "bool <NestedDelegatesTest>|(int)")
- }, "locals");
-
- await CompareObjectPropertiesFor (locals, "fn_func_arr", new [] {
- TDelegate (
- "System.Func<System.Func<int, bool>, bool>",
- "bool <NestedDelegatesTest>|(System.Func<int, bool>)")
- }, "locals#fn_func_arr");
-
- await CompareObjectPropertiesFor (locals, "fn_del_arr", new [] {
- TDelegate (
- "System.Func<System.Func<int, bool>, bool>",
- "bool DelegateTargetForNestedFunc (Func<int, bool>)")
- }, "locals#fn_del_arr");
- });
-
- [Theory]
- [InlineData (0, 247, 2, "MethodWithDelegateArgs", false)]
- [InlineData (0, 247, 2, "MethodWithDelegateArgs", true)]
- [InlineData (2, 90, 2, "InnerMethod2", false)]
- [InlineData (2, 90, 2, "InnerMethod2", true)]
- public async Task DelegatesAsMethodArgsTest (int frame, int line, int col, string bp_method, bool use_cfo)
- => await CheckInspectLocalsAtBreakpointSite (
- "dotnet://debugger-test.dll/debugger-test.cs", line, col,
- bp_method,
- "window.setTimeout (function () { invoke_static_method ('[debugger-test] Math:DelegatesAsMethodArgsTest'); }, 1)",
- use_cfo: use_cfo,
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties (pause_location ["callFrames"][frame]["callFrameId"].Value<string>());
-
- await CheckProps (locals, new {
- @this = TObject ("Math"),
- dst_arr = TArray ("Math.DelegateForSignatureTest[]", 2),
- fn_func = TDelegate ("System.Func<char[], bool>",
- "bool <DelegatesAsMethodArgsTest>|(char[])"),
- fn_action = TDelegate ("System.Action<Math.GenericStruct<int>[]>",
- "void <DelegatesAsMethodArgsTest>|(Math.GenericStruct<int>[])")
- }, "locals");
-
- await CompareObjectPropertiesFor (locals, "dst_arr", new [] {
- TDelegate ("Math.DelegateForSignatureTest",
- "Math.GenericStruct<bool[]> DelegateTargetForSignatureTest (Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
- TDelegate ("Math.DelegateForSignatureTest",
- "Math.GenericStruct<bool[]> <DelegatesAsMethodArgsTest>|(Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
- }, "locals#dst_arr");
- });
-
- [Theory]
- [InlineData (false)]
- [InlineData (true)]
- public async Task MethodWithDelegatesAsyncTest (bool use_cfo)
- => await CheckInspectLocalsAtBreakpointSite (
- "dotnet://debugger-test.dll/debugger-test.cs", 265, 2,
- "MoveNext", //"DelegatesAsMethodArgsTestAsync"
- "window.setTimeout (function () { invoke_static_method_async ('[debugger-test] Math:MethodWithDelegatesAsyncTest'); }, 1)",
- use_cfo: use_cfo,
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
-
- await CheckProps (locals, new {
- @this = TObject ("Math"),
- _dst_arr = TArray ("Math.DelegateForSignatureTest[]", 2),
- _fn_func = TDelegate ("System.Func<char[], bool>",
- "bool <MethodWithDelegatesAsync>|(char[])"),
- _fn_action = TDelegate ("System.Action<Math.GenericStruct<int>[]>",
- "void <MethodWithDelegatesAsync>|(Math.GenericStruct<int>[])")
- }, "locals");
-
- await CompareObjectPropertiesFor (locals, "_dst_arr", new [] {
- TDelegate (
- "Math.DelegateForSignatureTest",
- "Math.GenericStruct<bool[]> DelegateTargetForSignatureTest (Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
- TDelegate (
- "Math.DelegateForSignatureTest",
- "Math.GenericStruct<bool[]> <MethodWithDelegatesAsync>|(Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
- }, "locals#dst_arr");
- });
- }
+ public class DelegateTests : DebuggerTestBase
+ {
+
+ [Theory]
+ [InlineData(0, 53, 8, "DelegatesTest", false)]
+ [InlineData(0, 53, 8, "DelegatesTest", true)]
+ [InlineData(2, 99, 8, "InnerMethod2", false)]
+ [InlineData(2, 99, 8, "InnerMethod2", true)]
+ public async Task InspectLocalsWithDelegatesAtBreakpointSite(int frame, int line, int col, string method_name, bool use_cfo) =>
+ await CheckInspectLocalsAtBreakpointSite(
+ "dotnet://debugger-test.dll/debugger-test.cs", line, col, method_name,
+ "window.setTimeout(function() { invoke_delegates_test (); }, 1);",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var locals = await GetProperties(pause_location["callFrames"][frame]["callFrameId"].Value<string>());
+
+ await CheckProps(locals, new
+ {
+ fn_func = TDelegate("System.Func<Math, bool>", "bool <DelegatesTest>|(Math)"),
+ fn_func_null = TObject("System.Func<Math, bool>", is_null: true),
+ fn_func_arr = TArray("System.Func<Math, bool>[]", 1),
+ fn_del = TDelegate("Math.IsMathNull", "bool IsMathNullDelegateTarget (Math)"),
+ fn_del_null = TObject("Math.IsMathNull", is_null: true),
+ fn_del_arr = TArray("Math.IsMathNull[]", 1),
+
+ // Unused locals
+ fn_func_unused = TDelegate("System.Func<Math, bool>", "bool <DelegatesTest>|(Math)"),
+ fn_func_null_unused = TObject("System.Func<Math, bool>", is_null: true),
+ fn_func_arr_unused = TArray("System.Func<Math, bool>[]", 1),
+
+ fn_del_unused = TDelegate("Math.IsMathNull", "bool IsMathNullDelegateTarget (Math)"),
+ fn_del_null_unused = TObject("Math.IsMathNull", is_null: true),
+ fn_del_arr_unused = TArray("Math.IsMathNull[]", 1),
+
+ res = TBool(false),
+ m_obj = TObject("Math")
+ }, "locals");
+
+ await CompareObjectPropertiesFor(locals, "fn_func_arr", new[]
+ {
+ TDelegate(
+ "System.Func<Math, bool>",
+ "bool <DelegatesTest>|(Math)")
+ }, "locals#fn_func_arr");
+
+ await CompareObjectPropertiesFor(locals, "fn_del_arr", new[]
+ {
+ TDelegate(
+ "Math.IsMathNull",
+ "bool IsMathNullDelegateTarget (Math)")
+ }, "locals#fn_del_arr");
+
+ await CompareObjectPropertiesFor(locals, "fn_func_arr_unused", new[]
+ {
+ TDelegate(
+ "System.Func<Math, bool>",
+ "bool <DelegatesTest>|(Math)")
+ }, "locals#fn_func_arr_unused");
+
+ await CompareObjectPropertiesFor(locals, "fn_del_arr_unused", new[]
+ {
+ TDelegate(
+ "Math.IsMathNull",
+ "bool IsMathNullDelegateTarget (Math)")
+ }, "locals#fn_del_arr_unused");
+ }
+ );
+
+ [Theory]
+ [InlineData(0, 202, 8, "DelegatesSignatureTest", false)]
+ [InlineData(0, 202, 8, "DelegatesSignatureTest", true)]
+ [InlineData(2, 99, 8, "InnerMethod2", false)]
+ [InlineData(2, 99, 8, "InnerMethod2", true)]
+ public async Task InspectDelegateSignaturesWithFunc(int frame, int line, int col, string bp_method, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
+ "dotnet://debugger-test.dll/debugger-test.cs",
+ line, col,
+ bp_method,
+ "window.setTimeout (function () { invoke_static_method ('[debugger-test] Math:DelegatesSignatureTest'); }, 1)",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var locals = await GetProperties(pause_location["callFrames"][frame]["callFrameId"].Value<string>());
+
+ await CheckProps(locals, new
+ {
+ fn_func = TDelegate("System.Func<Math, Math.GenericStruct<Math.GenericStruct<int[]>>, Math.GenericStruct<bool[]>>",
+ "Math.GenericStruct<bool[]> <DelegatesSignatureTest>|(Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
+
+ fn_func_del = TDelegate("System.Func<Math, Math.GenericStruct<Math.GenericStruct<int[]>>, Math.GenericStruct<bool[]>>",
+ "Math.GenericStruct<bool[]> DelegateTargetForSignatureTest (Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
+
+ fn_func_null = TObject("System.Func<Math, Math.GenericStruct<Math.GenericStruct<int[]>>, Math.GenericStruct<bool[]>>", is_null: true),
+ fn_func_only_ret = TDelegate("System.Func<bool>", "bool <DelegatesSignatureTest>|()"),
+ fn_func_arr = TArray("System.Func<Math, Math.GenericStruct<Math.GenericStruct<int[]>>, Math.GenericStruct<bool[]>>[]", 1),
+
+ fn_del = TDelegate("Math.DelegateForSignatureTest",
+ "Math.GenericStruct<bool[]> DelegateTargetForSignatureTest (Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
+
+ fn_del_l = TDelegate("Math.DelegateForSignatureTest",
+ "Math.GenericStruct<bool[]> <DelegatesSignatureTest>|(Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
+
+ fn_del_null = TObject("Math.DelegateForSignatureTest", is_null: true),
+ fn_del_arr = TArray("Math.DelegateForSignatureTest[]", 2),
+ m_obj = TObject("Math"),
+ gs_gs = TValueType("Math.GenericStruct<Math.GenericStruct<int[]>>"),
+ fn_void_del = TDelegate("Math.DelegateWithVoidReturn",
+ "void DelegateTargetWithVoidReturn (Math.GenericStruct<int[]>)"),
+
+ fn_void_del_arr = TArray("Math.DelegateWithVoidReturn[]", 1),
+ fn_void_del_null = TObject("Math.DelegateWithVoidReturn", is_null: true),
+ gs = TValueType("Math.GenericStruct<int[]>"),
+ rets = TArray("Math.GenericStruct<bool[]>[]", 6)
+ }, "locals");
+
+ await CompareObjectPropertiesFor(locals, "fn_func_arr", new[]
+ {
+ TDelegate(
+ "System.Func<Math, Math.GenericStruct<Math.GenericStruct<int[]>>, Math.GenericStruct<bool[]>>",
+ "Math.GenericStruct<bool[]> <DelegatesSignatureTest>|(Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
+ }, "locals#fn_func_arr");
+
+ await CompareObjectPropertiesFor(locals, "fn_del_arr", new[]
+ {
+ TDelegate(
+ "Math.DelegateForSignatureTest",
+ "Math.GenericStruct<bool[]> DelegateTargetForSignatureTest (Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
+ TDelegate(
+ "Math.DelegateForSignatureTest",
+ "Math.GenericStruct<bool[]> <DelegatesSignatureTest>|(Math,Math.GenericStruct<Math.GenericStruct<int[]>>)")
+ }, "locals#fn_del_arr");
+
+ await CompareObjectPropertiesFor(locals, "fn_void_del_arr", new[]
+ {
+ TDelegate(
+ "Math.DelegateWithVoidReturn",
+ "void DelegateTargetWithVoidReturn (Math.GenericStruct<int[]>)")
+ }, "locals#fn_void_del_arr");
+ });
+
+ [Theory]
+ [InlineData(0, 224, 8, "ActionTSignatureTest", false)]
+ [InlineData(0, 224, 8, "ActionTSignatureTest", true)]
+ [InlineData(2, 99, 8, "InnerMethod2", false)]
+ [InlineData(2, 99, 8, "InnerMethod2", true)]
+ public async Task ActionTSignatureTest(int frame, int line, int col, string bp_method, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
+ "dotnet://debugger-test.dll/debugger-test.cs", line, col,
+ bp_method,
+ "window.setTimeout (function () { invoke_static_method ('[debugger-test] Math:ActionTSignatureTest'); }, 1)",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var locals = await GetProperties(pause_location["callFrames"][frame]["callFrameId"].Value<string>());
+
+ await CheckProps(locals, new
+ {
+ fn_action = TDelegate("System.Action<Math.GenericStruct<int[]>>",
+ "void <ActionTSignatureTest>|(Math.GenericStruct<int[]>)"),
+ fn_action_del = TDelegate("System.Action<Math.GenericStruct<int[]>>",
+ "void DelegateTargetWithVoidReturn (Math.GenericStruct<int[]>)"),
+ fn_action_bare = TDelegate("System.Action",
+ "void|()"),
+
+ fn_action_null = TObject("System.Action<Math.GenericStruct<int[]>>", is_null: true),
+
+ fn_action_arr = TArray("System.Action<Math.GenericStruct<int[]>>[]", 3),
+
+ gs = TValueType("Math.GenericStruct<int[]>"),
+ }, "locals");
+
+ await CompareObjectPropertiesFor(locals, "fn_action_arr", new[]
+ {
+ TDelegate(
+ "System.Action<Math.GenericStruct<int[]>>",
+ "void <ActionTSignatureTest>|(Math.GenericStruct<int[]>)"),
+ TDelegate(
+ "System.Action<Math.GenericStruct<int[]>>",
+ "void DelegateTargetWithVoidReturn (Math.GenericStruct<int[]>)"),
+ TObject("System.Action<Math.GenericStruct<int[]>>", is_null : true)
+ }, "locals#fn_action_arr");
+ });
+
+ [Theory]
+ [InlineData(0, 242, 8, "NestedDelegatesTest", false)]
+ [InlineData(0, 242, 8, "NestedDelegatesTest", true)]
+ [InlineData(2, 99, 8, "InnerMethod2", false)]
+ [InlineData(2, 99, 8, "InnerMethod2", true)]
+ public async Task NestedDelegatesTest(int frame, int line, int col, string bp_method, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
+ "dotnet://debugger-test.dll/debugger-test.cs", line, col,
+ bp_method,
+ "window.setTimeout (function () { invoke_static_method ('[debugger-test] Math:NestedDelegatesTest'); }, 1)",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var locals = await GetProperties(pause_location["callFrames"][frame]["callFrameId"].Value<string>());
+
+ await CheckProps(locals, new
+ {
+ fn_func = TDelegate("System.Func<System.Func<int, bool>, bool>",
+ "bool <NestedDelegatesTest>|(Func<int, bool>)"),
+ fn_func_null = TObject("System.Func<System.Func<int, bool>, bool>", is_null: true),
+ fn_func_arr = TArray("System.Func<System.Func<int, bool>, bool>[]", 1),
+ fn_del_arr = TArray("System.Func<System.Func<int, bool>, bool>[]", 1),
+
+ m_obj = TObject("Math"),
+ fn_del_null = TObject("System.Func<System.Func<int, bool>, bool>", is_null: true),
+ fs = TDelegate("System.Func<int, bool>",
+ "bool <NestedDelegatesTest>|(int)")
+ }, "locals");
+
+ await CompareObjectPropertiesFor(locals, "fn_func_arr", new[]
+ {
+ TDelegate(
+ "System.Func<System.Func<int, bool>, bool>",
+ "bool <NestedDelegatesTest>|(System.Func<int, bool>)")
+ }, "locals#fn_func_arr");
+
+ await CompareObjectPropertiesFor(locals, "fn_del_arr", new[]
+ {
+ TDelegate(
+ "System.Func<System.Func<int, bool>, bool>",
+ "bool DelegateTargetForNestedFunc (Func<int, bool>)")
+ }, "locals#fn_del_arr");
+ });
+
+ [Theory]
+ [InlineData(0, 262, 8, "MethodWithDelegateArgs", false)]
+ [InlineData(0, 262, 8, "MethodWithDelegateArgs", true)]
+ [InlineData(2, 99, 8, "InnerMethod2", false)]
+ [InlineData(2, 99, 8, "InnerMethod2", true)]
+ public async Task DelegatesAsMethodArgsTest(int frame, int line, int col, string bp_method, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
+ "dotnet://debugger-test.dll/debugger-test.cs", line, col,
+ bp_method,
+ "window.setTimeout (function () { invoke_static_method ('[debugger-test] Math:DelegatesAsMethodArgsTest'); }, 1)",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var locals = await GetProperties(pause_location["callFrames"][frame]["callFrameId"].Value<string>());
+
+ await CheckProps(locals, new
+ {
+ @this = TObject("Math"),
+ dst_arr = TArray("Math.DelegateForSignatureTest[]", 2),
+ fn_func = TDelegate("System.Func<char[], bool>",
+ "bool <DelegatesAsMethodArgsTest>|(char[])"),
+ fn_action = TDelegate("System.Action<Math.GenericStruct<int>[]>",
+ "void <DelegatesAsMethodArgsTest>|(Math.GenericStruct<int>[])")
+ }, "locals");
+
+ await CompareObjectPropertiesFor(locals, "dst_arr", new[]
+ {
+ TDelegate("Math.DelegateForSignatureTest",
+ "Math.GenericStruct<bool[]> DelegateTargetForSignatureTest (Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
+ TDelegate("Math.DelegateForSignatureTest",
+ "Math.GenericStruct<bool[]> <DelegatesAsMethodArgsTest>|(Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
+ }, "locals#dst_arr");
+ });
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task MethodWithDelegatesAsyncTest(bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
+ "dotnet://debugger-test.dll/debugger-test.cs", 281, 8,
+ "MoveNext", //"DelegatesAsMethodArgsTestAsync"
+ "window.setTimeout (function () { invoke_static_method_async ('[debugger-test] Math:MethodWithDelegatesAsyncTest'); }, 1)",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+
+ await CheckProps(locals, new
+ {
+ @this = TObject("Math"),
+ _dst_arr = TArray("Math.DelegateForSignatureTest[]", 2),
+ _fn_func = TDelegate("System.Func<char[], bool>",
+ "bool <MethodWithDelegatesAsync>|(char[])"),
+ _fn_action = TDelegate("System.Action<Math.GenericStruct<int>[]>",
+ "void <MethodWithDelegatesAsync>|(Math.GenericStruct<int>[])")
+ }, "locals");
+
+ await CompareObjectPropertiesFor(locals, "_dst_arr", new[]
+ {
+ TDelegate(
+ "Math.DelegateForSignatureTest",
+ "Math.GenericStruct<bool[]> DelegateTargetForSignatureTest (Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
+ TDelegate(
+ "Math.DelegateForSignatureTest",
+ "Math.GenericStruct<bool[]> <MethodWithDelegatesAsync>|(Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
+ }, "locals#dst_arr");
+ });
+ }
}
diff --git a/sdks/wasm/DebuggerTestSuite/DevToolsClient.cs b/sdks/wasm/DebuggerTestSuite/DevToolsClient.cs
index 4fb0fd0f866..5caae92eee1 100644
--- a/sdks/wasm/DebuggerTestSuite/DevToolsClient.cs
+++ b/sdks/wasm/DebuggerTestSuite/DevToolsClient.cs
@@ -1,142 +1,167 @@
-using System;
-using System.Threading.Tasks;
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
-using System.Net.WebSockets;
-using System.Threading;
+using System;
+using System.Collections.Generic;
using System.IO;
+using System.Net.WebSockets;
using System.Text;
-using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
-namespace WebAssembly.Net.Debugging {
- internal class DevToolsClient: IDisposable {
- ClientWebSocket socket;
- List<Task> pending_ops = new List<Task> ();
- TaskCompletionSource<bool> side_exit = new TaskCompletionSource<bool> ();
- List<byte []> pending_writes = new List<byte []> ();
- Task current_write;
- readonly ILogger logger;
-
- public DevToolsClient (ILogger logger) {
- this.logger = logger;
- }
-
- ~DevToolsClient() {
- Dispose(false);
- }
-
- public void Dispose() {
- Dispose(true);
- }
-
- public async Task Close (CancellationToken cancellationToken)
- {
- if (socket.State == WebSocketState.Open)
- await socket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, "Closing", cancellationToken);
- }
-
- protected virtual void Dispose (bool disposing) {
- if (disposing)
- socket.Dispose ();
- }
-
- Task Pump (Task task, CancellationToken token)
- {
- if (task != current_write)
- return null;
- current_write = null;
-
- pending_writes.RemoveAt (0);
-
- if (pending_writes.Count > 0) {
- current_write = socket.SendAsync (new ArraySegment<byte> (pending_writes [0]), WebSocketMessageType.Text, true, token);
- return current_write;
- }
- return null;
- }
-
- async Task<string> ReadOne (CancellationToken token)
- {
- byte [] buff = new byte [4000];
- var mem = new MemoryStream ();
- while (true) {
- var result = await this.socket.ReceiveAsync (new ArraySegment<byte> (buff), token);
- if (result.MessageType == WebSocketMessageType.Close) {
- return null;
- }
-
- if (result.EndOfMessage) {
- mem.Write (buff, 0, result.Count);
- return Encoding.UTF8.GetString (mem.GetBuffer (), 0, (int)mem.Length);
- } else {
- mem.Write (buff, 0, result.Count);
- }
- }
- }
-
- protected void Send (byte [] bytes, CancellationToken token)
- {
- pending_writes.Add (bytes);
- if (pending_writes.Count == 1) {
- if (current_write != null)
- throw new Exception ("Internal state is bad. current_write must be null if there are no pending writes");
-
- current_write = socket.SendAsync (new ArraySegment<byte> (bytes), WebSocketMessageType.Text, true, token);
- pending_ops.Add (current_write);
- }
- }
-
- async Task MarkCompleteAfterward (Func<CancellationToken, Task> send, CancellationToken token)
- {
- try {
- await send(token);
- side_exit.SetResult (true);
- } catch (Exception e) {
- side_exit.SetException (e);
- }
- }
-
- protected async Task<bool> ConnectWithMainLoops(
- Uri uri,
- Func<string, CancellationToken, Task> receive,
- Func<CancellationToken, Task> send,
- CancellationToken token) {
-
- logger.LogDebug ("connecting to {0}", uri);
- this.socket = new ClientWebSocket ();
- this.socket.Options.KeepAliveInterval = Timeout.InfiniteTimeSpan;
-
- await this.socket.ConnectAsync (uri, token);
- pending_ops.Add (ReadOne (token));
- pending_ops.Add (side_exit.Task);
- pending_ops.Add (MarkCompleteAfterward (send, token));
-
- while (!token.IsCancellationRequested) {
- var task = await Task.WhenAny (pending_ops);
- if (task == pending_ops [0]) { //pending_ops[0] is for message reading
- var msg = ((Task<string>)task).Result;
- pending_ops [0] = ReadOne (token);
- Task tsk = receive (msg, token);
- if (tsk != null)
- pending_ops.Add (tsk);
- } else if (task == pending_ops [1]) {
- var res = ((Task<bool>)task).Result;
- //it might not throw if exiting successfull
- return res;
- } else { //must be a background task
- pending_ops.Remove (task);
- var tsk = Pump (task, token);
- if (tsk != null)
- pending_ops.Add (tsk);
- }
- }
-
- return false;
- }
-
- protected virtual void Log (string priority, string msg)
- {
- //
- }
- }
+namespace Microsoft.WebAssembly.Diagnostics
+{
+ internal class DevToolsClient : IDisposable
+ {
+ ClientWebSocket socket;
+ List<Task> pending_ops = new List<Task>();
+ TaskCompletionSource<bool> side_exit = new TaskCompletionSource<bool>();
+ List<byte[]> pending_writes = new List<byte[]>();
+ Task current_write;
+ readonly ILogger logger;
+
+ public DevToolsClient(ILogger logger)
+ {
+ this.logger = logger;
+ }
+
+ ~DevToolsClient()
+ {
+ Dispose(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ public async Task Close(CancellationToken cancellationToken)
+ {
+ if (socket.State == WebSocketState.Open)
+ await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Closing", cancellationToken);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ socket.Dispose();
+ }
+
+ Task Pump(Task task, CancellationToken token)
+ {
+ if (task != current_write)
+ return null;
+ current_write = null;
+
+ pending_writes.RemoveAt(0);
+
+ if (pending_writes.Count > 0)
+ {
+ current_write = socket.SendAsync(new ArraySegment<byte>(pending_writes[0]), WebSocketMessageType.Text, true, token);
+ return current_write;
+ }
+ return null;
+ }
+
+ async Task<string> ReadOne(CancellationToken token)
+ {
+ byte[] buff = new byte[4000];
+ var mem = new MemoryStream();
+ while (true)
+ {
+ var result = await this.socket.ReceiveAsync(new ArraySegment<byte>(buff), token);
+ if (result.MessageType == WebSocketMessageType.Close)
+ {
+ return null;
+ }
+
+ if (result.EndOfMessage)
+ {
+ mem.Write(buff, 0, result.Count);
+ return Encoding.UTF8.GetString(mem.GetBuffer(), 0, (int)mem.Length);
+ }
+ else
+ {
+ mem.Write(buff, 0, result.Count);
+ }
+ }
+ }
+
+ protected void Send(byte[] bytes, CancellationToken token)
+ {
+ pending_writes.Add(bytes);
+ if (pending_writes.Count == 1)
+ {
+ if (current_write != null)
+ throw new Exception("Internal state is bad. current_write must be null if there are no pending writes");
+
+ current_write = socket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, token);
+ pending_ops.Add(current_write);
+ }
+ }
+
+ async Task MarkCompleteAfterward(Func<CancellationToken, Task> send, CancellationToken token)
+ {
+ try
+ {
+ await send(token);
+ side_exit.SetResult(true);
+ }
+ catch (Exception e)
+ {
+ side_exit.SetException(e);
+ }
+ }
+
+ protected async Task<bool> ConnectWithMainLoops(
+ Uri uri,
+ Func<string, CancellationToken, Task> receive,
+ Func<CancellationToken, Task> send,
+ CancellationToken token)
+ {
+
+ logger.LogDebug("connecting to {0}", uri);
+ this.socket = new ClientWebSocket();
+ this.socket.Options.KeepAliveInterval = Timeout.InfiniteTimeSpan;
+
+ await this.socket.ConnectAsync(uri, token);
+ pending_ops.Add(ReadOne(token));
+ pending_ops.Add(side_exit.Task);
+ pending_ops.Add(MarkCompleteAfterward(send, token));
+
+ while (!token.IsCancellationRequested)
+ {
+ var task = await Task.WhenAny(pending_ops);
+ if (task == pending_ops[0])
+ { //pending_ops[0] is for message reading
+ var msg = ((Task<string>)task).Result;
+ pending_ops[0] = ReadOne(token);
+ Task tsk = receive(msg, token);
+ if (tsk != null)
+ pending_ops.Add(tsk);
+ }
+ else if (task == pending_ops[1])
+ {
+ var res = ((Task<bool>)task).Result;
+ //it might not throw if exiting successfull
+ return res;
+ }
+ else
+ { //must be a background task
+ pending_ops.Remove(task);
+ var tsk = Pump(task, token);
+ if (tsk != null)
+ pending_ops.Add(tsk);
+ }
+ }
+
+ return false;
+ }
+
+ protected virtual void Log(string priority, string msg)
+ {
+ //
+ }
+ }
}
diff --git a/sdks/wasm/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/sdks/wasm/DebuggerTestSuite/EvaluateOnCallFrameTests.cs
index 315ee5f522c..dad28dff6f3 100644
--- a/sdks/wasm/DebuggerTestSuite/EvaluateOnCallFrameTests.cs
+++ b/sdks/wasm/DebuggerTestSuite/EvaluateOnCallFrameTests.cs
@@ -1,204 +1,214 @@
+// 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.Linq;
using System.Threading.Tasks;
+using Microsoft.WebAssembly.Diagnostics;
using Newtonsoft.Json.Linq;
using Xunit;
-using WebAssembly.Net.Debugging;
namespace DebuggerTests
{
- public class EvaluateOnCallFrameTests : DebuggerTestBase {
-
- [Fact]
- public async Task EvaluateThisProperties ()
- => await CheckInspectLocalsAtBreakpointSite (
- "dotnet://debugger-test.dll/debugger-evaluate-test.cs", 20, 16,
- "run",
- "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties (pause_location ["callFrames"][0] ["callFrameId"].Value<string> ());
- var evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "a");
- CheckContentValue (evaluate, "1");
- evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "b");
- CheckContentValue (evaluate, "2");
- evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "c");
- CheckContentValue (evaluate, "3");
-
- evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "dt");
- await CheckDateTimeValue (evaluate, new DateTime (2000, 5, 4, 3, 2, 1));
- });
-
- [Theory]
- [InlineData (58, 3, "EvaluateTestsStructInstanceMethod")]
- [InlineData (74, 3, "GenericInstanceMethodOnStruct<int>")]
- [InlineData (97, 3, "EvaluateTestsGenericStructInstanceMethod")]
- public async Task EvaluateThisPropertiesOnStruct (int line, int col, string method_name)
- => await CheckInspectLocalsAtBreakpointSite (
- "dotnet://debugger-test.dll/debugger-evaluate-test.cs", line, col,
- method_name,
- "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
- wait_for_event_fn: async (pause_location) => {
- var evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "a");
- CheckContentValue (evaluate, "1");
- evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "b");
- CheckContentValue (evaluate, "2");
- evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "c");
- CheckContentValue (evaluate, "3");
-
- evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "dateTime");
- await CheckDateTimeValue (evaluate, new DateTime (2020, 1, 2, 3, 4, 5));
- });
-
- [Fact]
- public async Task EvaluateParameters ()
- => await CheckInspectLocalsAtBreakpointSite (
- "dotnet://debugger-test.dll/debugger-evaluate-test.cs", 20, 16,
- "run",
- "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties (pause_location ["callFrames"][0] ["callFrameId"].Value<string> ());
- var evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "g");
- CheckContentValue (evaluate, "100");
- evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "h");
- CheckContentValue (evaluate, "200");
- evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "valString");
- CheckContentValue (evaluate, "test");
- });
-
- [Fact]
- public async Task EvaluateLocals ()
- => await CheckInspectLocalsAtBreakpointSite (
- "dotnet://debugger-test.dll/debugger-evaluate-test.cs", 20, 16,
- "run",
- "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties (pause_location ["callFrames"][0] ["callFrameId"].Value<string> ());
- var evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "d");
- CheckContentValue (evaluate, "101");
- evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "e");
- CheckContentValue (evaluate, "102");
- evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "f");
- CheckContentValue (evaluate, "103");
-
- evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "local_dt");
- await CheckDateTimeValue (evaluate, new DateTime (2010, 9, 8, 7, 6, 5));
- });
-
- [Fact]
- public async Task EvaluateLocalsAsync ()
- {
- var bp_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
- int line = 227; int col = 3;
- var function_name = "MoveNext";
- await CheckInspectLocalsAtBreakpointSite (
- bp_loc, line, col,
- function_name,
- "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.ArrayTestsClass:EntryPointForStructMethod', true); })",
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties (pause_location ["callFrames"][0] ["callFrameId"].Value<string> ());
-
- // sc_arg
- {
- var sc_arg = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "sc_arg");
- await CheckValue (sc_arg, TObject ("DebuggerTests.SimpleClass"), "sc_arg#1");
-
- var sc_arg_props = await GetProperties (sc_arg ["objectId"]?.Value<string> ());
- await CheckProps (sc_arg_props, new {
- X = TNumber (10),
- Y = TNumber (45),
- Id = TString ("sc#Id"),
- Color = TEnum ("DebuggerTests.RGB", "Blue"),
- PointWithCustomGetter = TGetter ("PointWithCustomGetter")
- }, "sc_arg_props#1");
- }
-
- // local_gs
- {
- var local_gs = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "local_gs");
- await CheckValue (local_gs, TValueType ("DebuggerTests.SimpleGenericStruct<int>"), "local_gs#1");
-
- var local_gs_props = await GetProperties (local_gs ["objectId"]?.Value<string> ());
- await CheckProps (local_gs_props, new {
- Id = TObject ("string", is_null: true),
- Color = TEnum ("DebuggerTests.RGB", "Red"),
- Value = TNumber (0)
- }, "local_gs_props#1");
- }
-
- // step, check local_gs
- pause_location = await StepAndCheck (StepKind.Over, bp_loc, line + 1, col, function_name);
- {
- var local_gs = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "local_gs");
- await CheckValue (local_gs, TValueType ("DebuggerTests.SimpleGenericStruct<int>"), "local_gs#2");
-
- var local_gs_props = await GetProperties (local_gs ["objectId"]?.Value<string> ());
- await CheckProps (local_gs_props, new {
- Id = TString ("local_gs#Id"),
- Color = TEnum ("DebuggerTests.RGB", "Green"),
- Value = TNumber (4)
- }, "local_gs_props#2");
- }
-
- // step check sc_arg.Id
- pause_location = await StepAndCheck (StepKind.Over, bp_loc, line + 2, col, function_name);
- {
- var sc_arg = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "sc_arg");
- await CheckValue (sc_arg, TObject ("DebuggerTests.SimpleClass"), "sc_arg#2");
-
- var sc_arg_props = await GetProperties (sc_arg ["objectId"]?.Value<string> ());
- await CheckProps (sc_arg_props, new {
- X = TNumber (10),
- Y = TNumber (45),
- Id = TString ("sc_arg#Id"), // <------- This changed
- Color = TEnum ("DebuggerTests.RGB", "Blue"),
- PointWithCustomGetter = TGetter ("PointWithCustomGetter")
- }, "sc_arg_props#2");
- }
- });
- }
-
- [Fact]
- public async Task EvaluateExpressions ()
- => await CheckInspectLocalsAtBreakpointSite (
- "dotnet://debugger-test.dll/debugger-evaluate-test.cs", 20, 16,
- "run",
- "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties (pause_location ["callFrames"][0] ["callFrameId"].Value<string> ());
- var evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "d + e");
- CheckContentValue (evaluate, "203");
- evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "e + 10");
- CheckContentValue (evaluate, "112");
- evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "a + a");
- CheckContentValue (evaluate, "2");
- evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "this.a + this.b");
- CheckContentValue (evaluate, "3");
- evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "\"test\" + \"test\"");
- CheckContentValue (evaluate, "testtest");
- evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "5 + 5");
- CheckContentValue (evaluate, "10");
- });
-
- [Fact]
- public async Task EvaluateThisExpressions ()
- => await CheckInspectLocalsAtBreakpointSite (
- "dotnet://debugger-test.dll/debugger-evaluate-test.cs", 20, 16,
- "run",
- "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties (pause_location ["callFrames"][0] ["callFrameId"].Value<string> ());
- var evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "this.a");
- CheckContentValue (evaluate, "1");
- evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "this.b");
- CheckContentValue (evaluate, "2");
- evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "this.c");
- CheckContentValue (evaluate, "3");
-
- // FIXME: not supported yet
- // evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "this.dt");
- // await CheckDateTimeValue (evaluate, new DateTime (2000, 5, 4, 3, 2, 1));
- });
- }
+ public class EvaluateOnCallFrameTests : DebuggerTestBase
+ {
+
+ [Fact]
+ public async Task EvaluateThisProperties() => await CheckInspectLocalsAtBreakpointSite(
+ "dotnet://debugger-test.dll/debugger-evaluate-test.cs", 25, 16,
+ "run",
+ "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "a");
+ CheckContentValue(evaluate, "1");
+ evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "b");
+ CheckContentValue(evaluate, "2");
+ evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "c");
+ CheckContentValue(evaluate, "3");
+
+ evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "dt");
+ await CheckDateTimeValue(evaluate, new DateTime(2000, 5, 4, 3, 2, 1));
+ });
+
+ [Theory]
+ [InlineData(63, 12, "EvaluateTestsStructInstanceMethod")]
+ [InlineData(79, 12, "GenericInstanceMethodOnStruct<int>")]
+ [InlineData(102, 12, "EvaluateTestsGenericStructInstanceMethod")]
+ public async Task EvaluateThisPropertiesOnStruct(int line, int col, string method_name) => await CheckInspectLocalsAtBreakpointSite(
+ "dotnet://debugger-test.dll/debugger-evaluate-test.cs", line, col,
+ method_name,
+ "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "a");
+ CheckContentValue(evaluate, "1");
+ evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "b");
+ CheckContentValue(evaluate, "2");
+ evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "c");
+ CheckContentValue(evaluate, "3");
+
+ evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "dateTime");
+ await CheckDateTimeValue(evaluate, new DateTime(2020, 1, 2, 3, 4, 5));
+ });
+
+ [Fact]
+ public async Task EvaluateParameters() => await CheckInspectLocalsAtBreakpointSite(
+ "dotnet://debugger-test.dll/debugger-evaluate-test.cs", 25, 16,
+ "run",
+ "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "g");
+ CheckContentValue(evaluate, "100");
+ evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "h");
+ CheckContentValue(evaluate, "200");
+ evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "valString");
+ CheckContentValue(evaluate, "test");
+ });
+
+ [Fact]
+ public async Task EvaluateLocals() => await CheckInspectLocalsAtBreakpointSite(
+ "dotnet://debugger-test.dll/debugger-evaluate-test.cs", 25, 16,
+ "run",
+ "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "d");
+ CheckContentValue(evaluate, "101");
+ evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "e");
+ CheckContentValue(evaluate, "102");
+ evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "f");
+ CheckContentValue(evaluate, "103");
+
+ evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "local_dt");
+ await CheckDateTimeValue(evaluate, new DateTime(2010, 9, 8, 7, 6, 5));
+ });
+
+ [Fact]
+ public async Task EvaluateLocalsAsync()
+ {
+ var bp_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
+ int line = 249;
+ int col = 12;
+ var function_name = "MoveNext";
+ await CheckInspectLocalsAtBreakpointSite(
+ bp_loc, line, col,
+ function_name,
+ "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.ArrayTestsClass:EntryPointForStructMethod', true); })",
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+
+ // sc_arg
+ {
+ var sc_arg = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "sc_arg");
+ await CheckValue(sc_arg, TObject("DebuggerTests.SimpleClass"), "sc_arg#1");
+
+ var sc_arg_props = await GetProperties(sc_arg["objectId"]?.Value<string>());
+ await CheckProps(sc_arg_props, new
+ {
+ X = TNumber(10),
+ Y = TNumber(45),
+ Id = TString("sc#Id"),
+ Color = TEnum("DebuggerTests.RGB", "Blue"),
+ PointWithCustomGetter = TGetter("PointWithCustomGetter")
+ }, "sc_arg_props#1");
+ }
+
+ // local_gs
+ {
+ var local_gs = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "local_gs");
+ await CheckValue(local_gs, TValueType("DebuggerTests.SimpleGenericStruct<int>"), "local_gs#1");
+
+ var local_gs_props = await GetProperties(local_gs["objectId"]?.Value<string>());
+ await CheckProps(local_gs_props, new
+ {
+ Id = TObject("string", is_null: true),
+ Color = TEnum("DebuggerTests.RGB", "Red"),
+ Value = TNumber(0)
+ }, "local_gs_props#1");
+ }
+
+ // step, check local_gs
+ pause_location = await StepAndCheck(StepKind.Over, bp_loc, line + 1, col, function_name);
+ {
+ var local_gs = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "local_gs");
+ await CheckValue(local_gs, TValueType("DebuggerTests.SimpleGenericStruct<int>"), "local_gs#2");
+
+ var local_gs_props = await GetProperties(local_gs["objectId"]?.Value<string>());
+ await CheckProps(local_gs_props, new
+ {
+ Id = TString("local_gs#Id"),
+ Color = TEnum("DebuggerTests.RGB", "Green"),
+ Value = TNumber(4)
+ }, "local_gs_props#2");
+ }
+
+ // step check sc_arg.Id
+ pause_location = await StepAndCheck(StepKind.Over, bp_loc, line + 2, col, function_name);
+ {
+ var sc_arg = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "sc_arg");
+ await CheckValue(sc_arg, TObject("DebuggerTests.SimpleClass"), "sc_arg#2");
+
+ var sc_arg_props = await GetProperties(sc_arg["objectId"]?.Value<string>());
+ await CheckProps(sc_arg_props, new
+ {
+ X = TNumber(10),
+ Y = TNumber(45),
+ Id = TString("sc_arg#Id"), // <------- This changed
+ Color = TEnum("DebuggerTests.RGB", "Blue"),
+ PointWithCustomGetter = TGetter("PointWithCustomGetter")
+ }, "sc_arg_props#2");
+ }
+ });
+ }
+
+ [Fact]
+ public async Task EvaluateExpressions() => await CheckInspectLocalsAtBreakpointSite(
+ "dotnet://debugger-test.dll/debugger-evaluate-test.cs", 25, 16,
+ "run",
+ "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "d + e");
+ CheckContentValue(evaluate, "203");
+ evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "e + 10");
+ CheckContentValue(evaluate, "112");
+ evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "a + a");
+ CheckContentValue(evaluate, "2");
+ evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "this.a + this.b");
+ CheckContentValue(evaluate, "3");
+ evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "\"test\" + \"test\"");
+ CheckContentValue(evaluate, "testtest");
+ evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "5 + 5");
+ CheckContentValue(evaluate, "10");
+ });
+
+ [Fact]
+ public async Task EvaluateThisExpressions() => await CheckInspectLocalsAtBreakpointSite(
+ "dotnet://debugger-test.dll/debugger-evaluate-test.cs", 25, 16,
+ "run",
+ "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "this.a");
+ CheckContentValue(evaluate, "1");
+ evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "this.b");
+ CheckContentValue(evaluate, "2");
+ evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "this.c");
+ CheckContentValue(evaluate, "3");
+
+ // FIXME: not supported yet
+ // evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "this.dt");
+ // await CheckDateTimeValue (evaluate, new DateTime (2000, 5, 4, 3, 2, 1));
+ });
+ }
}
diff --git a/sdks/wasm/DebuggerTestSuite/ExceptionTests.cs b/sdks/wasm/DebuggerTestSuite/ExceptionTests.cs
new file mode 100644
index 00000000000..415956a6c2e
--- /dev/null
+++ b/sdks/wasm/DebuggerTestSuite/ExceptionTests.cs
@@ -0,0 +1,264 @@
+// 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.Linq;
+using System.Threading.Tasks;
+using Microsoft.WebAssembly.Diagnostics;
+using Newtonsoft.Json.Linq;
+using Xunit;
+
+namespace DebuggerTests
+{
+
+ public class ExceptionTests : DebuggerTestBase
+ {
+ [Fact]
+ public async Task ExceptionTestAll()
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+ int line = 15;
+ int col = 20;
+ string entry_method_name = "[debugger-test] DebuggerTests.ExceptionTestsClass:TestExceptions";
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ var debugger_test_loc = "dotnet://debugger-test.dll/debugger-exception-test.cs";
+
+ await SetPauseOnException("all");
+
+ var eval_expr = "window.setTimeout(function() { invoke_static_method (" +
+ $"'{entry_method_name}'" +
+ "); }, 1);";
+
+ var pause_location = await EvaluateAndCheck(eval_expr, null, 0, 0, null);
+ //stop in the managed caught exception
+ pause_location = await WaitForManagedException(pause_location);
+
+ AssertEqual("run", pause_location["callFrames"]?[0]?["functionName"]?.Value<string>(), "pause0");
+
+ await CheckValue(pause_location["data"], JObject.FromObject(new
+ {
+ type = "object",
+ subtype = "error",
+ className = "DebuggerTests.CustomException",
+ uncaught = false
+ }), "exception0.data");
+
+ var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value<string>());
+ CheckString(exception_members, "message", "not implemented caught");
+
+ pause_location = await WaitForManagedException(null);
+ AssertEqual("run", pause_location["callFrames"]?[0]?["functionName"]?.Value<string>(), "pause1");
+
+ //stop in the uncaught exception
+ CheckLocation(debugger_test_loc, 28, 16, scripts, pause_location["callFrames"][0]["location"]);
+
+ await CheckValue(pause_location["data"], JObject.FromObject(new
+ {
+ type = "object",
+ subtype = "error",
+ className = "DebuggerTests.CustomException",
+ uncaught = true
+ }), "exception1.data");
+
+ exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value<string>());
+ CheckString(exception_members, "message", "not implemented uncaught");
+ });
+ }
+
+ [Fact]
+ public async Task JSExceptionTestAll()
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+
+ await SetPauseOnException("all");
+
+ var eval_expr = "window.setTimeout(function () { exceptions_test (); }, 1)";
+ var pause_location = await EvaluateAndCheck(eval_expr, null, 0, 0, "exception_caught_test", null, null);
+
+ Assert.Equal("exception", pause_location["reason"]);
+ await CheckValue(pause_location["data"], JObject.FromObject(new
+ {
+ type = "object",
+ subtype = "error",
+ className = "TypeError",
+ uncaught = false
+ }), "exception0.data");
+
+ var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value<string>());
+ CheckString(exception_members, "message", "exception caught");
+
+ pause_location = await SendCommandAndCheck(null, "Debugger.resume", null, 0, 0, "exception_uncaught_test");
+
+ Assert.Equal("exception", pause_location["reason"]);
+ await CheckValue(pause_location["data"], JObject.FromObject(new
+ {
+ type = "object",
+ subtype = "error",
+ className = "RangeError",
+ uncaught = true
+ }), "exception1.data");
+
+ exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value<string>());
+ CheckString(exception_members, "message", "exception uncaught");
+ });
+ }
+
+ // FIXME? BUG? We seem to get the stack trace for Runtime.exceptionThrown at `call_method`,
+ // but JS shows the original error type, and original trace
+ [Fact]
+ public async Task ExceptionTestNone()
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+ string entry_method_name = "[debugger-test] DebuggerTests.ExceptionTestsClass:TestExceptions";
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+
+ await SetPauseOnException("none");
+
+ var eval_expr = "window.setTimeout(function() { invoke_static_method (" +
+ $"'{entry_method_name}'" +
+ "); }, 1);";
+
+ try
+ {
+ await EvaluateAndCheck(eval_expr, null, 0, 0, "", null, null);
+ }
+ catch (ArgumentException ae)
+ {
+ var eo = JObject.Parse(ae.Message);
+
+ // AssertEqual (line, eo ["exceptionDetails"]?["lineNumber"]?.Value<int> (), "lineNumber");
+ AssertEqual("Uncaught", eo["exceptionDetails"]?["text"]?.Value<string>(), "text");
+
+ await CheckValue(eo["exceptionDetails"]?["exception"], JObject.FromObject(new
+ {
+ type = "object",
+ subtype = "error",
+ className = "Error" // BUG?: "DebuggerTests.CustomException"
+ }), "exception");
+
+ return;
+ }
+
+ Assert.True(false, "Expected to get an ArgumentException from the uncaught user exception");
+ });
+ }
+
+ [Fact]
+ public async Task JSExceptionTestNone()
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+
+ await SetPauseOnException("none");
+
+ var eval_expr = "window.setTimeout(function () { exceptions_test (); }, 1)";
+
+ int line = 44;
+ try
+ {
+ await EvaluateAndCheck(eval_expr, null, 0, 0, "", null, null);
+ }
+ catch (ArgumentException ae)
+ {
+ Console.WriteLine($"{ae}");
+ var eo = JObject.Parse(ae.Message);
+
+ AssertEqual(line, eo["exceptionDetails"]?["lineNumber"]?.Value<int>(), "lineNumber");
+ AssertEqual("Uncaught", eo["exceptionDetails"]?["text"]?.Value<string>(), "text");
+
+ await CheckValue(eo["exceptionDetails"]?["exception"], JObject.FromObject(new
+ {
+ type = "object",
+ subtype = "error",
+ className = "RangeError"
+ }), "exception");
+
+ return;
+ }
+
+ Assert.True(false, "Expected to get an ArgumentException from the uncaught user exception");
+ });
+ }
+
+ [Theory]
+ [InlineData("function () { exceptions_test (); }", null, 0, 0, "exception_uncaught_test", "RangeError", "exception uncaught")]
+ [InlineData("function () { invoke_static_method ('[debugger-test] DebuggerTests.ExceptionTestsClass:TestExceptions'); }",
+ "dotnet://debugger-test.dll/debugger-exception-test.cs", 28, 16, "run",
+ "DebuggerTests.CustomException", "not implemented uncaught")]
+ public async Task ExceptionTestUncaught(string eval_fn, string loc, int line, int col, string fn_name,
+ string exception_type, string exception_message)
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+
+ await SetPauseOnException("uncaught");
+
+ var eval_expr = $"window.setTimeout({eval_fn}, 1);";
+ var pause_location = await EvaluateAndCheck(eval_expr, loc, line, col, fn_name);
+
+ Assert.Equal("exception", pause_location["reason"]);
+ await CheckValue(pause_location["data"], JObject.FromObject(new
+ {
+ type = "object",
+ subtype = "error",
+ className = exception_type,
+ uncaught = true
+ }), "exception.data");
+
+ var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value<string>());
+ CheckString(exception_members, "message", exception_message);
+ });
+ }
+
+ async Task<JObject> WaitForManagedException(JObject pause_location)
+ {
+ while (true)
+ {
+ if (pause_location != null)
+ {
+ AssertEqual("exception", pause_location["reason"]?.Value<string>(), $"Expected to only pause because of an exception. {pause_location}");
+
+ // return in case of a managed exception, and ignore JS ones
+ if (pause_location["data"]?["objectId"]?.Value<string>()?.StartsWith("dotnet:object:") == true)
+ {
+ break;
+ }
+ }
+
+ pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", null, 0, 0, null);
+ }
+
+ return pause_location;
+ }
+ }
+}
diff --git a/sdks/wasm/DebuggerTestSuite/InspectorClient.cs b/sdks/wasm/DebuggerTestSuite/InspectorClient.cs
index 84b9414d9c6..7a8e1c87168 100644
--- a/sdks/wasm/DebuggerTestSuite/InspectorClient.cs
+++ b/sdks/wasm/DebuggerTestSuite/InspectorClient.cs
@@ -1,74 +1,81 @@
-using System;
-using System.Threading.Tasks;
-using Newtonsoft.Json.Linq;
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
-using System.Threading;
-using System.Text;
+using System;
using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using Newtonsoft.Json.Linq;
-namespace WebAssembly.Net.Debugging {
- internal class InspectorClient : DevToolsClient {
- List<(int, TaskCompletionSource<Result>)> pending_cmds = new List<(int, TaskCompletionSource<Result>)> ();
- Func<string, JObject, CancellationToken, Task> onEvent;
- int next_cmd_id;
+namespace Microsoft.WebAssembly.Diagnostics
+{
+ internal class InspectorClient : DevToolsClient
+ {
+ List<(int, TaskCompletionSource<Result>)> pending_cmds = new List<(int, TaskCompletionSource<Result>)>();
+ Func<string, JObject, CancellationToken, Task> onEvent;
+ int next_cmd_id;
- public InspectorClient (ILogger logger) : base(logger) {}
+ public InspectorClient(ILogger logger) : base(logger) { }
- Task HandleMessage (string msg, CancellationToken token)
- {
- var res = JObject.Parse (msg);
- if (res ["id"] == null)
- DumpProtocol (string.Format("Event method: {0} params: {1}", res ["method"], res ["params"]));
- else
- DumpProtocol (string.Format ("Response id: {0} res: {1}", res ["id"], res));
+ Task HandleMessage(string msg, CancellationToken token)
+ {
+ var res = JObject.Parse(msg);
+ if (res["id"] == null)
+ DumpProtocol(string.Format("Event method: {0} params: {1}", res["method"], res["params"]));
+ else
+ DumpProtocol(string.Format("Response id: {0} res: {1}", res["id"], res));
- if (res ["id"] == null)
- return onEvent (res ["method"].Value<string> (), res ["params"] as JObject, token);
- var id = res ["id"].Value<int> ();
- var idx = pending_cmds.FindIndex (e => e.Item1 == id);
- var item = pending_cmds [idx];
- pending_cmds.RemoveAt (idx);
- item.Item2.SetResult (Result.FromJson (res));
- return null;
- }
+ if (res["id"] == null)
+ return onEvent(res["method"].Value<string>(), res["params"] as JObject, token);
+ var id = res["id"].Value<int>();
+ var idx = pending_cmds.FindIndex(e => e.Item1 == id);
+ var item = pending_cmds[idx];
+ pending_cmds.RemoveAt(idx);
+ item.Item2.SetResult(Result.FromJson(res));
+ return null;
+ }
- public async Task Connect(
- Uri uri,
- Func<string, JObject, CancellationToken, Task> onEvent,
- Func<CancellationToken, Task> send,
- CancellationToken token) {
+ public async Task Connect(
+ Uri uri,
+ Func<string, JObject, CancellationToken, Task> onEvent,
+ Func<CancellationToken, Task> send,
+ CancellationToken token)
+ {
- this.onEvent = onEvent;
- await ConnectWithMainLoops (uri, HandleMessage, send, token);
- }
+ this.onEvent = onEvent;
+ await ConnectWithMainLoops(uri, HandleMessage, send, token);
+ }
- public Task<Result> SendCommand (string method, JObject args, CancellationToken token)
- {
- int id = ++next_cmd_id;
- if (args == null)
- args = new JObject ();
+ public Task<Result> SendCommand(string method, JObject args, CancellationToken token)
+ {
+ int id = ++next_cmd_id;
+ if (args == null)
+ args = new JObject();
- var o = JObject.FromObject (new {
- id = id,
- method = method,
- @params = args
- });
+ var o = JObject.FromObject(new
+ {
+ id = id,
+ method = method,
+ @params = args
+ });
- var tcs = new TaskCompletionSource<Result> ();
- pending_cmds.Add ((id, tcs));
+ var tcs = new TaskCompletionSource<Result>();
+ pending_cmds.Add((id, tcs));
- var str = o.ToString ();
- //Log ("protocol", $"SendCommand: id: {id} method: {method} params: {args}");
+ var str = o.ToString();
+ //Log ("protocol", $"SendCommand: id: {id} method: {method} params: {args}");
- var bytes = Encoding.UTF8.GetBytes (str);
- Send (bytes, token);
- return tcs.Task;
- }
+ var bytes = Encoding.UTF8.GetBytes(str);
+ Send(bytes, token);
+ return tcs.Task;
+ }
- protected virtual void DumpProtocol (string msg){
- // Console.WriteLine (msg);
- //XXX make logging not stupid
- }
- }
-}
+ protected virtual void DumpProtocol(string msg)
+ {
+ // Console.WriteLine (msg);
+ //XXX make logging not stupid
+ }
+ }
+}
diff --git a/sdks/wasm/DebuggerTestSuite/PointerTests.cs b/sdks/wasm/DebuggerTestSuite/PointerTests.cs
index 932f02a0c80..d18f054d9d7 100644
--- a/sdks/wasm/DebuggerTestSuite/PointerTests.cs
+++ b/sdks/wasm/DebuggerTestSuite/PointerTests.cs
@@ -1,520 +1,558 @@
+// 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.Linq;
using System.Threading.Tasks;
+using Microsoft.WebAssembly.Diagnostics;
using Newtonsoft.Json.Linq;
using Xunit;
-using WebAssembly.Net.Debugging;
namespace DebuggerTests
{
- public class PointerTests : DebuggerTestBase {
-
- public static TheoryData<string, string, string, int, string, bool> PointersTestData =>
- new TheoryData<string, string, string, int, string, bool> {
- {$"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "LocalPointers", 32, "LocalPointers", false},
- {$"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "LocalPointers", 32, "LocalPointers", true},
- {$"invoke_static_method_async ('[debugger-test] DebuggerTests.PointerTests:LocalPointersAsync');", "DebuggerTests.PointerTests", "LocalPointersAsync", 32, "MoveNext", false},
- {$"invoke_static_method_async ('[debugger-test] DebuggerTests.PointerTests:LocalPointersAsync');", "DebuggerTests.PointerTests", "LocalPointersAsync", 32, "MoveNext", true}
- };
-
- [Theory]
- [MemberDataAttribute (nameof (PointersTestData))]
- public async Task InspectLocalPointersToPrimitiveTypes (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,
- "window.setTimeout(function() { " + eval_fn + " })",
- use_cfo: use_cfo,
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
-
- var dt = new DateTime (5, 6, 7, 8, 9, 10);
- await CheckProps (locals, new {
- ip = TPointer ("int*"),
- ip_null = TPointer ("int*", is_null: true),
- ipp = TPointer ("int**"),
- ipp_null = TPointer ("int**"),
-
- cvalue0 = TSymbol ("113 'q'"),
- cp = TPointer ("char*"),
-
- vp = TPointer ("void*"),
- vp_null = TPointer ("void*", is_null: true),
- }, "locals", num_fields: 26);
-
- var props = await GetObjectOnLocals (locals, "ip");
- await CheckPointerValue (props, "*ip", TNumber (5), "locals");
-
- {
- var ipp_props = await GetObjectOnLocals (locals, "ipp");
- await CheckPointerValue (ipp_props, "*ipp", TPointer ("int*"));
-
- ipp_props = await GetObjectOnLocals (ipp_props, "*ipp");
- await CheckPointerValue (ipp_props, "**ipp", TNumber (5));
- }
-
- {
- var ipp_props = await GetObjectOnLocals (locals, "ipp_null");
- await CheckPointerValue (ipp_props, "*ipp_null", TPointer ("int*", is_null: true));
- }
-
- // *cp
- props = await GetObjectOnLocals (locals, "cp");
- await CheckPointerValue (props, "*cp", TSymbol ("113 'q'"));
- });
-
- [Theory]
- [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,
- "window.setTimeout(function() { " + eval_fn + " })",
- use_cfo: use_cfo,
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
-
- var dt = new DateTime (5, 6, 7, 8, 9, 10);
- await CheckProps (locals, new {
- ipa = TArray ("int*[]", 3)
- }, "locals", num_fields: 26);
-
- var ipa_elems = await CompareObjectPropertiesFor (locals, "ipa", new [] {
- TPointer ("int*"),
- TPointer ("int*"),
- TPointer ("int*", is_null: true)
- });
-
- await CheckArrayElements (ipa_elems, new [] {
- TNumber (5),
- TNumber (10),
- null
- });
- });
-
- [Theory]
- [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,
- "window.setTimeout(function() { " + eval_fn + " })",
- use_cfo: use_cfo,
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
-
- var dt = new DateTime (5, 6, 7, 8, 9, 10);
- await CheckProps (locals, new {
- ippa = TArray ("int**[]", 5)
- }, "locals", num_fields: 26);
-
- var ippa_elems = await CompareObjectPropertiesFor (locals, "ippa", new [] {
- TPointer ("int**"),
- TPointer ("int**"),
- TPointer ("int**"),
- TPointer ("int**"),
- TPointer ("int**", is_null: true)
- });
-
- {
- var actual_elems = await CheckArrayElements (ippa_elems, new [] {
- TPointer ("int*"),
- TPointer ("int*", is_null: true),
- TPointer ("int*"),
- TPointer ("int*", is_null: true),
- null
- });
-
- var val = await GetObjectOnLocals (actual_elems [0], "*[0]");
- await CheckPointerValue (val, "**[0]", TNumber (5));
-
- val = await GetObjectOnLocals (actual_elems [2], "*[2]");
- await CheckPointerValue (val, "**[2]", TNumber (5));
- }
- });
-
- [Theory]
- [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,
- "window.setTimeout(function() { " + eval_fn + " })",
- use_cfo: use_cfo,
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
-
- var dt = new DateTime (5, 6, 7, 8, 9, 10);
- await CheckProps (locals, new {
- dt = TValueType ("System.DateTime", dt.ToString ()),
- dtp = TPointer ("System.DateTime*"),
- dtp_null = TPointer ("System.DateTime*", is_null: true),
-
- gsp = TPointer ("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*"),
- gsp_null = TPointer ("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*")
- }, "locals", num_fields: 26);
-
- await CheckDateTime (locals, "dt", dt);
-
- // *dtp
- var props = await GetObjectOnLocals (locals, "dtp");
- await CheckDateTime (props, "*dtp", dt);
-
- var gsp_props = await GetObjectOnLocals (locals, "gsp");
- await CheckPointerValue (gsp_props, "*gsp", TValueType ("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>"), "locals#gsp");
-
- {
- var gs_dt = new DateTime (1, 2, 3, 4, 5, 6);
-
- var gsp_deref_props = await GetObjectOnLocals (gsp_props, "*gsp");
- await CheckProps (gsp_deref_props, new {
- Value = TValueType ("System.DateTime", gs_dt.ToString ()),
- IntField = TNumber (4),
- DTPP = TPointer ("System.DateTime**")
- }, "locals#gsp#deref");
- {
- var dtpp_props = await GetObjectOnLocals (gsp_deref_props, "DTPP");
- await CheckPointerValue (dtpp_props, "*DTPP", TPointer ("System.DateTime*"), "locals#*gsp");
-
- var dtpp_deref_props = await GetObjectOnLocals (dtpp_props, "*DTPP");
- await CheckDateTime (dtpp_deref_props, "**DTPP", dt);
- }
- }
-
- // gsp_null
- var gsp_w_n_props = await GetObjectOnLocals (locals, "gsp_null");
- await CheckPointerValue (gsp_w_n_props, "*gsp_null", TValueType ("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>"), "locals#gsp");
-
- {
- var gs_dt = new DateTime (1, 2, 3, 4, 5, 6);
-
- var gsp_deref_props = await GetObjectOnLocals (gsp_w_n_props, "*gsp_null");
- await CheckProps (gsp_deref_props, new {
- Value = TValueType ("System.DateTime", gs_dt.ToString ()),
- IntField = TNumber (4),
- DTPP = TPointer ("System.DateTime**")
- }, "locals#gsp#deref");
- {
- var dtpp_props = await GetObjectOnLocals (gsp_deref_props, "DTPP");
- await CheckPointerValue (dtpp_props, "*DTPP", TPointer ("System.DateTime*", is_null: true), "locals#*gsp");
- }
- }
- });
-
- [Theory]
- [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,
- "window.setTimeout(function() { " + eval_fn + " })",
- use_cfo: use_cfo,
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
-
- var dt = new DateTime (5, 6, 7, 8, 9, 10);
- await CheckProps (locals, new {
- dtpa = TArray ("System.DateTime*[]", 2)
- }, "locals", num_fields: 26);
-
- // dtpa
- var dtpa_elems = (await CompareObjectPropertiesFor (locals, "dtpa", new [] {
- TPointer ("System.DateTime*"),
- TPointer ("System.DateTime*", is_null: true)
- }));
- {
- var actual_elems = await CheckArrayElements (dtpa_elems, new [] {
- TValueType ("System.DateTime", dt.ToString ()),
- null
- });
-
- await CheckDateTime (actual_elems [0], "*[0]", dt);
- }
- });
-
- [Theory]
- [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,
- "window.setTimeout(function() { " + eval_fn + " })",
- use_cfo: use_cfo,
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
-
- var dt = new DateTime (5, 6, 7, 8, 9, 10);
- await CheckProps (locals, new {
- gspa = TArray ("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*[]", 3),
- }, "locals", num_fields: 26);
-
- // dtpa
- var gspa_elems = await CompareObjectPropertiesFor (locals, "gspa", new [] {
- TPointer ("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*", is_null: true),
- TPointer ("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*"),
- TPointer ("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*"),
- });
- {
- var gs_dt = new DateTime (1, 2, 3, 4, 5, 6);
- var actual_elems = await CheckArrayElements (gspa_elems, new [] {
- null,
- TValueType ("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>"),
- TValueType ("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>")
- });
-
- // *[1]
- {
- var gsp_deref_props = await GetObjectOnLocals (actual_elems [1], "*[1]");
- await CheckProps (gsp_deref_props, new {
- Value = TValueType ("System.DateTime", gs_dt.ToString ()),
- IntField = TNumber (4),
- DTPP = TPointer ("System.DateTime**")
- }, "locals#gsp#deref");
- {
- var dtpp_props = await GetObjectOnLocals (gsp_deref_props, "DTPP");
- await CheckPointerValue (dtpp_props, "*DTPP", TPointer ("System.DateTime*"), "locals#*gsp");
-
- dtpp_props = await GetObjectOnLocals (dtpp_props, "*DTPP");
- await CheckDateTime (dtpp_props, "**DTPP", dt);
- }
- }
-
- // *[2]
- {
- var gsp_deref_props = await GetObjectOnLocals (actual_elems [2], "*[2]");
- await CheckProps (gsp_deref_props, new {
- Value = TValueType ("System.DateTime", gs_dt.ToString ()),
- IntField = TNumber (4),
- DTPP = TPointer ("System.DateTime**")
- }, "locals#gsp#deref");
- {
- var dtpp_props = await GetObjectOnLocals (gsp_deref_props, "DTPP");
- await CheckPointerValue (dtpp_props, "*DTPP", TPointer ("System.DateTime*", is_null: true), "locals#*gsp");
- }
- }
- }
- });
-
- [Theory]
- [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,
- "window.setTimeout(function() { " + eval_fn + " })",
- use_cfo: use_cfo,
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
-
- var dt = new DateTime (5, 6, 7, 8, 9, 10);
- await CheckProps (locals, new {
- dtppa = TArray ("System.DateTime**[]", 3),
- }, "locals", num_fields: 26);
-
- // DateTime**[] dtppa = new DateTime**[] { &dtp, &dtp_null, null };
- var dtppa_elems = (await CompareObjectPropertiesFor (locals, "dtppa", new [] {
- TPointer ("System.DateTime**"),
- TPointer ("System.DateTime**"),
- TPointer ("System.DateTime**", is_null: true)
- }));
-
- var exp_elems = new [] {
- TPointer ("System.DateTime*"),
- TPointer ("System.DateTime*", is_null: true),
- null
- };
-
- var actual_elems = new JToken [exp_elems.Length];
- for (int i = 0; i < exp_elems.Length; i ++) {
- if (exp_elems [i] != null) {
- actual_elems [i] = await GetObjectOnLocals (dtppa_elems, i.ToString ());
- await CheckPointerValue (actual_elems [i], $"*[{i}]", exp_elems [i], $"dtppa->");
- }
- }
- });
-
- [Theory]
- [MemberDataAttribute (nameof (PointersTestData))]
- public async Task InspectLocalPointersInClasses (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,
- "window.setTimeout(function() { " + eval_fn + " })",
- use_cfo: use_cfo,
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
-
- var dt = new DateTime (5, 6, 7, 8, 9, 10);
- await CheckProps (locals, new {
- cwp = TObject ("DebuggerTests.GenericClassWithPointers<System.DateTime>"),
- cwp_null = TObject ("DebuggerTests.GenericClassWithPointers<System.DateTime>")
- }, "locals", num_fields: 26);
-
- var cwp_props = await GetObjectOnLocals (locals, "cwp");
- var ptr_props = await GetObjectOnLocals (cwp_props, "Ptr");
- await CheckDateTime (ptr_props, "*Ptr", dt);
- });
-
- public static TheoryData<string, string, string, int, string, bool> PointersAsMethodArgsTestData =>
- new TheoryData<string, string, string, int, string, bool> {
- {$"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "PointersAsArgsTest", 2, "PointersAsArgsTest", false},
- {$"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "PointersAsArgsTest", 2, "PointersAsArgsTest", true},
- };
-
- [Theory]
- [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,
- "window.setTimeout(function() { " + eval_fn + " })",
- use_cfo: use_cfo,
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
-
- var dt = new DateTime (5, 6, 7, 8, 9, 10);
- await CheckProps (locals, new {
- ip = TPointer ("int*"),
- ipp = TPointer ("int**"),
- ipa = TArray ("int*[]", 3),
- ippa = TArray ("int**[]", 5)
- }, "locals", num_fields: 8);
-
- // ip
- var props = await GetObjectOnLocals (locals, "ip");
- await CheckPointerValue (props, "*ip", TNumber (5), "locals");
-
- // ipp
- var ipp_props = await GetObjectOnLocals (locals, "ipp");
- await CheckPointerValue (ipp_props, "*ipp", TPointer ("int*"));
-
- ipp_props = await GetObjectOnLocals (ipp_props, "*ipp");
- await CheckPointerValue (ipp_props, "**ipp", TNumber (5));
-
- // ipa
- var ipa_elems = await CompareObjectPropertiesFor (locals, "ipa", new [] {
- TPointer ("int*"),
- TPointer ("int*"),
- TPointer ("int*", is_null: true)
- });
-
- await CheckArrayElements (ipa_elems, new [] {
- TNumber (5),
- TNumber (10),
- null
- });
-
- // ippa
- var ippa_elems = await CompareObjectPropertiesFor (locals, "ippa", new [] {
- TPointer ("int**"),
- TPointer ("int**"),
- TPointer ("int**"),
- TPointer ("int**"),
- TPointer ("int**", is_null: true)
- });
-
- {
- var actual_elems = await CheckArrayElements (ippa_elems, new [] {
- TPointer ("int*"),
- TPointer ("int*", is_null: true),
- TPointer ("int*"),
- TPointer ("int*", is_null: true),
- null
- });
-
- var val = await GetObjectOnLocals (actual_elems [0], "*[0]");
- await CheckPointerValue (val, "**[0]", TNumber (5));
-
- val = await GetObjectOnLocals (actual_elems [2], "*[2]");
- await CheckPointerValue (val, "**[2]", TNumber (5));
- }
- });
-
- [Theory]
- [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,
- "window.setTimeout(function() { " + eval_fn + " })",
- use_cfo: use_cfo,
- wait_for_event_fn: async (pause_location) => {
- var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
-
- var dt = new DateTime (5, 6, 7, 8, 9, 10);
- await CheckProps (locals, new {
- dtp = TPointer ("System.DateTime*"),
- dtpp = TPointer ("System.DateTime**"),
- dtpa = TArray ("System.DateTime*[]", 2),
- dtppa = TArray ("System.DateTime**[]", 3)
- }, "locals", num_fields: 8);
-
- // *dtp
- var dtp_props = await GetObjectOnLocals (locals, "dtp");
- await CheckDateTime (dtp_props, "*dtp", dt);
-
- // *dtpp
- var dtpp_props = await GetObjectOnLocals (locals, "dtpp");
- await CheckPointerValue (dtpp_props, "*dtpp", TPointer ("System.DateTime*"), "locals");
-
- dtpp_props = await GetObjectOnLocals (dtpp_props, "*dtpp");
- await CheckDateTime (dtpp_props, "**dtpp", dt);
-
- // dtpa
- var dtpa_elems = (await CompareObjectPropertiesFor (locals, "dtpa", new [] {
- TPointer ("System.DateTime*"),
- TPointer ("System.DateTime*", is_null: true)
- }));
- {
- var actual_elems = await CheckArrayElements (dtpa_elems, new [] {
- TValueType ("System.DateTime", dt.ToString ()),
- null
- });
-
- await CheckDateTime (actual_elems [0], "*[0]", dt);
- }
-
- // dtppa = new DateTime**[] { &dtp, &dtp_null, null };
- var dtppa_elems = (await CompareObjectPropertiesFor (locals, "dtppa", new [] {
- TPointer ("System.DateTime**"),
- TPointer ("System.DateTime**"),
- TPointer ("System.DateTime**", is_null: true)
- }));
-
- var exp_elems = new [] {
- TPointer ("System.DateTime*"),
- TPointer ("System.DateTime*", is_null: true),
- null
- };
-
- await CheckArrayElements (dtppa_elems, exp_elems);
- });
-
- [Theory]
- [InlineData ("invoke_static_method ('[debugger-test] Math:UseComplex', 0, 0);", "Math", "UseComplex", 3, "UseComplex", false)]
- [InlineData ("invoke_static_method ('[debugger-test] Math:UseComplex', 0, 0);", "Math", "UseComplex", 3, "UseComplex", true)]
- public async Task DerefNonPointerObject (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,
- "window.setTimeout(function() { " + eval_fn + " })",
- use_cfo: use_cfo,
- wait_for_event_fn: async (pause_location) => {
-
- // this will generate the object ids
- var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
- var complex = GetAndAssertObjectWithName (locals, "complex");
-
- // try to deref the non-pointer object, as a pointer
- var props = await GetProperties (complex ["value"]["objectId"].Value<string> ().Replace (":object:", ":pointer:"));
- Assert.Empty (props.Values ());
-
- // try to deref an invalid pointer id
- props = await GetProperties ("dotnet:pointer:123897");
- Assert.Empty (props.Values ());
- });
-
- async Task<JToken[]> CheckArrayElements (JToken array, JToken[] exp_elems)
- {
- var actual_elems = new JToken [exp_elems.Length];
- for (int i = 0; i < exp_elems.Length; i ++) {
- if (exp_elems [i] != null) {
- actual_elems [i] = await GetObjectOnLocals (array, i.ToString ());
- await CheckPointerValue (actual_elems [i], $"*[{i}]", exp_elems [i], $"dtppa->");
- }
- }
-
- return actual_elems;
- }
- }
-} \ No newline at end of file
+ public class PointerTests : DebuggerTestBase
+ {
+
+ public static TheoryData<string, string, string, int, string, bool> PointersTestData =>
+ new TheoryData<string, string, string, int, string, bool>
+ { { $"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "LocalPointers", 32, "LocalPointers", false },
+ { $"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "LocalPointers", 32, "LocalPointers", true },
+ { $"invoke_static_method_async ('[debugger-test] DebuggerTests.PointerTests:LocalPointersAsync');", "DebuggerTests.PointerTests", "LocalPointersAsync", 32, "MoveNext", false },
+ { $"invoke_static_method_async ('[debugger-test] DebuggerTests.PointerTests:LocalPointersAsync');", "DebuggerTests.PointerTests", "LocalPointersAsync", 32, "MoveNext", true }
+ };
+
+ [Theory]
+ [MemberDataAttribute(nameof(PointersTestData))]
+ public async Task InspectLocalPointersToPrimitiveTypes(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,
+ "window.setTimeout(function() { " + eval_fn + " })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+
+ var dt = new DateTime(5, 6, 7, 8, 9, 10);
+ await CheckProps(locals, new
+ {
+ ip = TPointer("int*"),
+ ip_null = TPointer("int*", is_null: true),
+ ipp = TPointer("int**"),
+ ipp_null = TPointer("int**"),
+
+ cvalue0 = TSymbol("113 'q'"),
+ cp = TPointer("char*"),
+
+ vp = TPointer("void*"),
+ vp_null = TPointer("void*", is_null: true),
+ }, "locals", num_fields: 26);
+
+ var props = await GetObjectOnLocals(locals, "ip");
+ await CheckPointerValue(props, "*ip", TNumber(5), "locals");
+
+ {
+ var ipp_props = await GetObjectOnLocals(locals, "ipp");
+ await CheckPointerValue(ipp_props, "*ipp", TPointer("int*"));
+
+ ipp_props = await GetObjectOnLocals(ipp_props, "*ipp");
+ await CheckPointerValue(ipp_props, "**ipp", TNumber(5));
+ }
+
+ {
+ var ipp_props = await GetObjectOnLocals(locals, "ipp_null");
+ await CheckPointerValue(ipp_props, "*ipp_null", TPointer("int*", is_null: true));
+ }
+
+ // *cp
+ props = await GetObjectOnLocals(locals, "cp");
+ await CheckPointerValue(props, "*cp", TSymbol("113 'q'"));
+ });
+
+ [Theory]
+ [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,
+ "window.setTimeout(function() { " + eval_fn + " })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+
+ var dt = new DateTime(5, 6, 7, 8, 9, 10);
+ await CheckProps(locals, new
+ {
+ ipa = TArray("int*[]", 3)
+ }, "locals", num_fields: 26);
+
+ var ipa_elems = await CompareObjectPropertiesFor(locals, "ipa", new[]
+ {
+ TPointer("int*"),
+ TPointer("int*"),
+ TPointer("int*", is_null : true)
+ });
+
+ await CheckArrayElements(ipa_elems, new[]
+ {
+ TNumber(5),
+ TNumber(10),
+ null
+ });
+ });
+
+ [Theory]
+ [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,
+ "window.setTimeout(function() { " + eval_fn + " })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+
+ var dt = new DateTime(5, 6, 7, 8, 9, 10);
+ await CheckProps(locals, new
+ {
+ ippa = TArray("int**[]", 5)
+ }, "locals", num_fields: 26);
+
+ var ippa_elems = await CompareObjectPropertiesFor(locals, "ippa", new[]
+ {
+ TPointer("int**"),
+ TPointer("int**"),
+ TPointer("int**"),
+ TPointer("int**"),
+ TPointer("int**", is_null : true)
+ });
+
+ {
+ var actual_elems = await CheckArrayElements(ippa_elems, new[]
+ {
+ TPointer("int*"),
+ TPointer("int*", is_null : true),
+ TPointer("int*"),
+ TPointer("int*", is_null : true),
+ null
+ });
+
+ var val = await GetObjectOnLocals(actual_elems[0], "*[0]");
+ await CheckPointerValue(val, "**[0]", TNumber(5));
+
+ val = await GetObjectOnLocals(actual_elems[2], "*[2]");
+ await CheckPointerValue(val, "**[2]", TNumber(5));
+ }
+ });
+
+ [Theory]
+ [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,
+ "window.setTimeout(function() { " + eval_fn + " })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+
+ var dt = new DateTime(5, 6, 7, 8, 9, 10);
+ await CheckProps(locals, new
+ {
+ dt = TValueType("System.DateTime", dt.ToString()),
+ dtp = TPointer("System.DateTime*"),
+ dtp_null = TPointer("System.DateTime*", is_null: true),
+
+ gsp = TPointer("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*"),
+ gsp_null = TPointer("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*")
+ }, "locals", num_fields: 26);
+
+ await CheckDateTime(locals, "dt", dt);
+
+ // *dtp
+ var props = await GetObjectOnLocals(locals, "dtp");
+ await CheckDateTime(props, "*dtp", dt);
+
+ var gsp_props = await GetObjectOnLocals(locals, "gsp");
+ await CheckPointerValue(gsp_props, "*gsp", TValueType("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>"), "locals#gsp");
+
+ {
+ var gs_dt = new DateTime(1, 2, 3, 4, 5, 6);
+
+ var gsp_deref_props = await GetObjectOnLocals(gsp_props, "*gsp");
+ await CheckProps(gsp_deref_props, new
+ {
+ Value = TValueType("System.DateTime", gs_dt.ToString()),
+ IntField = TNumber(4),
+ DTPP = TPointer("System.DateTime**")
+ }, "locals#gsp#deref");
+ {
+ var dtpp_props = await GetObjectOnLocals(gsp_deref_props, "DTPP");
+ await CheckPointerValue(dtpp_props, "*DTPP", TPointer("System.DateTime*"), "locals#*gsp");
+
+ var dtpp_deref_props = await GetObjectOnLocals(dtpp_props, "*DTPP");
+ await CheckDateTime(dtpp_deref_props, "**DTPP", dt);
+ }
+ }
+
+ // gsp_null
+ var gsp_w_n_props = await GetObjectOnLocals(locals, "gsp_null");
+ await CheckPointerValue(gsp_w_n_props, "*gsp_null", TValueType("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>"), "locals#gsp");
+
+ {
+ var gs_dt = new DateTime(1, 2, 3, 4, 5, 6);
+
+ var gsp_deref_props = await GetObjectOnLocals(gsp_w_n_props, "*gsp_null");
+ await CheckProps(gsp_deref_props, new
+ {
+ Value = TValueType("System.DateTime", gs_dt.ToString()),
+ IntField = TNumber(4),
+ DTPP = TPointer("System.DateTime**")
+ }, "locals#gsp#deref");
+ {
+ var dtpp_props = await GetObjectOnLocals(gsp_deref_props, "DTPP");
+ await CheckPointerValue(dtpp_props, "*DTPP", TPointer("System.DateTime*", is_null: true), "locals#*gsp");
+ }
+ }
+ });
+
+ [Theory]
+ [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,
+ "window.setTimeout(function() { " + eval_fn + " })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+
+ var dt = new DateTime(5, 6, 7, 8, 9, 10);
+ await CheckProps(locals, new
+ {
+ dtpa = TArray("System.DateTime*[]", 2)
+ }, "locals", num_fields: 26);
+
+ // dtpa
+ var dtpa_elems = (await CompareObjectPropertiesFor(locals, "dtpa", new[]
+ {
+ TPointer("System.DateTime*"),
+ TPointer("System.DateTime*", is_null : true)
+ }));
+ {
+ var actual_elems = await CheckArrayElements(dtpa_elems, new[]
+ {
+ TValueType("System.DateTime", dt.ToString()),
+ null
+ });
+
+ await CheckDateTime(actual_elems[0], "*[0]", dt);
+ }
+ });
+
+ [Theory]
+ [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,
+ "window.setTimeout(function() { " + eval_fn + " })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+
+ var dt = new DateTime(5, 6, 7, 8, 9, 10);
+ await CheckProps(locals, new
+ {
+ gspa = TArray("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*[]", 3),
+ }, "locals", num_fields: 26);
+
+ // dtpa
+ var gspa_elems = await CompareObjectPropertiesFor(locals, "gspa", new[]
+ {
+ TPointer("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*", is_null : true),
+ TPointer("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*"),
+ TPointer("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*"),
+ });
+ {
+ var gs_dt = new DateTime(1, 2, 3, 4, 5, 6);
+ var actual_elems = await CheckArrayElements(gspa_elems, new[]
+ {
+ null,
+ TValueType("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>"),
+ TValueType("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>")
+ });
+
+ // *[1]
+ {
+ var gsp_deref_props = await GetObjectOnLocals(actual_elems[1], "*[1]");
+ await CheckProps(gsp_deref_props, new
+ {
+ Value = TValueType("System.DateTime", gs_dt.ToString()),
+ IntField = TNumber(4),
+ DTPP = TPointer("System.DateTime**")
+ }, "locals#gsp#deref");
+ {
+ var dtpp_props = await GetObjectOnLocals(gsp_deref_props, "DTPP");
+ await CheckPointerValue(dtpp_props, "*DTPP", TPointer("System.DateTime*"), "locals#*gsp");
+
+ dtpp_props = await GetObjectOnLocals(dtpp_props, "*DTPP");
+ await CheckDateTime(dtpp_props, "**DTPP", dt);
+ }
+ }
+
+ // *[2]
+ {
+ var gsp_deref_props = await GetObjectOnLocals(actual_elems[2], "*[2]");
+ await CheckProps(gsp_deref_props, new
+ {
+ Value = TValueType("System.DateTime", gs_dt.ToString()),
+ IntField = TNumber(4),
+ DTPP = TPointer("System.DateTime**")
+ }, "locals#gsp#deref");
+ {
+ var dtpp_props = await GetObjectOnLocals(gsp_deref_props, "DTPP");
+ await CheckPointerValue(dtpp_props, "*DTPP", TPointer("System.DateTime*", is_null: true), "locals#*gsp");
+ }
+ }
+ }
+ });
+
+ [Theory]
+ [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,
+ "window.setTimeout(function() { " + eval_fn + " })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+
+ var dt = new DateTime(5, 6, 7, 8, 9, 10);
+ await CheckProps(locals, new
+ {
+ dtppa = TArray("System.DateTime**[]", 3),
+ }, "locals", num_fields: 26);
+
+ // DateTime**[] dtppa = new DateTime**[] { &dtp, &dtp_null, null };
+ var dtppa_elems = (await CompareObjectPropertiesFor(locals, "dtppa", new[]
+ {
+ TPointer("System.DateTime**"),
+ TPointer("System.DateTime**"),
+ TPointer("System.DateTime**", is_null : true)
+ }));
+
+ var exp_elems = new[]
+ {
+ TPointer("System.DateTime*"),
+ TPointer("System.DateTime*", is_null : true),
+ null
+ };
+
+ var actual_elems = new JToken[exp_elems.Length];
+ for (int i = 0; i < exp_elems.Length; i++)
+ {
+ if (exp_elems[i] != null)
+ {
+ actual_elems[i] = await GetObjectOnLocals(dtppa_elems, i.ToString());
+ await CheckPointerValue(actual_elems[i], $"*[{i}]", exp_elems[i], $"dtppa->");
+ }
+ }
+ });
+
+ [Theory]
+ [MemberDataAttribute(nameof(PointersTestData))]
+ public async Task InspectLocalPointersInClasses(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,
+ "window.setTimeout(function() { " + eval_fn + " })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+
+ var dt = new DateTime(5, 6, 7, 8, 9, 10);
+ await CheckProps(locals, new
+ {
+ cwp = TObject("DebuggerTests.GenericClassWithPointers<System.DateTime>"),
+ cwp_null = TObject("DebuggerTests.GenericClassWithPointers<System.DateTime>")
+ }, "locals", num_fields: 26);
+
+ var cwp_props = await GetObjectOnLocals(locals, "cwp");
+ var ptr_props = await GetObjectOnLocals(cwp_props, "Ptr");
+ await CheckDateTime(ptr_props, "*Ptr", dt);
+ });
+
+ public static TheoryData<string, string, string, int, string, bool> PointersAsMethodArgsTestData =>
+ new TheoryData<string, string, string, int, string, bool>
+ { { $"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "PointersAsArgsTest", 2, "PointersAsArgsTest", false },
+ { $"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "PointersAsArgsTest", 2, "PointersAsArgsTest", true },
+ };
+
+ [Theory]
+ [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,
+ "window.setTimeout(function() { " + eval_fn + " })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+
+ var dt = new DateTime(5, 6, 7, 8, 9, 10);
+ await CheckProps(locals, new
+ {
+ ip = TPointer("int*"),
+ ipp = TPointer("int**"),
+ ipa = TArray("int*[]", 3),
+ ippa = TArray("int**[]", 5)
+ }, "locals", num_fields: 8);
+
+ // ip
+ var props = await GetObjectOnLocals(locals, "ip");
+ await CheckPointerValue(props, "*ip", TNumber(5), "locals");
+
+ // ipp
+ var ipp_props = await GetObjectOnLocals(locals, "ipp");
+ await CheckPointerValue(ipp_props, "*ipp", TPointer("int*"));
+
+ ipp_props = await GetObjectOnLocals(ipp_props, "*ipp");
+ await CheckPointerValue(ipp_props, "**ipp", TNumber(5));
+
+ // ipa
+ var ipa_elems = await CompareObjectPropertiesFor(locals, "ipa", new[]
+ {
+ TPointer("int*"),
+ TPointer("int*"),
+ TPointer("int*", is_null : true)
+ });
+
+ await CheckArrayElements(ipa_elems, new[]
+ {
+ TNumber(5),
+ TNumber(10),
+ null
+ });
+
+ // ippa
+ var ippa_elems = await CompareObjectPropertiesFor(locals, "ippa", new[]
+ {
+ TPointer("int**"),
+ TPointer("int**"),
+ TPointer("int**"),
+ TPointer("int**"),
+ TPointer("int**", is_null : true)
+ });
+
+ {
+ var actual_elems = await CheckArrayElements(ippa_elems, new[]
+ {
+ TPointer("int*"),
+ TPointer("int*", is_null : true),
+ TPointer("int*"),
+ TPointer("int*", is_null : true),
+ null
+ });
+
+ var val = await GetObjectOnLocals(actual_elems[0], "*[0]");
+ await CheckPointerValue(val, "**[0]", TNumber(5));
+
+ val = await GetObjectOnLocals(actual_elems[2], "*[2]");
+ await CheckPointerValue(val, "**[2]", TNumber(5));
+ }
+ });
+
+ [Theory]
+ [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,
+ "window.setTimeout(function() { " + eval_fn + " })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+
+ var dt = new DateTime(5, 6, 7, 8, 9, 10);
+ await CheckProps(locals, new
+ {
+ dtp = TPointer("System.DateTime*"),
+ dtpp = TPointer("System.DateTime**"),
+ dtpa = TArray("System.DateTime*[]", 2),
+ dtppa = TArray("System.DateTime**[]", 3)
+ }, "locals", num_fields: 8);
+
+ // *dtp
+ var dtp_props = await GetObjectOnLocals(locals, "dtp");
+ await CheckDateTime(dtp_props, "*dtp", dt);
+
+ // *dtpp
+ var dtpp_props = await GetObjectOnLocals(locals, "dtpp");
+ await CheckPointerValue(dtpp_props, "*dtpp", TPointer("System.DateTime*"), "locals");
+
+ dtpp_props = await GetObjectOnLocals(dtpp_props, "*dtpp");
+ await CheckDateTime(dtpp_props, "**dtpp", dt);
+
+ // dtpa
+ var dtpa_elems = (await CompareObjectPropertiesFor(locals, "dtpa", new[]
+ {
+ TPointer("System.DateTime*"),
+ TPointer("System.DateTime*", is_null : true)
+ }));
+ {
+ var actual_elems = await CheckArrayElements(dtpa_elems, new[]
+ {
+ TValueType("System.DateTime", dt.ToString()),
+ null
+ });
+
+ await CheckDateTime(actual_elems[0], "*[0]", dt);
+ }
+
+ // dtppa = new DateTime**[] { &dtp, &dtp_null, null };
+ var dtppa_elems = (await CompareObjectPropertiesFor(locals, "dtppa", new[]
+ {
+ TPointer("System.DateTime**"),
+ TPointer("System.DateTime**"),
+ TPointer("System.DateTime**", is_null : true)
+ }));
+
+ var exp_elems = new[]
+ {
+ TPointer("System.DateTime*"),
+ TPointer("System.DateTime*", is_null : true),
+ null
+ };
+
+ await CheckArrayElements(dtppa_elems, exp_elems);
+ });
+
+ [Theory]
+ [InlineData("invoke_static_method ('[debugger-test] Math:UseComplex', 0, 0);", "Math", "UseComplex", 3, "UseComplex", false)]
+ [InlineData("invoke_static_method ('[debugger-test] Math:UseComplex', 0, 0);", "Math", "UseComplex", 3, "UseComplex", true)]
+ public async Task DerefNonPointerObject(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,
+ "window.setTimeout(function() { " + eval_fn + " })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) =>
+ {
+
+ // this will generate the object ids
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ var complex = GetAndAssertObjectWithName(locals, "complex");
+
+ // try to deref the non-pointer object, as a pointer
+ await GetProperties(complex["value"]["objectId"].Value<string>().Replace(":object:", ":pointer:"), expect_ok: false);
+
+ // try to deref an invalid pointer id
+ await GetProperties("dotnet:pointer:123897", expect_ok: false);
+ });
+
+ async Task<JToken[]> CheckArrayElements(JToken array, JToken[] exp_elems)
+ {
+ var actual_elems = new JToken[exp_elems.Length];
+ for (int i = 0; i < exp_elems.Length; i++)
+ {
+ if (exp_elems[i] != null)
+ {
+ actual_elems[i] = await GetObjectOnLocals(array, i.ToString());
+ await CheckPointerValue(actual_elems[i], $"*[{i}]", exp_elems[i], $"dtppa->");
+ }
+ }
+
+ return actual_elems;
+ }
+ }
+}
diff --git a/sdks/wasm/DebuggerTestSuite/Support.cs b/sdks/wasm/DebuggerTestSuite/Support.cs
index efa16109087..d7df3358991 100644
--- a/sdks/wasm/DebuggerTestSuite/Support.cs
+++ b/sdks/wasm/DebuggerTestSuite/Support.cs
@@ -1,915 +1,1039 @@
+// 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.IO;
using System.Linq;
-using System.Threading.Tasks;
-
using System.Net.WebSockets;
-using System.Threading;
-using System.IO;
using System.Text;
-using System.Collections.Generic;
-
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
-using WebAssembly.Net.Debugging;
+using Microsoft.WebAssembly.Diagnostics;
+using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;
namespace DebuggerTests
{
- class Inspector
- {
- // InspectorClient client;
- Dictionary<string, TaskCompletionSource<JObject>> notifications = new Dictionary<string, TaskCompletionSource<JObject>> ();
- Dictionary<string, Func<JObject, CancellationToken, Task>> eventListeners = new Dictionary<string, Func<JObject, CancellationToken, Task>> ();
-
- public const string PAUSE = "pause";
- public const string READY = "ready";
-
- public Task<JObject> WaitFor(string what) {
- if (notifications.ContainsKey (what))
- throw new Exception ($"Invalid internal state, waiting for {what} while another wait is already setup");
- var n = new TaskCompletionSource<JObject> ();
- notifications [what] = n;
- return n.Task;
- }
-
- void NotifyOf (string what, JObject args) {
- if (!notifications.ContainsKey (what))
- throw new Exception ($"Invalid internal state, notifying of {what}, but nobody waiting");
- notifications [what].SetResult (args);
- notifications.Remove (what);
- }
-
- public void On(string evtName, Func<JObject, CancellationToken, Task> cb) {
- eventListeners[evtName] = cb;
- }
-
- void FailAllWaitersWithException (JObject exception)
- {
- foreach (var tcs in notifications.Values)
- tcs.SetException (new ArgumentException (exception.ToString ()));
- }
-
- async Task OnMessage(string method, JObject args, CancellationToken token)
- {
- //System.Console.WriteLine("OnMessage " + method + args);
- switch (method) {
- case "Debugger.paused":
- NotifyOf (PAUSE, args);
- break;
- case "Mono.runtimeReady":
- NotifyOf (READY, args);
- break;
- case "Runtime.consoleAPICalled":
- Console.WriteLine ("CWL: {0}", args? ["args"]? [0]? ["value"]);
- break;
- }
- if (eventListeners.ContainsKey (method))
- await eventListeners[method](args, token);
- else if (String.Compare (method, "Runtime.exceptionThrown") == 0)
- FailAllWaitersWithException (args);
- }
-
- public async Task Ready (Func<InspectorClient, CancellationToken, Task> cb = null, TimeSpan? span = null) {
- using (var cts = new CancellationTokenSource ()) {
- cts.CancelAfter (span?.Milliseconds ?? 60 * 1000); //tests have 1 minute to complete by default
- var uri = new Uri ($"ws://{TestHarnessProxy.Endpoint.Authority}/launch-chrome-and-connect");
- using var loggerFactory = LoggerFactory.Create(
- builder => builder.AddConsole().AddFilter(null, LogLevel.Information));
- using (var client = new InspectorClient (loggerFactory.CreateLogger<Inspector>())) {
- await client.Connect (uri, OnMessage, async token => {
- Task[] init_cmds = {
- client.SendCommand ("Profiler.enable", null, token),
- client.SendCommand ("Runtime.enable", null, token),
- client.SendCommand ("Debugger.enable", null, token),
- client.SendCommand ("Runtime.runIfWaitingForDebugger", null, token),
- WaitFor (READY),
- };
- // await Task.WhenAll (init_cmds);
- Console.WriteLine ("waiting for the runtime to be ready");
- await init_cmds [4];
- Console.WriteLine ("runtime ready, TEST TIME");
- if (cb != null) {
- Console.WriteLine("await cb(client, token)");
- await cb(client, token);
- }
-
- }, cts.Token);
- await client.Close (cts.Token);
- }
- }
- }
- }
-
- public class DebuggerTestBase {
- protected Task startTask;
-
- static string FindTestPath () {
- //FIXME how would I locate it otherwise?
- var test_path = Environment.GetEnvironmentVariable ("TEST_SUITE_PATH");
- //Lets try to guest
- if (test_path != null && Directory.Exists (test_path))
- return test_path;
-
- var cwd = Environment.CurrentDirectory;
- Console.WriteLine ("guessing from {0}", cwd);
- //tests run from DebuggerTestSuite/bin/Debug/netcoreapp2.1
- var new_path = Path.Combine (cwd, "../../../../bin/debugger-test-suite");
- if (File.Exists (Path.Combine (new_path, "debugger-driver.html")))
- return new_path;
-
- throw new Exception ("Missing TEST_SUITE_PATH env var and could not guess path from CWD");
- }
-
- static string[] PROBE_LIST = {
- "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
- "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
- "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
- "/usr/bin/chromium",
- "/usr/bin/chromium-browser",
- };
- static string chrome_path;
-
- static string FindChromePath ()
- {
- if (chrome_path != null)
- return chrome_path;
-
- foreach (var s in PROBE_LIST){
- if (File.Exists (s)) {
- chrome_path = s;
- Console.WriteLine($"Using chrome path: ${s}");
- return s;
- }
- }
- throw new Exception ("Could not find an installed Chrome to use");
- }
-
- public DebuggerTestBase (string driver = "debugger-driver.html") {
- startTask = TestHarnessProxy.Start (FindChromePath (), FindTestPath (), driver);
- }
-
- public Task Ready ()
- => startTask;
-
- internal DebugTestContext ctx;
- internal Dictionary<string, string> dicScriptsIdToUrl;
- internal Dictionary<string, string> dicFileToUrl;
- internal Dictionary<string, string> SubscribeToScripts (Inspector insp) {
- dicScriptsIdToUrl = new Dictionary<string, string> ();
- dicFileToUrl = new Dictionary<string, string>();
- insp.On("Debugger.scriptParsed", async (args, c) => {
- var script_id = args? ["scriptId"]?.Value<string> ();
- var url = args["url"]?.Value<string> ();
- if (script_id.StartsWith("dotnet://"))
- {
- var dbgUrl = args["dotNetUrl"]?.Value<string>();
- var arrStr = dbgUrl.Split("/");
- dbgUrl = arrStr[0] + "/" + arrStr[1] + "/" + arrStr[2] + "/" + arrStr[arrStr.Length - 1];
- dicScriptsIdToUrl[script_id] = dbgUrl;
- dicFileToUrl[dbgUrl] = args["url"]?.Value<string>();
- } else if (!String.IsNullOrEmpty (url)) {
- dicFileToUrl[new Uri (url).AbsolutePath] = url;
- }
- await Task.FromResult (0);
- });
- return dicScriptsIdToUrl;
- }
-
- internal async Task CheckInspectLocalsAtBreakpointSite (string url_key, int line, int column, string function_name, string eval_expression,
- Action<JToken> test_fn = null, Func<JObject, Task> wait_for_event_fn = null, bool use_cfo = false)
- {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready ();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
-
- var bp = await SetBreakpoint (url_key, line, column);
-
- await EvaluateAndCheck (
- eval_expression, url_key, line, column,
- function_name,
- wait_for_event_fn: async (pause_location) => {
- //make sure we're on the right bp
-
- Assert.Equal (bp.Value ["breakpointId"]?.ToString (), pause_location ["hitBreakpoints"]?[0]?.Value<string> ());
-
- var top_frame = pause_location ["callFrames"][0];
-
- var scope = top_frame ["scopeChain"][0];
- Assert.Equal ("dotnet:scope:0", scope ["object"]["objectId"]);
- if (wait_for_event_fn != null)
- await wait_for_event_fn(pause_location);
- else
- await Task.CompletedTask;
- },
- locals_fn: (locals) => {
- if (test_fn != null)
- test_fn (locals);
- }
- );
- });
- }
-
- // sets breakpoint by method name and line offset
- internal async Task CheckInspectLocalsAtBreakpointSite (string type, string method, int line_offset, string bp_function_name, string eval_expression,
- Action<JToken> locals_fn = null, Func<JObject, Task> wait_for_event_fn = null, bool use_cfo = false, string assembly="debugger-test.dll", int col = 0)
- {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready ();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
-
- var bp = await SetBreakpointInMethod (assembly, type, method, line_offset, col);
-
- var args = JObject.FromObject (new { expression = eval_expression });
- var res = await ctx.cli.SendCommand ("Runtime.evaluate", args, ctx.token);
- if (!res.IsOk) {
- Console.WriteLine ($"Failed to run command {method} with args: {args?.ToString ()}\nresult: {res.Error.ToString ()}");
- Assert.True (false, $"SendCommand for {method} failed with {res.Error.ToString ()}");
- }
-
- var pause_location = await ctx.insp.WaitFor (Inspector.PAUSE);
-
- if (bp_function_name != null)
- Assert.Equal (bp_function_name, pause_location ["callFrames"]?[0]?["functionName"]?.Value<string> ());
-
- Assert.Equal (bp.Value ["breakpointId"]?.ToString (), pause_location ["hitBreakpoints"]?[0]?.Value<string> ());
-
- var top_frame = pause_location ["callFrames"][0];
-
- var scope = top_frame ["scopeChain"][0];
- Assert.Equal ("dotnet:scope:0", scope ["object"]["objectId"]);
-
- if (wait_for_event_fn != null)
- await wait_for_event_fn (pause_location);
-
- if (locals_fn != null) {
- var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string> ());
- locals_fn (locals);
- }
- });
- }
-
- internal void CheckLocation (string script_loc, int line, int column, Dictionary<string, string> scripts, JToken location)
- {
- var loc_str = $"{ scripts[location["scriptId"].Value<string>()] }"
- + $"#{ location ["lineNumber"].Value<int> () }"
- + $"#{ location ["columnNumber"].Value<int> () }";
-
- var expected_loc_str = $"{script_loc}#{line}#{column}";
- Assert.Equal (expected_loc_str, loc_str);
- }
-
- internal void CheckNumber<T> (JToken locals, string name, T value) {
- foreach (var l in locals) {
- if (name != l["name"]?.Value<string> ())
- continue;
- var val = l["value"];
- Assert.Equal ("number", val ["type"]?.Value<string> ());
- Assert.Equal (value, val["value"].Value <T> ());
- return;
- }
- Assert.True(false, $"Could not find variable '{name}'");
- }
-
- internal void CheckString (JToken locals, string name, string value) {
- foreach (var l in locals) {
- if (name != l["name"]?.Value<string> ())
- continue;
- var val = l["value"];
- if (value == null) {
- Assert.Equal ("object", val ["type"]?.Value<string> ());
- Assert.Equal ("null", val["subtype"]?.Value<string> ());
- } else {
- Assert.Equal ("string", val ["type"]?.Value<string> ());
- Assert.Equal (value, val["value"]?.Value <string> ());
- }
- return;
- }
- Assert.True(false, $"Could not find variable '{name}'");
- }
-
- internal JToken CheckSymbol (JToken locals, string name, string value)
- {
- var l = GetAndAssertObjectWithName (locals, name);
- var val = l["value"];
- Assert.Equal ("symbol", val ["type"]?.Value<string> ());
- Assert.Equal (value, val ["value"]?.Value<string> ());
- return l;
- }
-
- internal JToken CheckObject (JToken locals, string name, string class_name, string subtype=null, bool is_null=false) {
- var l = GetAndAssertObjectWithName (locals, name);
- var val = l["value"];
- Assert.Equal ("object", val ["type"]?.Value<string> ());
- Assert.True (val ["isValueType"] == null || !val ["isValueType"].Value<bool> ());
- Assert.Equal (class_name, val ["className"]?.Value<string> ());
-
- var has_null_subtype = val ["subtype"] != null && val ["subtype"]?.Value<string> () == "null";
- Assert.Equal (is_null, has_null_subtype);
- if (subtype != null)
- Assert.Equal (subtype, val ["subtype"]?.Value<string> ());
-
- return l;
- }
-
- internal async Task<JToken> CheckPointerValue (JToken locals, string name, JToken expected, string label = null)
- {
- var l = GetAndAssertObjectWithName (locals, name);
- await CheckValue (l ["value"], expected, $"{label ?? String.Empty}-{name}");
- return l;
- }
-
- internal async Task CheckDateTime (JToken locals, string name, DateTime expected)
- {
- var obj = GetAndAssertObjectWithName(locals, name);
- await CheckDateTimeValue (obj ["value"], expected);
- }
-
- internal async Task CheckDateTimeValue (JToken value, DateTime expected)
- {
- AssertEqual ("System.DateTime", value ["className"]?.Value<string> (), "className");
- AssertEqual (expected.ToString (), value ["description"]?.Value<string> (), "description");
-
- var members = await GetProperties (value ["objectId"]?.Value<string> ());
-
- // not checking everything
- CheckNumber (members, "Year", expected.Year);
- CheckNumber (members, "Month", expected.Month);
- CheckNumber (members, "Day", expected.Day);
- CheckNumber (members, "Hour", expected.Hour);
- CheckNumber (members, "Minute", expected.Minute);
- CheckNumber (members, "Second", expected.Second);
-
- // FIXME: check some float properties too
- }
-
- internal JToken CheckBool (JToken locals, string name, bool expected)
- {
- var l = GetAndAssertObjectWithName (locals, name);
- var val = l["value"];
- Assert.Equal ("boolean", val ["type"]?.Value<string> ());
- if (val ["value"] == null)
- Assert.True (false, "expected bool value not found for variable named {name}");
- Assert.Equal (expected, val ["value"]?.Value<bool> ());
-
- return l;
- }
-
- internal void CheckContentValue (JToken token, string value) {
- var val = token["value"].Value<string> ();
- Assert.Equal (value, val);
- }
-
- internal JToken CheckValueType (JToken locals, string name, string class_name) {
- var l = GetAndAssertObjectWithName (locals, name);
- var val = l["value"];
- Assert.Equal ("object", val ["type"]?.Value<string> ());
- Assert.True (val ["isValueType"] != null && val ["isValueType"].Value<bool> ());
- Assert.Equal (class_name, val ["className"]?.Value<string> ());
- return l;
- }
-
- internal JToken CheckEnum (JToken locals, string name, string class_name, string descr) {
- var l = GetAndAssertObjectWithName (locals, name);
- var val = l["value"];
- Assert.Equal ("object", val ["type"]?.Value<string> ());
- Assert.True (val ["isEnum"] != null && val ["isEnum"].Value<bool> ());
- Assert.Equal (class_name, val ["className"]?.Value<string> ());
- Assert.Equal (descr, val ["description"]?.Value<string> ());
- return l;
- }
-
- internal void CheckArray (JToken locals, string name, string class_name) {
- foreach (var l in locals) {
- if (name != l["name"]?.Value<string> ())
- continue;
-
- var val = l["value"];
- Assert.Equal ("object", val ["type"]?.Value<string> ());
- Assert.Equal ("array", val ["subtype"]?.Value<string> ());
- Assert.Equal (class_name, val ["className"]?.Value<string> ());
-
- //FIXME: elements?
- return;
- }
- Assert.True(false, $"Could not find variable '{name}'");
- }
-
- internal JToken GetAndAssertObjectWithName (JToken obj, string name)
- {
- var l = obj.FirstOrDefault (jt => jt ["name"]?.Value<string> () == name);
- if (l == null)
- Assert.True (false, $"Could not find variable '{name}'");
- return l;
- }
-
- internal async Task<Result> SendCommand (string method, JObject args) {
- var res = await ctx.cli.SendCommand (method, args, ctx.token);
- if (!res.IsOk) {
- Console.WriteLine ($"Failed to run command {method} with args: {args?.ToString ()}\nresult: {res.Error.ToString ()}");
- Assert.True (false, $"SendCommand for {method} failed with {res.Error.ToString ()}");
- }
- return res;
- }
-
- internal async Task<Result> Evaluate (string expression) {
- return await SendCommand ("Runtime.evaluate", JObject.FromObject (new { expression = expression }));
- }
-
- internal void AssertLocation (JObject args, string methodName) {
- Assert.Equal (methodName, args ["callFrames"]?[0]?["functionName"]?.Value<string> ());
- }
-
- // Place a breakpoint in the given method and run until its hit
- // Return the Debugger.paused data
- internal async Task<JObject> RunUntil (string methodName) {
- await SetBreakpointInMethod ("debugger-test", "DebuggerTest", methodName);
- // This will run all the tests until it hits the bp
- await Evaluate ("window.setTimeout(function() { invoke_run_all (); }, 1);");
- var wait_res = await ctx.insp.WaitFor (Inspector.PAUSE);
- AssertLocation (wait_res, "locals_inner");
- return wait_res;
- }
-
- internal async Task<JObject> StepAndCheck (StepKind kind, string script_loc, int line, int column, string function_name,
- Func<JObject, Task> wait_for_event_fn = null, Action<JToken> locals_fn = null, int times=1)
- {
- for (int i = 0; i < times - 1; i ++) {
- await SendCommandAndCheck (null, $"Debugger.step{kind.ToString ()}", null, -1, -1, null);
- }
-
- // Check for method/line etc only at the last step
- return await SendCommandAndCheck (
- null, $"Debugger.step{kind.ToString ()}", script_loc, line, column, function_name,
- wait_for_event_fn: wait_for_event_fn,
- locals_fn: locals_fn);
- }
-
- internal async Task<JObject> EvaluateAndCheck (string expression, string script_loc, int line, int column, string function_name,
- Func<JObject, Task> wait_for_event_fn = null, Action<JToken> locals_fn = null)
- => await SendCommandAndCheck (
- JObject.FromObject (new { expression = expression }),
- "Runtime.evaluate", script_loc, line, column, function_name,
- wait_for_event_fn: wait_for_event_fn,
- locals_fn: locals_fn);
-
- internal async Task<JObject> SendCommandAndCheck (JObject args, string method, string script_loc, int line, int column, string function_name,
- Func<JObject, Task> wait_for_event_fn = null, Action<JToken> locals_fn = null, string waitForEvent = Inspector.PAUSE)
- {
- var res = await ctx.cli.SendCommand (method, args, ctx.token);
- if (!res.IsOk) {
- Console.WriteLine ($"Failed to run command {method} with args: {args?.ToString ()}\nresult: {res.Error.ToString ()}");
- Assert.True (false, $"SendCommand for {method} failed with {res.Error.ToString ()}");
- }
-
- var wait_res = await ctx.insp.WaitFor(waitForEvent);
-
- if (function_name != null)
- Assert.Equal (function_name, wait_res ["callFrames"]?[0]?["functionName"]?.Value<string> ());
-
- if (script_loc != null)
- CheckLocation (script_loc, line, column, ctx.scripts, wait_res ["callFrames"][0]["location"]);
-
- if (wait_for_event_fn != null)
- await wait_for_event_fn (wait_res);
-
- if (locals_fn != null) {
- var locals = await GetProperties (wait_res ["callFrames"][0]["callFrameId"].Value<string> ());
- locals_fn (locals);
- }
-
- return wait_res;
- }
-
- internal async Task CheckDelegate (JToken locals, string name, string className, string target)
- {
- var l = GetAndAssertObjectWithName (locals, name);
- var val = l["value"];
-
- await CheckDelegate (l, TDelegate (className, target), name);
- }
-
- internal async Task CheckDelegate (JToken actual_val, JToken exp_val, string label)
- {
- AssertEqual ("object", actual_val["type"]?.Value<string>(), $"{label}-type");
- AssertEqual (exp_val ["className"]?.Value<string> (), actual_val ["className"]?.Value<string>(), $"{label}-className");
-
- var actual_target = actual_val["description"]?.Value<string>();
- Assert.True(actual_target != null, $"${label}-description");
- var exp_target = exp_val["target"].Value<string>();
-
- CheckDelegateTarget (actual_target, exp_target);
-
- var del_props = await GetProperties(actual_val["objectId"]?.Value<string>());
- AssertEqual (1, del_props.Count(), $"${label}-delegate-properties-count");
-
- var obj = del_props.Where (jt => jt ["name"]?.Value<string> () == "Target").FirstOrDefault ();
- Assert.True (obj != null, $"[{label}] Property named 'Target' found found in delegate properties");
-
- AssertEqual("symbol", obj ["value"]?["type"]?.Value<string>(), $"{label}#Target#type");
- CheckDelegateTarget(obj ["value"]?["value"]?.Value<string>(), exp_target);
-
- return;
-
- void CheckDelegateTarget(string actual_target, string exp_target)
- {
- var parts = exp_target.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
- if (parts.Length == 1) {
- // not a generated method
- AssertEqual(exp_target, actual_target, $"{label}-description");
- } else {
- bool prefix = actual_target.StartsWith(parts[0], StringComparison.Ordinal);
- Assert.True(prefix, $"{label}-description, Expected target to start with '{parts[0]}'. Actual: '{actual_target}'");
-
- var remaining = actual_target.Substring(parts[0].Length);
- bool suffix = remaining.EndsWith(parts[1], StringComparison.Ordinal);
- Assert.True(prefix, $"{label}-description, Expected target to end with '{parts[1]}'. Actual: '{remaining}'");
- }
- }
- }
-
- internal async Task CheckCustomType (JToken actual_val, JToken exp_val, string label)
- {
- var ctype = exp_val["__custom_type"].Value<string>();
- switch (ctype) {
- case "delegate":
- await CheckDelegate (actual_val, exp_val, label);
- break;
-
- case "pointer": {
-
- if (exp_val ["is_null"]?.Value<bool>() == true) {
- AssertEqual ("symbol", actual_val ["type"]?.Value<string>(), $"{label}-type");
-
- var exp_val_str = $"({exp_val ["type_name"]?.Value<string>()}) 0";
- AssertEqual (exp_val_str, actual_val ["value"]?.Value<string> (), $"{label}-value");
- AssertEqual (exp_val_str, actual_val ["description"]?.Value<string> (), $"{label}-description");
- } else if (exp_val ["is_void"]?.Value<bool> () == true) {
- AssertEqual ("symbol", actual_val ["type"]?.Value<string>(), $"{label}-type");
-
- var exp_val_str = $"({exp_val ["type_name"]?.Value<string>()})";
- AssertStartsWith (exp_val_str, actual_val ["value"]?.Value<string> (), $"{label}-value");
- AssertStartsWith (exp_val_str, actual_val ["description"]?.Value<string> (), $"{label}-description");
- } else {
- AssertEqual ("object", actual_val ["type"]?.Value<string>(), $"{label}-type");
-
- var exp_prefix = $"({exp_val ["type_name"]?.Value<string>()})";
- AssertStartsWith (exp_prefix, actual_val ["className"]?.Value<string> (), $"{label}-className");
- AssertStartsWith (exp_prefix, actual_val ["description"]?.Value<string> (), $"{label}-description");
- Assert.False (actual_val ["className"]?.Value<string> () == $"{exp_prefix} 0", $"[{label}] Expected a non-null value, but got {actual_val}");
- }
- break;
- }
-
- case "getter": {
- // For getter, `actual_val` is not `.value`, instead it's the container object
- // which has a `.get` instead of a `.value`
- var get = actual_val ["get"];
- Assert.True (get != null, $"[{label}] No `get` found. {(actual_val != null ? "Make sure to pass the container object for testing getters, and not the ['value']": String.Empty)}");
-
- AssertEqual ("Function", get ["className"]?.Value<string> (), $"{label}-className");
- AssertStartsWith ($"get {exp_val ["type_name"]?.Value<string> ()} ()", get ["description"]?.Value<string> (), $"{label}-description");
- AssertEqual ("function", get ["type"]?.Value<string> (), $"{label}-type");
-
- break;
- }
-
- case "ignore_me":
- // nothing to check ;)
- break;
-
- default:
- throw new ArgumentException($"{ctype} not supported");
- }
- }
-
- internal async Task CheckProps (JToken actual, object exp_o, string label, int num_fields=-1)
- {
- if (exp_o.GetType ().IsArray || exp_o is JArray) {
- if (! (actual is JArray actual_arr)) {
- Assert.True (false, $"[{label}] Expected to get an array here but got {actual}");
- return;
- }
-
- var exp_v_arr = JArray.FromObject (exp_o);
- AssertEqual (exp_v_arr.Count, actual_arr.Count (), $"{label}-count");
-
- for (int i = 0; i < exp_v_arr.Count; i ++) {
- var exp_i = exp_v_arr [i];
- var act_i = actual_arr [i];
-
- AssertEqual (i.ToString (), act_i ["name"]?.Value<string> (), $"{label}-[{i}].name");
- if (exp_i != null)
- await CheckValue (act_i["value"], exp_i, $"{label}-{i}th value");
- }
-
- return;
- }
-
- // Not an array
- var exp = exp_o as JObject;
- if (exp == null)
- exp = JObject.FromObject(exp_o);
-
- num_fields = num_fields < 0 ? exp.Values<JToken>().Count() : num_fields;
- Assert.True(num_fields == actual.Count(), $"[{label}] Number of fields don't match, Expected: {num_fields}, Actual: {actual.Count()}");
-
- foreach (var kvp in exp) {
- var exp_name = kvp.Key;
- var exp_val = kvp.Value;
-
- var actual_obj = actual.FirstOrDefault(jt => jt["name"]?.Value<string>() == exp_name);
- if (actual_obj == null) {
- Assert.True(actual_obj != null, $"[{label}] Could not find property named '{exp_name}'");
- }
-
- Assert.True(actual_obj != null, $"[{label}] not value found for property named '{exp_name}'");
-
- var actual_val = actual_obj["value"];
- if (exp_val.Type == JTokenType.Array) {
- var actual_props = await GetProperties(actual_val["objectId"]?.Value<string>());
- await CheckProps (actual_props, exp_val, $"{label}-{exp_name}");
- } else if (exp_val ["__custom_type"] != null && exp_val ["__custom_type"]?.Value<string> () == "getter") {
- // hack: for getters, actual won't have a .value
- await CheckCustomType (actual_obj, exp_val, $"{label}#{exp_name}");
- } else {
- await CheckValue (actual_val, exp_val, $"{label}#{exp_name}");
- }
- }
- }
-
- internal async Task CheckValue (JToken actual_val, JToken exp_val, string label)
- {
- if (exp_val ["__custom_type"] != null) {
- await CheckCustomType (actual_val, exp_val, label);
- return;
- }
-
- if (exp_val ["type"] == null && actual_val ["objectId"] != null) {
- var new_val = await GetProperties (actual_val ["objectId"].Value<string> ());
- await CheckProps (new_val, exp_val, $"{label}-{actual_val["objectId"]?.Value<string>()}");
- return;
- }
-
- foreach (var jp in exp_val.Values<JProperty> ()) {
- if (jp.Value.Type == JTokenType.Object) {
- var new_val = await GetProperties (actual_val ["objectId"].Value<string> ());
- await CheckProps (new_val, jp.Value, $"{label}-{actual_val["objectId"]?.Value<string>()}");
-
- continue;
- }
-
- var exp_val_str = jp.Value.Value<string> ();
- bool null_or_empty_exp_val = String.IsNullOrEmpty (exp_val_str);
-
- var actual_field_val = actual_val.Values<JProperty> ().FirstOrDefault (a_jp => a_jp.Name == jp.Name);
- var actual_field_val_str = actual_field_val?.Value?.Value<string> ();
- if (null_or_empty_exp_val && String.IsNullOrEmpty (actual_field_val_str))
- continue;
-
- Assert.True (actual_field_val != null, $"[{label}] Could not find value field named {jp.Name}");
-
- Assert.True (exp_val_str == actual_field_val_str,
- $"[{label}] Value for json property named {jp.Name} didn't match.\n" +
- $"Expected: {jp.Value.Value<string> ()}\n" +
- $"Actual: {actual_field_val.Value.Value<string> ()}");
- }
- }
-
- internal async Task<JToken> GetLocalsForFrame (JToken frame, string script_loc, int line, int column, string function_name)
- {
- CheckLocation (script_loc, line, column, ctx.scripts, frame ["location"]);
- Assert.Equal (function_name, frame ["functionName"].Value<string> ());
-
- return await GetProperties (frame ["callFrameId"].Value<string> ());
- }
-
- internal async Task<JToken> GetObjectOnFrame (JToken frame, string name)
- {
- var locals = await GetProperties (frame ["callFrameId"].Value<string> ());
- return await GetObjectOnLocals (locals, name);
- }
-
- // Find an object with @name, *fetch* the object, and check against @o
- internal async Task<JToken> CompareObjectPropertiesFor (JToken locals, string name, object o, string label = null, int num_fields = -1)
- {
- if (label == null)
- label = name;
- var props = await GetObjectOnLocals (locals, name);
- try {
- if (o != null)
- await CheckProps (props, o, label, num_fields);
- return props;
- } catch {
- throw;
- }
- }
-
- internal async Task<JToken> GetObjectOnLocals (JToken locals, string name)
- {
- var obj = GetAndAssertObjectWithName (locals, name);
- var objectId = obj ["value"]["objectId"]?.Value<string> ();
- Assert.True (!String.IsNullOrEmpty (objectId), $"No objectId found for {name}");
-
- return await GetProperties (objectId);
- }
-
- /* @fn_args is for use with `Runtime.callFunctionOn` only */
- internal async Task<JToken> GetProperties (string id, JToken fn_args = null)
- {
- if (ctx.UseCallFunctionOnBeforeGetProperties && !id.StartsWith ("dotnet:scope:")) {
- var fn_decl = "function () { return this; }";
- var cfo_args = JObject.FromObject (new {
- functionDeclaration = fn_decl,
- objectId = id
- });
- if (fn_args != null)
- cfo_args ["arguments"] = fn_args;
-
- var result = await ctx.cli.SendCommand ("Runtime.callFunctionOn", cfo_args, ctx.token);
- AssertEqual (true, result.IsOk, $"Runtime.getProperties failed for {cfo_args.ToString ()}, with Result: {result}");
- id = result.Value ["result"]?["objectId"]?.Value<string> ();
- }
-
- var get_prop_req = JObject.FromObject (new {
- objectId = id
- });
-
- var frame_props = await ctx.cli.SendCommand ("Runtime.getProperties", get_prop_req, ctx.token);
- if (!frame_props.IsOk)
- Assert.True (false, $"Runtime.getProperties failed for {get_prop_req.ToString ()}, with Result: {frame_props}");
-
- var locals = frame_props.Value ["result"];
- // FIXME: Should be done when generating the list in library_mono.js, but not sure yet
- // whether to remove it, and how to do it correctly.
- if (locals is JArray) {
- foreach (var p in locals) {
- if (p ["name"]?.Value<string> () == "length" && p ["enumerable"]?.Value<bool> () != true) {
- p.Remove ();
- break;
- }
- }
- }
-
- return locals;
- }
-
- internal async Task<JToken> EvaluateOnCallFrame (string id, string expression)
- {
- var evaluate_req = JObject.FromObject (new {
- callFrameId = id,
- expression = expression
- });
-
- var frame_evaluate = await ctx.cli.SendCommand ("Debugger.evaluateOnCallFrame", evaluate_req, ctx.token);
- if (!frame_evaluate.IsOk)
- Assert.True (false, $"Debugger.evaluateOnCallFrame failed for {evaluate_req.ToString ()}, with Result: {frame_evaluate}");
-
- var evaluate_result = frame_evaluate.Value ["result"];
- return evaluate_result;
- }
-
- internal async Task<Result> SetBreakpoint (string url_key, int line, int column, bool expect_ok=true, bool use_regex = false)
- {
- var bp1_req = !use_regex ?
- JObject.FromObject(new { lineNumber = line, columnNumber = column, url = dicFileToUrl[url_key],}) :
- JObject.FromObject(new { lineNumber = line, columnNumber = column, urlRegex = url_key, });
-
- var bp1_res = await ctx.cli.SendCommand ("Debugger.setBreakpointByUrl", bp1_req, ctx.token);
- Assert.True (expect_ok ? bp1_res.IsOk : bp1_res.IsErr);
-
- return bp1_res;
- }
-
- internal async Task<Result> SetBreakpointInMethod (string assembly, string type, string method, int lineOffset = 0, int col = 0) {
- var req = JObject.FromObject (new { assemblyName = assembly, typeName = type, methodName = method, lineOffset = lineOffset });
-
- // Protocol extension
- var res = await ctx.cli.SendCommand ("DotnetDebugger.getMethodLocation", req, ctx.token);
- Assert.True (res.IsOk);
-
- var m_url = res.Value ["result"]["url"].Value<string> ();
- var m_line = res.Value ["result"]["line"].Value<int> ();
-
- var bp1_req = JObject.FromObject(new {
- lineNumber = m_line + lineOffset,
- columnNumber = col,
- url = m_url
- });
-
- res = await ctx.cli.SendCommand ("Debugger.setBreakpointByUrl", bp1_req, ctx.token);
- Assert.True (res.IsOk);
-
- return res;
- }
-
- internal void AssertEqual (object expected, object actual, string label)
- => Assert.True (expected?.Equals (actual),
- $"[{label}]\n" +
- $"Expected: {expected?.ToString()}\n" +
- $"Actual: {actual?.ToString()}\n");
-
- internal void AssertStartsWith (string expected, string actual, string label)
- => Assert.True(actual?.StartsWith (expected), $"[{label}] Does not start with the expected string\nExpected: {expected}\nActual: {actual}");
-
- internal static Func<int, int, string, string, object> TSimpleClass = (X, Y, Id, Color) => new {
- X = TNumber (X),
- Y = TNumber (Y),
- Id = TString (Id),
- Color = TEnum ("DebuggerTests.RGB", Color),
- PointWithCustomGetter = TGetter ("PointWithCustomGetter")
- };
-
- internal static Func<int, int, string, string, object> TPoint = (X, Y, Id, Color) => new {
- X = TNumber (X),
- Y = TNumber (Y),
- Id = TString (Id),
- Color = TEnum ("DebuggerTests.RGB", Color),
- };
-
- //FIXME: um maybe we don't need to convert jobject right here!
- internal static JObject TString (string value) =>
- value == null
- ? TObject ("string", is_null: true)
- : JObject.FromObject (new { type = "string", value = @value, description = @value });
-
- internal static JObject TNumber (int value) =>
- JObject.FromObject (new { type = "number", value = @value.ToString (), description = value.ToString () });
-
- internal static JObject TValueType (string className, string description = null, object members = null) =>
- JObject.FromObject (new { type = "object", isValueType = true, className = className, description = description ?? className });
-
- internal static JObject TEnum (string className, string descr, object members = null) =>
- JObject.FromObject (new { type = "object", isEnum = true, className = className, description = descr });
-
- internal static JObject TObject (string className, string description = null, bool is_null = false) =>
- is_null
- ? JObject.FromObject (new { type = "object", className = className, description = description ?? className, subtype = is_null ? "null" : null })
- : JObject.FromObject (new { type = "object", className = className, description = description ?? className });
-
- internal static JObject TArray (string className, int length = 0)
- => JObject.FromObject (new { type = "object", className = className, description = $"{className}({length})", subtype = "array" });
-
- internal static JObject TBool (bool value)
- => JObject.FromObject (new { type = "boolean", value = @value, description = @value ? "true" : "false" });
-
- internal static JObject TSymbol (string value)
- => JObject.FromObject (new { type = "symbol", value = @value, description = @value });
-
- /*
- For target names with generated method names like
- `void <ActionTSignatureTest>b__11_0 (Math.GenericStruct<int[]>)`
-
- .. pass target "as `target: "void <ActionTSignatureTest>|(Math.GenericStruct<int[]>)"`
- */
- internal static JObject TDelegate(string className, string target)
- => JObject.FromObject(new {
- __custom_type = "delegate",
- className = className,
- target = target
- });
-
- internal static JObject TPointer (string type_name, bool is_null = false)
- => JObject.FromObject (new { __custom_type = "pointer", type_name = type_name, is_null = is_null, is_void = type_name.StartsWith ("void*") });
-
- internal static JObject TIgnore ()
- => JObject.FromObject (new { __custom_type = "ignore_me" });
-
- internal static JObject TGetter (string type)
- => JObject.FromObject (new { __custom_type = "getter", type_name = type });
- }
-
- class DebugTestContext
- {
- public InspectorClient cli;
- public Inspector insp;
- public CancellationToken token;
- public Dictionary<string, string> scripts;
-
- public bool UseCallFunctionOnBeforeGetProperties;
-
- public DebugTestContext (InspectorClient cli, Inspector insp, CancellationToken token, Dictionary<string, string> scripts)
- {
- this.cli = cli;
- this.insp = insp;
- this.token = token;
- this.scripts = scripts;
- }
- }
-
- enum StepKind
- {
- Into,
- Over,
- Out
- }
-}
+ class Inspector
+ {
+ // InspectorClient client;
+ Dictionary<string, TaskCompletionSource<JObject>> notifications = new Dictionary<string, TaskCompletionSource<JObject>>();
+ Dictionary<string, Func<JObject, CancellationToken, Task>> eventListeners = new Dictionary<string, Func<JObject, CancellationToken, Task>>();
+
+ public const string PAUSE = "pause";
+ public const string READY = "ready";
+
+ public Task<JObject> WaitFor(string what)
+ {
+ if (notifications.ContainsKey(what))
+ throw new Exception($"Invalid internal state, waiting for {what} while another wait is already setup");
+ var n = new TaskCompletionSource<JObject>();
+ notifications[what] = n;
+ return n.Task;
+ }
+
+ void NotifyOf(string what, JObject args)
+ {
+ if (!notifications.ContainsKey(what))
+ throw new Exception($"Invalid internal state, notifying of {what}, but nobody waiting");
+ notifications[what].SetResult(args);
+ notifications.Remove(what);
+ }
+
+ public void On(string evtName, Func<JObject, CancellationToken, Task> cb)
+ {
+ eventListeners[evtName] = cb;
+ }
+
+ void FailAllWaitersWithException(JObject exception)
+ {
+ foreach (var tcs in notifications.Values)
+ tcs.SetException(new ArgumentException(exception.ToString()));
+ }
+
+ async Task OnMessage(string method, JObject args, CancellationToken token)
+ {
+ //System.Console.WriteLine("OnMessage " + method + args);
+ switch (method)
+ {
+ case "Debugger.paused":
+ NotifyOf(PAUSE, args);
+ break;
+ case "Mono.runtimeReady":
+ NotifyOf(READY, args);
+ break;
+ case "Runtime.consoleAPICalled":
+ Console.WriteLine("CWL: {0}", args?["args"]?[0]?["value"]);
+ break;
+ }
+ if (eventListeners.ContainsKey(method))
+ await eventListeners[method](args, token);
+ else if (String.Compare(method, "Runtime.exceptionThrown") == 0)
+ FailAllWaitersWithException(args);
+ }
+
+ public async Task Ready(Func<InspectorClient, CancellationToken, Task> cb = null, TimeSpan? span = null)
+ {
+ using (var cts = new CancellationTokenSource())
+ {
+ cts.CancelAfter(span?.Milliseconds ?? 60 * 1000); //tests have 1 minute to complete by default
+ var uri = new Uri($"ws://{TestHarnessProxy.Endpoint.Authority}/launch-chrome-and-connect");
+ using var loggerFactory = LoggerFactory.Create(
+ builder => builder.AddConsole().AddFilter(null, LogLevel.Information));
+ using (var client = new InspectorClient(loggerFactory.CreateLogger<Inspector>()))
+ {
+ await client.Connect(uri, OnMessage, async token =>
+ {
+ Task[] init_cmds = {
+ client.SendCommand("Profiler.enable", null, token),
+ client.SendCommand("Runtime.enable", null, token),
+ client.SendCommand("Debugger.enable", null, token),
+ client.SendCommand("Runtime.runIfWaitingForDebugger", null, token),
+ WaitFor(READY),
+ };
+ // await Task.WhenAll (init_cmds);
+ Console.WriteLine("waiting for the runtime to be ready");
+ await init_cmds[4];
+ Console.WriteLine("runtime ready, TEST TIME");
+ if (cb != null)
+ {
+ Console.WriteLine("await cb(client, token)");
+ await cb(client, token);
+ }
+
+ }, cts.Token);
+ await client.Close(cts.Token);
+ }
+ }
+ }
+ }
+
+ public class DebuggerTestBase
+ {
+ protected Task startTask;
+
+ static string FindTestPath()
+ {
+ //FIXME how would I locate it otherwise?
+ var test_path = Environment.GetEnvironmentVariable("TEST_SUITE_PATH");
+ //Lets try to guest
+ if (test_path != null && Directory.Exists(test_path))
+ return test_path;
+
+ var cwd = Environment.CurrentDirectory;
+ Console.WriteLine("guessing from {0}", cwd);
+ //tests run from DebuggerTestSuite/bin/Debug/netcoreapp2.1
+ var new_path = Path.Combine(cwd, "../../../../bin/debugger-test-suite");
+ if (File.Exists(Path.Combine(new_path, "debugger-driver.html")))
+ return new_path;
+
+ throw new Exception("Missing TEST_SUITE_PATH env var and could not guess path from CWD");
+ }
+
+ static string[] PROBE_LIST = {
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
+ "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
+ "/usr/bin/chromium",
+ "/usr/bin/chromium-browser",
+ };
+ static string chrome_path;
+
+ static string FindChromePath()
+ {
+ if (chrome_path != null)
+ return chrome_path;
+
+ foreach (var s in PROBE_LIST)
+ {
+ if (File.Exists(s))
+ {
+ chrome_path = s;
+ Console.WriteLine($"Using chrome path: ${s}");
+ return s;
+ }
+ }
+ throw new Exception("Could not find an installed Chrome to use");
+ }
+
+ public DebuggerTestBase(string driver = "debugger-driver.html")
+ {
+ startTask = TestHarnessProxy.Start(FindChromePath(), FindTestPath(), driver);
+ }
+
+ public Task Ready() => startTask;
+
+ internal DebugTestContext ctx;
+ internal Dictionary<string, string> dicScriptsIdToUrl;
+ internal Dictionary<string, string> dicFileToUrl;
+ internal Dictionary<string, string> SubscribeToScripts(Inspector insp)
+ {
+ dicScriptsIdToUrl = new Dictionary<string, string>();
+ dicFileToUrl = new Dictionary<string, string>();
+ insp.On("Debugger.scriptParsed", async (args, c) =>
+ {
+ var script_id = args?["scriptId"]?.Value<string>();
+ var url = args["url"]?.Value<string>();
+ if (script_id.StartsWith("dotnet://"))
+ {
+ var dbgUrl = args["dotNetUrl"]?.Value<string>();
+ var arrStr = dbgUrl.Split("/");
+ dbgUrl = arrStr[0] + "/" + arrStr[1] + "/" + arrStr[2] + "/" + arrStr[arrStr.Length - 1];
+ dicScriptsIdToUrl[script_id] = dbgUrl;
+ dicFileToUrl[dbgUrl] = args["url"]?.Value<string>();
+ }
+ else if (!String.IsNullOrEmpty(url))
+ {
+ dicFileToUrl[new Uri(url).AbsolutePath] = url;
+ }
+ await Task.FromResult(0);
+ });
+ return dicScriptsIdToUrl;
+ }
+
+ internal async Task CheckInspectLocalsAtBreakpointSite(string url_key, int line, int column, string function_name, string eval_expression,
+ Action<JToken> test_fn = null, Func<JObject, Task> wait_for_event_fn = null, bool use_cfo = false)
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
+
+ var bp = await SetBreakpoint(url_key, line, column);
+
+ await EvaluateAndCheck(
+ eval_expression, url_key, line, column,
+ function_name,
+ wait_for_event_fn: async (pause_location) =>
+ {
+ //make sure we're on the right bp
+
+ Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value<string>());
+
+ var top_frame = pause_location["callFrames"][0];
+
+ var scope = top_frame["scopeChain"][0];
+ Assert.Equal("dotnet:scope:0", scope["object"]["objectId"]);
+ if (wait_for_event_fn != null)
+ await wait_for_event_fn(pause_location);
+ else
+ await Task.CompletedTask;
+ },
+ locals_fn: (locals) =>
+ {
+ if (test_fn != null)
+ test_fn(locals);
+ }
+ );
+ });
+ }
+
+ // sets breakpoint by method name and line offset
+ internal async Task CheckInspectLocalsAtBreakpointSite(string type, string method, int line_offset, string bp_function_name, string eval_expression,
+ Action<JToken> locals_fn = null, Func<JObject, Task> wait_for_event_fn = null, bool use_cfo = false, string assembly = "debugger-test.dll", int col = 0)
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
+
+ var bp = await SetBreakpointInMethod(assembly, type, method, line_offset, col);
+
+ var args = JObject.FromObject(new { expression = eval_expression });
+ var res = await ctx.cli.SendCommand("Runtime.evaluate", args, ctx.token);
+ if (!res.IsOk)
+ {
+ Console.WriteLine($"Failed to run command {method} with args: {args?.ToString()}\nresult: {res.Error.ToString()}");
+ Assert.True(false, $"SendCommand for {method} failed with {res.Error.ToString()}");
+ }
+
+ var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE);
+
+ if (bp_function_name != null)
+ Assert.Equal(bp_function_name, pause_location["callFrames"]?[0]?["functionName"]?.Value<string>());
+
+ Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value<string>());
+
+ var top_frame = pause_location["callFrames"][0];
+
+ var scope = top_frame["scopeChain"][0];
+ Assert.Equal("dotnet:scope:0", scope["object"]["objectId"]);
+
+ if (wait_for_event_fn != null)
+ await wait_for_event_fn(pause_location);
+
+ if (locals_fn != null)
+ {
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ locals_fn(locals);
+ }
+ });
+ }
+
+ internal void CheckLocation(string script_loc, int line, int column, Dictionary<string, string> scripts, JToken location)
+ {
+ var loc_str = $"{ scripts[location["scriptId"].Value<string>()] }" +
+ $"#{ location["lineNumber"].Value<int>() }" +
+ $"#{ location["columnNumber"].Value<int>() }";
+
+ var expected_loc_str = $"{script_loc}#{line}#{column}";
+ Assert.Equal(expected_loc_str, loc_str);
+ }
+
+ internal void CheckNumber<T>(JToken locals, string name, T value)
+ {
+ foreach (var l in locals)
+ {
+ if (name != l["name"]?.Value<string>())
+ continue;
+ var val = l["value"];
+ Assert.Equal("number", val["type"]?.Value<string>());
+ Assert.Equal(value, val["value"].Value<T>());
+ Assert.Equal(value.ToString(), val["description"].Value<T>().ToString());
+ return;
+ }
+ Assert.True(false, $"Could not find variable '{name}'");
+ }
+
+ internal void CheckString(JToken locals, string name, string value)
+ {
+ var l = GetAndAssertObjectWithName(locals, name);
+ CheckValue(l["value"], TString(value), name).Wait();
+ }
+
+ internal JToken CheckSymbol(JToken locals, string name, string value)
+ {
+ var l = GetAndAssertObjectWithName(locals, name);
+ CheckValue(l["value"], TSymbol(value), name).Wait();
+ return l;
+ }
+
+ internal JToken CheckObject(JToken locals, string name, string class_name, string subtype = null, bool is_null = false)
+ {
+ var l = GetAndAssertObjectWithName(locals, name);
+ var val = l["value"];
+ CheckValue(val, TObject(class_name, is_null: is_null), name).Wait();
+ Assert.True(val["isValueType"] == null || !val["isValueType"].Value<bool>());
+
+ return l;
+ }
+
+ internal async Task<JToken> CheckPointerValue(JToken locals, string name, JToken expected, string label = null)
+ {
+ var l = GetAndAssertObjectWithName(locals, name);
+ await CheckValue(l["value"], expected, $"{label ?? String.Empty}-{name}");
+ return l;
+ }
+
+ internal async Task CheckDateTime(JToken locals, string name, DateTime expected)
+ {
+ var obj = GetAndAssertObjectWithName(locals, name);
+ await CheckDateTimeValue(obj["value"], expected);
+ }
+
+ internal async Task CheckDateTimeValue(JToken value, DateTime expected)
+ {
+ await CheckDateTimeMembers(value, expected);
+
+ var res = await InvokeGetter(JObject.FromObject(new { value = value }), "Date");
+ await CheckDateTimeMembers(res.Value["result"], expected.Date);
+
+ // FIXME: check some float properties too
+
+ async Task CheckDateTimeMembers(JToken v, DateTime exp_dt)
+ {
+ AssertEqual("System.DateTime", v["className"]?.Value<string>(), "className");
+ AssertEqual(exp_dt.ToString(), v["description"]?.Value<string>(), "description");
+
+ var members = await GetProperties(v["objectId"]?.Value<string>());
+
+ // not checking everything
+ CheckNumber(members, "Year", exp_dt.Year);
+ CheckNumber(members, "Month", exp_dt.Month);
+ CheckNumber(members, "Day", exp_dt.Day);
+ CheckNumber(members, "Hour", exp_dt.Hour);
+ CheckNumber(members, "Minute", exp_dt.Minute);
+ CheckNumber(members, "Second", exp_dt.Second);
+ }
+ }
+
+ internal JToken CheckBool(JToken locals, string name, bool expected)
+ {
+ var l = GetAndAssertObjectWithName(locals, name);
+ CheckValue(l["value"], TBool(expected), name).Wait();
+ return l;
+ }
+
+ internal void CheckContentValue(JToken token, string value)
+ {
+ var val = token["value"].Value<string>();
+ Assert.Equal(value, val);
+ }
+
+ internal JToken CheckValueType(JToken locals, string name, string class_name)
+ {
+ var l = GetAndAssertObjectWithName(locals, name);
+ CheckValue(l["value"], TValueType(class_name), name).Wait();
+ return l;
+ }
+
+ internal JToken CheckEnum(JToken locals, string name, string class_name, string descr)
+ {
+ var l = GetAndAssertObjectWithName(locals, name);
+ CheckValue(l["value"], TEnum(class_name, descr), name).Wait();
+ return l;
+ }
+
+ internal void CheckArray(JToken locals, string name, string class_name, int length)
+ => CheckValue(
+ GetAndAssertObjectWithName(locals, name)["value"],
+ TArray(class_name, length), name).Wait();
+
+ internal JToken GetAndAssertObjectWithName(JToken obj, string name)
+ {
+ var l = obj.FirstOrDefault(jt => jt["name"]?.Value<string>() == name);
+ if (l == null)
+ Assert.True(false, $"Could not find variable '{name}'");
+ return l;
+ }
+
+ internal async Task<Result> SendCommand(string method, JObject args)
+ {
+ var res = await ctx.cli.SendCommand(method, args, ctx.token);
+ if (!res.IsOk)
+ {
+ Console.WriteLine($"Failed to run command {method} with args: {args?.ToString()}\nresult: {res.Error.ToString()}");
+ Assert.True(false, $"SendCommand for {method} failed with {res.Error.ToString()}");
+ }
+ return res;
+ }
+
+ internal async Task<Result> Evaluate(string expression)
+ {
+ return await SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = expression }));
+ }
+
+ internal void AssertLocation(JObject args, string methodName)
+ {
+ Assert.Equal(methodName, args["callFrames"]?[0]?["functionName"]?.Value<string>());
+ }
+
+ // Place a breakpoint in the given method and run until its hit
+ // Return the Debugger.paused data
+ internal async Task<JObject> RunUntil(string methodName)
+ {
+ await SetBreakpointInMethod("debugger-test", "DebuggerTest", methodName);
+ // This will run all the tests until it hits the bp
+ await Evaluate("window.setTimeout(function() { invoke_run_all (); }, 1);");
+ var wait_res = await ctx.insp.WaitFor(Inspector.PAUSE);
+ AssertLocation(wait_res, "locals_inner");
+ return wait_res;
+ }
+
+ internal async Task<Result> InvokeGetter(JToken obj, object arguments, string fn = "function(e){return this[e]}", bool expect_ok = true, bool? returnByValue = null)
+ {
+ var req = JObject.FromObject(new
+ {
+ functionDeclaration = fn,
+ objectId = obj["value"]?["objectId"]?.Value<string>(),
+ arguments = new[] { new { value = arguments } }
+ });
+ if (returnByValue != null)
+ req["returnByValue"] = returnByValue.Value;
+
+ var res = await ctx.cli.SendCommand("Runtime.callFunctionOn", req, ctx.token);
+ Assert.True(expect_ok == res.IsOk, $"InvokeGetter failed for {req} with {res}");
+
+ return res;
+ }
+
+ internal async Task<JObject> StepAndCheck(StepKind kind, string script_loc, int line, int column, string function_name,
+ Func<JObject, Task> wait_for_event_fn = null, Action<JToken> locals_fn = null, int times = 1)
+ {
+ for (int i = 0; i < times - 1; i++)
+ {
+ await SendCommandAndCheck(null, $"Debugger.step{kind.ToString()}", null, -1, -1, null);
+ }
+
+ // Check for method/line etc only at the last step
+ return await SendCommandAndCheck(
+ null, $"Debugger.step{kind.ToString()}", script_loc, line, column, function_name,
+ wait_for_event_fn: wait_for_event_fn,
+ locals_fn: locals_fn);
+ }
+
+ internal async Task<JObject> EvaluateAndCheck(string expression, string script_loc, int line, int column, string function_name,
+ Func<JObject, Task> wait_for_event_fn = null, Action<JToken> locals_fn = null) => await SendCommandAndCheck(
+ JObject.FromObject(new { expression = expression }),
+ "Runtime.evaluate", script_loc, line, column, function_name,
+ wait_for_event_fn: wait_for_event_fn,
+ locals_fn: locals_fn);
+
+ internal async Task<JObject> SendCommandAndCheck(JObject args, string method, string script_loc, int line, int column, string function_name,
+ Func<JObject, Task> wait_for_event_fn = null, Action<JToken> locals_fn = null, string waitForEvent = Inspector.PAUSE)
+ {
+ var res = await ctx.cli.SendCommand(method, args, ctx.token);
+ if (!res.IsOk)
+ {
+ Console.WriteLine($"Failed to run command {method} with args: {args?.ToString()}\nresult: {res.Error.ToString()}");
+ Assert.True(false, $"SendCommand for {method} failed with {res.Error.ToString()}");
+ }
+
+ var wait_res = await ctx.insp.WaitFor(waitForEvent);
+
+ if (function_name != null)
+ Assert.Equal(function_name, wait_res["callFrames"]?[0]?["functionName"]?.Value<string>());
+
+ if (script_loc != null)
+ CheckLocation(script_loc, line, column, ctx.scripts, wait_res["callFrames"][0]["location"]);
+
+ if (wait_for_event_fn != null)
+ await wait_for_event_fn(wait_res);
+
+ if (locals_fn != null)
+ {
+ var locals = await GetProperties(wait_res["callFrames"][0]["callFrameId"].Value<string>());
+ locals_fn(locals);
+ }
+
+ return wait_res;
+ }
+
+ internal async Task CheckDelegate(JToken locals, string name, string className, string target)
+ {
+ var l = GetAndAssertObjectWithName(locals, name);
+ var val = l["value"];
+
+ await CheckDelegate(l, TDelegate(className, target), name);
+ }
+
+ internal async Task CheckDelegate(JToken actual_val, JToken exp_val, string label)
+ {
+ AssertEqual("object", actual_val["type"]?.Value<string>(), $"{label}-type");
+ AssertEqual(exp_val["className"]?.Value<string>(), actual_val["className"]?.Value<string>(), $"{label}-className");
+
+ var actual_target = actual_val["description"]?.Value<string>();
+ Assert.True(actual_target != null, $"${label}-description");
+ var exp_target = exp_val["target"].Value<string>();
+
+ CheckDelegateTarget(actual_target, exp_target);
+
+ var del_props = await GetProperties(actual_val["objectId"]?.Value<string>());
+ AssertEqual(1, del_props.Count(), $"${label}-delegate-properties-count");
+
+ var obj = del_props.Where(jt => jt["name"]?.Value<string>() == "Target").FirstOrDefault();
+ Assert.True(obj != null, $"[{label}] Property named 'Target' found found in delegate properties");
+
+ AssertEqual("symbol", obj["value"]?["type"]?.Value<string>(), $"{label}#Target#type");
+ CheckDelegateTarget(obj["value"]?["value"]?.Value<string>(), exp_target);
+
+ return;
+
+ void CheckDelegateTarget(string actual_target, string exp_target)
+ {
+ var parts = exp_target.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
+ if (parts.Length == 1)
+ {
+ // not a generated method
+ AssertEqual(exp_target, actual_target, $"{label}-description");
+ }
+ else
+ {
+ bool prefix = actual_target.StartsWith(parts[0], StringComparison.Ordinal);
+ Assert.True(prefix, $"{label}-description, Expected target to start with '{parts[0]}'. Actual: '{actual_target}'");
+
+ var remaining = actual_target.Substring(parts[0].Length);
+ bool suffix = remaining.EndsWith(parts[1], StringComparison.Ordinal);
+ Assert.True(prefix, $"{label}-description, Expected target to end with '{parts[1]}'. Actual: '{remaining}'");
+ }
+ }
+ }
+
+ internal async Task CheckCustomType(JToken actual_val, JToken exp_val, string label)
+ {
+ var ctype = exp_val["__custom_type"].Value<string>();
+ switch (ctype)
+ {
+ case "delegate":
+ await CheckDelegate(actual_val, exp_val, label);
+ break;
+
+ case "pointer":
+ {
+
+ if (exp_val["is_null"]?.Value<bool>() == true)
+ {
+ AssertEqual("symbol", actual_val["type"]?.Value<string>(), $"{label}-type");
+
+ var exp_val_str = $"({exp_val["type_name"]?.Value<string>()}) 0";
+ AssertEqual(exp_val_str, actual_val["value"]?.Value<string>(), $"{label}-value");
+ AssertEqual(exp_val_str, actual_val["description"]?.Value<string>(), $"{label}-description");
+ }
+ else if (exp_val["is_void"]?.Value<bool>() == true)
+ {
+ AssertEqual("symbol", actual_val["type"]?.Value<string>(), $"{label}-type");
+
+ var exp_val_str = $"({exp_val["type_name"]?.Value<string>()})";
+ AssertStartsWith(exp_val_str, actual_val["value"]?.Value<string>(), $"{label}-value");
+ AssertStartsWith(exp_val_str, actual_val["description"]?.Value<string>(), $"{label}-description");
+ }
+ else
+ {
+ AssertEqual("object", actual_val["type"]?.Value<string>(), $"{label}-type");
+
+ var exp_prefix = $"({exp_val["type_name"]?.Value<string>()})";
+ AssertStartsWith(exp_prefix, actual_val["className"]?.Value<string>(), $"{label}-className");
+ AssertStartsWith(exp_prefix, actual_val["description"]?.Value<string>(), $"{label}-description");
+ Assert.False(actual_val["className"]?.Value<string>() == $"{exp_prefix} 0", $"[{label}] Expected a non-null value, but got {actual_val}");
+ }
+ break;
+ }
+
+ case "getter":
+ {
+ // For getter, `actual_val` is not `.value`, instead it's the container object
+ // which has a `.get` instead of a `.value`
+ var get = actual_val["get"];
+ Assert.True(get != null, $"[{label}] No `get` found. {(actual_val != null ? "Make sure to pass the container object for testing getters, and not the ['value']" : String.Empty)}");
+
+ AssertEqual("Function", get["className"]?.Value<string>(), $"{label}-className");
+ AssertStartsWith($"get {exp_val["type_name"]?.Value<string>()} ()", get["description"]?.Value<string>(), $"{label}-description");
+ AssertEqual("function", get["type"]?.Value<string>(), $"{label}-type");
+
+ break;
+ }
+
+ case "ignore_me":
+ // nothing to check ;)
+ break;
+
+ default:
+ throw new ArgumentException($"{ctype} not supported");
+ }
+ }
+
+ internal async Task CheckProps(JToken actual, object exp_o, string label, int num_fields = -1)
+ {
+ if (exp_o.GetType().IsArray || exp_o is JArray)
+ {
+ if (!(actual is JArray actual_arr))
+ {
+ Assert.True(false, $"[{label}] Expected to get an array here but got {actual}");
+ return;
+ }
+
+ var exp_v_arr = JArray.FromObject(exp_o);
+ AssertEqual(exp_v_arr.Count, actual_arr.Count(), $"{label}-count");
+
+ for (int i = 0; i < exp_v_arr.Count; i++)
+ {
+ var exp_i = exp_v_arr[i];
+ var act_i = actual_arr[i];
+
+ AssertEqual(i.ToString(), act_i["name"]?.Value<string>(), $"{label}-[{i}].name");
+ if (exp_i != null)
+ await CheckValue(act_i["value"], exp_i, $"{label}-{i}th value");
+ }
+
+ return;
+ }
+
+ // Not an array
+ var exp = exp_o as JObject;
+ if (exp == null)
+ exp = JObject.FromObject(exp_o);
+
+ num_fields = num_fields < 0 ? exp.Values<JToken>().Count() : num_fields;
+ Assert.True(num_fields == actual.Count(), $"[{label}] Number of fields don't match, Expected: {num_fields}, Actual: {actual.Count()}");
+
+ foreach (var kvp in exp)
+ {
+ var exp_name = kvp.Key;
+ var exp_val = kvp.Value;
+
+ var actual_obj = actual.FirstOrDefault(jt => jt["name"]?.Value<string>() == exp_name);
+ if (actual_obj == null)
+ {
+ Assert.True(actual_obj != null, $"[{label}] Could not find property named '{exp_name}'");
+ }
+
+ Assert.True(actual_obj != null, $"[{label}] not value found for property named '{exp_name}'");
+
+ var actual_val = actual_obj["value"];
+ if (exp_val.Type == JTokenType.Array)
+ {
+ var actual_props = await GetProperties(actual_val["objectId"]?.Value<string>());
+ await CheckProps(actual_props, exp_val, $"{label}-{exp_name}");
+ }
+ else if (exp_val["__custom_type"] != null && exp_val["__custom_type"]?.Value<string>() == "getter")
+ {
+ // hack: for getters, actual won't have a .value
+ await CheckCustomType(actual_obj, exp_val, $"{label}#{exp_name}");
+ }
+ else
+ {
+ await CheckValue(actual_val, exp_val, $"{label}#{exp_name}");
+ }
+ }
+ }
+
+ internal async Task CheckValue(JToken actual_val, JToken exp_val, string label)
+ {
+ if (exp_val["__custom_type"] != null)
+ {
+ await CheckCustomType(actual_val, exp_val, label);
+ return;
+ }
+
+ if (exp_val["type"] == null && actual_val["objectId"] != null)
+ {
+ var new_val = await GetProperties(actual_val["objectId"].Value<string>());
+ await CheckProps(new_val, exp_val, $"{label}-{actual_val["objectId"]?.Value<string>()}");
+ return;
+ }
+
+ foreach (var jp in exp_val.Values<JProperty>())
+ {
+ if (jp.Value.Type == JTokenType.Object)
+ {
+ var new_val = await GetProperties(actual_val["objectId"].Value<string>());
+ await CheckProps(new_val, jp.Value, $"{label}-{actual_val["objectId"]?.Value<string>()}");
+
+ continue;
+ }
+
+ var exp_val_str = jp.Value.Value<string>();
+ bool null_or_empty_exp_val = String.IsNullOrEmpty(exp_val_str);
+
+ var actual_field_val = actual_val.Values<JProperty>().FirstOrDefault(a_jp => a_jp.Name == jp.Name);
+ var actual_field_val_str = actual_field_val?.Value?.Value<string>();
+ if (null_or_empty_exp_val && String.IsNullOrEmpty(actual_field_val_str))
+ continue;
+
+ Assert.True(actual_field_val != null, $"[{label}] Could not find value field named {jp.Name}");
+
+ Assert.True(exp_val_str == actual_field_val_str,
+ $"[{label}] Value for json property named {jp.Name} didn't match.\n" +
+ $"Expected: {jp.Value.Value<string>()}\n" +
+ $"Actual: {actual_field_val.Value.Value<string>()}");
+ }
+ }
+
+ internal async Task<JToken> GetLocalsForFrame(JToken frame, string script_loc, int line, int column, string function_name)
+ {
+ CheckLocation(script_loc, line, column, ctx.scripts, frame["location"]);
+ Assert.Equal(function_name, frame["functionName"].Value<string>());
+
+ return await GetProperties(frame["callFrameId"].Value<string>());
+ }
+
+ internal async Task<JToken> GetObjectOnFrame(JToken frame, string name)
+ {
+ var locals = await GetProperties(frame["callFrameId"].Value<string>());
+ return await GetObjectOnLocals(locals, name);
+ }
+
+ // Find an object with @name, *fetch* the object, and check against @o
+ internal async Task<JToken> CompareObjectPropertiesFor(JToken locals, string name, object o, string label = null, int num_fields = -1)
+ {
+ if (label == null)
+ label = name;
+ var props = await GetObjectOnLocals(locals, name);
+ try
+ {
+ if (o != null)
+ await CheckProps(props, o, label, num_fields);
+ return props;
+ }
+ catch
+ {
+ throw;
+ }
+ }
+
+ internal async Task<JToken> GetObjectOnLocals(JToken locals, string name)
+ {
+ var obj = GetAndAssertObjectWithName(locals, name);
+ var objectId = obj["value"]["objectId"]?.Value<string>();
+ Assert.True(!String.IsNullOrEmpty(objectId), $"No objectId found for {name}");
+
+ return await GetProperties(objectId);
+ }
+
+ /* @fn_args is for use with `Runtime.callFunctionOn` only */
+ internal async Task<JToken> GetProperties(string id, JToken fn_args = null, bool expect_ok = true)
+ {
+ if (ctx.UseCallFunctionOnBeforeGetProperties && !id.StartsWith("dotnet:scope:"))
+ {
+ var fn_decl = "function () { return this; }";
+ var cfo_args = JObject.FromObject(new
+ {
+ functionDeclaration = fn_decl,
+ objectId = id
+ });
+ if (fn_args != null)
+ cfo_args["arguments"] = fn_args;
+
+ var result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token);
+ AssertEqual(expect_ok, result.IsOk, $"Runtime.getProperties returned {result.IsOk} instead of {expect_ok}, for {cfo_args.ToString()}, with Result: {result}");
+ if (!result.IsOk)
+ return null;
+ id = result.Value["result"]?["objectId"]?.Value<string>();
+ }
+
+ var get_prop_req = JObject.FromObject(new
+ {
+ objectId = id
+ });
+
+ var frame_props = await ctx.cli.SendCommand("Runtime.getProperties", get_prop_req, ctx.token);
+ AssertEqual(expect_ok, frame_props.IsOk, $"Runtime.getProperties returned {frame_props.IsOk} instead of {expect_ok}, for {get_prop_req}, with Result: {frame_props}");
+ if (!frame_props.IsOk)
+ return null;
+
+ var locals = frame_props.Value["result"];
+ // FIXME: Should be done when generating the list in library_mono.js, but not sure yet
+ // whether to remove it, and how to do it correctly.
+ if (locals is JArray)
+ {
+ foreach (var p in locals)
+ {
+ if (p["name"]?.Value<string>() == "length" && p["enumerable"]?.Value<bool>() != true)
+ {
+ p.Remove();
+ break;
+ }
+ }
+ }
+
+ return locals;
+ }
+
+ internal async Task<JToken> EvaluateOnCallFrame(string id, string expression)
+ {
+ var evaluate_req = JObject.FromObject(new
+ {
+ callFrameId = id,
+ expression = expression
+ });
+
+ var frame_evaluate = await ctx.cli.SendCommand("Debugger.evaluateOnCallFrame", evaluate_req, ctx.token);
+ if (!frame_evaluate.IsOk)
+ Assert.True(false, $"Debugger.evaluateOnCallFrame failed for {evaluate_req.ToString()}, with Result: {frame_evaluate}");
+
+ var evaluate_result = frame_evaluate.Value["result"];
+ return evaluate_result;
+ }
+
+ internal async Task<Result> SetBreakpoint(string url_key, int line, int column, bool expect_ok = true, bool use_regex = false)
+ {
+ var bp1_req = !use_regex ?
+ JObject.FromObject(new { lineNumber = line, columnNumber = column, url = dicFileToUrl[url_key], }) :
+ JObject.FromObject(new { lineNumber = line, columnNumber = column, urlRegex = url_key, });
+
+ var bp1_res = await ctx.cli.SendCommand("Debugger.setBreakpointByUrl", bp1_req, ctx.token);
+ Assert.True(expect_ok ? bp1_res.IsOk : bp1_res.IsErr);
+
+ return bp1_res;
+ }
+
+ internal async Task<Result> SetPauseOnException(string state)
+ {
+ var exc_res = await ctx.cli.SendCommand("Debugger.setPauseOnExceptions", JObject.FromObject(new { state = state }), ctx.token);
+ return exc_res;
+ }
+
+ internal async Task<Result> SetBreakpointInMethod(string assembly, string type, string method, int lineOffset = 0, int col = 0)
+ {
+ var req = JObject.FromObject(new { assemblyName = assembly, typeName = type, methodName = method, lineOffset = lineOffset });
+
+ // Protocol extension
+ var res = await ctx.cli.SendCommand("DotnetDebugger.getMethodLocation", req, ctx.token);
+ Assert.True(res.IsOk);
+
+ var m_url = res.Value["result"]["url"].Value<string>();
+ var m_line = res.Value["result"]["line"].Value<int>();
+
+ var bp1_req = JObject.FromObject(new
+ {
+ lineNumber = m_line + lineOffset,
+ columnNumber = col,
+ url = m_url
+ });
+
+ res = await ctx.cli.SendCommand("Debugger.setBreakpointByUrl", bp1_req, ctx.token);
+ Assert.True(res.IsOk);
+
+ return res;
+ }
+
+ internal void AssertEqual(object expected, object actual, string label) => Assert.True(expected?.Equals(actual),
+ $"[{label}]\n" +
+ $"Expected: {expected?.ToString()}\n" +
+ $"Actual: {actual?.ToString()}\n");
+
+ internal void AssertStartsWith(string expected, string actual, string label) => Assert.True(actual?.StartsWith(expected), $"[{label}] Does not start with the expected string\nExpected: {expected}\nActual: {actual}");
+
+ internal static Func<int, int, string, string, object> TSimpleClass = (X, Y, Id, Color) => new
+ {
+ X = TNumber(X),
+ Y = TNumber(Y),
+ Id = TString(Id),
+ Color = TEnum("DebuggerTests.RGB", Color),
+ PointWithCustomGetter = TGetter("PointWithCustomGetter")
+ };
+
+ internal static Func<int, int, string, string, object> TPoint = (X, Y, Id, Color) => new
+ {
+ X = TNumber(X),
+ Y = TNumber(Y),
+ Id = TString(Id),
+ Color = TEnum("DebuggerTests.RGB", Color),
+ };
+
+ //FIXME: um maybe we don't need to convert jobject right here!
+ internal static JObject TString(string value) =>
+ value == null ?
+ TObject("string", is_null: true) :
+ JObject.FromObject(new { type = "string", value = @value });
+
+ internal static JObject TNumber(int value) =>
+ JObject.FromObject(new { type = "number", value = @value.ToString(), description = value.ToString() });
+
+ internal static JObject TNumber(uint value) =>
+ JObject.FromObject(new { type = "number", value = @value.ToString(), description = value.ToString() });
+
+ internal static JObject TValueType(string className, string description = null, object members = null) =>
+ JObject.FromObject(new { type = "object", isValueType = true, className = className, description = description ?? className });
+
+ internal static JObject TEnum(string className, string descr, object members = null) =>
+ JObject.FromObject(new { type = "object", isEnum = true, className = className, description = descr });
+
+ internal static JObject TObject(string className, string description = null, bool is_null = false) =>
+ is_null ?
+ JObject.FromObject(new { type = "object", className = className, description = description ?? className, subtype = is_null ? "null" : null }) :
+ JObject.FromObject(new { type = "object", className = className, description = description ?? className });
+
+ internal static JObject TArray(string className, int length = 0) => JObject.FromObject(new { type = "object", className = className, description = $"{className}({length})", subtype = "array" });
+
+ internal static JObject TBool(bool value) => JObject.FromObject(new { type = "boolean", value = @value, description = @value ? "true" : "false" });
+
+ internal static JObject TSymbol(string value) => JObject.FromObject(new { type = "symbol", value = @value, description = @value });
+
+ /*
+ For target names with generated method names like
+ `void <ActionTSignatureTest>b__11_0 (Math.GenericStruct<int[]>)`
+
+ .. pass target "as `target: "void <ActionTSignatureTest>|(Math.GenericStruct<int[]>)"`
+ */
+ internal static JObject TDelegate(string className, string target) => JObject.FromObject(new
+ {
+ __custom_type = "delegate",
+ className = className,
+ target = target
+ });
+
+ internal static JObject TPointer(string type_name, bool is_null = false) => JObject.FromObject(new { __custom_type = "pointer", type_name = type_name, is_null = is_null, is_void = type_name.StartsWith("void*") });
+
+ internal static JObject TIgnore() => JObject.FromObject(new { __custom_type = "ignore_me" });
+
+ internal static JObject TGetter(string type) => JObject.FromObject(new { __custom_type = "getter", type_name = type });
+ }
+
+ class DebugTestContext
+ {
+ public InspectorClient cli;
+ public Inspector insp;
+ public CancellationToken token;
+ public Dictionary<string, string> scripts;
+
+ public bool UseCallFunctionOnBeforeGetProperties;
+
+ public DebugTestContext(InspectorClient cli, Inspector insp, CancellationToken token, Dictionary<string, string> scripts)
+ {
+ this.cli = cli;
+ this.insp = insp;
+ this.token = token;
+ this.scripts = scripts;
+ }
+ }
+
+ class DotnetObjectId
+ {
+ public string Scheme { get; }
+ public string Value { get; }
+
+ JObject value_json;
+ public JObject ValueAsJson
+ {
+ get
+ {
+ if (value_json == null)
+ {
+ try
+ {
+ value_json = JObject.Parse(Value);
+ }
+ catch (JsonReaderException) { }
+ }
+
+ return value_json;
+ }
+ }
+
+ public static bool TryParse(JToken jToken, out DotnetObjectId objectId) => TryParse(jToken?.Value<string>(), out objectId);
+
+ public static bool TryParse(string id, out DotnetObjectId objectId)
+ {
+ objectId = null;
+ if (id == null)
+ {
+ return false;
+ }
+
+ if (!id.StartsWith("dotnet:"))
+ {
+ return false;
+ }
+
+ var parts = id.Split(":", 3);
+
+ if (parts.Length < 3)
+ {
+ return false;
+ }
+
+ objectId = new DotnetObjectId(parts[1], parts[2]);
+
+ return true;
+ }
+
+ public DotnetObjectId(string scheme, string value)
+ {
+ Scheme = scheme;
+ Value = value;
+ }
+
+ public override string ToString() => $"dotnet:{Scheme}:{Value}";
+ }
+
+ enum StepKind
+ {
+ Into,
+ Over,
+ Out
+ }
+}
diff --git a/sdks/wasm/DebuggerTestSuite/TestHarnessOptions.cs b/sdks/wasm/DebuggerTestSuite/TestHarnessOptions.cs
new file mode 100644
index 00000000000..96a6c13eb97
--- /dev/null
+++ b/sdks/wasm/DebuggerTestSuite/TestHarnessOptions.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.WebAssembly.Diagnostics
+{
+ public class TestHarnessOptions : ProxyOptions
+ {
+ public string ChromePath { get; set; }
+ public string AppPath { get; set; }
+ public string PagePath { get; set; }
+ public string NodeApp { get; set; }
+ }
+} \ No newline at end of file
diff --git a/sdks/wasm/DebuggerTestSuite/TestHarnessProxy.cs b/sdks/wasm/DebuggerTestSuite/TestHarnessProxy.cs
new file mode 100644
index 00000000000..3995dcc88de
--- /dev/null
+++ b/sdks/wasm/DebuggerTestSuite/TestHarnessProxy.cs
@@ -0,0 +1,59 @@
+// 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.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.WebAssembly.Diagnostics
+{
+ public class TestHarnessProxy
+ {
+ static IWebHost host;
+ static Task hostTask;
+ static CancellationTokenSource cts = new CancellationTokenSource();
+ static object proxyLock = new object();
+
+ public static readonly Uri Endpoint = new Uri("http://localhost:9400");
+
+ public static Task Start(string chromePath, string appPath, string pagePath)
+ {
+ lock (proxyLock)
+ {
+ if (host != null)
+ return hostTask;
+
+ host = WebHost.CreateDefaultBuilder()
+ .UseSetting("UseIISIntegration", false.ToString())
+ .ConfigureAppConfiguration((hostingContext, config) =>
+ {
+ config.AddEnvironmentVariables(prefix: "WASM_TESTS_");
+ })
+ .ConfigureServices((ctx, services) =>
+ {
+ services.Configure<TestHarnessOptions>(ctx.Configuration);
+ services.Configure<TestHarnessOptions>(options =>
+ {
+ options.ChromePath = options.ChromePath ?? chromePath;
+ options.AppPath = appPath;
+ options.PagePath = pagePath;
+ options.DevToolsUrl = new Uri("http://localhost:0");
+ });
+ })
+ .UseStartup<TestHarnessStartup>()
+ .UseUrls(Endpoint.ToString())
+ .Build();
+ hostTask = host.StartAsync(cts.Token);
+ }
+
+ Console.WriteLine("WebServer Ready!");
+ return hostTask;
+ }
+ }
+} \ No newline at end of file
diff --git a/sdks/wasm/DebuggerTestSuite/TestHarnessStartup.cs b/sdks/wasm/DebuggerTestSuite/TestHarnessStartup.cs
new file mode 100644
index 00000000000..265f1a35816
--- /dev/null
+++ b/sdks/wasm/DebuggerTestSuite/TestHarnessStartup.cs
@@ -0,0 +1,255 @@
+// 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.Diagnostics;
+using System.IO;
+using System.Net.Http;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.AspNetCore.StaticFiles;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.FileProviders;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.WebAssembly.Diagnostics
+{
+ public class TestHarnessStartup
+ {
+ static Regex parseConnection = new Regex(@"listening on (ws?s://[^\s]*)");
+ public TestHarnessStartup(IConfiguration configuration)
+ {
+ Configuration = configuration;
+ }
+
+ public IConfiguration Configuration { get; set; }
+
+ // This method gets called by the runtime. Use this method to add services to the container.
+ // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddRouting()
+ .Configure<TestHarnessOptions>(Configuration);
+ }
+
+ async Task SendNodeVersion(HttpContext context)
+ {
+ Console.WriteLine("hello chrome! json/version");
+ var resp_obj = new JObject();
+ resp_obj["Browser"] = "node.js/v9.11.1";
+ resp_obj["Protocol-Version"] = "1.1";
+
+ var response = resp_obj.ToString();
+ await context.Response.WriteAsync(response, new CancellationTokenSource().Token);
+ }
+
+ async Task SendNodeList(HttpContext context)
+ {
+ Console.WriteLine("hello chrome! json/list");
+ try
+ {
+ var response = new JArray(JObject.FromObject(new
+ {
+ description = "node.js instance",
+ devtoolsFrontendUrl = "chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=localhost:9300/91d87807-8a81-4f49-878c-a5604103b0a4",
+ faviconUrl = "https://nodejs.org/static/favicon.ico",
+ id = "91d87807-8a81-4f49-878c-a5604103b0a4",
+ title = "foo.js",
+ type = "node",
+ webSocketDebuggerUrl = "ws://localhost:9300/91d87807-8a81-4f49-878c-a5604103b0a4"
+ })).ToString();
+
+ Console.WriteLine($"sending: {response}");
+ await context.Response.WriteAsync(response, new CancellationTokenSource().Token);
+ }
+ catch (Exception e) { Console.WriteLine(e); }
+ }
+
+ public async Task LaunchAndServe(ProcessStartInfo psi, HttpContext context, Func<string, Task<string>> extract_conn_url)
+ {
+
+ if (!context.WebSockets.IsWebSocketRequest)
+ {
+ context.Response.StatusCode = 400;
+ return;
+ }
+
+ var tcs = new TaskCompletionSource<string>();
+
+ var proc = Process.Start(psi);
+ try
+ {
+ proc.ErrorDataReceived += (sender, e) =>
+ {
+ var str = e.Data;
+ Console.WriteLine($"stderr: {str}");
+
+ if (tcs.Task.IsCompleted)
+ return;
+
+ var match = parseConnection.Match(str);
+ if (match.Success)
+ {
+ tcs.TrySetResult(match.Groups[1].Captures[0].Value);
+ }
+ };
+
+ proc.OutputDataReceived += (sender, e) =>
+ {
+ Console.WriteLine($"stdout: {e.Data}");
+ };
+
+ proc.BeginErrorReadLine();
+ proc.BeginOutputReadLine();
+
+ if (await Task.WhenAny(tcs.Task, Task.Delay(5000)) != tcs.Task)
+ {
+ Console.WriteLine("Didnt get the con string after 5s.");
+ throw new Exception("node.js timedout");
+ }
+ var line = await tcs.Task;
+ var con_str = extract_conn_url != null ? await extract_conn_url(line) : line;
+
+ Console.WriteLine($"launching proxy for {con_str}");
+
+ using var loggerFactory = LoggerFactory.Create(
+ builder => builder.AddConsole().AddFilter(null, LogLevel.Information));
+ var proxy = new DebuggerProxy(loggerFactory);
+ var browserUri = new Uri(con_str);
+ var ideSocket = await context.WebSockets.AcceptWebSocketAsync();
+
+ await proxy.Run(browserUri, ideSocket);
+ Console.WriteLine("Proxy done");
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("got exception {0}", e);
+ }
+ finally
+ {
+ proc.CancelErrorRead();
+ proc.CancelOutputRead();
+ proc.Kill();
+ proc.WaitForExit();
+ proc.Close();
+ }
+ }
+
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ public void Configure(IApplicationBuilder app, IOptionsMonitor<TestHarnessOptions> optionsAccessor, IWebHostEnvironment env)
+ {
+ app.UseWebSockets();
+ app.UseStaticFiles();
+
+ TestHarnessOptions options = optionsAccessor.CurrentValue;
+
+ var provider = new FileExtensionContentTypeProvider();
+ provider.Mappings[".wasm"] = "application/wasm";
+
+ app.UseStaticFiles(new StaticFileOptions
+ {
+ FileProvider = new PhysicalFileProvider(options.AppPath),
+ ServeUnknownFileTypes = true, //Cuz .wasm is not a known file type :cry:
+ RequestPath = "",
+ ContentTypeProvider = provider
+ });
+
+ var devToolsUrl = options.DevToolsUrl;
+ app.UseRouter(router =>
+ {
+ router.MapGet("launch-chrome-and-connect", async context =>
+ {
+ Console.WriteLine("New test request");
+ try
+ {
+ var client = new HttpClient();
+ var psi = new ProcessStartInfo();
+
+ psi.Arguments = $"--headless --disable-gpu --lang=en-US --incognito --remote-debugging-port={devToolsUrl.Port} http://{TestHarnessProxy.Endpoint.Authority}/{options.PagePath}";
+ psi.UseShellExecute = false;
+ psi.FileName = options.ChromePath;
+ psi.RedirectStandardError = true;
+ psi.RedirectStandardOutput = true;
+
+ await LaunchAndServe(psi, context, async (str) =>
+ {
+ var start = DateTime.Now;
+ JArray obj = null;
+
+ while (true)
+ {
+ // Unfortunately it does look like we have to wait
+ // for a bit after getting the response but before
+ // making the list request. We get an empty result
+ // if we make the request too soon.
+ await Task.Delay(100);
+
+ var res = await client.GetStringAsync(new Uri(new Uri(str), "/json/list"));
+ Console.WriteLine("res is {0}", res);
+
+ if (!String.IsNullOrEmpty(res))
+ {
+ // Sometimes we seem to get an empty array `[ ]`
+ obj = JArray.Parse(res);
+ if (obj != null && obj.Count >= 1)
+ break;
+ }
+
+ var elapsed = DateTime.Now - start;
+ if (elapsed.Milliseconds > 5000)
+ {
+ Console.WriteLine($"Unable to get DevTools /json/list response in {elapsed.Seconds} seconds, stopping");
+ return null;
+ }
+ }
+
+ var wsURl = obj[0]?["webSocketDebuggerUrl"]?.Value<string>();
+ Console.WriteLine(">>> {0}", wsURl);
+
+ return wsURl;
+ });
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"launch-chrome-and-connect failed with {ex.ToString()}");
+ }
+ });
+ });
+
+ if (options.NodeApp != null)
+ {
+ Console.WriteLine($"Doing the nodejs: {options.NodeApp}");
+ var nodeFullPath = Path.GetFullPath(options.NodeApp);
+ Console.WriteLine(nodeFullPath);
+ var psi = new ProcessStartInfo();
+
+ psi.UseShellExecute = false;
+ psi.RedirectStandardError = true;
+ psi.RedirectStandardOutput = true;
+
+ psi.Arguments = $"--inspect-brk=localhost:0 {nodeFullPath}";
+ psi.FileName = "node";
+
+ app.UseRouter(router =>
+ {
+ //Inspector API for using chrome devtools directly
+ router.MapGet("json", SendNodeList);
+ router.MapGet("json/list", SendNodeList);
+ router.MapGet("json/version", SendNodeVersion);
+ router.MapGet("launch-done-and-connect", async context =>
+ {
+ await LaunchAndServe(psi, context, null);
+ });
+ });
+ }
+ }
+ }
+}
diff --git a/sdks/wasm/DebuggerTestSuite/Tests.cs b/sdks/wasm/DebuggerTestSuite/Tests.cs
index af0ed721916..34dc5ef605d 100644
--- a/sdks/wasm/DebuggerTestSuite/Tests.cs
+++ b/sdks/wasm/DebuggerTestSuite/Tests.cs
@@ -1,1378 +1,1528 @@
+// 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.Linq;
using System.Threading.Tasks;
+using Microsoft.WebAssembly.Diagnostics;
using Newtonsoft.Json.Linq;
using Xunit;
-using WebAssembly.Net.Debugging;
-[assembly: CollectionBehavior (CollectionBehavior.CollectionPerAssembly)]
+[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]
namespace DebuggerTests
{
- public class SourceList : DebuggerTestBase {
-
- [Fact]
- public async Task CheckThatAllSourcesAreSent () {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready();
- //all sources are sent before runtime ready is sent, nothing to check
- await insp.Ready ();
- Assert.Contains ("dotnet://debugger-test.dll/debugger-test.cs", scripts.Values);
- Assert.Contains ("dotnet://debugger-test.dll/debugger-test2.cs", scripts.Values);
- Assert.Contains ("dotnet://Simple.Dependency.dll/dependency.cs", scripts.Values);
- }
-
- [Fact]
- public async Task CreateGoodBreakpoint () {
- var insp = new Inspector ();
-
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready ();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
-
- var bp1_res = await SetBreakpoint ("dotnet://debugger-test.dll/debugger-test.cs", 5, 2);
-
- Assert.EndsWith ("debugger-test.cs", bp1_res.Value ["breakpointId"].ToString());
- Assert.Equal (1, bp1_res.Value ["locations"]?.Value<JArray> ()?.Count);
-
- var loc = bp1_res.Value ["locations"]?.Value<JArray> ()[0];
-
- Assert.NotNull (loc ["scriptId"]);
- Assert.Equal("dotnet://debugger-test.dll/debugger-test.cs", scripts [loc["scriptId"]?.Value<string> ()]);
- Assert.Equal (5, loc ["lineNumber"]);
- Assert.Equal (2, loc ["columnNumber"]);
- });
- }
-
- [Fact]
- public async Task CreateJSBreakpoint () {
- // Test that js breakpoints get set correctly
- var insp = new Inspector ();
-
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready ();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- // 13 24
- // 13 31
- var bp1_res = await SetBreakpoint ("/debugger-driver.html", 13, 24);
-
- Assert.EndsWith ("debugger-driver.html", bp1_res.Value ["breakpointId"].ToString());
- Assert.Equal (1, bp1_res.Value ["locations"]?.Value<JArray> ()?.Count);
-
- var loc = bp1_res.Value ["locations"]?.Value<JArray> ()[0];
-
- Assert.NotNull (loc ["scriptId"]);
- Assert.Equal (13, loc ["lineNumber"]);
- Assert.Equal (24, loc ["columnNumber"]);
-
- var bp2_res = await SetBreakpoint ("/debugger-driver.html", 13, 31);
-
- Assert.EndsWith ("debugger-driver.html", bp2_res.Value ["breakpointId"].ToString());
- Assert.Equal (1, bp2_res.Value ["locations"]?.Value<JArray> ()?.Count);
-
- var loc2 = bp2_res.Value ["locations"]?.Value<JArray> ()[0];
-
- Assert.NotNull (loc2 ["scriptId"]);
- Assert.Equal (13, loc2 ["lineNumber"]);
- Assert.Equal (31, loc2 ["columnNumber"]);
- });
- }
-
- [Fact]
- public async Task CreateJS0Breakpoint () {
- // Test that js column 0 does as expected
- var insp = new Inspector ();
-
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready ();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- // 13 24
- // 13 31
- var bp1_res = await SetBreakpoint ("/debugger-driver.html", 13, 0);
-
- Assert.EndsWith ("debugger-driver.html", bp1_res.Value ["breakpointId"].ToString());
- Assert.Equal (1, bp1_res.Value ["locations"]?.Value<JArray> ()?.Count);
-
- var loc = bp1_res.Value ["locations"]?.Value<JArray> ()[0];
-
- Assert.NotNull (loc ["scriptId"]);
- Assert.Equal (13, loc ["lineNumber"]);
- Assert.Equal (24, loc ["columnNumber"]);
+ public class SourceList : DebuggerTestBase
+ {
+
+ [Fact]
+ public async Task CheckThatAllSourcesAreSent()
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
- var bp2_res = await SetBreakpoint ("/debugger-driver.html", 13, 31);
+ await Ready();
+ //all sources are sent before runtime ready is sent, nothing to check
+ await insp.Ready();
+ Assert.Contains("dotnet://debugger-test.dll/debugger-test.cs", scripts.Values);
+ Assert.Contains("dotnet://debugger-test.dll/debugger-test2.cs", scripts.Values);
+ Assert.Contains("dotnet://debugger-test.dll/dependency.cs", scripts.Values);
+ }
- Assert.EndsWith ("debugger-driver.html", bp2_res.Value ["breakpointId"].ToString());
- Assert.Equal (1, bp2_res.Value ["locations"]?.Value<JArray> ()?.Count);
+ [Fact]
+ public async Task CreateGoodBreakpoint()
+ {
+ var insp = new Inspector();
- var loc2 = bp2_res.Value ["locations"]?.Value<JArray> ()[0];
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
- Assert.NotNull (loc2 ["scriptId"]);
- Assert.Equal (13, loc2 ["lineNumber"]);
- Assert.Equal (31, loc2 ["columnNumber"]);
- });
- }
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+
+ var bp1_res = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8);
- [Theory]
- [InlineData (0)]
- [InlineData (44)]
- public async Task CheckMultipleBreakpointsOnSameLine (int col) {
- var insp = new Inspector ();
+ Assert.EndsWith("debugger-test.cs", bp1_res.Value["breakpointId"].ToString());
+ Assert.Equal(1, bp1_res.Value["locations"]?.Value<JArray>()?.Count);
+
+ var loc = bp1_res.Value["locations"]?.Value<JArray>()[0];
- var scripts = SubscribeToScripts(insp);
+ Assert.NotNull(loc["scriptId"]);
+ Assert.Equal("dotnet://debugger-test.dll/debugger-test.cs", scripts[loc["scriptId"]?.Value<string>()]);
+ Assert.Equal(10, loc["lineNumber"]);
+ Assert.Equal(8, loc["columnNumber"]);
+ });
+ }
- await Ready ();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
+ [Fact]
+ public async Task CreateJSBreakpoint()
+ {
+ // Test that js breakpoints get set correctly
+ var insp = new Inspector();
- var bp1_res = await SetBreakpoint ("dotnet://debugger-test.dll/debugger-array-test.cs", 197, col);
- Assert.EndsWith ("debugger-array-test.cs", bp1_res.Value["breakpointId"].ToString());
- Assert.Equal (1, bp1_res.Value ["locations"]?.Value<JArray> ()?.Count);
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
- var loc = bp1_res.Value ["locations"]?.Value<JArray> ()[0];
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ // 13 24
+ // 13 31
+ var bp1_res = await SetBreakpoint("/debugger-driver.html", 13, 24);
- CheckLocation ("dotnet://debugger-test.dll/debugger-array-test.cs", 197, 44, scripts, loc);
+ Assert.EndsWith("debugger-driver.html", bp1_res.Value["breakpointId"].ToString());
+ Assert.Equal(1, bp1_res.Value["locations"]?.Value<JArray>()?.Count);
- var bp2_res = await SetBreakpoint ("dotnet://debugger-test.dll/debugger-array-test.cs", 197, 49);
- Assert.EndsWith ("debugger-array-test.cs", bp2_res.Value["breakpointId"].ToString());
- Assert.Equal (1, bp2_res.Value ["locations"]?.Value<JArray> ()?.Count);
+ var loc = bp1_res.Value["locations"]?.Value<JArray>()[0];
- var loc2 = bp2_res.Value ["locations"]?.Value<JArray> ()[0];
+ Assert.NotNull(loc["scriptId"]);
+ Assert.Equal(13, loc["lineNumber"]);
+ Assert.Equal(24, loc["columnNumber"]);
+
+ var bp2_res = await SetBreakpoint("/debugger-driver.html", 13, 31);
- CheckLocation ("dotnet://debugger-test.dll/debugger-array-test.cs", 197, 49, scripts, loc2);
- });
- }
+ Assert.EndsWith("debugger-driver.html", bp2_res.Value["breakpointId"].ToString());
+ Assert.Equal(1, bp2_res.Value["locations"]?.Value<JArray>()?.Count);
- [Fact]
- public async Task CreateBadBreakpoint () {
- var insp = new Inspector ();
+ var loc2 = bp2_res.Value["locations"]?.Value<JArray>()[0];
- //Collect events
- var scripts = SubscribeToScripts(insp);
+ Assert.NotNull(loc2["scriptId"]);
+ Assert.Equal(13, loc2["lineNumber"]);
+ Assert.Equal(31, loc2["columnNumber"]);
+ });
+ }
+
+ [Fact]
+ public async Task CreateJS0Breakpoint()
+ {
+ // Test that js column 0 does as expected
+ var insp = new Inspector();
- await Ready ();
- await insp.Ready (async (cli, token) => {
- var bp1_req = JObject.FromObject(new {
- lineNumber = 5,
- columnNumber = 2,
- url = "dotnet://debugger-test.dll/this-file-doesnt-exist.cs",
- });
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ // 13 24
+ // 13 31
+ var bp1_res = await SetBreakpoint("/debugger-driver.html", 13, 0);
+
+ Assert.EndsWith("debugger-driver.html", bp1_res.Value["breakpointId"].ToString());
+ Assert.Equal(1, bp1_res.Value["locations"]?.Value<JArray>()?.Count);
+
+ var loc = bp1_res.Value["locations"]?.Value<JArray>()[0];
+
+ Assert.NotNull(loc["scriptId"]);
+ Assert.Equal(13, loc["lineNumber"]);
+ Assert.Equal(24, loc["columnNumber"]);
+
+ var bp2_res = await SetBreakpoint("/debugger-driver.html", 13, 31);
+
+ Assert.EndsWith("debugger-driver.html", bp2_res.Value["breakpointId"].ToString());
+ Assert.Equal(1, bp2_res.Value["locations"]?.Value<JArray>()?.Count);
+
+ var loc2 = bp2_res.Value["locations"]?.Value<JArray>()[0];
- var bp1_res = await cli.SendCommand ("Debugger.setBreakpointByUrl", bp1_req, token);
+ Assert.NotNull(loc2["scriptId"]);
+ Assert.Equal(13, loc2["lineNumber"]);
+ Assert.Equal(31, loc2["columnNumber"]);
+ });
+ }
- Assert.True (bp1_res.IsOk);
- Assert.Empty (bp1_res.Value["locations"].Values<object>());
- //Assert.Equal ((int)MonoErrorCodes.BpNotFound, bp1_res.Error ["code"]?.Value<int> ());
- });
- }
+ [Theory]
+ [InlineData(0)]
+ [InlineData(50)]
+ public async Task CheckMultipleBreakpointsOnSameLine(int col)
+ {
+ var insp = new Inspector();
+
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+
+ var bp1_res = await SetBreakpoint("dotnet://debugger-test.dll/debugger-array-test.cs", 219, col);
+ Assert.EndsWith("debugger-array-test.cs", bp1_res.Value["breakpointId"].ToString());
+ Assert.Equal(1, bp1_res.Value["locations"]?.Value<JArray>()?.Count);
+
+ var loc = bp1_res.Value["locations"]?.Value<JArray>()[0];
+
+ CheckLocation("dotnet://debugger-test.dll/debugger-array-test.cs", 219, 50, scripts, loc);
+
+ var bp2_res = await SetBreakpoint("dotnet://debugger-test.dll/debugger-array-test.cs", 219, 55);
+ Assert.EndsWith("debugger-array-test.cs", bp2_res.Value["breakpointId"].ToString());
+ Assert.Equal(1, bp2_res.Value["locations"]?.Value<JArray>()?.Count);
+
+ var loc2 = bp2_res.Value["locations"]?.Value<JArray>()[0];
+
+ CheckLocation("dotnet://debugger-test.dll/debugger-array-test.cs", 219, 55, scripts, loc2);
+ });
+ }
+
+ [Fact]
+ public async Task CreateBadBreakpoint()
+ {
+ var insp = new Inspector();
+
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ var bp1_req = JObject.FromObject(new
+ {
+ lineNumber = 8,
+ columnNumber = 2,
+ url = "dotnet://debugger-test.dll/this-file-doesnt-exist.cs",
+ });
+
+ var bp1_res = await cli.SendCommand("Debugger.setBreakpointByUrl", bp1_req, token);
+
+ Assert.True(bp1_res.IsOk);
+ Assert.Empty(bp1_res.Value["locations"].Values<object>());
+ //Assert.Equal ((int)MonoErrorCodes.BpNotFound, bp1_res.Error ["code"]?.Value<int> ());
+ });
+ }
+
+ [Fact]
+ public async Task CreateGoodBreakpointAndHit()
+ {
+ var insp = new Inspector();
+
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+
+ var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8);
+
+ var eval_req = JObject.FromObject(new
+ {
+ expression = "window.setTimeout(function() { invoke_add(); }, 1);",
+ });
+
+ await EvaluateAndCheck(
+ "window.setTimeout(function() { invoke_add(); }, 1);",
+ "dotnet://debugger-test.dll/debugger-test.cs", 10, 8,
+ "IntAdd",
+ wait_for_event_fn: (pause_location) =>
+ {
+ Assert.Equal("other", pause_location["reason"]?.Value<string>());
+ Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value<string>());
+
+ var top_frame = pause_location["callFrames"][0];
+ Assert.Equal("IntAdd", top_frame["functionName"].Value<string>());
+ Assert.Contains("debugger-test.cs", top_frame["url"].Value<string>());
+
+ CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 8, 4, scripts, top_frame["functionLocation"]);
+
+ //now check the scope
+ var scope = top_frame["scopeChain"][0];
+ Assert.Equal("local", scope["type"]);
+ Assert.Equal("IntAdd", scope["name"]);
+
+ Assert.Equal("object", scope["object"]["type"]);
+ Assert.Equal("dotnet:scope:0", scope["object"]["objectId"]);
+ CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 8, 4, scripts, scope["startLocation"]);
+ CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 14, 4, scripts, scope["endLocation"]);
+ return Task.CompletedTask;
+ }
+ );
+
+ });
+ }
+
+ [Fact]
+ public async Task ExceptionThrownInJS()
+ {
+ var insp = new Inspector();
+
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ var eval_req = JObject.FromObject(new
+ {
+ expression = "invoke_bad_js_test();"
+ });
+
+ var eval_res = await cli.SendCommand("Runtime.evaluate", eval_req, token);
+ Assert.True(eval_res.IsErr);
+ Assert.Equal("Uncaught", eval_res.Error["exceptionDetails"]?["text"]?.Value<string>());
+ });
+ }
+
+ [Fact]
+ public async Task ExceptionThrownInJSOutOfBand()
+ {
+ var insp = new Inspector();
+
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+
+ await SetBreakpoint("/debugger-driver.html", 27, 2);
+
+ var eval_req = JObject.FromObject(new
+ {
+ expression = "window.setTimeout(function() { invoke_bad_js_test(); }, 1);",
+ });
+
+ var eval_res = await cli.SendCommand("Runtime.evaluate", eval_req, token);
+ // Response here will be the id for the timer from JS!
+ Assert.True(eval_res.IsOk);
+
+ var ex = await Assert.ThrowsAsync<ArgumentException>(async () => await insp.WaitFor("Runtime.exceptionThrown"));
+ var ex_json = JObject.Parse(ex.Message);
+ Assert.Equal(dicFileToUrl["/debugger-driver.html"], ex_json["exceptionDetails"]?["url"]?.Value<string>());
+ });
+
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task InspectLocalsAtBreakpointSite(bool use_cfo) =>
+ await CheckInspectLocalsAtBreakpointSite(
+ "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, "IntAdd",
+ "window.setTimeout(function() { invoke_add(); }, 1);",
+ use_cfo: use_cfo,
+ test_fn: (locals) =>
+ {
+ CheckNumber(locals, "a", 10);
+ CheckNumber(locals, "b", 20);
+ CheckNumber(locals, "c", 30);
+ CheckNumber(locals, "d", 0);
+ CheckNumber(locals, "e", 0);
+ }
+ );
+
+ [Fact]
+ public async Task InspectPrimitiveTypeLocalsAtBreakpointSite() =>
+ await CheckInspectLocalsAtBreakpointSite(
+ "dotnet://debugger-test.dll/debugger-test.cs", 154, 8, "PrimitiveTypesTest",
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] Math:PrimitiveTypesTest'); }, 1);",
+ test_fn: (locals) =>
+ {
+ CheckSymbol(locals, "c0", "8364 '€'");
+ CheckSymbol(locals, "c1", "65 'A'");
+ }
+ );
+
+ [Fact]
+ public async Task InspectLocalsTypesAtBreakpointSite() =>
+ await CheckInspectLocalsAtBreakpointSite(
+ "dotnet://debugger-test.dll/debugger-test2.cs", 48, 8, "Types",
+ "window.setTimeout(function() { invoke_static_method (\"[debugger-test] Fancy:Types\")(); }, 1);",
+ use_cfo: false,
+ test_fn: (locals) =>
+ {
+ CheckNumber(locals, "dPI", Math.PI);
+ CheckNumber(locals, "fPI", (float)Math.PI);
+ CheckNumber(locals, "iMax", int.MaxValue);
+ CheckNumber(locals, "iMin", int.MinValue);
+ CheckNumber(locals, "uiMax", uint.MaxValue);
+ CheckNumber(locals, "uiMin", uint.MinValue);
+
+ CheckNumber(locals, "l", uint.MaxValue * (long)2);
+ //CheckNumber (locals, "lMax", long.MaxValue); // cannot be represented as double
+ //CheckNumber (locals, "lMin", long.MinValue); // cannot be represented as double
+
+ CheckNumber(locals, "sbMax", sbyte.MaxValue);
+ CheckNumber(locals, "sbMin", sbyte.MinValue);
+ CheckNumber(locals, "bMax", byte.MaxValue);
+ CheckNumber(locals, "bMin", byte.MinValue);
+
+ CheckNumber(locals, "sMax", short.MaxValue);
+ CheckNumber(locals, "sMin", short.MinValue);
+ CheckNumber(locals, "usMin", ushort.MinValue);
+ CheckNumber(locals, "usMax", ushort.MaxValue);
+ }
+ );
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task InspectLocalsWithGenericTypesAtBreakpointSite(bool use_cfo) =>
+ await CheckInspectLocalsAtBreakpointSite(
+ "dotnet://debugger-test.dll/debugger-test.cs", 74, 8, "GenericTypesTest",
+ "window.setTimeout(function() { invoke_generic_types_test (); }, 1);",
+ use_cfo: use_cfo,
+ test_fn: (locals) =>
+ {
+ CheckObject(locals, "list", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>");
+ CheckObject(locals, "list_null", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>", is_null: true);
+
+ CheckArray(locals, "list_arr", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>[]", 1);
+ CheckObject(locals, "list_arr_null", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>[]", is_null: true);
+
+ // Unused locals
+ CheckObject(locals, "list_unused", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>");
+ CheckObject(locals, "list_null_unused", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>", is_null: true);
+
+ CheckArray(locals, "list_arr_unused", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>[]", 1);
+ CheckObject(locals, "list_arr_null_unused", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>[]", is_null: true);
+ }
+ );
+
+ object TGenericStruct(string typearg, string stringField) => new
+ {
+ List = TObject($"System.Collections.Generic.List<{typearg}>"),
+ StringField = TString(stringField)
+ };
+
+ [Fact]
+ public async Task RuntimeGetPropertiesWithInvalidScopeIdTest()
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+
+ var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 49, 8);
+
+ await EvaluateAndCheck(
+ "window.setTimeout(function() { invoke_delegates_test (); }, 1);",
+ "dotnet://debugger-test.dll/debugger-test.cs", 49, 8,
+ "DelegatesTest",
+ wait_for_event_fn: async (pause_location) =>
+ {
+ //make sure we're on the right bp
+ Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value<string>());
+
+ var top_frame = pause_location["callFrames"][0];
+
+ var scope = top_frame["scopeChain"][0];
+ Assert.Equal("dotnet:scope:0", scope["object"]["objectId"]);
+
+ // Try to get an invalid scope!
+ var get_prop_req = JObject.FromObject(new
+ {
+ objectId = "dotnet:scope:23490871",
+ });
+
+ var frame_props = await cli.SendCommand("Runtime.getProperties", get_prop_req, token);
+ Assert.True(frame_props.IsErr);
+ }
+ );
+ });
+ }
+
+ [Fact]
+ public async Task TrivalStepping()
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+
+ var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8);
+
+ await EvaluateAndCheck(
+ "window.setTimeout(function() { invoke_add(); }, 1);",
+ "dotnet://debugger-test.dll/debugger-test.cs", 10, 8,
+ "IntAdd",
+ wait_for_event_fn: (pause_location) =>
+ {
+ //make sure we're on the right bp
+ Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value<string>());
+
+ var top_frame = pause_location["callFrames"][0];
+ CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 8, 4, scripts, top_frame["functionLocation"]);
+ return Task.CompletedTask;
+ }
+ );
+
+ await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 11, 8, "IntAdd",
+ wait_for_event_fn: (pause_location) =>
+ {
+ var top_frame = pause_location["callFrames"][0];
+ CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 8, 4, scripts, top_frame["functionLocation"]);
+ return Task.CompletedTask;
+ }
+ );
+ });
+ }
+
+ [Fact]
+ public async Task InspectLocalsDuringStepping()
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+
+ var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs";
+ await SetBreakpoint(debugger_test_loc, 10, 8);
+
+ await EvaluateAndCheck(
+ "window.setTimeout(function() { invoke_add(); }, 1);",
+ debugger_test_loc, 10, 8, "IntAdd",
+ locals_fn: (locals) =>
+ {
+ CheckNumber(locals, "a", 10);
+ CheckNumber(locals, "b", 20);
+ CheckNumber(locals, "c", 30);
+ CheckNumber(locals, "d", 0);
+ CheckNumber(locals, "e", 0);
+ }
+ );
+
+ await StepAndCheck(StepKind.Over, debugger_test_loc, 11, 8, "IntAdd",
+ locals_fn: (locals) =>
+ {
+ CheckNumber(locals, "a", 10);
+ CheckNumber(locals, "b", 20);
+ CheckNumber(locals, "c", 30);
+ CheckNumber(locals, "d", 50);
+ CheckNumber(locals, "e", 0);
+ }
+ );
+
+ //step and get locals
+ await StepAndCheck(StepKind.Over, debugger_test_loc, 12, 8, "IntAdd",
+ locals_fn: (locals) =>
+ {
+ CheckNumber(locals, "a", 10);
+ CheckNumber(locals, "b", 20);
+ CheckNumber(locals, "c", 30);
+ CheckNumber(locals, "d", 50);
+ CheckNumber(locals, "e", 60);
+ }
+ );
+ });
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task InspectLocalsInPreviousFramesDuringSteppingIn2(bool use_cfo)
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
+
+ var dep_cs_loc = "dotnet://debugger-test.dll/dependency.cs";
+ await SetBreakpoint(dep_cs_loc, 33, 8);
+
+ var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs";
+
+ // Will stop in Complex.DoEvenMoreStuff
+ var pause_location = await EvaluateAndCheck(
+ "window.setTimeout(function() { invoke_use_complex (); }, 1);",
+ dep_cs_loc, 33, 8, "DoEvenMoreStuff",
+ locals_fn: (locals) =>
+ {
+ Assert.Single(locals);
+ CheckObject(locals, "this", "Simple.Complex");
+ }
+ );
+
+ var props = await GetObjectOnFrame(pause_location["callFrames"][0], "this");
+ Assert.Equal(3, props.Count());
+ CheckNumber(props, "A", 10);
+ CheckString(props, "B", "xx");
+ CheckObject(props, "c", "object");
+
+ // Check UseComplex frame
+ var locals_m1 = await GetLocalsForFrame(pause_location["callFrames"][3], debugger_test_loc, 23, 8, "UseComplex");
+ Assert.Equal(7, locals_m1.Count());
+
+ CheckNumber(locals_m1, "a", 10);
+ CheckNumber(locals_m1, "b", 20);
+ CheckObject(locals_m1, "complex", "Simple.Complex");
+ CheckNumber(locals_m1, "c", 30);
+ CheckNumber(locals_m1, "d", 50);
+ CheckNumber(locals_m1, "e", 60);
+ CheckNumber(locals_m1, "f", 0);
+
+ props = await GetObjectOnFrame(pause_location["callFrames"][3], "complex");
+ Assert.Equal(3, props.Count());
+ CheckNumber(props, "A", 10);
+ CheckString(props, "B", "xx");
+ CheckObject(props, "c", "object");
+
+ pause_location = await StepAndCheck(StepKind.Over, dep_cs_loc, 23, 8, "DoStuff", times: 2);
+ // Check UseComplex frame again
+ locals_m1 = await GetLocalsForFrame(pause_location["callFrames"][1], debugger_test_loc, 23, 8, "UseComplex");
+ Assert.Equal(7, locals_m1.Count());
+
+ CheckNumber(locals_m1, "a", 10);
+ CheckNumber(locals_m1, "b", 20);
+ CheckObject(locals_m1, "complex", "Simple.Complex");
+ CheckNumber(locals_m1, "c", 30);
+ CheckNumber(locals_m1, "d", 50);
+ CheckNumber(locals_m1, "e", 60);
+ CheckNumber(locals_m1, "f", 0);
+
+ props = await GetObjectOnFrame(pause_location["callFrames"][1], "complex");
+ Assert.Equal(3, props.Count());
+ CheckNumber(props, "A", 10);
+ CheckString(props, "B", "xx");
+ CheckObject(props, "c", "object");
+ });
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task InspectLocalsInPreviousFramesDuringSteppingIn(bool use_cfo)
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
+
+ var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs";
+ await SetBreakpoint(debugger_test_loc, 111, 12);
+
+ // Will stop in InnerMethod
+ var wait_res = await EvaluateAndCheck(
+ "window.setTimeout(function() { invoke_outer_method(); }, 1);",
+ debugger_test_loc, 111, 12, "InnerMethod",
+ locals_fn: (locals) =>
+ {
+ Assert.Equal(4, locals.Count());
+ CheckNumber(locals, "i", 5);
+ CheckNumber(locals, "j", 24);
+ CheckString(locals, "foo_str", "foo");
+ CheckObject(locals, "this", "Math.NestedInMath");
+ }
+ );
+
+ var this_props = await GetObjectOnFrame(wait_res["callFrames"][0], "this");
+ Assert.Equal(2, this_props.Count());
+ CheckObject(this_props, "m", "Math");
+ CheckValueType(this_props, "SimpleStructProperty", "Math.SimpleStruct");
+
+ var ss_props = await GetObjectOnLocals(this_props, "SimpleStructProperty");
+ var dt = new DateTime(2020, 1, 2, 3, 4, 5);
+ await CheckProps(ss_props, new
+ {
+ dt = TValueType("System.DateTime", dt.ToString()),
+ gs = TValueType("Math.GenericStruct<System.DateTime>")
+ }, "ss_props");
+
+ await CheckDateTime(ss_props, "dt", new DateTime(2020, 1, 2, 3, 4, 5));
+
+ // Check OuterMethod frame
+ var locals_m1 = await GetLocalsForFrame(wait_res["callFrames"][1], debugger_test_loc, 87, 8, "OuterMethod");
+ Assert.Equal(5, locals_m1.Count());
+ // FIXME: Failing test CheckNumber (locals_m1, "i", 5);
+ // FIXME: Failing test CheckString (locals_m1, "text", "Hello");
+ CheckNumber(locals_m1, "new_i", 0);
+ CheckNumber(locals_m1, "k", 0);
+ CheckObject(locals_m1, "nim", "Math.NestedInMath");
+
+ // step back into OuterMethod
+ await StepAndCheck(StepKind.Over, debugger_test_loc, 91, 8, "OuterMethod", times: 9,
+ locals_fn: (locals) =>
+ {
+ Assert.Equal(5, locals.Count());
+
+ // FIXME: Failing test CheckNumber (locals_m1, "i", 5);
+ CheckString(locals, "text", "Hello");
+ // FIXME: Failing test CheckNumber (locals, "new_i", 24);
+ CheckNumber(locals, "k", 19);
+ CheckObject(locals, "nim", "Math.NestedInMath");
+ }
+ );
+
+ //await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 81, 2, "OuterMethod", times: 2);
+
+ // step into InnerMethod2
+ await StepAndCheck(StepKind.Into, "dotnet://debugger-test.dll/debugger-test.cs", 96, 4, "InnerMethod2",
+ locals_fn: (locals) =>
+ {
+ Assert.Equal(3, locals.Count());
+
+ CheckString(locals, "s", "test string");
+ //out var: CheckNumber (locals, "k", 0);
+ CheckNumber(locals, "i", 24);
+ }
+ );
+
+ await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 100, 4, "InnerMethod2", times: 4,
+ locals_fn: (locals) =>
+ {
+ Assert.Equal(3, locals.Count());
+
+ CheckString(locals, "s", "test string");
+ // FIXME: Failing test CheckNumber (locals, "k", 34);
+ CheckNumber(locals, "i", 24);
+ }
+ );
+
+ await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 92, 8, "OuterMethod", times: 2,
+ locals_fn: (locals) =>
+ {
+ Assert.Equal(5, locals.Count());
+
+ CheckString(locals, "text", "Hello");
+ // FIXME: failing test CheckNumber (locals, "i", 5);
+ CheckNumber(locals, "new_i", 22);
+ CheckNumber(locals, "k", 34);
+ CheckObject(locals, "nim", "Math.NestedInMath");
+ }
+ );
+ });
+ }
+
+ [Fact]
+ public async Task InspectLocalsDuringSteppingIn()
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+
+ await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 86, 8);
+
+ await EvaluateAndCheck("window.setTimeout(function() { invoke_outer_method(); }, 1);",
+ "dotnet://debugger-test.dll/debugger-test.cs", 86, 8, "OuterMethod",
+ locals_fn: (locals) =>
+ {
+ Assert.Equal(5, locals.Count());
+
+ CheckObject(locals, "nim", "Math.NestedInMath");
+ CheckNumber(locals, "i", 5);
+ CheckNumber(locals, "k", 0);
+ CheckNumber(locals, "new_i", 0);
+ CheckString(locals, "text", null);
+ }
+ );
+
+ await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 87, 8, "OuterMethod",
+ locals_fn: (locals) =>
+ {
+ Assert.Equal(5, locals.Count());
+
+ CheckObject(locals, "nim", "Math.NestedInMath");
+ // FIXME: Failing test CheckNumber (locals, "i", 5);
+ CheckNumber(locals, "k", 0);
+ CheckNumber(locals, "new_i", 0);
+ CheckString(locals, "text", "Hello");
+ }
+ );
+
+ // Step into InnerMethod
+ await StepAndCheck(StepKind.Into, "dotnet://debugger-test.dll/debugger-test.cs", 105, 8, "InnerMethod");
+ await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 109, 12, "InnerMethod", times: 5,
+ locals_fn: (locals) =>
+ {
+ Assert.Equal(4, locals.Count());
+
+ CheckNumber(locals, "i", 5);
+ CheckNumber(locals, "j", 15);
+ CheckString(locals, "foo_str", "foo");
+ CheckObject(locals, "this", "Math.NestedInMath");
+ }
+ );
+
+ // Step back to OuterMethod
+ await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 88, 8, "OuterMethod", times: 6,
+ locals_fn: (locals) =>
+ {
+ Assert.Equal(5, locals.Count());
+
+ CheckObject(locals, "nim", "Math.NestedInMath");
+ // FIXME: Failing test CheckNumber (locals, "i", 5);
+ CheckNumber(locals, "k", 0);
+ CheckNumber(locals, "new_i", 24);
+ CheckString(locals, "text", "Hello");
+ }
+ );
+ });
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task InspectLocalsInAsyncMethods(bool use_cfo)
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
+ var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs";
+
+ await SetBreakpoint(debugger_test_loc, 120, 12);
+ await SetBreakpoint(debugger_test_loc, 135, 12);
+
+ // Will stop in Asyncmethod0
+ var wait_res = await EvaluateAndCheck(
+ "window.setTimeout(function() { invoke_async_method_with_await(); }, 1);",
+ debugger_test_loc, 120, 12, "MoveNext", //FIXME:
+ locals_fn: (locals) =>
+ {
+ Assert.Equal(4, locals.Count());
+ CheckString(locals, "s", "string from js");
+ CheckNumber(locals, "i", 42);
+ CheckString(locals, "local0", "value0");
+ CheckObject(locals, "this", "Math.NestedInMath");
+ }
+ );
+ Console.WriteLine(wait_res);
- [Fact]
- public async Task CreateGoodBreakpointAndHit () {
- var insp = new Inspector ();
-
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready ();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
-
- var bp = await SetBreakpoint ("dotnet://debugger-test.dll/debugger-test.cs", 5, 2);
-
- var eval_req = JObject.FromObject(new {
- expression = "window.setTimeout(function() { invoke_add(); }, 1);",
- });
-
- await EvaluateAndCheck (
- "window.setTimeout(function() { invoke_add(); }, 1);",
- "dotnet://debugger-test.dll/debugger-test.cs", 5, 2,
- "IntAdd",
- wait_for_event_fn: (pause_location) => {
- Assert.Equal ("other", pause_location ["reason"]?.Value<string> ());
- Assert.Equal (bp.Value["breakpointId"]?.ToString(), pause_location ["hitBreakpoints"]?[0]?.Value<string> ());
-
- var top_frame = pause_location ["callFrames"][0];
- Assert.Equal ("IntAdd", top_frame ["functionName"].Value<string>());
- Assert.Contains ("debugger-test.cs", top_frame ["url"].Value<string> ());
-
- CheckLocation ("dotnet://debugger-test.dll/debugger-test.cs", 3, 41, scripts, top_frame["functionLocation"]);
-
- //now check the scope
- var scope = top_frame ["scopeChain"][0];
- Assert.Equal ("local", scope ["type"]);
- Assert.Equal ("IntAdd", scope ["name"]);
-
- Assert.Equal ("object", scope ["object"]["type"]);
- Assert.Equal ("dotnet:scope:0", scope ["object"]["objectId"]);
- CheckLocation ("dotnet://debugger-test.dll/debugger-test.cs", 3, 41, scripts, scope["startLocation"]);
- CheckLocation ("dotnet://debugger-test.dll/debugger-test.cs", 9, 1, scripts, scope["endLocation"]);
- return Task.CompletedTask;
- }
- );
-
- });
- }
-
- [Fact]
- public async Task ExceptionThrownInJS () {
- var insp = new Inspector ();
-
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready ();
- await insp.Ready (async (cli, token) => {
- var eval_req = JObject.FromObject(new {
- expression = "invoke_bad_js_test();"
- });
-
- var eval_res = await cli.SendCommand ("Runtime.evaluate", eval_req, token);
- Assert.True (eval_res.IsErr);
- Assert.Equal ("Uncaught", eval_res.Error ["exceptionDetails"]? ["text"]? .Value<string> ());
- });
- }
-
- [Fact]
- public async Task ExceptionThrownInJSOutOfBand () {
- var insp = new Inspector ();
-
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready ();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
-
- await SetBreakpoint ("/debugger-driver.html", 27, 2);
-
- var eval_req = JObject.FromObject(new {
- expression = "window.setTimeout(function() { invoke_bad_js_test(); }, 1);",
- });
-
- var eval_res = await cli.SendCommand ("Runtime.evaluate", eval_req, token);
- // Response here will be the id for the timer from JS!
- Assert.True (eval_res.IsOk);
-
- var ex = await Assert.ThrowsAsync<ArgumentException> (async () => await insp.WaitFor("Runtime.exceptionThrown"));
- var ex_json = JObject.Parse (ex.Message);
- Assert.Equal (dicFileToUrl["/debugger-driver.html"], ex_json ["exceptionDetails"]? ["url"]? .Value<string> ());
- });
-
- }
-
- [Theory]
- [InlineData (false)]
- [InlineData (true)]
- public async Task InspectLocalsAtBreakpointSite (bool use_cfo) =>
- await CheckInspectLocalsAtBreakpointSite (
- "dotnet://debugger-test.dll/debugger-test.cs", 5, 2, "IntAdd",
- "window.setTimeout(function() { invoke_add(); }, 1);",
- use_cfo: use_cfo,
- test_fn: (locals) => {
- CheckNumber (locals, "a", 10);
- CheckNumber (locals, "b", 20);
- CheckNumber (locals, "c", 30);
- CheckNumber (locals, "d", 0);
- CheckNumber (locals, "e", 0);
- }
- );
-
- [Fact]
- public async Task InspectPrimitiveTypeLocalsAtBreakpointSite () =>
- await CheckInspectLocalsAtBreakpointSite (
- "dotnet://debugger-test.dll/debugger-test.cs", 145, 2, "PrimitiveTypesTest",
- "window.setTimeout(function() { invoke_static_method ('[debugger-test] Math:PrimitiveTypesTest'); }, 1);",
- test_fn: (locals) => {
- CheckSymbol (locals, "c0", "8364 '€'");
- CheckSymbol (locals, "c1", "65 'A'");
- }
- );
-
- [Fact]
- public async Task InspectLocalsTypesAtBreakpointSite () =>
- await CheckInspectLocalsAtBreakpointSite (
- "dotnet://debugger-test.dll/debugger-test2.cs", 40, 2, "Types",
- "window.setTimeout(function() { invoke_static_method (\"[debugger-test] Fancy:Types\")(); }, 1);",
- use_cfo: false,
- test_fn: (locals) => {
- CheckNumber (locals, "dPI", Math.PI);
- CheckNumber (locals, "fPI", (float)Math.PI);
- CheckNumber (locals, "iMax", int.MaxValue);
- CheckNumber (locals, "iMin", int.MinValue);
- CheckNumber (locals, "uiMax", uint.MaxValue);
- CheckNumber (locals, "uiMin", uint.MinValue);
-
- CheckNumber (locals, "l", uint.MaxValue * (long)2);
- //CheckNumber (locals, "lMax", long.MaxValue); // cannot be represented as double
- //CheckNumber (locals, "lMin", long.MinValue); // cannot be represented as double
-
- CheckNumber (locals, "sbMax", sbyte.MaxValue);
- CheckNumber (locals, "sbMin", sbyte.MinValue);
- CheckNumber (locals, "bMax", byte.MaxValue);
- CheckNumber (locals, "bMin", byte.MinValue);
-
- CheckNumber (locals, "sMax", short.MaxValue);
- CheckNumber (locals, "sMin", short.MinValue);
- CheckNumber (locals, "usMin", ushort.MinValue);
- CheckNumber (locals, "usMax", ushort.MaxValue);
- }
- );
-
- [Theory]
- [InlineData (false)]
- [InlineData (true)]
- public async Task InspectLocalsWithGenericTypesAtBreakpointSite (bool use_cfo) =>
- await CheckInspectLocalsAtBreakpointSite (
- "dotnet://debugger-test.dll/debugger-test.cs", 65, 2, "GenericTypesTest",
- "window.setTimeout(function() { invoke_generic_types_test (); }, 1);",
- use_cfo: use_cfo,
- test_fn: (locals) => {
- CheckObject (locals, "list", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>");
- CheckObject (locals, "list_null", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>", is_null: true);
-
- CheckArray (locals, "list_arr", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>[]");
- CheckObject (locals, "list_arr_null", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>[]", is_null: true);
-
- // Unused locals
- CheckObject (locals, "list_unused", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>");
- CheckObject (locals, "list_null_unused", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>", is_null: true);
-
- CheckObject (locals, "list_arr_unused", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>[]");
- CheckObject (locals, "list_arr_null_unused", "System.Collections.Generic.Dictionary<Math[], Math.IsMathNull>[]", is_null: true);
- }
- );
-
- object TGenericStruct(string typearg, string stringField)
- => new {
- List = TObject ($"System.Collections.Generic.List<{typearg}>"),
- StringField = TString (stringField)
- };
-
- [Fact]
- public async Task RuntimeGetPropertiesWithInvalidScopeIdTest () {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready ();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
-
- var bp = await SetBreakpoint ("dotnet://debugger-test.dll/debugger-test.cs", 41, 2);
-
- await EvaluateAndCheck (
- "window.setTimeout(function() { invoke_delegates_test (); }, 1);",
- "dotnet://debugger-test.dll/debugger-test.cs", 41, 2,
- "DelegatesTest",
- wait_for_event_fn: async (pause_location) => {
- //make sure we're on the right bp
- Assert.Equal (bp.Value ["breakpointId"]?.ToString (), pause_location ["hitBreakpoints"]?[0]?.Value<string> ());
-
- var top_frame = pause_location ["callFrames"][0];
-
- var scope = top_frame ["scopeChain"][0];
- Assert.Equal ("dotnet:scope:0", scope ["object"]["objectId"]);
-
- // Try to get an invalid scope!
- var get_prop_req = JObject.FromObject(new {
- objectId = "dotnet:scope:23490871",
- });
-
- var frame_props = await cli.SendCommand ("Runtime.getProperties", get_prop_req, token);
- Assert.True (frame_props.IsErr);
- }
- );
- });
- }
-
- [Fact]
- public async Task TrivalStepping () {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready ();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
-
- var bp = await SetBreakpoint ("dotnet://debugger-test.dll/debugger-test.cs", 5, 2);
-
- await EvaluateAndCheck (
- "window.setTimeout(function() { invoke_add(); }, 1);",
- "dotnet://debugger-test.dll/debugger-test.cs", 5, 2,
- "IntAdd",
- wait_for_event_fn: (pause_location) => {
- //make sure we're on the right bp
- Assert.Equal (bp.Value ["breakpointId"]?.ToString (), pause_location ["hitBreakpoints"]?[0]?.Value<string> ());
-
- var top_frame = pause_location ["callFrames"][0];
- CheckLocation ("dotnet://debugger-test.dll/debugger-test.cs", 3, 41, scripts, top_frame["functionLocation"]);
- return Task.CompletedTask;
- }
- );
-
- await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 6, 2, "IntAdd",
- wait_for_event_fn: (pause_location) => {
- var top_frame = pause_location ["callFrames"][0];
- CheckLocation ("dotnet://debugger-test.dll/debugger-test.cs", 3, 41, scripts, top_frame["functionLocation"]);
- return Task.CompletedTask;
- }
- );
- });
- }
-
- [Fact]
- public async Task InspectLocalsDuringStepping () {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
-
- var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs";
- await SetBreakpoint (debugger_test_loc, 4, 2);
-
- await EvaluateAndCheck (
- "window.setTimeout(function() { invoke_add(); }, 1);",
- debugger_test_loc, 4, 2, "IntAdd",
- locals_fn: (locals) => {
- CheckNumber (locals, "a", 10);
- CheckNumber (locals, "b", 20);
- CheckNumber (locals, "c", 0);
- CheckNumber (locals, "d", 0);
- CheckNumber (locals, "e", 0);
- }
- );
-
- await StepAndCheck (StepKind.Over, debugger_test_loc, 5, 2, "IntAdd",
- locals_fn: (locals) => {
- CheckNumber (locals, "a", 10);
- CheckNumber (locals, "b", 20);
- CheckNumber (locals, "c", 30);
- CheckNumber (locals, "d", 0);
- CheckNumber (locals, "e", 0);
- }
- );
-
- //step and get locals
- await StepAndCheck (StepKind.Over, debugger_test_loc, 6, 2, "IntAdd",
- locals_fn: (locals) => {
- CheckNumber (locals, "a", 10);
- CheckNumber (locals, "b", 20);
- CheckNumber (locals, "c", 30);
- CheckNumber (locals, "d", 50);
- CheckNumber (locals, "e", 0);
- }
- );
- });
- }
-
- [Theory]
- [InlineData (false)]
- [InlineData (true)]
- public async Task InspectLocalsInPreviousFramesDuringSteppingIn2 (bool use_cfo) {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
-
- var dep_cs_loc = "dotnet://Simple.Dependency.dll/dependency.cs";
- await SetBreakpoint (dep_cs_loc, 24, 2);
-
- var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs";
-
- // Will stop in Complex.DoEvenMoreStuff
- var pause_location = await EvaluateAndCheck (
- "window.setTimeout(function() { invoke_use_complex (); }, 1);",
- dep_cs_loc, 24, 2, "DoEvenMoreStuff",
- locals_fn: (locals) => {
- Assert.Single (locals);
- CheckObject (locals, "this", "Simple.Complex");
- }
- );
-
- var props = await GetObjectOnFrame (pause_location["callFrames"][0], "this");
- Assert.Equal (3, props.Count());
- CheckNumber (props, "A", 10);
- CheckString (props, "B", "xx");
- CheckObject (props, "c", "object");
-
- // Check UseComplex frame
- var locals_m1 = await GetLocalsForFrame (pause_location ["callFrames"][3], debugger_test_loc, 17, 2, "UseComplex");
- Assert.Equal (7, locals_m1.Count());
-
- CheckNumber (locals_m1, "a", 10);
- CheckNumber (locals_m1, "b", 20);
- CheckObject (locals_m1, "complex", "Simple.Complex");
- CheckNumber (locals_m1, "c", 30);
- CheckNumber (locals_m1, "d", 50);
- CheckNumber (locals_m1, "e", 60);
- CheckNumber (locals_m1, "f", 0);
-
- props = await GetObjectOnFrame (pause_location["callFrames"][3], "complex");
- Assert.Equal (3, props.Count());
- CheckNumber (props, "A", 10);
- CheckString (props, "B", "xx");
- CheckObject (props, "c", "object");
-
- pause_location = await StepAndCheck (StepKind.Over, dep_cs_loc, 16, 2, "DoStuff", times: 2);
- // Check UseComplex frame again
- locals_m1 = await GetLocalsForFrame (pause_location ["callFrames"][1], debugger_test_loc, 17, 2, "UseComplex");
- Assert.Equal (7, locals_m1.Count());
-
- CheckNumber (locals_m1, "a", 10);
- CheckNumber (locals_m1, "b", 20);
- CheckObject (locals_m1, "complex", "Simple.Complex");
- CheckNumber (locals_m1, "c", 30);
- CheckNumber (locals_m1, "d", 50);
- CheckNumber (locals_m1, "e", 60);
- CheckNumber (locals_m1, "f", 0);
-
- props = await GetObjectOnFrame (pause_location["callFrames"][1], "complex");
- Assert.Equal (3, props.Count());
- CheckNumber (props, "A", 10);
- CheckString (props, "B", "xx");
- CheckObject (props, "c", "object");
- });
- }
-
- [Theory]
- [InlineData (false)]
- [InlineData (true)]
- public async Task InspectLocalsInPreviousFramesDuringSteppingIn (bool use_cfo) {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
-
- var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs";
- await SetBreakpoint (debugger_test_loc, 102, 3);
-
- // Will stop in InnerMethod
- var wait_res = await EvaluateAndCheck (
- "window.setTimeout(function() { invoke_outer_method(); }, 1);",
- debugger_test_loc, 102, 3, "InnerMethod",
- locals_fn: (locals) => {
- Assert.Equal (4, locals.Count());
- CheckNumber (locals, "i", 5);
- CheckNumber (locals, "j", 24);
- CheckString (locals, "foo_str", "foo");
- CheckObject (locals, "this", "Math.NestedInMath");
- }
- );
-
- var this_props = await GetObjectOnFrame (wait_res["callFrames"][0], "this");
- Assert.Equal (2, this_props.Count());
- CheckObject (this_props, "m", "Math");
- CheckValueType (this_props, "SimpleStructProperty", "Math.SimpleStruct");
-
- var ss_props = await GetObjectOnLocals (this_props, "SimpleStructProperty");
- Assert.Equal (2, ss_props.Count());
- CheckValueType (ss_props, "dt", "System.DateTime");
- CheckValueType (ss_props, "gs", "Math.GenericStruct<System.DateTime>");
-
- await CheckDateTime (ss_props, "dt", new DateTime (2020, 1, 2, 3, 4, 5));
-
- // Check OuterMethod frame
- var locals_m1 = await GetLocalsForFrame (wait_res ["callFrames"][1], debugger_test_loc, 78, 2, "OuterMethod");
- Assert.Equal (5, locals_m1.Count());
- // FIXME: Failing test CheckNumber (locals_m1, "i", 5);
- // FIXME: Failing test CheckString (locals_m1, "text", "Hello");
- CheckNumber (locals_m1, "new_i", 0);
- CheckNumber (locals_m1, "k", 0);
- CheckObject (locals_m1, "nim", "Math.NestedInMath");
-
- // step back into OuterMethod
- await StepAndCheck (StepKind.Over, debugger_test_loc, 82, 2, "OuterMethod", times: 9,
- locals_fn: (locals) => {
- Assert.Equal (5, locals.Count());
-
- // FIXME: Failing test CheckNumber (locals_m1, "i", 5);
- CheckString (locals, "text", "Hello");
- // FIXME: Failing test CheckNumber (locals, "new_i", 24);
- CheckNumber (locals, "k", 19);
- CheckObject (locals, "nim", "Math.NestedInMath");
- }
- );
-
- //await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 81, 2, "OuterMethod", times: 2);
-
- // step into InnerMethod2
- await StepAndCheck (StepKind.Into, "dotnet://debugger-test.dll/debugger-test.cs", 87, 1, "InnerMethod2",
- locals_fn: (locals) => {
- Assert.Equal (3, locals.Count());
-
- CheckString (locals, "s", "test string");
- //out var: CheckNumber (locals, "k", 0);
- CheckNumber (locals, "i", 24);
- }
- );
-
- await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 91, 1, "InnerMethod2", times: 4,
- locals_fn: (locals) => {
- Assert.Equal (3, locals.Count());
-
- CheckString (locals, "s", "test string");
- // FIXME: Failing test CheckNumber (locals, "k", 34);
- CheckNumber (locals, "i", 24);
- }
- );
-
- await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 83, 2, "OuterMethod", times: 2,
- locals_fn: (locals) => {
- Assert.Equal (5, locals.Count());
-
- CheckString (locals, "text", "Hello");
- // FIXME: failing test CheckNumber (locals, "i", 5);
- CheckNumber (locals, "new_i", 22);
- CheckNumber (locals, "k", 34);
- CheckObject (locals, "nim", "Math.NestedInMath");
- }
- );
- });
- }
-
- [Fact]
- public async Task InspectLocalsDuringSteppingIn () {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
-
- await SetBreakpoint ("dotnet://debugger-test.dll/debugger-test.cs", 77, 2);
-
- await EvaluateAndCheck ("window.setTimeout(function() { invoke_outer_method(); }, 1);",
- "dotnet://debugger-test.dll/debugger-test.cs", 77, 2, "OuterMethod",
- locals_fn: (locals) => {
- Assert.Equal (5, locals.Count());
-
- CheckObject (locals, "nim", "Math.NestedInMath");
- CheckNumber (locals, "i", 5);
- CheckNumber (locals, "k", 0);
- CheckNumber (locals, "new_i", 0);
- CheckString (locals, "text", null);
- }
- );
-
- await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 78, 2, "OuterMethod",
- locals_fn: (locals) => {
- Assert.Equal (5, locals.Count());
-
- CheckObject (locals, "nim", "Math.NestedInMath");
- // FIXME: Failing test CheckNumber (locals, "i", 5);
- CheckNumber (locals, "k", 0);
- CheckNumber (locals, "new_i", 0);
- CheckString (locals, "text", "Hello");
- }
- );
-
- // Step into InnerMethod
- await StepAndCheck (StepKind.Into, "dotnet://debugger-test.dll/debugger-test.cs", 96, 2, "InnerMethod");
- await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 100, 3, "InnerMethod", times: 5,
- locals_fn: (locals) => {
- Assert.Equal (4, locals.Count());
-
- CheckNumber (locals, "i", 5);
- CheckNumber (locals, "j", 15);
- CheckString (locals, "foo_str", "foo");
- CheckObject (locals, "this", "Math.NestedInMath");
- }
- );
-
- // Step back to OuterMethod
- await StepAndCheck (StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", 79, 2, "OuterMethod", times: 6,
- locals_fn: (locals) => {
- Assert.Equal (5, locals.Count());
-
- CheckObject (locals, "nim", "Math.NestedInMath");
- // FIXME: Failing test CheckNumber (locals, "i", 5);
- CheckNumber (locals, "k", 0);
- CheckNumber (locals, "new_i", 24);
- CheckString (locals, "text", "Hello");
- }
- );
- });
- }
-
- [Theory]
- [InlineData (false)]
- [InlineData (true)]
- public async Task InspectLocalsInAsyncMethods (bool use_cfo) {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
- var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs";
-
- await SetBreakpoint (debugger_test_loc, 111, 3);
- await SetBreakpoint (debugger_test_loc, 126, 3);
-
- // Will stop in Asyncmethod0
- var wait_res = await EvaluateAndCheck (
- "window.setTimeout(function() { invoke_async_method_with_await(); }, 1);",
- debugger_test_loc, 111, 3, "MoveNext", //FIXME:
- locals_fn: (locals) => {
- Assert.Equal (4, locals.Count());
- CheckString (locals, "s", "string from js");
- CheckNumber (locals, "i", 42);
- CheckString (locals, "local0", "value0");
- CheckObject (locals, "this", "Math.NestedInMath");
- }
- );
- Console.WriteLine (wait_res);
-
#if false // Disabled for now, as we don't have proper async traces
- var locals = await GetProperties (wait_res ["callFrames"][2]["callFrameId"].Value<string> ());
- Assert.Equal (4, locals.Count());
- CheckString (locals, "ls", "string from jstest");
- CheckNumber (locals, "li", 52);
+ var locals = await GetProperties(wait_res["callFrames"][2]["callFrameId"].Value<string>());
+ Assert.Equal(4, locals.Count());
+ CheckString(locals, "ls", "string from jstest");
+ CheckNumber(locals, "li", 52);
#endif
- // TODO: previous frames have async machinery details, so no point checking that right now
-
- var pause_loc = await SendCommandAndCheck (null, "Debugger.resume", debugger_test_loc, 126, 3, /*FIXME: "AsyncMethodNoReturn"*/ "MoveNext",
- locals_fn: (locals) => {
- Assert.Equal (4, locals.Count());
- CheckString (locals, "str", "AsyncMethodNoReturn's local");
- CheckObject (locals, "this", "Math.NestedInMath");
- //FIXME: check fields
- CheckValueType (locals, "ss", "Math.SimpleStruct");
- CheckArray (locals, "ss_arr", "Math.SimpleStruct[]");
- // TODO: struct fields
- }
- );
-
- var this_props = await GetObjectOnFrame (pause_loc ["callFrames"][0], "this");
- Assert.Equal (2, this_props.Count ());
- CheckObject (this_props, "m", "Math");
- CheckValueType (this_props, "SimpleStructProperty", "Math.SimpleStruct");
-
- // TODO: Check `this` properties
- });
- }
-
- [Theory]
- [InlineData (false)]
- [InlineData (true)]
- public async Task InspectLocalsWithStructs (bool use_cfo) {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
- var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs";
-
- await SetBreakpoint (debugger_test_loc, 16, 2);
-
- var pause_location = await EvaluateAndCheck (
- "window.setTimeout(function() { invoke_method_with_structs(); }, 1);",
- debugger_test_loc, 16, 2, "MethodWithLocalStructs",
- locals_fn: (locals) => {
- Assert.Equal (3, locals.Count ());
-
- CheckValueType (locals, "ss_local", "DebuggerTests.ValueTypesTest.SimpleStruct");
- CheckValueType (locals, "gs_local", "DebuggerTests.ValueTypesTest.GenericStruct<DebuggerTests.ValueTypesTest>");
- CheckObject (locals, "vt_local", "DebuggerTests.ValueTypesTest");
- }
- );
-
- var dt = new DateTime (2021, 2, 3, 4, 6, 7);
- // Check ss_local's properties
- var ss_local_props = await GetObjectOnFrame (pause_location ["callFrames"][0], "ss_local");
- await CheckProps (ss_local_props, new {
- str_member = TString ("set in MethodWithLocalStructs#SimpleStruct#str_member"),
- dt = TValueType ("System.DateTime", dt.ToString ()),
- gs = TValueType ("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"),
- Kind = TEnum ("System.DateTimeKind", "Utc")
- }, "ss_local");
-
- {
- // Check ss_local.dt
- await CheckDateTime (ss_local_props, "dt", dt);
-
- // Check ss_local.gs
- var gs_props = await GetObjectOnLocals (ss_local_props, "gs");
- CheckString (gs_props, "StringField", "set in MethodWithLocalStructs#SimpleStruct#gs#StringField");
- CheckObject (gs_props, "List", "System.Collections.Generic.List<System.DateTime>");
- }
-
- // Check gs_local's properties
- var gs_local_props = await GetObjectOnFrame (pause_location ["callFrames"][0], "gs_local");
- await CheckProps (gs_local_props, new {
- StringField = TString ("gs_local#GenericStruct<ValueTypesTest>#StringField"),
- List = TObject ("System.Collections.Generic.List<DebuggerTests.ValueTypesTest>", is_null: true),
- Options = TEnum ("DebuggerTests.Options", "None")
- }, "gs_local");
-
- // Check vt_local's properties
- var vt_local_props = await GetObjectOnFrame (pause_location ["callFrames"][0], "vt_local");
- Assert.Equal (5, vt_local_props.Count());
-
- CheckString (vt_local_props, "StringField", "string#0");
- CheckValueType (vt_local_props, "SimpleStructField", "DebuggerTests.ValueTypesTest.SimpleStruct");
- CheckValueType (vt_local_props, "SimpleStructProperty", "DebuggerTests.ValueTypesTest.SimpleStruct");
- await CheckDateTime (vt_local_props, "DT", new DateTime (2020, 1, 2, 3, 4, 5));
- CheckEnum (vt_local_props, "RGB", "DebuggerTests.RGB", "Blue");
-
- {
- // SimpleStructProperty
- dt = new DateTime (2022, 3, 4, 5, 7, 8);
- var ssp_props = await CompareObjectPropertiesFor (vt_local_props, "SimpleStructProperty",
- new {
- str_member = TString ("SimpleStructProperty#string#0#SimpleStruct#str_member"),
- dt = TValueType ("System.DateTime", dt.ToString ()),
- gs = TValueType ("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"),
- Kind = TEnum ("System.DateTimeKind", "Utc")
- },
- label: "vt_local_props.SimpleStructProperty");
-
- await CheckDateTime (ssp_props, "dt", dt);
-
- // SimpleStructField
- dt = new DateTime (2025, 6, 7, 8, 10, 11);
- var ssf_props = await CompareObjectPropertiesFor (vt_local_props, "SimpleStructField",
- new {
- str_member = TString ("SimpleStructField#string#0#SimpleStruct#str_member"),
- dt = TValueType ("System.DateTime", dt.ToString ()),
- gs = TValueType ("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"),
- Kind = TEnum ("System.DateTimeKind", "Local")
- },
- label: "vt_local_props.SimpleStructField");
-
- await CheckDateTime (ssf_props, "dt", dt);
- }
-
- // FIXME: check ss_local.gs.List's members
- });
- }
-
- [Theory]
- [InlineData (false)]
- [InlineData (true)]
- public async Task InspectValueTypeMethodArgs (bool use_cfo) {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
- var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs";
-
- await SetBreakpoint (debugger_test_loc, 27, 3);
-
-
- var pause_location = await EvaluateAndCheck (
- "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:TestStructsAsMethodArgs'); }, 1);",
- debugger_test_loc, 27, 3, "MethodWithStructArgs",
- locals_fn: (locals) => {
- Assert.Equal (3, locals.Count ());
-
- CheckString (locals, "label", "TestStructsAsMethodArgs#label");
- CheckValueType (locals, "ss_arg", "DebuggerTests.ValueTypesTest.SimpleStruct");
- CheckNumber (locals, "x", 3);
- }
- );
-
- var dt = new DateTime (2025, 6, 7, 8, 10, 11);
- var ss_local_as_ss_arg = new {
- str_member = TString ("ss_local#SimpleStruct#string#0#SimpleStruct#str_member"),
- dt = TValueType ("System.DateTime", dt.ToString ()),
- gs = TValueType ("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"),
- Kind = TEnum ("System.DateTimeKind", "Local")
- };
- var ss_local_gs = new {
- StringField = TString ("ss_local#SimpleStruct#string#0#SimpleStruct#gs#StringField"),
- List = TObject ("System.Collections.Generic.List<System.DateTime>"),
- Options = TEnum ("DebuggerTests.Options", "Option1")
- };
-
- // Check ss_arg's properties
- var ss_arg_props = await GetObjectOnFrame (pause_location ["callFrames"][0], "ss_arg");
- await CheckProps (ss_arg_props, ss_local_as_ss_arg, "ss_arg");
-
- {
- // Check ss_local.dt
- await CheckDateTime (ss_arg_props, "dt", dt);
-
- // Check ss_local.gs
- await CompareObjectPropertiesFor (ss_arg_props, "gs", ss_local_gs);
- }
-
- pause_location = await StepAndCheck (StepKind.Over, debugger_test_loc, 31, 3, "MethodWithStructArgs", times: 4,
- locals_fn: (locals) => {
- Assert.Equal (3, locals.Count());
-
- CheckString (locals, "label", "TestStructsAsMethodArgs#label");
- CheckValueType (locals, "ss_arg", "DebuggerTests.ValueTypesTest.SimpleStruct");
- CheckNumber (locals, "x", 3);
-
- }
- );
-
- var ss_arg_updated = new {
- str_member = TString ("ValueTypesTest#MethodWithStructArgs#updated#ss_arg#str_member"),
- dt = TValueType ("System.DateTime", dt.ToString ()),
- gs = TValueType ("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"),
- Kind = TEnum ("System.DateTimeKind", "Utc")
- };
-
- ss_arg_props = await GetObjectOnFrame (pause_location ["callFrames"][0], "ss_arg");
- await CheckProps (ss_arg_props, ss_arg_updated, "ss_ar");
-
- {
- // Check ss_local.gs
- await CompareObjectPropertiesFor (ss_arg_props, "gs", new {
- StringField = TString ("ValueTypesTest#MethodWithStructArgs#updated#gs#StringField#3"),
- List = TObject ("System.Collections.Generic.List<System.DateTime>"),
- Options = TEnum ("DebuggerTests.Options", "Option1")
- });
-
- await CheckDateTime (ss_arg_props, "dt", dt);
- }
-
- // Check locals on previous frame, same as earlier in this test
- ss_arg_props = await GetObjectOnFrame (pause_location ["callFrames"][1], "ss_local");
- await CheckProps (ss_arg_props, ss_local_as_ss_arg, "ss_local");
-
- {
- // Check ss_local.dt
- await CheckDateTime (ss_arg_props, "dt", dt);
-
- // Check ss_local.gs
- var gs_props = await GetObjectOnLocals (ss_arg_props, "gs");
- CheckString (gs_props, "StringField", "ss_local#SimpleStruct#string#0#SimpleStruct#gs#StringField");
- CheckObject (gs_props, "List", "System.Collections.Generic.List<System.DateTime>");
- }
-
- // ----------- Step back to the caller ---------
-
- pause_location = await StepAndCheck (StepKind.Over, debugger_test_loc, 22, 3, "TestStructsAsMethodArgs",
- times: 2, locals_fn: (l) => { /* non-null to make sure that locals get fetched */} );
- var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string> ());
- await CheckProps (locals, new {
- ss_local = TValueType ("DebuggerTests.ValueTypesTest.SimpleStruct"),
- ss_ret = TValueType ("DebuggerTests.ValueTypesTest.SimpleStruct")
- },
- "locals#0");
-
- ss_arg_props = await GetObjectOnFrame (pause_location ["callFrames"] [0], "ss_local");
- await CheckProps (ss_arg_props, ss_local_as_ss_arg, "ss_local");
-
- {
- // Check ss_local.gs
- await CompareObjectPropertiesFor (ss_arg_props, "gs", ss_local_gs, label: "ss_local_gs");
- }
-
- // FIXME: check ss_local.gs.List's members
- });
- }
-
- [Fact]
- public async Task CheckUpdatedValueTypeFieldsOnResume ()
- {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs";
-
- var lines = new [] {186, 189};
- await SetBreakpoint (debugger_test_loc, lines [0], 3);
- await SetBreakpoint (debugger_test_loc, lines [1], 3);
-
- var pause_location = await EvaluateAndCheck (
- "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:MethodUpdatingValueTypeMembers'); }, 1);",
- debugger_test_loc, lines [0], 3, "MethodUpdatingValueTypeMembers");
-
- var dt = new DateTime (1, 2, 3, 4, 5, 6);
- await CheckLocals (pause_location, dt);
-
- // Resume
- dt = new DateTime (9, 8, 7, 6, 5, 4);
- pause_location = await SendCommandAndCheck (JObject.FromObject (new{}), "Debugger.resume", debugger_test_loc, lines[1], 3, "MethodUpdatingValueTypeMembers");
- await CheckLocals (pause_location, dt);
- });
-
- async Task CheckLocals (JToken pause_location, DateTime dt)
- {
- var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string> ());
- await CheckProps (locals, new {
- obj = TObject ("DebuggerTests.ClassForToStringTests"),
- vt = TObject ("DebuggerTests.StructForToStringTests")
- }, "locals");
-
- var obj_props = await GetObjectOnLocals (locals, "obj");
- {
- await CheckProps (obj_props, new {
- DT = TValueType ("System.DateTime", dt.ToString ())
- }, "locals#obj.DT", num_fields: 5);
-
- await CheckDateTime (obj_props, "DT", dt);
- }
-
- var vt_props = await GetObjectOnLocals (locals, "obj");
- {
- await CheckProps (vt_props, new {
- DT = TValueType ("System.DateTime", dt.ToString ())
- }, "locals#obj.DT", num_fields: 5);
-
- await CheckDateTime (vt_props, "DT", dt);
- }
- }
- }
-
- [Fact]
- public async Task CheckUpdatedValueTypeLocalsOnResumeAsync ()
- {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs";
-
- var lines = new [] { 195, 197 };
- await SetBreakpoint (debugger_test_loc, lines [0], 3);
- await SetBreakpoint (debugger_test_loc, lines [1], 3);
-
- var pause_location = await EvaluateAndCheck (
- "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:MethodUpdatingValueTypeLocalsAsync'); }, 1);",
- debugger_test_loc, lines [0], 3, "MoveNext");
-
- var dt = new DateTime (1, 2, 3, 4, 5, 6);
- var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string> ());
- await CheckDateTime (locals, "dt", dt);
-
- // Resume
- dt = new DateTime (9, 8, 7, 6, 5, 4);
- pause_location = await SendCommandAndCheck (JObject.FromObject (new{}), "Debugger.resume", debugger_test_loc, lines[1], 3, "MoveNext");
- locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string> ());
- await CheckDateTime (locals, "dt", dt);
- });
- }
-
- [Fact]
- public async Task CheckUpdatedVTArrayMembersOnResume ()
- {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs";
-
- var lines = new [] { 205, 207 };
- await SetBreakpoint (debugger_test_loc, lines [0], 3);
- await SetBreakpoint (debugger_test_loc, lines [1], 3);
-
- var dt = new DateTime (1, 2, 3, 4, 5, 6);
- var pause_location = await EvaluateAndCheck (
- "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:MethodUpdatingVTArrayMembers'); }, 1);",
- debugger_test_loc, lines [0], 3, "MethodUpdatingVTArrayMembers");
- await CheckArrayElements (pause_location, dt);
-
- // Resume
- dt = new DateTime (9, 8, 7, 6, 5, 4);
- pause_location = await SendCommandAndCheck (JObject.FromObject (new{}), "Debugger.resume", debugger_test_loc, lines[1], 3, "MethodUpdatingVTArrayMembers");
- await CheckArrayElements (pause_location, dt);
- });
-
- async Task CheckArrayElements (JToken pause_location, DateTime dt)
- {
- var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string> ());
- await CheckProps (locals, new {
- ssta = TArray ("DebuggerTests.StructForToStringTests[]", 1)
- }, "locals");
-
- var ssta = await GetObjectOnLocals (locals, "ssta");
- var sst0 = await GetObjectOnLocals (ssta, "0");
- await CheckProps (sst0, new {
- DT = TValueType ("System.DateTime", dt.ToString ())
- }, "dta [0]", num_fields: 5);
-
- await CheckDateTime (sst0, "DT", dt);
- }
- }
- [Theory]
- [InlineData (false)]
- [InlineData (true)]
- public async Task InspectLocalsWithStructsStaticAsync (bool use_cfo) {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
- var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs";
-
- await SetBreakpoint (debugger_test_loc, 47, 3);
-
- var pause_location = await EvaluateAndCheck (
- "window.setTimeout(function() { invoke_static_method_async ("
- + "'[debugger-test] DebuggerTests.ValueTypesTest:MethodWithLocalStructsStaticAsync'"
- + "); }, 1);",
- debugger_test_loc, 47, 3, "MoveNext"); //BUG: method name
-
- var locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string> ());
- await CheckProps (locals, new {
- ss_local = TObject ("DebuggerTests.ValueTypesTest.SimpleStruct"),
- gs_local = TValueType ("DebuggerTests.ValueTypesTest.GenericStruct<int>"),
- result = TBool (true)
- },
- "locals#0");
-
- var dt = new DateTime (2021, 2, 3, 4, 6, 7);
- // Check ss_local's properties
- var ss_local_props = await GetObjectOnFrame (pause_location ["callFrames"][0], "ss_local");
- await CheckProps (ss_local_props, new {
- str_member = TString ("set in MethodWithLocalStructsStaticAsync#SimpleStruct#str_member"),
- dt = TValueType ("System.DateTime", dt.ToString ()),
- gs = TValueType ("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"),
- Kind = TEnum ("System.DateTimeKind", "Utc")
- }, "ss_local");
-
- {
- // Check ss_local.dt
- await CheckDateTime (ss_local_props, "dt", dt);
-
- // Check ss_local.gs
- await CompareObjectPropertiesFor (ss_local_props, "gs",
- new {
- StringField = TString ("set in MethodWithLocalStructsStaticAsync#SimpleStruct#gs#StringField"),
- List = TObject ("System.Collections.Generic.List<System.DateTime>"),
- Options = TEnum ("DebuggerTests.Options", "Option1")
- }
- );
- }
-
- // Check gs_local's properties
- var gs_local_props = await GetObjectOnFrame (pause_location ["callFrames"][0], "gs_local");
- await CheckProps (gs_local_props, new {
- StringField = TString ("gs_local#GenericStruct<ValueTypesTest>#StringField"),
- List = TObject ("System.Collections.Generic.List<int>"),
- Options = TEnum ("DebuggerTests.Options", "Option2")
- }, "gs_local");
-
- // FIXME: check ss_local.gs.List's members
- });
- }
-
- [Theory]
- [InlineData (123, 3, "MethodWithLocalsForToStringTest", false, false)]
- [InlineData (133, 3, "MethodWithArgumentsForToStringTest", true, false)]
- [InlineData (175, 3, "MethodWithArgumentsForToStringTestAsync", true, true)]
- [InlineData (165, 3, "MethodWithArgumentsForToStringTestAsync", false, true)]
- public async Task InspectLocalsForToStringDescriptions (int line, int col, string method_name, bool call_other, bool invoke_async)
- {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
- string entry_method_name = $"[debugger-test] DebuggerTests.ValueTypesTest:MethodWithLocalsForToStringTest{(invoke_async ? "Async" : String.Empty)}";
- int frame_idx = 0;
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
- var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs";
-
- await SetBreakpoint (debugger_test_loc, line, col);
-
- var eval_expr = "window.setTimeout(function() {"
- + (invoke_async ? "invoke_static_method_async (" : "invoke_static_method (")
- + $"'{entry_method_name}',"
- + (call_other ? "true" : "false")
- + "); }, 1);";
- Console.WriteLine ($"{eval_expr}");
-
- var pause_location = await EvaluateAndCheck (eval_expr, debugger_test_loc, line, col, invoke_async ? "MoveNext" : method_name);
-
- var dt0 = new DateTime (2020, 1, 2, 3, 4, 5);
- var dt1 = new DateTime (2010, 5, 4, 3, 2, 1);
- var ts = dt0 - dt1;
- var dto = new DateTimeOffset (dt0, new TimeSpan(4, 5, 0));
-
- var frame_locals = await GetProperties (pause_location ["callFrames"][frame_idx]["callFrameId"].Value<string> ());
- await CheckProps (frame_locals, new {
- call_other = TBool (call_other),
- dt0 = TValueType ("System.DateTime", dt0.ToString ()),
- dt1 = TValueType ("System.DateTime", dt1.ToString ()),
- dto = TValueType ("System.DateTimeOffset", dto.ToString ()),
- ts = TValueType ("System.TimeSpan", ts.ToString ()),
- dec = TValueType ("System.Decimal", "123987123"),
- guid = TValueType ("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014"),
- dts = TArray ("System.DateTime[]", 2),
- obj = TObject ("DebuggerTests.ClassForToStringTests"),
- sst = TObject ("DebuggerTests.StructForToStringTests")
- }, "locals#0");
-
- var dts_0 = new DateTime (1983, 6, 7, 5, 6, 10);
- var dts_1 = new DateTime (1999, 10, 15, 1, 2, 3);
- var dts_elements = await GetObjectOnLocals (frame_locals, "dts");
- await CheckDateTime (dts_elements, "0", dts_0);
- await CheckDateTime (dts_elements, "1", dts_1);
-
- // TimeSpan
- await CompareObjectPropertiesFor (frame_locals, "ts",
- new {
- Days = TNumber (3530),
- Minutes = TNumber (2),
- Seconds = TNumber (4),
- }, "ts_props", num_fields: 12);
-
- // DateTimeOffset
- await CompareObjectPropertiesFor (frame_locals, "dto",
- new {
- Day = TNumber (2),
- Year = TNumber (2020),
- DayOfWeek = TEnum ("System.DayOfWeek", "Thursday")
- }, "dto_props", num_fields: 22);
-
- var DT = new DateTime (2004, 10, 15, 1, 2, 3);
- var DTO = new DateTimeOffset (dt0, new TimeSpan(2, 14, 0));
-
- var obj_props = await CompareObjectPropertiesFor (frame_locals, "obj",
- new {
- DT = TValueType ("System.DateTime", DT.ToString ()),
- DTO = TValueType ("System.DateTimeOffset", DTO.ToString ()),
- TS = TValueType ("System.TimeSpan", ts.ToString ()),
- Dec = TValueType ("System.Decimal", "1239871"),
- Guid = TValueType ("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014")
- }, "obj_props");
-
- DTO = new DateTimeOffset (dt0, new TimeSpan (3, 15, 0));
- var sst_props = await CompareObjectPropertiesFor (frame_locals, "sst",
- new {
- DT = TValueType ("System.DateTime", DT.ToString ()),
- DTO = TValueType ("System.DateTimeOffset", DTO.ToString ()),
- TS = TValueType ("System.TimeSpan", ts.ToString ()),
- Dec = TValueType ("System.Decimal", "1239871"),
- Guid = TValueType ("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014")
- }, "sst_props");
- });
- }
-
- [Fact]
- public async Task InspectLocals () {
- var insp = new Inspector ();
- var scripts = SubscribeToScripts (insp);
-
- await Ready();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
-
- var wait_res = await RunUntil ("locals_inner");
- var locals = await GetProperties (wait_res ["callFrames"][1]["callFrameId"].Value<string> ());
- });
- }
-
- [Theory]
- [InlineData (false)]
- [InlineData (true)]
- public async Task InspectLocalsForStructInstanceMethod (bool use_cfo)
- => await CheckInspectLocalsAtBreakpointSite (
- "dotnet://debugger-test.dll/debugger-array-test.cs", 236, 3,
- "GenericInstanceMethod<DebuggerTests.SimpleClass>",
- "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EntryClass:run'); })",
- use_cfo: use_cfo,
- wait_for_event_fn: async (pause_location) => {
- var frame_locals = await GetProperties (pause_location ["callFrames"][0]["callFrameId"].Value<string>());
-
- await CheckProps (frame_locals, new {
- sc_arg = TObject ("DebuggerTests.SimpleClass"),
- @this = TValueType ("DebuggerTests.Point"),
- local_gs = TValueType ("DebuggerTests.SimpleGenericStruct<int>")
- },
- "locals#0");
-
- await CompareObjectPropertiesFor (frame_locals, "local_gs",
- new {
- Id = TString ("local_gs#Id"),
- Color = TEnum ("DebuggerTests.RGB", "Green"),
- Value = TNumber (4)
- },
- label: "local_gs#0");
-
- await CompareObjectPropertiesFor (frame_locals, "sc_arg",
- TSimpleClass (10, 45, "sc_arg#Id", "Blue"),
- label: "sc_arg#0");
-
- await CompareObjectPropertiesFor (frame_locals, "this",
- TPoint (90, -4, "point#Id", "Green"),
- label: "this#0");
-
- });
-
- [Fact]
- public async Task SteppingIntoMscorlib () {
- var insp = new Inspector ();
- //Collect events
- var scripts = SubscribeToScripts(insp);
-
- await Ready ();
- await insp.Ready (async (cli, token) => {
- ctx = new DebugTestContext (cli, insp, token, scripts);
-
- var bp = await SetBreakpoint ("dotnet://debugger-test.dll/debugger-test.cs", 74, 2);
- var pause_location = await EvaluateAndCheck (
- "window.setTimeout(function() { invoke_static_method ('[debugger-test] Math:OuterMethod'); }, 1);",
- "dotnet://debugger-test.dll/debugger-test.cs", 74, 2,
- "OuterMethod");
-
- //make sure we're on the right bp
- Assert.Equal (bp.Value ["breakpointId"]?.ToString (), pause_location ["hitBreakpoints"]?[0]?.Value<string> ());
-
- pause_location = await SendCommandAndCheck (null, $"Debugger.stepInto", null, -1, -1, null);
- var top_frame = pause_location ["callFrames"][0];
-
- AssertEqual ("WriteLine", top_frame ["functionName"]?.Value<string> (), "Expected to be in WriteLine method");
- var script_id = top_frame ["functionLocation"]["scriptId"].Value<string> ();
- AssertEqual ("dotnet://mscorlib.dll/Console.cs", scripts [script_id], "Expected to stopped in System.Console.WriteLine");
- });
- }
-
- //TODO add tests covering basic stepping behavior as step in/out/over
- }
+ // TODO: previous frames have async machinery details, so no point checking that right now
+
+ var pause_loc = await SendCommandAndCheck(null, "Debugger.resume", debugger_test_loc, 135, 12, /*FIXME: "AsyncMethodNoReturn"*/ "MoveNext",
+ locals_fn: (locals) =>
+ {
+ Assert.Equal(4, locals.Count());
+ CheckString(locals, "str", "AsyncMethodNoReturn's local");
+ CheckObject(locals, "this", "Math.NestedInMath");
+ //FIXME: check fields
+ CheckValueType(locals, "ss", "Math.SimpleStruct");
+ CheckArray(locals, "ss_arr", "Math.SimpleStruct[]", 0);
+ // TODO: struct fields
+ }
+ );
+
+ var this_props = await GetObjectOnFrame(pause_loc["callFrames"][0], "this");
+ Assert.Equal(2, this_props.Count());
+ CheckObject(this_props, "m", "Math");
+ CheckValueType(this_props, "SimpleStructProperty", "Math.SimpleStruct");
+
+ // TODO: Check `this` properties
+ });
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task InspectLocalsWithStructs(bool use_cfo)
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
+ var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs";
+
+ await SetBreakpoint(debugger_test_loc, 24, 8);
+
+ var pause_location = await EvaluateAndCheck(
+ "window.setTimeout(function() { invoke_method_with_structs(); }, 1);",
+ debugger_test_loc, 24, 8, "MethodWithLocalStructs");
+
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ await CheckProps(locals, new
+ {
+ ss_local = TValueType("DebuggerTests.ValueTypesTest.SimpleStruct"),
+ gs_local = TValueType("DebuggerTests.ValueTypesTest.GenericStruct<DebuggerTests.ValueTypesTest>"),
+ vt_local = TObject("DebuggerTests.ValueTypesTest")
+ }, "locals");
+
+ var dt = new DateTime(2021, 2, 3, 4, 6, 7);
+ var vt_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "vt_local");
+ Assert.Equal(5, vt_local_props.Count());
+
+ CheckString(vt_local_props, "StringField", "string#0");
+ CheckValueType(vt_local_props, "SimpleStructField", "DebuggerTests.ValueTypesTest.SimpleStruct");
+ CheckValueType(vt_local_props, "SimpleStructProperty", "DebuggerTests.ValueTypesTest.SimpleStruct");
+ await CheckDateTime(vt_local_props, "DT", new DateTime(2020, 1, 2, 3, 4, 5));
+ CheckEnum(vt_local_props, "RGB", "DebuggerTests.RGB", "Blue");
+
+ // Check ss_local's properties
+ var ss_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_local");
+ await CheckProps(ss_local_props, new
+ {
+ V = TGetter("V"),
+ str_member = TString("set in MethodWithLocalStructs#SimpleStruct#str_member"),
+ dt = TValueType("System.DateTime", dt.ToString()),
+ gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"),
+ Kind = TEnum("System.DateTimeKind", "Utc")
+ }, "ss_local");
+
+ {
+ var gres = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_local"), "V");
+ await CheckValue(gres.Value["result"], TNumber(0xDEADBEEF + 2), $"ss_local#V");
+ // Check ss_local.dt
+ await CheckDateTime(ss_local_props, "dt", dt);
+
+ // Check ss_local.gs
+ var gs_props = await GetObjectOnLocals(ss_local_props, "gs");
+ CheckString(gs_props, "StringField", "set in MethodWithLocalStructs#SimpleStruct#gs#StringField");
+ CheckObject(gs_props, "List", "System.Collections.Generic.List<System.DateTime>");
+ }
+
+ // Check gs_local's properties
+ var gs_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "gs_local");
+ await CheckProps(gs_local_props, new
+ {
+ StringField = TString("gs_local#GenericStruct<ValueTypesTest>#StringField"),
+ List = TObject("System.Collections.Generic.List<DebuggerTests.ValueTypesTest>", is_null: true),
+ Options = TEnum("DebuggerTests.Options", "None")
+ }, "gs_local");
+
+ // Check vt_local's properties
+
+ var exp = new[]
+ {
+ ("SimpleStructProperty", 2, "Utc"),
+ ("SimpleStructField", 5, "Local")
+ };
+
+ foreach (var (name, bias, dt_kind) in exp)
+ {
+ dt = new DateTime(2020 + bias, 1 + bias, 2 + bias, 3 + bias, 5 + bias, 6 + bias);
+ var ssp_props = await CompareObjectPropertiesFor(vt_local_props, name,
+ new
+ {
+ V = TGetter("V"),
+ str_member = TString($"{name}#string#0#SimpleStruct#str_member"),
+ dt = TValueType("System.DateTime", dt.ToString()),
+ gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"),
+ Kind = TEnum("System.DateTimeKind", dt_kind)
+ },
+ label: $"vt_local_props.{name}");
+
+ await CheckDateTime(ssp_props, "dt", dt);
+ var gres = await InvokeGetter(GetAndAssertObjectWithName(vt_local_props, name), "V");
+ await CheckValue(gres.Value["result"], TNumber(0xDEADBEEF + (uint)dt.Month), $"{name}#V");
+ }
+
+ // FIXME: check ss_local.gs.List's members
+ });
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task InspectValueTypeMethodArgs(bool use_cfo)
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
+ var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs";
+
+ await SetBreakpoint(debugger_test_loc, 36, 12);
+
+ var pause_location = await EvaluateAndCheck(
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:TestStructsAsMethodArgs'); }, 1);",
+ debugger_test_loc, 36, 12, "MethodWithStructArgs");
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ {
+ Assert.Equal(3, locals.Count());
+ CheckString(locals, "label", "TestStructsAsMethodArgs#label");
+ CheckValueType(locals, "ss_arg", "DebuggerTests.ValueTypesTest.SimpleStruct");
+ CheckNumber(locals, "x", 3);
+ }
+
+ var dt = new DateTime(2025, 6, 7, 8, 10, 11);
+ var ss_local_as_ss_arg = new
+ {
+ V = TGetter("V"),
+ str_member = TString("ss_local#SimpleStruct#string#0#SimpleStruct#str_member"),
+ dt = TValueType("System.DateTime", dt.ToString()),
+ gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"),
+ Kind = TEnum("System.DateTimeKind", "Local")
+ };
+ var ss_local_gs = new
+ {
+ StringField = TString("ss_local#SimpleStruct#string#0#SimpleStruct#gs#StringField"),
+ List = TObject("System.Collections.Generic.List<System.DateTime>"),
+ Options = TEnum("DebuggerTests.Options", "Option1")
+ };
+
+ // Check ss_arg's properties
+ var ss_arg_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_arg");
+ await CheckProps(ss_arg_props, ss_local_as_ss_arg, "ss_arg");
+
+ var res = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_arg"), "V");
+ await CheckValue(res.Value["result"], TNumber(0xDEADBEEF + (uint)dt.Month), "ss_arg#V");
+
+ {
+ // Check ss_local.dt
+ await CheckDateTime(ss_arg_props, "dt", dt);
+
+ // Check ss_local.gs
+ await CompareObjectPropertiesFor(ss_arg_props, "gs", ss_local_gs);
+ }
+
+ pause_location = await StepAndCheck(StepKind.Over, debugger_test_loc, 40, 8, "MethodWithStructArgs", times: 4,
+ locals_fn: (l) => { /* non-null to make sure that locals get fetched */ });
+ locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ {
+ Assert.Equal(3, locals.Count());
+
+ CheckString(locals, "label", "TestStructsAsMethodArgs#label");
+ CheckValueType(locals, "ss_arg", "DebuggerTests.ValueTypesTest.SimpleStruct");
+ CheckNumber(locals, "x", 3);
+ }
+
+ var ss_arg_updated = new
+ {
+ V = TGetter("V"),
+ str_member = TString("ValueTypesTest#MethodWithStructArgs#updated#ss_arg#str_member"),
+ dt = TValueType("System.DateTime", dt.ToString()),
+ gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"),
+ Kind = TEnum("System.DateTimeKind", "Utc")
+ };
+
+ ss_arg_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_arg");
+ await CheckProps(ss_arg_props, ss_arg_updated, "ss_arg");
+
+ res = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_arg"), "V");
+ await CheckValue(res.Value["result"], TNumber(0xDEADBEEF + (uint)dt.Month), "ss_arg#V");
+
+ {
+ // Check ss_local.gs
+ await CompareObjectPropertiesFor(ss_arg_props, "gs", new
+ {
+ StringField = TString("ValueTypesTest#MethodWithStructArgs#updated#gs#StringField#3"),
+ List = TObject("System.Collections.Generic.List<System.DateTime>"),
+ Options = TEnum("DebuggerTests.Options", "Option1")
+ });
+
+ await CheckDateTime(ss_arg_props, "dt", dt);
+ }
+
+ // Check locals on previous frame, same as earlier in this test
+ ss_arg_props = await GetObjectOnFrame(pause_location["callFrames"][1], "ss_local");
+ await CheckProps(ss_arg_props, ss_local_as_ss_arg, "ss_local");
+
+ {
+ // Check ss_local.dt
+ await CheckDateTime(ss_arg_props, "dt", dt);
+
+ // Check ss_local.gs
+ var gs_props = await GetObjectOnLocals(ss_arg_props, "gs");
+ CheckString(gs_props, "StringField", "ss_local#SimpleStruct#string#0#SimpleStruct#gs#StringField");
+ CheckObject(gs_props, "List", "System.Collections.Generic.List<System.DateTime>");
+ }
+
+ // ----------- Step back to the caller ---------
+
+ pause_location = await StepAndCheck(StepKind.Over, debugger_test_loc, 30, 12, "TestStructsAsMethodArgs",
+ times: 2, locals_fn: (l) => { /* non-null to make sure that locals get fetched */ });
+ locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ await CheckProps(locals, new
+ {
+ ss_local = TValueType("DebuggerTests.ValueTypesTest.SimpleStruct"),
+ ss_ret = TValueType("DebuggerTests.ValueTypesTest.SimpleStruct")
+ },
+ "locals#0");
+
+ ss_arg_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_local");
+ await CheckProps(ss_arg_props, ss_local_as_ss_arg, "ss_local");
+
+ {
+ // Check ss_local.gs
+ await CompareObjectPropertiesFor(ss_arg_props, "gs", ss_local_gs, label: "ss_local_gs");
+ }
+
+ // FIXME: check ss_local.gs.List's members
+ });
+ }
+
+ [Fact]
+ public async Task CheckUpdatedValueTypeFieldsOnResume()
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs";
+
+ var lines = new[] { 205, 208 };
+ await SetBreakpoint(debugger_test_loc, lines[0], 12);
+ await SetBreakpoint(debugger_test_loc, lines[1], 12);
+
+ var pause_location = await EvaluateAndCheck(
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:MethodUpdatingValueTypeMembers'); }, 1);",
+ debugger_test_loc, lines[0], 12, "MethodUpdatingValueTypeMembers");
+
+ await CheckLocals(pause_location, new DateTime(1, 2, 3, 4, 5, 6), new DateTime(4, 5, 6, 7, 8, 9));
+
+ // Resume
+ pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", debugger_test_loc, lines[1], 12, "MethodUpdatingValueTypeMembers");
+ await CheckLocals(pause_location, new DateTime(9, 8, 7, 6, 5, 4), new DateTime(5, 1, 3, 7, 9, 10));
+ });
+
+ async Task CheckLocals(JToken pause_location, DateTime obj_dt, DateTime vt_dt)
+ {
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ await CheckProps(locals, new
+ {
+ obj = TObject("DebuggerTests.ClassForToStringTests"),
+ vt = TObject("DebuggerTests.StructForToStringTests")
+ }, "locals");
+
+ var obj_props = await GetObjectOnLocals(locals, "obj");
+ {
+ await CheckProps(obj_props, new
+ {
+ DT = TValueType("System.DateTime", obj_dt.ToString())
+ }, "locals#obj.DT", num_fields: 5);
+
+ await CheckDateTime(obj_props, "DT", obj_dt);
+ }
+
+ var vt_props = await GetObjectOnLocals(locals, "vt");
+ {
+ await CheckProps(vt_props, new
+ {
+ DT = TValueType("System.DateTime", vt_dt.ToString())
+ }, "locals#obj.DT", num_fields: 5);
+
+ await CheckDateTime(vt_props, "DT", vt_dt);
+ }
+ }
+ }
+
+ [Fact]
+ public async Task CheckUpdatedValueTypeLocalsOnResumeAsync()
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs";
+
+ var lines = new[] { 214, 216 };
+ await SetBreakpoint(debugger_test_loc, lines[0], 12);
+ await SetBreakpoint(debugger_test_loc, lines[1], 12);
+
+ var pause_location = await EvaluateAndCheck(
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:MethodUpdatingValueTypeLocalsAsync'); }, 1);",
+ debugger_test_loc, lines[0], 12, "MoveNext");
+
+ var dt = new DateTime(1, 2, 3, 4, 5, 6);
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ await CheckDateTime(locals, "dt", dt);
+
+ // Resume
+ dt = new DateTime(9, 8, 7, 6, 5, 4);
+ pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", debugger_test_loc, lines[1], 12, "MoveNext");
+ locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ await CheckDateTime(locals, "dt", dt);
+ });
+ }
+
+ [Fact]
+ public async Task CheckUpdatedVTArrayMembersOnResume()
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs";
+
+ var lines = new[] { 225, 227 };
+ await SetBreakpoint(debugger_test_loc, lines[0], 12);
+ await SetBreakpoint(debugger_test_loc, lines[1], 12);
+
+ var dt = new DateTime(1, 2, 3, 4, 5, 6);
+ var pause_location = await EvaluateAndCheck(
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.ValueTypesTest:MethodUpdatingVTArrayMembers'); }, 1);",
+ debugger_test_loc, lines[0], 12, "MethodUpdatingVTArrayMembers");
+ await CheckArrayElements(pause_location, dt);
+
+ // Resume
+ dt = new DateTime(9, 8, 7, 6, 5, 4);
+ pause_location = await SendCommandAndCheck(JObject.FromObject(new { }), "Debugger.resume", debugger_test_loc, lines[1], 12, "MethodUpdatingVTArrayMembers");
+ await CheckArrayElements(pause_location, dt);
+ });
+
+ async Task CheckArrayElements(JToken pause_location, DateTime dt)
+ {
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ await CheckProps(locals, new
+ {
+ ssta = TArray("DebuggerTests.StructForToStringTests[]", 1)
+ }, "locals");
+
+ var ssta = await GetObjectOnLocals(locals, "ssta");
+ var sst0 = await GetObjectOnLocals(ssta, "0");
+ await CheckProps(sst0, new
+ {
+ DT = TValueType("System.DateTime", dt.ToString())
+ }, "dta [0]", num_fields: 5);
+
+ await CheckDateTime(sst0, "DT", dt);
+ }
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task InspectLocalsWithStructsStaticAsync(bool use_cfo)
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
+ var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs";
+
+ await SetBreakpoint(debugger_test_loc, 54, 12);
+
+ var pause_location = await EvaluateAndCheck(
+ "window.setTimeout(function() { invoke_static_method_async (" +
+ "'[debugger-test] DebuggerTests.ValueTypesTest:MethodWithLocalStructsStaticAsync'" +
+ "); }, 1);",
+ debugger_test_loc, 54, 12, "MoveNext"); //BUG: method name
+
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ await CheckProps(locals, new
+ {
+ ss_local = TObject("DebuggerTests.ValueTypesTest.SimpleStruct"),
+ gs_local = TValueType("DebuggerTests.ValueTypesTest.GenericStruct<int>"),
+ result = TBool(true)
+ },
+ "locals#0");
+
+ var dt = new DateTime(2021, 2, 3, 4, 6, 7);
+ // Check ss_local's properties
+ var ss_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_local");
+ await CheckProps(ss_local_props, new
+ {
+ V = TGetter("V"),
+ str_member = TString("set in MethodWithLocalStructsStaticAsync#SimpleStruct#str_member"),
+ dt = TValueType("System.DateTime", dt.ToString()),
+ gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct<System.DateTime>"),
+ Kind = TEnum("System.DateTimeKind", "Utc")
+ }, "ss_local");
+
+ {
+ var gres = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_local"), "V");
+ await CheckValue(gres.Value["result"], TNumber(0xDEADBEEF + 2), $"ss_local#V");
+
+ // Check ss_local.dt
+ await CheckDateTime(ss_local_props, "dt", dt);
+
+ // Check ss_local.gs
+ await CompareObjectPropertiesFor(ss_local_props, "gs",
+ new
+ {
+ StringField = TString("set in MethodWithLocalStructsStaticAsync#SimpleStruct#gs#StringField"),
+ List = TObject("System.Collections.Generic.List<System.DateTime>"),
+ Options = TEnum("DebuggerTests.Options", "Option1")
+ }
+ );
+ }
+
+ // Check gs_local's properties
+ var gs_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "gs_local");
+ await CheckProps(gs_local_props, new
+ {
+ StringField = TString("gs_local#GenericStruct<ValueTypesTest>#StringField"),
+ List = TObject("System.Collections.Generic.List<int>"),
+ Options = TEnum("DebuggerTests.Options", "Option2")
+ }, "gs_local");
+
+ // FIXME: check ss_local.gs.List's members
+ });
+ }
+
+ [Theory]
+ [InlineData(137, 12, "MethodWithLocalsForToStringTest", false, false)]
+ [InlineData(147, 12, "MethodWithArgumentsForToStringTest", true, false)]
+ [InlineData(192, 12, "MethodWithArgumentsForToStringTestAsync", true, true)]
+ [InlineData(182, 12, "MethodWithArgumentsForToStringTestAsync", false, true)]
+ public async Task InspectLocalsForToStringDescriptions(int line, int col, string method_name, bool call_other, bool invoke_async)
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+ string entry_method_name = $"[debugger-test] DebuggerTests.ValueTypesTest:MethodWithLocalsForToStringTest{(invoke_async ? "Async" : String.Empty)}";
+ int frame_idx = 0;
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs";
+
+ await SetBreakpoint(debugger_test_loc, line, col);
+
+ var eval_expr = "window.setTimeout(function() {" +
+ (invoke_async ? "invoke_static_method_async (" : "invoke_static_method (") +
+ $"'{entry_method_name}'," +
+ (call_other ? "true" : "false") +
+ "); }, 1);";
+ Console.WriteLine($"{eval_expr}");
+
+ var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, invoke_async ? "MoveNext" : method_name);
+
+ var dt0 = new DateTime(2020, 1, 2, 3, 4, 5);
+ var dt1 = new DateTime(2010, 5, 4, 3, 2, 1);
+ var ts = dt0 - dt1;
+ var dto = new DateTimeOffset(dt0, new TimeSpan(4, 5, 0));
+
+ var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value<string>());
+ await CheckProps(frame_locals, new
+ {
+ call_other = TBool(call_other),
+ dt0 = TValueType("System.DateTime", dt0.ToString()),
+ dt1 = TValueType("System.DateTime", dt1.ToString()),
+ dto = TValueType("System.DateTimeOffset", dto.ToString()),
+ ts = TValueType("System.TimeSpan", ts.ToString()),
+ dec = TValueType("System.Decimal", "123987123"),
+ guid = TValueType("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014"),
+ dts = TArray("System.DateTime[]", 2),
+ obj = TObject("DebuggerTests.ClassForToStringTests"),
+ sst = TObject("DebuggerTests.StructForToStringTests")
+ }, "locals#0");
+
+ var dts_0 = new DateTime(1983, 6, 7, 5, 6, 10);
+ var dts_1 = new DateTime(1999, 10, 15, 1, 2, 3);
+ var dts_elements = await GetObjectOnLocals(frame_locals, "dts");
+ await CheckDateTime(dts_elements, "0", dts_0);
+ await CheckDateTime(dts_elements, "1", dts_1);
+
+ // TimeSpan
+ await CompareObjectPropertiesFor(frame_locals, "ts",
+ new
+ {
+ Days = TNumber(3530),
+ Minutes = TNumber(2),
+ Seconds = TNumber(4),
+ }, "ts_props", num_fields: 12);
+
+ // DateTimeOffset
+ await CompareObjectPropertiesFor(frame_locals, "dto",
+ new
+ {
+ Day = TNumber(2),
+ Year = TNumber(2020),
+ DayOfWeek = TEnum("System.DayOfWeek", "Thursday")
+ }, "dto_props", num_fields: 22);
+
+ var DT = new DateTime(2004, 10, 15, 1, 2, 3);
+ var DTO = new DateTimeOffset(dt0, new TimeSpan(2, 14, 0));
+
+ var obj_props = await CompareObjectPropertiesFor(frame_locals, "obj",
+ new
+ {
+ DT = TValueType("System.DateTime", DT.ToString()),
+ DTO = TValueType("System.DateTimeOffset", DTO.ToString()),
+ TS = TValueType("System.TimeSpan", ts.ToString()),
+ Dec = TValueType("System.Decimal", "1239871"),
+ Guid = TValueType("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014")
+ }, "obj_props");
+
+ DTO = new DateTimeOffset(dt0, new TimeSpan(3, 15, 0));
+ var sst_props = await CompareObjectPropertiesFor(frame_locals, "sst",
+ new
+ {
+ DT = TValueType("System.DateTime", DT.ToString()),
+ DTO = TValueType("System.DateTimeOffset", DTO.ToString()),
+ TS = TValueType("System.TimeSpan", ts.ToString()),
+ Dec = TValueType("System.Decimal", "1239871"),
+ Guid = TValueType("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014")
+ }, "sst_props");
+ });
+ }
+
+ [Fact]
+ public async Task InspectLocals()
+ {
+ var insp = new Inspector();
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+
+ var wait_res = await RunUntil("locals_inner");
+ var locals = await GetProperties(wait_res["callFrames"][1]["callFrameId"].Value<string>());
+ });
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task InspectLocalsForStructInstanceMethod(bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
+ "dotnet://debugger-test.dll/debugger-array-test.cs", 258, 12,
+ "GenericInstanceMethod<DebuggerTests.SimpleClass>",
+ "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EntryClass:run'); })",
+ use_cfo: use_cfo,
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var frame_locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+
+ await CheckProps(frame_locals, new
+ {
+ sc_arg = TObject("DebuggerTests.SimpleClass"),
+ @this = TValueType("DebuggerTests.Point"),
+ local_gs = TValueType("DebuggerTests.SimpleGenericStruct<int>")
+ },
+ "locals#0");
+
+ await CompareObjectPropertiesFor(frame_locals, "local_gs",
+ new
+ {
+ Id = TString("local_gs#Id"),
+ Color = TEnum("DebuggerTests.RGB", "Green"),
+ Value = TNumber(4)
+ },
+ label: "local_gs#0");
+
+ await CompareObjectPropertiesFor(frame_locals, "sc_arg",
+ TSimpleClass(10, 45, "sc_arg#Id", "Blue"),
+ label: "sc_arg#0");
+
+ await CompareObjectPropertiesFor(frame_locals, "this",
+ TPoint(90, -4, "point#Id", "Green"),
+ label: "this#0");
+
+ });
+
+ [Fact]
+ public async Task SteppingIntoMscorlib()
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+
+ var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 83, 8);
+ var pause_location = await EvaluateAndCheck(
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] Math:OuterMethod'); }, 1);",
+ "dotnet://debugger-test.dll/debugger-test.cs", 83, 8,
+ "OuterMethod");
+
+ //make sure we're on the right bp
+ Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value<string>());
+
+ pause_location = await SendCommandAndCheck(null, $"Debugger.stepInto", null, -1, -1, null);
+ var top_frame = pause_location["callFrames"][0];
+
+ AssertEqual("WriteLine", top_frame["functionName"]?.Value<string>(), "Expected to be in WriteLine method");
+ var script_id = top_frame["functionLocation"]["scriptId"].Value<string>();
+ Assert.Matches ("^dotnet://(mscorlib|System\\.Console)\\.dll/Console.cs", scripts[script_id]);
+ });
+ }
+
+ [Fact]
+ public async Task InvalidValueTypeData()
+ {
+ await CheckInspectLocalsAtBreakpointSite(
+ "dotnet://debugger-test.dll/debugger-test.cs", 85, 8,
+ "OuterMethod",
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] Math:OuterMethod'); })",
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var new_id = await CreateNewId(@"MONO._new_or_add_id_props ({ scheme: 'valuetype', idArgs: { containerId: 1 }, props: { klass: 3, value64: 4 }});");
+ await _invoke_getter(new_id, "NonExistant", expect_ok: false);
+
+ new_id = await CreateNewId(@"MONO._new_or_add_id_props ({ scheme: 'valuetype', idArgs: { containerId: 1 }, props: { klass: 3 }});");
+ await _invoke_getter(new_id, "NonExistant", expect_ok: false);
+
+ new_id = await CreateNewId(@"MONO._new_or_add_id_props ({ scheme: 'valuetype', idArgs: { containerId: 1 }, props: { klass: 3, value64: 'AA' }});");
+ await _invoke_getter(new_id, "NonExistant", expect_ok: false);
+ });
+
+ async Task<string> CreateNewId(string expr)
+ {
+ var res = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = expr }), ctx.token);
+ Assert.True(res.IsOk, "Expected Runtime.evaluate to succeed");
+ AssertEqual("string", res.Value["result"]?["type"]?.Value<string>(), "Expected Runtime.evaluate to return a string type result");
+ return res.Value["result"]?["value"]?.Value<string>();
+ }
+
+ async Task<Result> _invoke_getter(string obj_id, string property_name, bool expect_ok)
+ {
+ var expr = $"MONO._invoke_getter ('{obj_id}', '{property_name}')";
+ var res = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = expr }), ctx.token);
+ AssertEqual(expect_ok, res.IsOk, "Runtime.evaluate result not as expected for {expr}");
+
+ return res;
+ }
+ }
+
+ //TODO add tests covering basic stepping behavior as step in/out/over
+ }
}
diff --git a/sdks/wasm/Makefile b/sdks/wasm/Makefile
index 1e0faa2400f..22711c7b67b 100644
--- a/sdks/wasm/Makefile
+++ b/sdks/wasm/Makefile
@@ -467,7 +467,7 @@ $(BROWSER_TEST_DYNAMIC)/.stamp-browser-test-dynamic-suite: packager.exe $(WASM_F
touch $@
bin/debugger-test-suite/dotnet.wasm: packager.exe binding_tests.dll debugger-test.dll tests/debugger/debugger-driver.html other.js builds/debug/dotnet.js
- $(PACKAGER) --copy=always -debugrt -debug --template=runtime.js --builddir=obj/debugger-test-suite --appdir=bin/debugger-test-suite --asset=tests/debugger/debugger-driver.html --asset=other.js debugger-test.dll
+ $(PACKAGER) --copy=always -debugrt -debug --template=runtime.js --template-output-name=runtime-debugger.js --builddir=obj/debugger-test-suite --appdir=bin/debugger-test-suite --asset=tests/debugger/debugger-driver.html --asset=other.js debugger-test.dll
ninja -v -C obj/debugger-test-suite
touch $@
@@ -791,8 +791,8 @@ package: build build-sdk build-dbg-proxy
cp $(OPTIONS_CS) tmp/
cp packager.exe tmp/
cp runtime.js tmp/
- cp BrowserDebugProxy/bin/Debug/netstandard2.1/BrowserDebugProxy.dll tmp/
- cp BrowserDebugProxy/bin/Debug/netstandard2.1/BrowserDebugProxy.pdb tmp/
+ cp BrowserDebugProxy/bin/Debug/netcoreapp3.0/BrowserDebugProxy.dll tmp/
+ cp BrowserDebugProxy/bin/Debug/netcoreapp3.0/BrowserDebugProxy.pdb tmp/
mkdir tmp/dbg-proxy
cp -r BrowserDebugHost/bin/Debug/netcoreapp3.0/ tmp/dbg-proxy/
mkdir tmp/docs
diff --git a/sdks/wasm/other.js b/sdks/wasm/other.js
index 93e091f7ad5..93532686914 100644
--- a/sdks/wasm/other.js
+++ b/sdks/wasm/other.js
@@ -1,3 +1,6 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
function big_array_js_test (len) {
var big = new Array(len);
for (let i=0; i < len; i ++) {
@@ -27,4 +30,36 @@ function getters_js_test () {
};
console.log (`break here`);
return ptd;
-} \ No newline at end of file
+}
+
+function exception_caught_test () {
+ try {
+ throw new TypeError ('exception caught');
+ } catch (e) {
+ console.log(e);
+ }
+}
+
+function exception_uncaught_test () {
+ console.log('uncaught test');
+ throw new RangeError ('exception uncaught');
+}
+
+function exceptions_test () {
+ exception_caught_test ();
+ exception_uncaught_test ();
+}
+
+function negative_cfo_test (str_value = null) {
+ var ptd = {
+ get Int () { return 5; },
+ get String () { return "foobar"; },
+ get DT () { return "dt"; },
+ get IntArray () { return [1,2,3]; },
+ get DTArray () { return ["dt0", "dt1"]; },
+ DTAutoProperty: "dt",
+ StringField: str_value
+ };
+ console.log (`break here`);
+ return ptd;
+}
diff --git a/sdks/wasm/packager.cs b/sdks/wasm/packager.cs
index 5b23dd3a03c..28d4f53dc65 100644
--- a/sdks/wasm/packager.cs
+++ b/sdks/wasm/packager.cs
@@ -408,6 +408,7 @@ class Driver {
var il_strip = false;
var linker_verbose = false;
var runtimeTemplate = "runtime.js";
+ var runtimeTemplateOutputName = "runtime.js";
var assets = new List<string> ();
var profilers = new List<string> ();
var native_libs = new List<string> ();
@@ -458,6 +459,7 @@ class Driver {
{ "aot", s => ee_mode = ExecMode.Aot },
{ "aot-interp", s => ee_mode = ExecMode.AotInterp },
{ "template=", s => runtimeTemplate = s },
+ { "template-output-name=", s => runtimeTemplateOutputName = s },
{ "asset=", s => assets.Add(s) },
{ "search-path=", s => root_search_paths.Add(s) },
{ "profile=", s => profilers.Add (s) },
@@ -689,7 +691,7 @@ class Driver {
wasm_core_support = BINDINGS_MODULE_SUPPORT;
wasm_core_support_library = $"--js-library {BINDINGS_MODULE_SUPPORT}";
}
- var runtime_js = Path.Combine (emit_ninja ? builddir : out_prefix, "runtime.js");
+ var runtime_js = Path.Combine (emit_ninja ? builddir : out_prefix, runtimeTemplateOutputName);
if (emit_ninja) {
File.Delete (runtime_js);
File.Copy (runtimeTemplate, runtime_js);
@@ -953,7 +955,7 @@ class Driver {
// Targets
ninja.WriteLine ("build $appdir: mkdir");
ninja.WriteLine ("build $appdir/$deploy_prefix: mkdir");
- ninja.WriteLine ("build $appdir/runtime.js: cpifdiff $builddir/runtime.js");
+ ninja.WriteLine ($"build $appdir/{runtimeTemplateOutputName}: cpifdiff $builddir/{runtimeTemplateOutputName}");
ninja.WriteLine ("build $appdir/mono-config.js: cpifdiff $builddir/mono-config.js");
if (build_wasm) {
string src_prefix;
diff --git a/sdks/wasm/tests/debugger/debugger-array-test.cs b/sdks/wasm/tests/debugger/debugger-array-test.cs
index fb09fdeea9b..282db743e88 100644
--- a/sdks/wasm/tests/debugger/debugger-array-test.cs
+++ b/sdks/wasm/tests/debugger/debugger-array-test.cs
@@ -1,285 +1,308 @@
+// 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.Threading.Tasks;
namespace DebuggerTests
{
- public class ArrayTestsClass
- {
- public static void PrimitiveTypeLocals (bool call_other = false)
- {
- var int_arr = new int[] { 4, 70, 1 };
- var int_arr_empty = new int[0];
- int[] int_arr_null = null;
-
- if (call_other)
- OtherMethod ();
-
- Console.WriteLine ($"int_arr: {int_arr.Length}, {int_arr_empty.Length}, {int_arr_null?.Length}");
- }
-
- public static void ValueTypeLocals (bool call_other = false)
- {
- var point_arr = new Point[] {
- new Point { X = 5, Y = -2, Id = "point_arr#Id#0", Color = RGB.Green },
- new Point { X = 123, Y = 0, Id = "point_arr#Id#1", Color = RGB.Blue },
- };
-
- var point_arr_empty = new Point[0];
- Point[] point_arr_null = null;
-
- if (call_other)
- OtherMethod ();
-
- Console.WriteLine ($"point_arr: {point_arr.Length}, {point_arr_empty.Length}, {point_arr_null?.Length}");
- }
-
- public static void ObjectTypeLocals (bool call_other = false)
- {
- var class_arr = new SimpleClass [] {
- new SimpleClass { X = 5, Y = -2, Id = "class_arr#Id#0", Color = RGB.Green },
- null,
- new SimpleClass { X = 123, Y = 0, Id = "class_arr#Id#2", Color = RGB.Blue },
- };
-
- var class_arr_empty = new SimpleClass [0];
- SimpleClass [] class_arr_null = null;
-
- if (call_other)
- OtherMethod ();
-
- Console.WriteLine ($"class_arr: {class_arr.Length}, {class_arr_empty.Length}, {class_arr_null?.Length}");
- }
-
- public static void GenericTypeLocals (bool call_other = false)
- {
- var gclass_arr = new GenericClass<int> [] {
- null,
- new GenericClass<int> { Id = "gclass_arr#1#Id", Color = RGB.Red, Value = 5 },
- new GenericClass<int> { Id = "gclass_arr#2#Id", Color = RGB.Blue, Value = -12 },
- };
-
- var gclass_arr_empty = new GenericClass<int> [0];
- GenericClass<int> [] gclass_arr_null = null;
-
- if (call_other)
- OtherMethod ();
-
- Console.WriteLine ($"gclass_arr: {gclass_arr.Length}, {gclass_arr_empty.Length}, {gclass_arr_null?.Length}");
- }
-
- public static void GenericValueTypeLocals (bool call_other = false)
- {
- var gvclass_arr = new SimpleGenericStruct<Point> [] {
- new SimpleGenericStruct<Point> { Id = "gvclass_arr#1#Id", Color = RGB.Red, Value = new Point { X = 100, Y = 200, Id = "gvclass_arr#1#Value#Id", Color = RGB.Red } },
- new SimpleGenericStruct<Point> { Id = "gvclass_arr#2#Id", Color = RGB.Blue, Value = new Point { X = 10, Y = 20, Id = "gvclass_arr#2#Value#Id", Color = RGB.Green } }
- };
-
- var gvclass_arr_empty = new SimpleGenericStruct<Point> [0];
- SimpleGenericStruct<Point> [] gvclass_arr_null = null;
-
- if (call_other)
- OtherMethod ();
-
- Console.WriteLine ($"gvclass_arr: {gvclass_arr.Length}, {gvclass_arr_empty.Length}, {gvclass_arr_null?.Length}");
- }
-
- static void OtherMethod ()
- {
- YetAnotherMethod ();
- Console.WriteLine ($"Just a placeholder for breakpoints");
- }
-
- static void YetAnotherMethod ()
- {
- Console.WriteLine ($"Just a placeholder for breakpoints");
- }
-
- public static void ObjectArrayMembers ()
- {
- var c = new Container {
- id = "c#id",
- ClassArrayProperty = new SimpleClass[] {
- new SimpleClass { X = 5, Y = -2, Id = "ClassArrayProperty#Id#0", Color = RGB.Green },
- new SimpleClass { X = 30, Y = 1293, Id = "ClassArrayProperty#Id#1", Color = RGB.Green },
- null
- },
- ClassArrayField = new SimpleClass[] {
- null,
- new SimpleClass { X = 5, Y = -2, Id = "ClassArrayField#Id#1", Color = RGB.Blue },
- new SimpleClass { X = 30, Y = 1293, Id = "ClassArrayField#Id#2", Color = RGB.Green },
- },
- PointsProperty = new Point[] {
- new Point { X = 5, Y = -2, Id = "PointsProperty#Id#0", Color = RGB.Green },
- new Point { X = 123, Y = 0, Id = "PointsProperty#Id#1", Color = RGB.Blue },
- },
- PointsField = new Point[] {
- new Point { X = 5, Y = -2, Id = "PointsField#Id#0", Color = RGB.Green },
- new Point { X = 123, Y = 0, Id = "PointsField#Id#1", Color = RGB.Blue },
- }
- };
-
- Console.WriteLine ($"Back from PlaceholderMethod, {c.ClassArrayProperty?.Length}");
- c.PlaceholderMethod ();
- Console.WriteLine ($"Back from PlaceholderMethod, {c.id}");
- }
-
- public static async Task<bool> ValueTypeLocalsAsync (bool call_other = false)
- {
- var gvclass_arr = new SimpleGenericStruct<Point> [] {
- new SimpleGenericStruct<Point> { Id = "gvclass_arr#1#Id", Color = RGB.Red, Value = new Point { X = 100, Y = 200, Id = "gvclass_arr#1#Value#Id", Color = RGB.Red } },
- new SimpleGenericStruct<Point> { Id = "gvclass_arr#2#Id", Color = RGB.Blue, Value = new Point { X = 10, Y = 20, Id = "gvclass_arr#2#Value#Id", Color = RGB.Green } }
- };
-
- var gvclass_arr_empty = new SimpleGenericStruct<Point> [0];
- SimpleGenericStruct<Point> [] gvclass_arr_null = null;
- Console.WriteLine ($"ValueTypeLocalsAsync: call_other: {call_other}");
- SimpleGenericStruct<Point> gvclass;
- Point[] points = null;
-
- if (call_other) {
- (gvclass, points) = await new ArrayTestsClass ().InstanceMethodValueTypeLocalsAsync<SimpleGenericStruct<Point>> (gvclass_arr [0]);
- Console.WriteLine ($"* gvclass: {gvclass}, points: {points.Length}");
- }
-
- Console.WriteLine ($"gvclass_arr: {gvclass_arr.Length}, {gvclass_arr_empty.Length}, {gvclass_arr_null?.Length}");
- return true;
- }
-
- public async Task<(T, Point[])> InstanceMethodValueTypeLocalsAsync<T> (T t1)
- {
- var point_arr = new Point[] {
- new Point { X = 5, Y = -2, Id = "point_arr#Id#0", Color = RGB.Red },
- new Point { X = 123, Y = 0, Id = "point_arr#Id#1", Color = RGB.Blue }
- };
- var point = new Point { X = 45, Y = 51, Id = "point#Id", Color = RGB.Green };
-
- Console.WriteLine ($"point_arr: {point_arr.Length}, T: {t1}, point: {point}");
- return (t1, new Point[] {point_arr [0], point_arr [1], point});
- }
-
- // A workaround for method invocations on structs not working right now
- public static async Task EntryPointForStructMethod (bool call_other = false)
- {
- await Point.AsyncMethod (call_other);
- }
-
- public static void GenericValueTypeLocals2 (bool call_other = false)
- {
- var gvclass_arr = new SimpleGenericStruct<Point[]> [] {
- new SimpleGenericStruct<Point[]> {
- Id = "gvclass_arr#0#Id", Color = RGB.Red,
- Value = new Point[] {
- new Point { X = 100, Y = 200, Id = "gvclass_arr#0#0#Value#Id", Color = RGB.Red },
- new Point { X = 100, Y = 200, Id = "gvclass_arr#0#1#Value#Id", Color = RGB.Green }
- }
- },
-
- new SimpleGenericStruct<Point[]> {
- Id = "gvclass_arr#1#Id", Color = RGB.Blue,
- Value = new Point[] {
- new Point { X = 100, Y = 200, Id = "gvclass_arr#1#0#Value#Id", Color = RGB.Green },
- new Point { X = 100, Y = 200, Id = "gvclass_arr#1#1#Value#Id", Color = RGB.Blue }
- }
- },
- };
-
- var gvclass_arr_empty = new SimpleGenericStruct<Point[]> [0];
- SimpleGenericStruct<Point[]> [] gvclass_arr_null = null;
-
- if (call_other)
- OtherMethod ();
-
- Console.WriteLine ($"gvclass_arr: {gvclass_arr.Length}, {gvclass_arr_empty.Length}, {gvclass_arr_null?.Length}");
- }
- }
-
- public class Container
- {
- public string id;
- public SimpleClass[] ClassArrayProperty { get; set; }
- public SimpleClass[] ClassArrayField;
-
- public Point[] PointsProperty { get; set; }
- public Point[] PointsField;
-
- public void PlaceholderMethod ()
- {
- Console.WriteLine ($"Container.PlaceholderMethod");
- }
- }
-
- public struct Point
- {
- public int X, Y;
- public string Id { get; set; }
- public RGB Color { get; set; }
-
- /* instance too */
- public static async Task AsyncMethod (bool call_other)
- {
- int local_i = 5;
- var sc = new SimpleClass { X = 10, Y = 45, Id = "sc#Id", Color = RGB.Blue };
- if (call_other)
- await new Point { X = 90, Y = -4, Id = "point#Id", Color = RGB.Green }.AsyncInstanceMethod (sc);
- Console.WriteLine ($"AsyncMethod local_i: {local_i}, sc: {sc.Id}");
- }
-
- public async Task AsyncInstanceMethod (SimpleClass sc_arg)
- {
- var local_gs = new SimpleGenericStruct<int> { Id = "local_gs#Id", Color = RGB.Green, Value = 4 };
- sc_arg.Id = "sc_arg#Id";
- Console.WriteLine ($"AsyncInstanceMethod sc_arg: {sc_arg.Id}, local_gs: {local_gs.Id}");
- }
-
- public void GenericInstanceMethod<T> (T sc_arg) where T: SimpleClass
- {
- var local_gs = new SimpleGenericStruct<int> { Id = "local_gs#Id", Color = RGB.Green, Value = 4 };
- sc_arg.Id = "sc_arg#Id";
- Console.WriteLine ($"AsyncInstanceMethod sc_arg: {sc_arg.Id}, local_gs: {local_gs.Id}");
- }
- }
-
- public class SimpleClass
- {
- public int X, Y;
- public string Id { get; set; }
- public RGB Color { get; set; }
-
- public Point PointWithCustomGetter { get { return new Point { X = 100, Y = 400, Id = "SimpleClass#Point#gen#Id", Color = RGB.Green }; } }
- }
-
- public class GenericClass<T>
- {
- public string Id { get; set; }
- public RGB Color { get; set; }
- public T Value { get; set; }
- }
-
- public struct SimpleGenericStruct<T>
- {
- public string Id { get; set; }
- public RGB Color { get; set; }
- public T Value { get; set; }
- }
-
- public class EntryClass {
- public static void run ()
- {
- ArrayTestsClass.PrimitiveTypeLocals (true);
- ArrayTestsClass.ValueTypeLocals (true);
- ArrayTestsClass.ObjectTypeLocals (true);
-
- ArrayTestsClass.GenericTypeLocals (true);
- ArrayTestsClass.GenericValueTypeLocals (true);
- ArrayTestsClass.GenericValueTypeLocals2 (true);
-
- ArrayTestsClass.ObjectArrayMembers ();
-
- ArrayTestsClass.ValueTypeLocalsAsync (true).Wait ();
-
- ArrayTestsClass.EntryPointForStructMethod (true).Wait ();
-
- var sc = new SimpleClass { X = 10, Y = 45, Id = "sc#Id", Color = RGB.Blue };
- new Point { X = 90, Y = -4, Id = "point#Id", Color = RGB.Green }.GenericInstanceMethod (sc);
- }
- }
+ public class ArrayTestsClass
+ {
+ public static void PrimitiveTypeLocals(bool call_other = false)
+ {
+ var int_arr = new int[] { 4, 70, 1 };
+ var int_arr_empty = new int[0];
+ int[] int_arr_null = null;
+
+ if (call_other)
+ OtherMethod();
+
+ Console.WriteLine($"int_arr: {int_arr.Length}, {int_arr_empty.Length}, {int_arr_null?.Length}");
+ }
+
+ public static void ValueTypeLocals(bool call_other = false)
+ {
+ var point_arr = new Point[]
+ {
+ new Point { X = 5, Y = -2, Id = "point_arr#Id#0", Color = RGB.Green },
+ new Point { X = 123, Y = 0, Id = "point_arr#Id#1", Color = RGB.Blue },
+ };
+
+ var point_arr_empty = new Point[0];
+ Point[] point_arr_null = null;
+
+ if (call_other)
+ OtherMethod();
+
+ Console.WriteLine($"point_arr: {point_arr.Length}, {point_arr_empty.Length}, {point_arr_null?.Length}");
+ }
+
+ public static void ObjectTypeLocals(bool call_other = false)
+ {
+ var class_arr = new SimpleClass[]
+ {
+ new SimpleClass { X = 5, Y = -2, Id = "class_arr#Id#0", Color = RGB.Green },
+ null,
+ new SimpleClass { X = 123, Y = 0, Id = "class_arr#Id#2", Color = RGB.Blue },
+ };
+
+ var class_arr_empty = new SimpleClass[0];
+ SimpleClass[] class_arr_null = null;
+
+ if (call_other)
+ OtherMethod();
+
+ Console.WriteLine($"class_arr: {class_arr.Length}, {class_arr_empty.Length}, {class_arr_null?.Length}");
+ }
+
+ public static void GenericTypeLocals(bool call_other = false)
+ {
+ var gclass_arr = new GenericClass<int>[]
+ {
+ null,
+ new GenericClass<int> { Id = "gclass_arr#1#Id", Color = RGB.Red, Value = 5 },
+ new GenericClass<int> { Id = "gclass_arr#2#Id", Color = RGB.Blue, Value = -12 },
+ };
+
+ var gclass_arr_empty = new GenericClass<int>[0];
+ GenericClass<int>[] gclass_arr_null = null;
+
+ if (call_other)
+ OtherMethod();
+
+ Console.WriteLine($"gclass_arr: {gclass_arr.Length}, {gclass_arr_empty.Length}, {gclass_arr_null?.Length}");
+ }
+
+ public static void GenericValueTypeLocals(bool call_other = false)
+ {
+ var gvclass_arr = new SimpleGenericStruct<Point>[]
+ {
+ new SimpleGenericStruct<Point> { Id = "gvclass_arr#1#Id", Color = RGB.Red, Value = new Point { X = 100, Y = 200, Id = "gvclass_arr#1#Value#Id", Color = RGB.Red } },
+ new SimpleGenericStruct<Point> { Id = "gvclass_arr#2#Id", Color = RGB.Blue, Value = new Point { X = 10, Y = 20, Id = "gvclass_arr#2#Value#Id", Color = RGB.Green } }
+ };
+
+ var gvclass_arr_empty = new SimpleGenericStruct<Point>[0];
+ SimpleGenericStruct<Point>[] gvclass_arr_null = null;
+
+ if (call_other)
+ OtherMethod();
+
+ Console.WriteLine($"gvclass_arr: {gvclass_arr.Length}, {gvclass_arr_empty.Length}, {gvclass_arr_null?.Length}");
+ }
+
+ static void OtherMethod()
+ {
+ YetAnotherMethod();
+ Console.WriteLine($"Just a placeholder for breakpoints");
+ }
+
+ static void YetAnotherMethod()
+ {
+ Console.WriteLine($"Just a placeholder for breakpoints");
+ }
+
+ public static void ObjectArrayMembers()
+ {
+ var c = new Container
+ {
+ id = "c#id",
+ ClassArrayProperty = new SimpleClass[]
+ {
+ new SimpleClass { X = 5, Y = -2, Id = "ClassArrayProperty#Id#0", Color = RGB.Green },
+ new SimpleClass { X = 30, Y = 1293, Id = "ClassArrayProperty#Id#1", Color = RGB.Green },
+ null
+ },
+ ClassArrayField = new SimpleClass[]
+ {
+ null,
+ new SimpleClass { X = 5, Y = -2, Id = "ClassArrayField#Id#1", Color = RGB.Blue },
+ new SimpleClass { X = 30, Y = 1293, Id = "ClassArrayField#Id#2", Color = RGB.Green },
+ },
+ PointsProperty = new Point[]
+ {
+ new Point { X = 5, Y = -2, Id = "PointsProperty#Id#0", Color = RGB.Green },
+ new Point { X = 123, Y = 0, Id = "PointsProperty#Id#1", Color = RGB.Blue },
+ },
+ PointsField = new Point[]
+ {
+ new Point { X = 5, Y = -2, Id = "PointsField#Id#0", Color = RGB.Green },
+ new Point { X = 123, Y = 0, Id = "PointsField#Id#1", Color = RGB.Blue },
+ }
+ };
+
+ Console.WriteLine($"Back from PlaceholderMethod, {c.ClassArrayProperty?.Length}");
+ c.PlaceholderMethod();
+ Console.WriteLine($"Back from PlaceholderMethod, {c.id}");
+ }
+
+ public static async Task<bool> ValueTypeLocalsAsync(bool call_other = false)
+ {
+ var gvclass_arr = new SimpleGenericStruct<Point>[]
+ {
+ new SimpleGenericStruct<Point> { Id = "gvclass_arr#1#Id", Color = RGB.Red, Value = new Point { X = 100, Y = 200, Id = "gvclass_arr#1#Value#Id", Color = RGB.Red } },
+ new SimpleGenericStruct<Point> { Id = "gvclass_arr#2#Id", Color = RGB.Blue, Value = new Point { X = 10, Y = 20, Id = "gvclass_arr#2#Value#Id", Color = RGB.Green } }
+ };
+
+ var gvclass_arr_empty = new SimpleGenericStruct<Point>[0];
+ SimpleGenericStruct<Point>[] gvclass_arr_null = null;
+ Console.WriteLine($"ValueTypeLocalsAsync: call_other: {call_other}");
+ SimpleGenericStruct<Point> gvclass;
+ Point[] points = null;
+
+ if (call_other)
+ {
+ (gvclass, points) = await new ArrayTestsClass().InstanceMethodValueTypeLocalsAsync<SimpleGenericStruct<Point>>(gvclass_arr[0]);
+ Console.WriteLine($"* gvclass: {gvclass}, points: {points.Length}");
+ }
+
+ Console.WriteLine($"gvclass_arr: {gvclass_arr.Length}, {gvclass_arr_empty.Length}, {gvclass_arr_null?.Length}");
+ return true;
+ }
+
+ public async Task<(T, Point[])> InstanceMethodValueTypeLocalsAsync<T>(T t1)
+ {
+ var point_arr = new Point[]
+ {
+ new Point { X = 5, Y = -2, Id = "point_arr#Id#0", Color = RGB.Red },
+ new Point { X = 123, Y = 0, Id = "point_arr#Id#1", Color = RGB.Blue }
+ };
+ var point = new Point { X = 45, Y = 51, Id = "point#Id", Color = RGB.Green };
+
+ Console.WriteLine($"point_arr: {point_arr.Length}, T: {t1}, point: {point}");
+ return (t1, new Point[] { point_arr[0], point_arr[1], point });
+ }
+
+ // A workaround for method invocations on structs not working right now
+ public static async Task EntryPointForStructMethod(bool call_other = false)
+ {
+ await Point.AsyncMethod(call_other);
+ }
+
+ public static void GenericValueTypeLocals2(bool call_other = false)
+ {
+ var gvclass_arr = new SimpleGenericStruct<Point[]>[]
+ {
+ new SimpleGenericStruct<Point[]>
+ {
+ Id = "gvclass_arr#0#Id",
+ Color = RGB.Red,
+ Value = new Point[]
+ {
+ new Point { X = 100, Y = 200, Id = "gvclass_arr#0#0#Value#Id", Color = RGB.Red },
+ new Point { X = 100, Y = 200, Id = "gvclass_arr#0#1#Value#Id", Color = RGB.Green }
+ }
+ },
+
+ new SimpleGenericStruct<Point[]>
+ {
+ Id = "gvclass_arr#1#Id",
+ Color = RGB.Blue,
+ Value = new Point[]
+ {
+ new Point { X = 100, Y = 200, Id = "gvclass_arr#1#0#Value#Id", Color = RGB.Green },
+ new Point { X = 100, Y = 200, Id = "gvclass_arr#1#1#Value#Id", Color = RGB.Blue }
+ }
+ },
+ };
+
+ var gvclass_arr_empty = new SimpleGenericStruct<Point[]>[0];
+ SimpleGenericStruct<Point[]>[] gvclass_arr_null = null;
+
+ if (call_other)
+ OtherMethod();
+
+ Console.WriteLine($"gvclass_arr: {gvclass_arr.Length}, {gvclass_arr_empty.Length}, {gvclass_arr_null?.Length}");
+ }
+ }
+
+ public class Container
+ {
+ public string id;
+ public SimpleClass[] ClassArrayProperty { get; set; }
+ public SimpleClass[] ClassArrayField;
+
+ public Point[] PointsProperty { get; set; }
+ public Point[] PointsField;
+
+ public void PlaceholderMethod()
+ {
+ Console.WriteLine($"Container.PlaceholderMethod");
+ }
+ }
+
+ public struct Point
+ {
+ public int X, Y;
+ public string Id { get; set; }
+ public RGB Color { get; set; }
+
+ /* instance too */
+ public static async Task AsyncMethod(bool call_other)
+ {
+ int local_i = 5;
+ var sc = new SimpleClass { X = 10, Y = 45, Id = "sc#Id", Color = RGB.Blue };
+ if (call_other)
+ await new Point { X = 90, Y = -4, Id = "point#Id", Color = RGB.Green }.AsyncInstanceMethod(sc);
+ Console.WriteLine($"AsyncMethod local_i: {local_i}, sc: {sc.Id}");
+ }
+
+ public async Task AsyncInstanceMethod(SimpleClass sc_arg)
+ {
+ var local_gs = new SimpleGenericStruct<int> { Id = "local_gs#Id", Color = RGB.Green, Value = 4 };
+ sc_arg.Id = "sc_arg#Id";
+ Console.WriteLine($"AsyncInstanceMethod sc_arg: {sc_arg.Id}, local_gs: {local_gs.Id}");
+ }
+
+ public void GenericInstanceMethod<T>(T sc_arg) where T : SimpleClass
+ {
+ var local_gs = new SimpleGenericStruct<int> { Id = "local_gs#Id", Color = RGB.Green, Value = 4 };
+ sc_arg.Id = "sc_arg#Id";
+ Console.WriteLine($"AsyncInstanceMethod sc_arg: {sc_arg.Id}, local_gs: {local_gs.Id}");
+ }
+ }
+
+ public class SimpleClass
+ {
+ public int X, Y;
+ public string Id { get; set; }
+ public RGB Color { get; set; }
+
+ public Point PointWithCustomGetter { get { return new Point { X = 100, Y = 400, Id = "SimpleClass#Point#gen#Id", Color = RGB.Green }; } }
+ }
+
+ public class GenericClass<T>
+ {
+ public string Id { get; set; }
+ public RGB Color { get; set; }
+ public T Value { get; set; }
+ }
+
+ public struct SimpleGenericStruct<T>
+ {
+ public string Id { get; set; }
+ public RGB Color { get; set; }
+ public T Value { get; set; }
+ }
+
+ public class EntryClass
+ {
+ public static void run()
+ {
+ ArrayTestsClass.PrimitiveTypeLocals(true);
+ ArrayTestsClass.ValueTypeLocals(true);
+ ArrayTestsClass.ObjectTypeLocals(true);
+
+ ArrayTestsClass.GenericTypeLocals(true);
+ ArrayTestsClass.GenericValueTypeLocals(true);
+ ArrayTestsClass.GenericValueTypeLocals2(true);
+
+ ArrayTestsClass.ObjectArrayMembers();
+
+ ArrayTestsClass.ValueTypeLocalsAsync(true).Wait();
+
+ ArrayTestsClass.EntryPointForStructMethod(true).Wait();
+
+ var sc = new SimpleClass { X = 10, Y = 45, Id = "sc#Id", Color = RGB.Blue };
+ new Point { X = 90, Y = -4, Id = "point#Id", Color = RGB.Green }.GenericInstanceMethod(sc);
+ }
+ }
}
diff --git a/sdks/wasm/tests/debugger/debugger-cfo-test.cs b/sdks/wasm/tests/debugger/debugger-cfo-test.cs
index b99c093ab63..66a964a2808 100644
--- a/sdks/wasm/tests/debugger/debugger-cfo-test.cs
+++ b/sdks/wasm/tests/debugger/debugger-cfo-test.cs
@@ -1,60 +1,75 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
using System;
namespace DebuggerTests
{
- public class CallFunctionOnTest {
- public static void LocalsTest (int len)
- {
- var big = new int[len];
- for (int i = 0; i < len; i ++)
- big [i] = i + 1000;
-
- var simple_struct = new Math.SimpleStruct () { dt = new DateTime (2020, 1, 2, 3, 4, 5), gs = new Math.GenericStruct<DateTime> { StringField = $"simple_struct # gs # StringField" } };
-
- var ss_arr = new Math.SimpleStruct [len];
- for (int i = 0; i < len; i ++)
- ss_arr [i] = new Math.SimpleStruct () { dt = new DateTime (2020+i, 1, 2, 3, 4, 5), gs = new Math.GenericStruct<DateTime> { StringField = $"ss_arr # {i} # gs # StringField" } };
-
- var nim = new Math.NestedInMath { SimpleStructProperty = new Math.SimpleStruct () { dt = new DateTime (2010, 6, 7, 8, 9, 10) } };
- Action<Math.GenericStruct<int[]>> action = Math.DelegateTargetWithVoidReturn;
- Console.WriteLine("foo");
- }
-
- public static void PropertyGettersTest ()
- {
- var ptd = new ClassWithProperties { DTAutoProperty = new DateTime (4, 5, 6, 7, 8, 9) };
- var swp = new StructWithProperties ();
- System.Console.WriteLine("break here");
- }
-
- public static async System.Threading.Tasks.Task PropertyGettersTestAsync ()
- {
- var ptd = new ClassWithProperties { DTAutoProperty = new DateTime (4, 5, 6, 7, 8, 9) };
- var swp = new StructWithProperties ();
- System.Console.WriteLine("break here");
- await System.Threading.Tasks.Task.CompletedTask;
- }
- }
-
- class ClassWithProperties
- {
- public int Int { get { return 5; } }
- public string String { get { return "foobar"; } }
- public DateTime DT { get { return new DateTime (3, 4, 5, 6, 7, 8); } }
-
- public int[] IntArray { get { return new int[] { 10, 20 }; } }
- public DateTime[] DTArray { get { return new DateTime[] { new DateTime (6, 7, 8, 9, 10, 11), new DateTime (1, 2, 3, 4, 5, 6) }; }}
- public DateTime DTAutoProperty { get; set; }
- public string StringField;
- }
-
- struct StructWithProperties
- {
- public int Int { get { return 5; } }
- public string String { get { return "foobar"; } }
- public DateTime DT { get { return new DateTime (3, 4, 5, 6, 7, 8); } }
-
- public int[] IntArray { get { return new int[] { 10, 20 }; } }
- public DateTime[] DTArray { get { return new DateTime[] { new DateTime (6, 7, 8, 9, 10, 11), new DateTime (1, 2, 3, 4, 5, 6) }; }}
- }
+ public class CallFunctionOnTest
+ {
+ public static void LocalsTest(int len)
+ {
+ var big = new int[len];
+ for (int i = 0; i < len; i++)
+ big[i] = i + 1000;
+
+ var simple_struct = new Math.SimpleStruct() { dt = new DateTime(2020, 1, 2, 3, 4, 5), gs = new Math.GenericStruct<DateTime> { StringField = $"simple_struct # gs # StringField" } };
+
+ var ss_arr = new Math.SimpleStruct[len];
+ for (int i = 0; i < len; i++)
+ ss_arr[i] = new Math.SimpleStruct() { dt = new DateTime(2020 + i, 1, 2, 3, 4, 5), gs = new Math.GenericStruct<DateTime> { StringField = $"ss_arr # {i} # gs # StringField" } };
+
+ var nim = new Math.NestedInMath { SimpleStructProperty = new Math.SimpleStruct() { dt = new DateTime(2010, 6, 7, 8, 9, 10) } };
+ Action<Math.GenericStruct<int[]>> action = Math.DelegateTargetWithVoidReturn;
+ Console.WriteLine("foo");
+ }
+
+ public static void PropertyGettersTest()
+ {
+ var ptd = new ClassWithProperties { DTAutoProperty = new DateTime(4, 5, 6, 7, 8, 9), V = 0xDEADBEEF };
+ var swp = new StructWithProperties { DTAutoProperty = new DateTime(4, 5, 6, 7, 8, 9), V = 0xDEADBEEF };
+ System.Console.WriteLine("break here");
+ }
+
+ public static async System.Threading.Tasks.Task PropertyGettersTestAsync()
+ {
+ var ptd = new ClassWithProperties { DTAutoProperty = new DateTime(4, 5, 6, 7, 8, 9), V = 0xDEADBEEF };
+ var swp = new StructWithProperties { DTAutoProperty = new DateTime(4, 5, 6, 7, 8, 9), V = 0xDEADBEEF };
+ System.Console.WriteLine("break here");
+ await System.Threading.Tasks.Task.CompletedTask;
+ }
+
+ public static void MethodForNegativeTests(string value = null)
+ {
+ var ptd = new ClassWithProperties { StringField = value };
+ var swp = new StructWithProperties { StringField = value };
+ Console.WriteLine("break here");
+ }
+ }
+
+ class ClassWithProperties
+ {
+ public uint V;
+ public uint Int { get { return V + (uint)DT.Month; } }
+ public string String { get { return $"String property, V: 0x{V:X}"; } }
+ public DateTime DT { get { return new DateTime(3, 4, 5, 6, 7, 8); } }
+
+ public int[] IntArray { get { return new int[] { 10, 20 }; } }
+ public DateTime[] DTArray { get { return new DateTime[] { new DateTime(6, 7, 8, 9, 10, 11), new DateTime(1, 2, 3, 4, 5, 6) }; } }
+ public DateTime DTAutoProperty { get; set; }
+ public string StringField;
+ }
+
+ struct StructWithProperties
+ {
+ public uint V;
+ public uint Int { get { return V + (uint)DT.Month; } }
+ public string String { get { return $"String property, V: 0x{V:X}"; } }
+ public DateTime DT { get { return new DateTime(3, 4, 5, 6, 7, 8); } }
+
+ public int[] IntArray { get { return new int[] { 10, 20 }; } }
+ public DateTime[] DTArray { get { return new DateTime[] { new DateTime(6, 7, 8, 9, 10, 11), new DateTime(1, 2, 3, 4, 5, 6) }; } }
+ public DateTime DTAutoProperty { get; set; }
+ public string StringField;
+ }
}
diff --git a/sdks/wasm/tests/debugger/debugger-datetime-test.cs b/sdks/wasm/tests/debugger/debugger-datetime-test.cs
index 55a56761966..62890a7c0ff 100644
--- a/sdks/wasm/tests/debugger/debugger-datetime-test.cs
+++ b/sdks/wasm/tests/debugger/debugger-datetime-test.cs
@@ -1,24 +1,29 @@
+// 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.Globalization;
-namespace DebuggerTests {
- public class DateTimeTest {
- public static string LocaleTest (string locale)
- {
- CultureInfo.CurrentCulture = new CultureInfo (locale, false);
- Console.WriteLine("CurrentCulture is {0}", CultureInfo.CurrentCulture.Name);
+namespace DebuggerTests
+{
+ public class DateTimeTest
+ {
+ public static string LocaleTest(string locale)
+ {
+ CultureInfo.CurrentCulture = new CultureInfo(locale, false);
+ Console.WriteLine("CurrentCulture is {0}", CultureInfo.CurrentCulture.Name);
- DateTimeFormatInfo dtfi = CultureInfo.GetCultureInfo(locale).DateTimeFormat;
- var fdtp = dtfi.FullDateTimePattern;
- var ldp = dtfi.LongDatePattern;
- var ltp = dtfi.LongTimePattern;
- var sdp = dtfi.ShortDatePattern;
- var stp = dtfi.ShortTimePattern;
+ DateTimeFormatInfo dtfi = CultureInfo.GetCultureInfo(locale).DateTimeFormat;
+ var fdtp = dtfi.FullDateTimePattern;
+ var ldp = dtfi.LongDatePattern;
+ var ltp = dtfi.LongTimePattern;
+ var sdp = dtfi.ShortDatePattern;
+ var stp = dtfi.ShortTimePattern;
- DateTime dt = new DateTime (2020, 1, 2, 3, 4, 5);
- string dt_str = dt.ToString();
- Console.WriteLine("Current time is {0}", dt_str);
+ DateTime dt = new DateTime(2020, 1, 2, 3, 4, 5);
+ string dt_str = dt.ToString();
+ Console.WriteLine("Current time is {0}", dt_str);
- return dt_str;
- }
- }
-} \ No newline at end of file
+ return dt_str;
+ }
+ }
+}
diff --git a/sdks/wasm/tests/debugger/debugger-driver.html b/sdks/wasm/tests/debugger/debugger-driver.html
index 160a4e53b1e..049b6b65c60 100644
--- a/sdks/wasm/tests/debugger/debugger-driver.html
+++ b/sdks/wasm/tests/debugger/debugger-driver.html
@@ -75,7 +75,7 @@
}
</script>
<script type="text/javascript" src="mono-config.js"></script>
- <script type="text/javascript" src="runtime.js"></script>
+ <script type="text/javascript" src="runtime-debugger.js"></script>
<script type="text/javascript" src="other.js"></script>
<script async type="text/javascript" src="dotnet.js"></script>
Stuff goes here
diff --git a/sdks/wasm/tests/debugger/debugger-evaluate-test.cs b/sdks/wasm/tests/debugger/debugger-evaluate-test.cs
index 869708a64da..ffb6fadafb1 100644
--- a/sdks/wasm/tests/debugger/debugger-evaluate-test.cs
+++ b/sdks/wasm/tests/debugger/debugger-evaluate-test.cs
@@ -1,20 +1,25 @@
+// 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.Threading.Tasks;
namespace DebuggerTests
{
- public class EvaluateTestsClass
- {
- public class TestEvaluate {
+ public class EvaluateTestsClass
+ {
+ public class TestEvaluate
+ {
public int a;
public int b;
public int c;
- public DateTime dt = new DateTime (2000, 5, 4, 3, 2, 1);
- public void run(int g, int h, string valString) {
+ public DateTime dt = new DateTime(2000, 5, 4, 3, 2, 1);
+ public void run(int g, int h, string valString)
+ {
int d = g + 1;
int e = g + 2;
int f = g + 3;
int i = d + e + f;
- var local_dt = new DateTime (2010, 9, 8, 7, 6, 5);
+ var local_dt = new DateTime(2010, 9, 8, 7, 6, 5);
a = 1;
b = 2;
c = 3;
@@ -22,82 +27,82 @@ namespace DebuggerTests
b = b + 1;
c = c + 1;
}
- }
+ }
- public static void EvaluateLocals ()
- {
- TestEvaluate f = new TestEvaluate();
- f.run(100, 200, "test");
+ public static void EvaluateLocals()
+ {
+ TestEvaluate f = new TestEvaluate();
+ f.run(100, 200, "test");
- var f_s = new EvaluateTestsStruct ();
- f_s.EvaluateTestsStructInstanceMethod (100, 200, "test");
- f_s.GenericInstanceMethodOnStruct <int> (100, 200, "test");
+ var f_s = new EvaluateTestsStruct();
+ f_s.EvaluateTestsStructInstanceMethod(100, 200, "test");
+ f_s.GenericInstanceMethodOnStruct<int>(100, 200, "test");
- var f_g_s = new EvaluateTestsGenericStruct<int> ();
- f_g_s.EvaluateTestsGenericStructInstanceMethod (100, 200, "test");
- Console.WriteLine ($"a: {f.a}, b: {f.b}, c: {f.c}");
- }
+ var f_g_s = new EvaluateTestsGenericStruct<int>();
+ f_g_s.EvaluateTestsGenericStructInstanceMethod(100, 200, "test");
+ Console.WriteLine($"a: {f.a}, b: {f.b}, c: {f.c}");
+ }
}
- public struct EvaluateTestsStruct
- {
- public int a;
- public int b;
- public int c;
- DateTime dateTime;
- public void EvaluateTestsStructInstanceMethod (int g, int h, string valString)
- {
- int d = g + 1;
- int e = g + 2;
- int f = g + 3;
- int i = d + e + f;
- a = 1;
- b = 2;
- c = 3;
- dateTime = new DateTime (2020, 1, 2, 3, 4, 5);
- a = a + 1;
- b = b + 1;
- c = c + 1;
- }
+ public struct EvaluateTestsStruct
+ {
+ public int a;
+ public int b;
+ public int c;
+ DateTime dateTime;
+ public void EvaluateTestsStructInstanceMethod(int g, int h, string valString)
+ {
+ int d = g + 1;
+ int e = g + 2;
+ int f = g + 3;
+ int i = d + e + f;
+ a = 1;
+ b = 2;
+ c = 3;
+ dateTime = new DateTime(2020, 1, 2, 3, 4, 5);
+ a = a + 1;
+ b = b + 1;
+ c = c + 1;
+ }
- public void GenericInstanceMethodOnStruct<T> (int g, int h, string valString)
- {
- int d = g + 1;
- int e = g + 2;
- int f = g + 3;
- int i = d + e + f;
- a = 1;
- b = 2;
- c = 3;
- dateTime = new DateTime (2020, 1, 2, 3, 4, 5);
- T t = default(T);
- a = a + 1;
- b = b + 1;
- c = c + 1;
- }
- }
+ public void GenericInstanceMethodOnStruct<T>(int g, int h, string valString)
+ {
+ int d = g + 1;
+ int e = g + 2;
+ int f = g + 3;
+ int i = d + e + f;
+ a = 1;
+ b = 2;
+ c = 3;
+ dateTime = new DateTime(2020, 1, 2, 3, 4, 5);
+ T t = default(T);
+ a = a + 1;
+ b = b + 1;
+ c = c + 1;
+ }
+ }
- public struct EvaluateTestsGenericStruct<T>
- {
- public int a;
- public int b;
- public int c;
- DateTime dateTime;
- public void EvaluateTestsGenericStructInstanceMethod (int g, int h, string valString)
- {
- int d = g + 1;
- int e = g + 2;
- int f = g + 3;
- int i = d + e + f;
- a = 1;
- b = 2;
- c = 3;
- dateTime = new DateTime (2020, 1, 2, 3, 4, 5);
- T t = default(T);
- a = a + 1;
- b = b + 2;
- c = c + 3;
- }
- }
+ public struct EvaluateTestsGenericStruct<T>
+ {
+ public int a;
+ public int b;
+ public int c;
+ DateTime dateTime;
+ public void EvaluateTestsGenericStructInstanceMethod(int g, int h, string valString)
+ {
+ int d = g + 1;
+ int e = g + 2;
+ int f = g + 3;
+ int i = d + e + f;
+ a = 1;
+ b = 2;
+ c = 3;
+ dateTime = new DateTime(2020, 1, 2, 3, 4, 5);
+ T t = default(T);
+ a = a + 1;
+ b = b + 2;
+ c = c + 3;
+ }
+ }
}
diff --git a/sdks/wasm/tests/debugger/debugger-exception-test.cs b/sdks/wasm/tests/debugger/debugger-exception-test.cs
new file mode 100644
index 00000000000..86904ca27ea
--- /dev/null
+++ b/sdks/wasm/tests/debugger/debugger-exception-test.cs
@@ -0,0 +1,55 @@
+// 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.Threading.Tasks;
+namespace DebuggerTests
+{
+ public class ExceptionTestsClass
+ {
+ public class TestCaughtException
+ {
+ public void run()
+ {
+ try
+ {
+ throw new CustomException("not implemented caught");
+ }
+ catch
+ {
+ Console.WriteLine("caught exception");
+ }
+ }
+ }
+
+ public class TestUncaughtException
+ {
+ public void run()
+ {
+ throw new CustomException("not implemented uncaught");
+ }
+ }
+
+ public static void TestExceptions()
+ {
+ TestCaughtException f = new TestCaughtException();
+ f.run();
+
+ TestUncaughtException g = new TestUncaughtException();
+ g.run();
+ }
+
+ }
+
+ public class CustomException : Exception
+ {
+ // Using this name to match with what js has.
+ // helps with the tests
+ public string message;
+ public CustomException(string message)
+ : base(message)
+ {
+ this.message = message;
+ }
+ }
+}
diff --git a/sdks/wasm/tests/debugger/debugger-pointers-test.cs b/sdks/wasm/tests/debugger/debugger-pointers-test.cs
index 6cca499566e..a82973e5ac3 100644
--- a/sdks/wasm/tests/debugger/debugger-pointers-test.cs
+++ b/sdks/wasm/tests/debugger/debugger-pointers-test.cs
@@ -1,109 +1,112 @@
+// 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.Threading.Tasks;
namespace DebuggerTests
{
- public class PointerTests
- {
-
- public static unsafe void LocalPointers ()
- {
- int ivalue0 = 5;
- int ivalue1 = 10;
-
- int* ip = &ivalue0;
- int* ip_null = null;
- int** ipp = &ip;
- int** ipp_null = &ip_null;
- int*[] ipa = new int*[] { &ivalue0, &ivalue1, null };
- int**[] ippa = new int**[] { &ip, &ip_null, ipp, ipp_null, null };
- char cvalue0 = 'q';
- char* cp = &cvalue0;
-
- DateTime dt = new DateTime(5, 6, 7, 8, 9, 10);
- void* vp = &dt;
- void* vp_null = null;
- void** vpp = &vp;
- void** vpp_null = &vp_null;
-
- DateTime* dtp = &dt;
- DateTime* dtp_null = null;
- DateTime*[] dtpa = new DateTime*[] { dtp, dtp_null };
- DateTime**[] dtppa = new DateTime**[] { &dtp, &dtp_null, null };
- Console.WriteLine($"-- break here: ip_null==null: {ip_null == null}, ipp_null: {ipp_null == null}, *ipp_null==ip_null: {*ipp_null == ip_null}, *ipp_null==null: {*ipp_null == null}");
-
- var gs = new GenericStructWithUnmanagedT<DateTime> { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp };
- var gs_null = new GenericStructWithUnmanagedT<DateTime> { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp_null };
- var gsp = &gs;
- var gsp_null = &gs_null;
- var gspa = new GenericStructWithUnmanagedT<DateTime>*[] { null, gsp, gsp_null };
-
- var cwp = new GenericClassWithPointers<DateTime> { Ptr = dtp };
- var cwp_null = new GenericClassWithPointers<DateTime>();
- Console.WriteLine($"{(int)*ip}, {(int)**ipp}, {ipp_null == null}, {ip_null == null}, {ippa == null}, {ipa}, {(char)*cp}, {(vp == null ? "null" : "not null")}, {dtp->Second}, {gsp->IntField}, {cwp}, {cwp_null}, {gs_null}");
-
- PointersAsArgsTest (ip, ipp, ipa, ippa, &dt, &dtp, dtpa, dtppa);
- }
-
- static unsafe void PointersAsArgsTest(int* ip, int** ipp, int*[] ipa, int**[] ippa,
- DateTime* dtp, DateTime** dtpp, DateTime*[] dtpa, DateTime**[] dtppa)
- {
- Console.WriteLine($"break here!");
- if (ip == null)
- Console.WriteLine($"ip is null");
- Console.WriteLine($"done!");
- }
-
- public static unsafe async Task LocalPointersAsync ()
- {
- int ivalue0 = 5;
- int ivalue1 = 10;
-
- int* ip = &ivalue0;
- int* ip_null = null;
- int** ipp = &ip;
- int** ipp_null = &ip_null;
- int*[] ipa = new int*[] { &ivalue0, &ivalue1, null };
- int**[] ippa = new int**[] { &ip, &ip_null, ipp, ipp_null, null };
- char cvalue0 = 'q';
- char* cp = &cvalue0;
-
- DateTime dt = new DateTime(5, 6, 7, 8, 9, 10);
- void* vp = &dt;
- void* vp_null = null;
- void** vpp = &vp;
- void** vpp_null = &vp_null;
-
- DateTime* dtp = &dt;
- DateTime* dtp_null = null;
- DateTime*[] dtpa = new DateTime*[] { dtp, dtp_null };
- DateTime**[] dtppa = new DateTime**[] { &dtp, &dtp_null, null };
- Console.WriteLine($"-- break here: ip_null==null: {ip_null == null}, ipp_null: {ipp_null == null}, *ipp_null==ip_null: {*ipp_null == ip_null}, *ipp_null==null: {*ipp_null == null}");
-
- var gs = new GenericStructWithUnmanagedT<DateTime> { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp };
- var gs_null = new GenericStructWithUnmanagedT<DateTime> { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp_null };
- var gsp = &gs;
- var gsp_null = &gs_null;
- var gspa = new GenericStructWithUnmanagedT<DateTime>*[] { null, gsp, gsp_null };
-
- var cwp = new GenericClassWithPointers<DateTime> { Ptr = dtp };
- var cwp_null = new GenericClassWithPointers<DateTime>();
- Console.WriteLine($"{(int)*ip}, {(int)**ipp}, {ipp_null == null}, {ip_null == null}, {ippa == null}, {ipa}, {(char)*cp}, {(vp == null ? "null" : "not null")}, {dtp->Second}, {gsp->IntField}, {cwp}, {cwp_null}, {gs_null}");
- }
-
- // async methods cannot have unsafe params, so no test for that
- }
-
- public unsafe struct GenericStructWithUnmanagedT<T> where T : unmanaged
- {
- public T Value;
- public int IntField;
-
- public DateTime** DTPP;
- }
-
- public unsafe class GenericClassWithPointers<T> where T : unmanaged
- {
- public unsafe T* Ptr;
- }
-} \ No newline at end of file
+ public class PointerTests
+ {
+
+ public static unsafe void LocalPointers()
+ {
+ int ivalue0 = 5;
+ int ivalue1 = 10;
+
+ int* ip = &ivalue0;
+ int* ip_null = null;
+ int** ipp = &ip;
+ int** ipp_null = &ip_null;
+ int*[] ipa = new int*[] { &ivalue0, &ivalue1, null };
+ int**[] ippa = new int**[] { &ip, &ip_null, ipp, ipp_null, null };
+ char cvalue0 = 'q';
+ char* cp = &cvalue0;
+
+ DateTime dt = new DateTime(5, 6, 7, 8, 9, 10);
+ void* vp = &dt;
+ void* vp_null = null;
+ void** vpp = &vp;
+ void** vpp_null = &vp_null;
+
+ DateTime* dtp = &dt;
+ DateTime* dtp_null = null;
+ DateTime*[] dtpa = new DateTime*[] { dtp, dtp_null };
+ DateTime**[] dtppa = new DateTime**[] { &dtp, &dtp_null, null };
+ Console.WriteLine($"-- break here: ip_null==null: {ip_null == null}, ipp_null: {ipp_null == null}, *ipp_null==ip_null: {*ipp_null == ip_null}, *ipp_null==null: {*ipp_null == null}");
+
+ var gs = new GenericStructWithUnmanagedT<DateTime> { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp };
+ var gs_null = new GenericStructWithUnmanagedT<DateTime> { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp_null };
+ var gsp = &gs;
+ var gsp_null = &gs_null;
+ var gspa = new GenericStructWithUnmanagedT<DateTime>*[] { null, gsp, gsp_null };
+
+ var cwp = new GenericClassWithPointers<DateTime> { Ptr = dtp };
+ var cwp_null = new GenericClassWithPointers<DateTime>();
+ Console.WriteLine($"{(int)*ip}, {(int)**ipp}, {ipp_null == null}, {ip_null == null}, {ippa == null}, {ipa}, {(char)*cp}, {(vp == null ? "null" : "not null")}, {dtp->Second}, {gsp->IntField}, {cwp}, {cwp_null}, {gs_null}");
+
+ PointersAsArgsTest(ip, ipp, ipa, ippa, &dt, &dtp, dtpa, dtppa);
+ }
+
+ static unsafe void PointersAsArgsTest(int* ip, int** ipp, int*[] ipa, int**[] ippa,
+ DateTime* dtp, DateTime** dtpp, DateTime*[] dtpa, DateTime**[] dtppa)
+ {
+ Console.WriteLine($"break here!");
+ if (ip == null)
+ Console.WriteLine($"ip is null");
+ Console.WriteLine($"done!");
+ }
+
+ public static unsafe async Task LocalPointersAsync()
+ {
+ int ivalue0 = 5;
+ int ivalue1 = 10;
+
+ int* ip = &ivalue0;
+ int* ip_null = null;
+ int** ipp = &ip;
+ int** ipp_null = &ip_null;
+ int*[] ipa = new int*[] { &ivalue0, &ivalue1, null };
+ int**[] ippa = new int**[] { &ip, &ip_null, ipp, ipp_null, null };
+ char cvalue0 = 'q';
+ char* cp = &cvalue0;
+
+ DateTime dt = new DateTime(5, 6, 7, 8, 9, 10);
+ void* vp = &dt;
+ void* vp_null = null;
+ void** vpp = &vp;
+ void** vpp_null = &vp_null;
+
+ DateTime* dtp = &dt;
+ DateTime* dtp_null = null;
+ DateTime*[] dtpa = new DateTime*[] { dtp, dtp_null };
+ DateTime**[] dtppa = new DateTime**[] { &dtp, &dtp_null, null };
+ Console.WriteLine($"-- break here: ip_null==null: {ip_null == null}, ipp_null: {ipp_null == null}, *ipp_null==ip_null: {*ipp_null == ip_null}, *ipp_null==null: {*ipp_null == null}");
+
+ var gs = new GenericStructWithUnmanagedT<DateTime> { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp };
+ var gs_null = new GenericStructWithUnmanagedT<DateTime> { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp_null };
+ var gsp = &gs;
+ var gsp_null = &gs_null;
+ var gspa = new GenericStructWithUnmanagedT<DateTime>*[] { null, gsp, gsp_null };
+
+ var cwp = new GenericClassWithPointers<DateTime> { Ptr = dtp };
+ var cwp_null = new GenericClassWithPointers<DateTime>();
+ Console.WriteLine($"{(int)*ip}, {(int)**ipp}, {ipp_null == null}, {ip_null == null}, {ippa == null}, {ipa}, {(char)*cp}, {(vp == null ? "null" : "not null")}, {dtp->Second}, {gsp->IntField}, {cwp}, {cwp_null}, {gs_null}");
+ }
+
+ // async methods cannot have unsafe params, so no test for that
+ }
+
+ public unsafe struct GenericStructWithUnmanagedT<T> where T : unmanaged
+ {
+ public T Value;
+ public int IntField;
+
+ public DateTime** DTPP;
+ }
+
+ public unsafe class GenericClassWithPointers<T> where T : unmanaged
+ {
+ public unsafe T* Ptr;
+ }
+}
diff --git a/sdks/wasm/tests/debugger/debugger-test.cs b/sdks/wasm/tests/debugger/debugger-test.cs
index 84e3bcfe0a7..9b53cea250c 100644
--- a/sdks/wasm/tests/debugger/debugger-test.cs
+++ b/sdks/wasm/tests/debugger/debugger-test.cs
@@ -1,309 +1,325 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
using System;
-public partial class Math { //Only append content to this class as the test suite depends on line info
- public static int IntAdd (int a, int b) {
- int c = a + b;
- int d = c + b;
- int e = d + a;
- int f = 0;
- return e;
- }
-
- public static int UseComplex (int a, int b) {
- var complex = new Simple.Complex (10, "xx");
- int c = a + b;
- int d = c + b;
- int e = d + a;
- int f = 0;
- e += complex.DoStuff ();
- return e;
- }
-
- delegate bool IsMathNull (Math m);
-
- public static int DelegatesTest () {
- Func<Math, bool> fn_func = (Math m) => m == null;
- Func<Math, bool> fn_func_null = null;
- Func<Math, bool>[] fn_func_arr = new Func<Math, bool>[] { (Math m) => m == null };
-
- Math.IsMathNull fn_del = Math.IsMathNullDelegateTarget;
- var fn_del_arr = new Math.IsMathNull[] { Math.IsMathNullDelegateTarget };
- var m_obj = new Math ();
- Math.IsMathNull fn_del_null = null;
- bool res = fn_func (m_obj) && fn_del (m_obj) && fn_del_arr[0] (m_obj) && fn_del_null == null && fn_func_null == null && fn_func_arr[0] != null;
-
- // Unused locals
-
- Func<Math, bool> fn_func_unused = (Math m) => m == null;
- Func<Math, bool> fn_func_null_unused = null;
- Func<Math, bool>[] fn_func_arr_unused = new Func<Math, bool>[] { (Math m) => m == null };
-
- Math.IsMathNull fn_del_unused = Math.IsMathNullDelegateTarget;
- Math.IsMathNull fn_del_null_unused = null;
- var fn_del_arr_unused = new Math.IsMathNull[] { Math.IsMathNullDelegateTarget };
- OuterMethod ();
- Console.WriteLine ("Just a test message, ignore");
- return res ? 0 : 1;
- }
-
- public static int GenericTypesTest () {
- var list = new System.Collections.Generic.Dictionary<Math[], IsMathNull> ();
- System.Collections.Generic.Dictionary<Math[], IsMathNull> list_null = null;
-
- var list_arr = new System.Collections.Generic.Dictionary<Math[], IsMathNull>[] { new System.Collections.Generic.Dictionary<Math[], IsMathNull> () };
- System.Collections.Generic.Dictionary<Math[], IsMathNull>[] list_arr_null = null;
-
- Console.WriteLine ($"list_arr.Length: {list_arr.Length}, list.Count: {list.Count}");
-
- // Unused locals
-
- var list_unused = new System.Collections.Generic.Dictionary<Math[], IsMathNull> ();
- System.Collections.Generic.Dictionary<Math[], IsMathNull> list_null_unused = null;
-
- var list_arr_unused = new System.Collections.Generic.Dictionary<Math[], IsMathNull>[] { new System.Collections.Generic.Dictionary<Math[], IsMathNull> () };
- System.Collections.Generic.Dictionary<Math[], IsMathNull>[] list_arr_null_unused = null;
-
- OuterMethod ();
- Console.WriteLine ("Just a test message, ignore");
- return 0;
- }
-
- static bool IsMathNullDelegateTarget (Math m) => m == null;
-
- public static void OuterMethod ()
- {
- Console.WriteLine ($"OuterMethod called");
- var nim = new Math.NestedInMath ();
- var i = 5;
- var text = "Hello";
- var new_i = nim.InnerMethod (i);
- Console.WriteLine ($"i: {i}");
- Console.WriteLine ($"-- InnerMethod returned: {new_i}, nim: {nim}, text: {text}");
- int k = 19;
- new_i = InnerMethod2 ("test string", new_i, out k);
- Console.WriteLine ($"-- InnerMethod2 returned: {new_i}, and k: {k}");
- }
-
- static int InnerMethod2 (string s, int i, out int k)
- {
- k = i + 10;
- Console.WriteLine ($"s: {s}, i: {i}, k: {k}");
- return i - 2;
- }
-
- public class NestedInMath
- {
- public int InnerMethod (int i)
- {
- SimpleStructProperty = new SimpleStruct () { dt = new DateTime (2020, 1, 2, 3, 4, 5) };
- int j = i + 10;
- string foo_str = "foo";
- Console.WriteLine ($"i: {i} and j: {j}, foo_str: {foo_str} ");
- j += 9;
- Console.WriteLine ($"i: {i} and j: {j}");
- return j;
- }
-
- Math m = new Math ();
- public async System.Threading.Tasks.Task<bool> AsyncMethod0 (string s, int i)
- {
- string local0 = "value0";
- await System.Threading.Tasks.Task.Delay (1);
- Console.WriteLine ($"* time for the second await, local0: {local0}");
- await AsyncMethodNoReturn ();
- return true;
- }
-
- public async System.Threading.Tasks.Task AsyncMethodNoReturn ()
- {
- var ss = new SimpleStruct () { dt = new DateTime (2020, 1, 2, 3, 4, 5) };
- var ss_arr = new SimpleStruct [] {};
- //ss.gs.StringField = "field in GenericStruct";
-
- //Console.WriteLine ($"Using the struct: {ss.dt}, {ss.gs.StringField}, ss_arr: {ss_arr.Length}");
- string str = "AsyncMethodNoReturn's local";
- //Console.WriteLine ($"* field m: {m}");
- await System.Threading.Tasks.Task.Delay (1);
- Console.WriteLine ($"str: {str}");
- }
-
- public static async System.Threading.Tasks.Task<bool> AsyncTest (string s, int i)
- {
- var li = 10 + i;
- var ls = s + "test";
- return await new NestedInMath().AsyncMethod0 (s, i);
- }
-
- public SimpleStruct SimpleStructProperty { get; set; }
- }
-
- public static void PrimitiveTypesTest ()
- {
- char c0 = '€';
- char c1 = 'A';
- // TODO: other types!
- // just trying to ensure vars don't get optimized out
- if (c0 < 32 || c1 > 32)
- Console.WriteLine ($"{c0}, {c1}");
- }
-
- public static int DelegatesSignatureTest ()
- {
- Func<Math, GenericStruct<GenericStruct<int[]>>, GenericStruct<bool[]>> fn_func = (m, gs) => new GenericStruct<bool[]>();
- Func<Math, GenericStruct<GenericStruct<int[]>>, GenericStruct<bool[]>> fn_func_del = GenericStruct<int>.DelegateTargetForSignatureTest;
- Func<Math, GenericStruct<GenericStruct<int[]>>, GenericStruct<bool[]>> fn_func_null = null;
- Func<bool> fn_func_only_ret = () => { Console.WriteLine ($"hello"); return true; };
- var fn_func_arr = new Func<Math, GenericStruct<GenericStruct<int[]>>, GenericStruct<bool[]>>[] { (m, gs) => new GenericStruct<bool[]>() };
-
- Math.DelegateForSignatureTest fn_del = GenericStruct<int>.DelegateTargetForSignatureTest;
- Math.DelegateForSignatureTest fn_del_l = (m, gs) => new GenericStruct<bool[]> { StringField = "fn_del_l#lambda" };
- var fn_del_arr = new Math.DelegateForSignatureTest[] { GenericStruct<int>.DelegateTargetForSignatureTest, (m, gs) => new GenericStruct<bool[]> { StringField = "fn_del_arr#1#lambda" } };
- var m_obj = new Math ();
- Math.DelegateForSignatureTest fn_del_null = null;
- var gs_gs = new GenericStruct<GenericStruct<int[]>>
- {
- List = new System.Collections.Generic.List<GenericStruct<int[]>> {
- new GenericStruct<int[]> { StringField = "gs#List#0#StringField" },
- new GenericStruct<int[]> { StringField = "gs#List#1#StringField" }
- }
- };
-
- Math.DelegateWithVoidReturn fn_void_del = Math.DelegateTargetWithVoidReturn;
- var fn_void_del_arr = new Math.DelegateWithVoidReturn[] { Math.DelegateTargetWithVoidReturn };
- Math.DelegateWithVoidReturn fn_void_del_null = null;
-
- var rets = new GenericStruct<bool[]>[] {
- fn_func (m_obj, gs_gs),
- fn_func_del (m_obj, gs_gs),
- fn_del (m_obj, gs_gs),
- fn_del_l (m_obj, gs_gs),
- fn_del_arr[0] (m_obj, gs_gs),
- fn_func_arr[0] (m_obj, gs_gs)
- };
-
- var gs = new GenericStruct<int[]>();
- fn_void_del (gs);
- fn_void_del_arr[0](gs);
- fn_func_only_ret ();
- foreach (var ret in rets) Console.WriteLine ($"ret: {ret}");
- OuterMethod ();
- Console.WriteLine ($"- {gs_gs.List[0].StringField}");
- return 0;
- }
-
- public static int ActionTSignatureTest ()
- {
- Action<GenericStruct<int[]>> fn_action = (_) => { };
- Action<GenericStruct<int[]>> fn_action_del = Math.DelegateTargetWithVoidReturn;
- Action fn_action_bare = () => {};
- Action<GenericStruct<int[]>> fn_action_null = null;
- var fn_action_arr = new Action<GenericStruct<int[]>>[] {
- (gs) => new GenericStruct<int[]>(),
- Math.DelegateTargetWithVoidReturn,
- null
- };
-
- var gs = new GenericStruct<int[]>();
- fn_action (gs);
- fn_action_del (gs);
- fn_action_arr[0](gs);
- fn_action_bare ();
- OuterMethod ();
- return 0;
- }
-
- public static int NestedDelegatesTest ()
- {
- Func<Func<int, bool>, bool> fn_func = (_) => { return true; };
- Func<Func<int, bool>, bool> fn_func_null = null;
- var fn_func_arr = new Func<Func<int, bool>, bool>[] { (gs) => { return true; } };
-
- var fn_del_arr = new Func<Func<int, bool>, bool>[] { DelegateTargetForNestedFunc<Func<int, bool>> };
- var m_obj = new Math ();
- Func<Func<int, bool>, bool> fn_del_null = null;
- Func<int, bool> fs = (i) => i == 0;
- fn_func (fs);
- fn_del_arr[0](fs);
- fn_func_arr[0](fs);
- OuterMethod ();
- return 0;
- }
-
- public static void DelegatesAsMethodArgsTest ()
- {
- var _dst_arr = new DelegateForSignatureTest[] {
- GenericStruct<int>.DelegateTargetForSignatureTest,
- (m, gs) => new GenericStruct<bool[]> ()
- };
- Func<char[], bool> _fn_func = (cs) => cs.Length == 0;
- Action<GenericStruct<int>[]> _fn_action = (gss) => { };
-
- new Math ().MethodWithDelegateArgs (_dst_arr, _fn_func, _fn_action);
- }
-
- void MethodWithDelegateArgs (Math.DelegateForSignatureTest[] dst_arr, Func<char[], bool> fn_func,
- Action<GenericStruct<int>[]> fn_action)
- {
- Console.WriteLine ($"Placeholder for breakpoint");
- OuterMethod ();
- }
-
- public static async System.Threading.Tasks.Task MethodWithDelegatesAsyncTest ()
- {
- await new Math ().MethodWithDelegatesAsync ();
- }
-
- async System.Threading.Tasks.Task MethodWithDelegatesAsync ()
- {
- var _dst_arr = new DelegateForSignatureTest[] {
- GenericStruct<int>.DelegateTargetForSignatureTest,
- (m, gs) => new GenericStruct<bool[]> ()
- };
- Func<char[], bool> _fn_func = (cs) => cs.Length == 0;
- Action<GenericStruct<int>[]> _fn_action = (gss) => { };
-
- Console.WriteLine ($"Placeholder for breakpoint");
- await System.Threading.Tasks.Task.CompletedTask;
- }
-
- public delegate void DelegateWithVoidReturn (GenericStruct<int[]> gs);
- public static void DelegateTargetWithVoidReturn (GenericStruct<int[]> gs) { }
-
- delegate GenericStruct<bool[]> DelegateForSignatureTest (Math m, GenericStruct<GenericStruct<int[]>> gs);
- static bool DelegateTargetForNestedFunc<T>(T arg) => true;
-
- public struct SimpleStruct
- {
- public DateTime dt;
- public GenericStruct<DateTime> gs;
- }
-
- public struct GenericStruct<T>
- {
- public System.Collections.Generic.List<T> List;
- public string StringField;
-
- public static GenericStruct<bool[]> DelegateTargetForSignatureTest (Math m, GenericStruct<GenericStruct<T[]>> gs)
- => new GenericStruct<bool[]> ();
- }
+public partial class Math
+{ //Only append content to this class as the test suite depends on line info
+ public static int IntAdd(int a, int b)
+ {
+ int c = a + b;
+ int d = c + b;
+ int e = d + a;
+ int f = 0;
+ return e;
+ }
+
+ public static int UseComplex(int a, int b)
+ {
+ var complex = new Simple.Complex(10, "xx");
+ int c = a + b;
+ int d = c + b;
+ int e = d + a;
+ int f = 0;
+ e += complex.DoStuff();
+ return e;
+ }
+
+ delegate bool IsMathNull(Math m);
+
+ public static int DelegatesTest()
+ {
+ Func<Math, bool> fn_func = (Math m) => m == null;
+ Func<Math, bool> fn_func_null = null;
+ Func<Math, bool>[] fn_func_arr = new Func<Math, bool>[] {
+ (Math m) => m == null };
+
+ Math.IsMathNull fn_del = Math.IsMathNullDelegateTarget;
+ var fn_del_arr = new Math.IsMathNull[] { Math.IsMathNullDelegateTarget };
+ var m_obj = new Math();
+ Math.IsMathNull fn_del_null = null;
+ bool res = fn_func(m_obj) && fn_del(m_obj) && fn_del_arr[0](m_obj) && fn_del_null == null && fn_func_null == null && fn_func_arr[0] != null;
+
+ // Unused locals
+
+ Func<Math, bool> fn_func_unused = (Math m) => m == null;
+ Func<Math, bool> fn_func_null_unused = null;
+ Func<Math, bool>[] fn_func_arr_unused = new Func<Math, bool>[] { (Math m) => m == null };
+
+ Math.IsMathNull fn_del_unused = Math.IsMathNullDelegateTarget;
+ Math.IsMathNull fn_del_null_unused = null;
+ var fn_del_arr_unused = new Math.IsMathNull[] { Math.IsMathNullDelegateTarget };
+ OuterMethod();
+ Console.WriteLine("Just a test message, ignore");
+ return res ? 0 : 1;
+ }
+
+ public static int GenericTypesTest()
+ {
+ var list = new System.Collections.Generic.Dictionary<Math[], IsMathNull>();
+ System.Collections.Generic.Dictionary<Math[], IsMathNull> list_null = null;
+
+ var list_arr = new System.Collections.Generic.Dictionary<Math[], IsMathNull>[] { new System.Collections.Generic.Dictionary<Math[], IsMathNull>() };
+ System.Collections.Generic.Dictionary<Math[], IsMathNull>[] list_arr_null = null;
+
+ Console.WriteLine($"list_arr.Length: {list_arr.Length}, list.Count: {list.Count}");
+
+ // Unused locals
+
+ var list_unused = new System.Collections.Generic.Dictionary<Math[], IsMathNull>();
+ System.Collections.Generic.Dictionary<Math[], IsMathNull> list_null_unused = null;
+
+ var list_arr_unused = new System.Collections.Generic.Dictionary<Math[], IsMathNull>[] { new System.Collections.Generic.Dictionary<Math[], IsMathNull>() };
+ System.Collections.Generic.Dictionary<Math[], IsMathNull>[] list_arr_null_unused = null;
+
+ OuterMethod();
+ Console.WriteLine("Just a test message, ignore");
+ return 0;
+ }
+
+ static bool IsMathNullDelegateTarget(Math m) => m == null;
+
+ public static void OuterMethod()
+ {
+ Console.WriteLine($"OuterMethod called");
+ var nim = new Math.NestedInMath();
+ var i = 5;
+ var text = "Hello";
+ var new_i = nim.InnerMethod(i);
+ Console.WriteLine($"i: {i}");
+ Console.WriteLine($"-- InnerMethod returned: {new_i}, nim: {nim}, text: {text}");
+ int k = 19;
+ new_i = InnerMethod2("test string", new_i, out k);
+ Console.WriteLine($"-- InnerMethod2 returned: {new_i}, and k: {k}");
+ }
+
+ static int InnerMethod2(string s, int i, out int k)
+ {
+ k = i + 10;
+ Console.WriteLine($"s: {s}, i: {i}, k: {k}");
+ return i - 2;
+ }
+
+ public class NestedInMath
+ {
+ public int InnerMethod(int i)
+ {
+ SimpleStructProperty = new SimpleStruct() { dt = new DateTime(2020, 1, 2, 3, 4, 5) };
+ int j = i + 10;
+ string foo_str = "foo";
+ Console.WriteLine($"i: {i} and j: {j}, foo_str: {foo_str} ");
+ j += 9;
+ Console.WriteLine($"i: {i} and j: {j}");
+ return j;
+ }
+
+ Math m = new Math();
+ public async System.Threading.Tasks.Task<bool> AsyncMethod0(string s, int i)
+ {
+ string local0 = "value0";
+ await System.Threading.Tasks.Task.Delay(1);
+ Console.WriteLine($"* time for the second await, local0: {local0}");
+ await AsyncMethodNoReturn();
+ return true;
+ }
+
+ public async System.Threading.Tasks.Task AsyncMethodNoReturn()
+ {
+ var ss = new SimpleStruct() { dt = new DateTime(2020, 1, 2, 3, 4, 5) };
+ var ss_arr = new SimpleStruct[] { };
+ //ss.gs.StringField = "field in GenericStruct";
+
+ //Console.WriteLine ($"Using the struct: {ss.dt}, {ss.gs.StringField}, ss_arr: {ss_arr.Length}");
+ string str = "AsyncMethodNoReturn's local";
+ //Console.WriteLine ($"* field m: {m}");
+ await System.Threading.Tasks.Task.Delay(1);
+ Console.WriteLine($"str: {str}");
+ }
+
+ public static async System.Threading.Tasks.Task<bool> AsyncTest(string s, int i)
+ {
+ var li = 10 + i;
+ var ls = s + "test";
+ return await new NestedInMath().AsyncMethod0(s, i);
+ }
+
+ public SimpleStruct SimpleStructProperty { get; set; }
+ }
+
+ public static void PrimitiveTypesTest()
+ {
+ char c0 = '€';
+ char c1 = 'A';
+ // TODO: other types!
+ // just trying to ensure vars don't get optimized out
+ if (c0 < 32 || c1 > 32)
+ Console.WriteLine($"{c0}, {c1}");
+ }
+
+ public static int DelegatesSignatureTest()
+ {
+ Func<Math, GenericStruct<GenericStruct<int[]>>, GenericStruct<bool[]>> fn_func = (m, gs) => new GenericStruct<bool[]>();
+ Func<Math, GenericStruct<GenericStruct<int[]>>, GenericStruct<bool[]>> fn_func_del = GenericStruct<int>.DelegateTargetForSignatureTest;
+ Func<Math, GenericStruct<GenericStruct<int[]>>, GenericStruct<bool[]>> fn_func_null = null;
+ Func<bool> fn_func_only_ret = () => { Console.WriteLine($"hello"); return true; };
+ var fn_func_arr = new Func<Math, GenericStruct<GenericStruct<int[]>>, GenericStruct<bool[]>>[] {
+ (m, gs) => new GenericStruct<bool[]> () };
+
+ Math.DelegateForSignatureTest fn_del = GenericStruct<int>.DelegateTargetForSignatureTest;
+ Math.DelegateForSignatureTest fn_del_l = (m, gs) => new GenericStruct<bool[]> { StringField = "fn_del_l#lambda" };
+ var fn_del_arr = new Math.DelegateForSignatureTest[] { GenericStruct<int>.DelegateTargetForSignatureTest, (m, gs) => new GenericStruct<bool[]> { StringField = "fn_del_arr#1#lambda" } };
+ var m_obj = new Math();
+ Math.DelegateForSignatureTest fn_del_null = null;
+ var gs_gs = new GenericStruct<GenericStruct<int[]>>
+ {
+ List = new System.Collections.Generic.List<GenericStruct<int[]>>
+ {
+ new GenericStruct<int[]> { StringField = "gs#List#0#StringField" },
+ new GenericStruct<int[]> { StringField = "gs#List#1#StringField" }
+ }
+ };
+
+ Math.DelegateWithVoidReturn fn_void_del = Math.DelegateTargetWithVoidReturn;
+ var fn_void_del_arr = new Math.DelegateWithVoidReturn[] { Math.DelegateTargetWithVoidReturn };
+ Math.DelegateWithVoidReturn fn_void_del_null = null;
+
+ var rets = new GenericStruct<bool[]>[]
+ {
+ fn_func(m_obj, gs_gs),
+ fn_func_del(m_obj, gs_gs),
+ fn_del(m_obj, gs_gs),
+ fn_del_l(m_obj, gs_gs),
+ fn_del_arr[0](m_obj, gs_gs),
+ fn_func_arr[0](m_obj, gs_gs)
+ };
+
+ var gs = new GenericStruct<int[]>();
+ fn_void_del(gs);
+ fn_void_del_arr[0](gs);
+ fn_func_only_ret();
+ foreach (var ret in rets) Console.WriteLine($"ret: {ret}");
+ OuterMethod();
+ Console.WriteLine($"- {gs_gs.List[0].StringField}");
+ return 0;
+ }
+
+ public static int ActionTSignatureTest()
+ {
+ Action<GenericStruct<int[]>> fn_action = (_) => { };
+ Action<GenericStruct<int[]>> fn_action_del = Math.DelegateTargetWithVoidReturn;
+ Action fn_action_bare = () => { };
+ Action<GenericStruct<int[]>> fn_action_null = null;
+ var fn_action_arr = new Action<GenericStruct<int[]>>[]
+ {
+ (gs) => new GenericStruct<int[]>(),
+ Math.DelegateTargetWithVoidReturn,
+ null
+ };
+
+ var gs = new GenericStruct<int[]>();
+ fn_action(gs);
+ fn_action_del(gs);
+ fn_action_arr[0](gs);
+ fn_action_bare();
+ OuterMethod();
+ return 0;
+ }
+
+ public static int NestedDelegatesTest()
+ {
+ Func<Func<int, bool>, bool> fn_func = (_) => { return true; };
+ Func<Func<int, bool>, bool> fn_func_null = null;
+ var fn_func_arr = new Func<Func<int, bool>, bool>[] {
+ (gs) => { return true; } };
+
+ var fn_del_arr = new Func<Func<int, bool>, bool>[] { DelegateTargetForNestedFunc<Func<int, bool>> };
+ var m_obj = new Math();
+ Func<Func<int, bool>, bool> fn_del_null = null;
+ Func<int, bool> fs = (i) => i == 0;
+ fn_func(fs);
+ fn_del_arr[0](fs);
+ fn_func_arr[0](fs);
+ OuterMethod();
+ return 0;
+ }
+
+ public static void DelegatesAsMethodArgsTest()
+ {
+ var _dst_arr = new DelegateForSignatureTest[]
+ {
+ GenericStruct<int>.DelegateTargetForSignatureTest,
+ (m, gs) => new GenericStruct<bool[]>()
+ };
+ Func<char[], bool> _fn_func = (cs) => cs.Length == 0;
+ Action<GenericStruct<int>[]> _fn_action = (gss) => { };
+
+ new Math().MethodWithDelegateArgs(_dst_arr, _fn_func, _fn_action);
+ }
+
+ void MethodWithDelegateArgs(Math.DelegateForSignatureTest[] dst_arr, Func<char[], bool> fn_func,
+ Action<GenericStruct<int>[]> fn_action)
+ {
+ Console.WriteLine($"Placeholder for breakpoint");
+ OuterMethod();
+ }
+
+ public static async System.Threading.Tasks.Task MethodWithDelegatesAsyncTest()
+ {
+ await new Math().MethodWithDelegatesAsync();
+ }
+
+ async System.Threading.Tasks.Task MethodWithDelegatesAsync()
+ {
+ var _dst_arr = new DelegateForSignatureTest[]
+ {
+ GenericStruct<int>.DelegateTargetForSignatureTest,
+ (m, gs) => new GenericStruct<bool[]>()
+ };
+ Func<char[], bool> _fn_func = (cs) => cs.Length == 0;
+ Action<GenericStruct<int>[]> _fn_action = (gss) => { };
+
+ Console.WriteLine($"Placeholder for breakpoint");
+ await System.Threading.Tasks.Task.CompletedTask;
+ }
+
+ public delegate void DelegateWithVoidReturn(GenericStruct<int[]> gs);
+ public static void DelegateTargetWithVoidReturn(GenericStruct<int[]> gs) { }
+
+ delegate GenericStruct<bool[]> DelegateForSignatureTest(Math m, GenericStruct<GenericStruct<int[]>> gs);
+ static bool DelegateTargetForNestedFunc<T>(T arg) => true;
+
+ public struct SimpleStruct
+ {
+ public DateTime dt;
+ public GenericStruct<DateTime> gs;
+ }
+
+ public struct GenericStruct<T>
+ {
+ public System.Collections.Generic.List<T> List;
+ public string StringField;
+
+ public static GenericStruct<bool[]> DelegateTargetForSignatureTest(Math m, GenericStruct<GenericStruct<T[]>> gs) => new GenericStruct<bool[]>();
+ }
}
public class DebuggerTest
{
- public static void run_all () {
- locals ();
- }
-
- public static int locals () {
- int l_int = 1;
- char l_char = 'A';
- long l_long = Int64.MaxValue;
- ulong l_ulong = UInt64.MaxValue;
- locals_inner ();
- return 0;
- }
-
- static void locals_inner () {
- }
+ public static void run_all()
+ {
+ locals();
+ }
+
+ public static int locals()
+ {
+ int l_int = 1;
+ char l_char = 'A';
+ long l_long = Int64.MaxValue;
+ ulong l_ulong = UInt64.MaxValue;
+ locals_inner();
+ return 0;
+ }
+
+ static void locals_inner() { }
}
diff --git a/sdks/wasm/tests/debugger/debugger-test2.cs b/sdks/wasm/tests/debugger/debugger-test2.cs
index 907cc52e336..7275bf4a2f1 100644
--- a/sdks/wasm/tests/debugger/debugger-test2.cs
+++ b/sdks/wasm/tests/debugger/debugger-test2.cs
@@ -1,44 +1,51 @@
-using System;
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
-public class Misc { //Only append content to this class as the test suite depends on line info
- public static int CreateObject (int foo, int bar) {
- var f = new Fancy () {
- Foo = foo,
- Bar = bar,
- };
+using System;
- Console.WriteLine ($"{f.Foo} {f.Bar}");
- return f.Foo + f.Bar;
- }
+public class Misc
+{ //Only append content to this class as the test suite depends on line info
+ public static int CreateObject(int foo, int bar)
+ {
+ var f = new Fancy()
+ {
+ Foo = foo,
+ Bar = bar,
+ };
+
+ Console.WriteLine($"{f.Foo} {f.Bar}");
+ return f.Foo + f.Bar;
+ }
}
-public class Fancy {
- public int Foo;
- public int Bar { get ; set; }
- public static void Types () {
- double dPI = System.Math.PI;
- float fPI = (float)System.Math.PI;
-
- int iMax = int.MaxValue;
- int iMin = int.MinValue;
- uint uiMax = uint.MaxValue;
- uint uiMin = uint.MinValue;
-
- long l = uiMax * (long)2;
- long lMax = long.MaxValue; // cannot be represented as double
- long lMin = long.MinValue; // cannot be represented as double
-
- sbyte sbMax = sbyte.MaxValue;
- sbyte sbMin = sbyte.MinValue;
- byte bMax = byte.MaxValue;
- byte bMin = byte.MinValue;
-
- short sMax = short.MaxValue;
- short sMin = short.MinValue;
- ushort usMin = ushort.MinValue;
- ushort usMax = ushort.MaxValue;
-
- var d = usMin + usMax;
- }
+public class Fancy
+{
+ public int Foo;
+ public int Bar { get; set; }
+ public static void Types()
+ {
+ double dPI = System.Math.PI;
+ float fPI = (float)System.Math.PI;
+
+ int iMax = int.MaxValue;
+ int iMin = int.MinValue;
+ uint uiMax = uint.MaxValue;
+ uint uiMin = uint.MinValue;
+
+ long l = uiMax * (long)2;
+ long lMax = long.MaxValue; // cannot be represented as double
+ long lMin = long.MinValue; // cannot be represented as double
+
+ sbyte sbMax = sbyte.MaxValue;
+ sbyte sbMin = sbyte.MinValue;
+ byte bMax = byte.MaxValue;
+ byte bMin = byte.MinValue;
+
+ short sMax = short.MaxValue;
+ short sMin = short.MinValue;
+ ushort usMin = ushort.MinValue;
+ ushort usMax = ushort.MaxValue;
+
+ var d = usMin + usMax;
+ }
}
-
diff --git a/sdks/wasm/tests/debugger/debugger-valuetypes-test.cs b/sdks/wasm/tests/debugger/debugger-valuetypes-test.cs
index cbeea53e267..0c5cc9019fb 100644
--- a/sdks/wasm/tests/debugger/debugger-valuetypes-test.cs
+++ b/sdks/wasm/tests/debugger/debugger-valuetypes-test.cs
@@ -1,245 +1,267 @@
+// 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.Threading.Tasks;
-namespace DebuggerTests {
- public class ValueTypesTest { //Only append content to this class as the test suite depends on line info
-
- public static void MethodWithLocalStructs ()
- {
- var ss_local = new SimpleStruct ("set in MethodWithLocalStructs", 1, DateTimeKind.Utc);
- var gs_local = new GenericStruct<ValueTypesTest> { StringField = "gs_local#GenericStruct<ValueTypesTest>#StringField" };
-
- ValueTypesTest vt_local = new ValueTypesTest {
- StringField = "string#0",
- SimpleStructField = new SimpleStruct ("SimpleStructField#string#0", 5, DateTimeKind.Local),
- SimpleStructProperty = new SimpleStruct ("SimpleStructProperty#string#0", 2, DateTimeKind.Utc), DT = new DateTime (2020, 1, 2, 3, 4, 5), RGB = RGB.Blue
- };
- Console.WriteLine ($"Using the struct: {ss_local.gs.StringField}, gs: {gs_local.StringField}, {vt_local.StringField}");
- }
-
- public static void TestStructsAsMethodArgs ()
- {
- var ss_local = new SimpleStruct ("ss_local#SimpleStruct#string#0", 5, DateTimeKind.Local);
- var ss_ret = MethodWithStructArgs ("TestStructsAsMethodArgs#label", ss_local, 3);
- Console.WriteLine ($"got back ss_local: {ss_local.gs.StringField}, ss_ret: {ss_ret.gs.StringField}");
- }
-
- static SimpleStruct MethodWithStructArgs (string label, SimpleStruct ss_arg, int x)
- {
- Console.WriteLine ($"- ss_arg: {ss_arg.str_member}");
- ss_arg.Kind = DateTimeKind.Utc;
- ss_arg.str_member = $"ValueTypesTest#MethodWithStructArgs#updated#ss_arg#str_member";
- ss_arg.gs.StringField = $"ValueTypesTest#MethodWithStructArgs#updated#gs#StringField#{x}";
- return ss_arg;
- }
-
- public static async Task<bool> MethodWithLocalStructsStaticAsync ()
- {
- var ss_local = new SimpleStruct ("set in MethodWithLocalStructsStaticAsync", 1, DateTimeKind.Utc);
- var gs_local = new GenericStruct<int> {
- StringField = "gs_local#GenericStruct<ValueTypesTest>#StringField",
- List = new System.Collections.Generic.List<int> { 5, 3 },
- Options = Options.Option2
-
- };
-
- var result = await ss_local.AsyncMethodWithStructArgs (gs_local);
- Console.WriteLine ($"Using the struct: {ss_local.gs.StringField}, result: {result}");
-
- return result;
- }
-
- public string StringField;
- public SimpleStruct SimpleStructProperty { get; set; }
- public SimpleStruct SimpleStructField;
-
- public struct SimpleStruct
- {
- public string str_member;
- public DateTime dt;
- public GenericStruct<DateTime> gs;
- public DateTimeKind Kind;
-
- public SimpleStruct (string str, int f, DateTimeKind kind)
- {
- str_member = $"{str}#SimpleStruct#str_member";
- dt = new DateTime (2020+f, 1+f, 2+f, 3+f, 5+f, 6+f);
- gs = new GenericStruct<DateTime> {
- StringField = $"{str}#SimpleStruct#gs#StringField",
- List = new System.Collections.Generic.List<DateTime> { new DateTime (2010+f, 2+f, 3+f, 10+f, 2+f, 3+f) },
- Options = Options.Option1
- };
- Kind = kind;
- }
-
- public Task<bool> AsyncMethodWithStructArgs (GenericStruct<int> gs)
- {
- Console.WriteLine ($"placeholder line for a breakpoint");
- if (gs.List.Count > 0)
- return Task.FromResult (true);
-
- return Task.FromResult (false);
- }
- }
-
- public struct GenericStruct<T>
- {
- public System.Collections.Generic.List<T> List;
- public string StringField;
-
- public Options Options { get; set; }
- }
-
- public DateTime DT { get; set; }
- public RGB RGB;
-
- public static void MethodWithLocalsForToStringTest (bool call_other)
- {
- var dt0 = new DateTime (2020, 1, 2, 3, 4, 5);
- var dt1 = new DateTime (2010, 5, 4, 3, 2, 1);
- var ts = dt0 - dt1;
- var dto = new DateTimeOffset (dt0, new TimeSpan(4, 5, 0));
- decimal dec = 123987123;
- var guid = new Guid ("3d36e07e-ac90-48c6-b7ec-a481e289d014");
-
- var dts = new DateTime [] {
- new DateTime (1983, 6, 7, 5, 6, 10),
- new DateTime (1999, 10, 15, 1, 2, 3)
- };
-
- var obj = new ClassForToStringTests {
- DT = new DateTime (2004, 10, 15, 1, 2, 3),
- DTO = new DateTimeOffset (dt0, new TimeSpan(2, 14, 0)),
- TS = ts,
- Dec = 1239871,
- Guid = guid
- };
-
- var sst = new StructForToStringTests {
- DT = new DateTime (2004, 10, 15, 1, 2, 3),
- DTO = new DateTimeOffset (dt0, new TimeSpan (3, 15, 0)),
- TS = ts,
- Dec = 1239871,
- Guid = guid
- };
- Console.WriteLine ($"MethodWithLocalsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}");
- if (call_other)
- MethodWithArgumentsForToStringTest (call_other, dt0, dt1, ts, dto, dec, guid, dts, obj, sst);
- }
-
- static void MethodWithArgumentsForToStringTest (
- bool call_other, // not really used, just to help with using common code in the tests
- DateTime dt0, DateTime dt1, TimeSpan ts, DateTimeOffset dto, decimal dec,
- Guid guid, DateTime[] dts, ClassForToStringTests obj, StructForToStringTests sst)
- {
- Console.WriteLine ($"MethodWithArgumentsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}");
- }
-
- public static async Task MethodWithLocalsForToStringTestAsync (bool call_other)
- {
- var dt0 = new DateTime (2020, 1, 2, 3, 4, 5);
- var dt1 = new DateTime (2010, 5, 4, 3, 2, 1);
- var ts = dt0 - dt1;
- var dto = new DateTimeOffset (dt0, new TimeSpan(4, 5, 0));
- decimal dec = 123987123;
- var guid = new Guid ("3d36e07e-ac90-48c6-b7ec-a481e289d014");
-
- var dts = new DateTime [] {
- new DateTime (1983, 6, 7, 5, 6, 10),
- new DateTime (1999, 10, 15, 1, 2, 3)
- };
-
- var obj = new ClassForToStringTests {
- DT = new DateTime (2004, 10, 15, 1, 2, 3),
- DTO = new DateTimeOffset (dt0, new TimeSpan(2, 14, 0)),
- TS = ts,
- Dec = 1239871,
- Guid = guid
- };
-
- var sst = new StructForToStringTests {
- DT = new DateTime (2004, 10, 15, 1, 2, 3),
- DTO = new DateTimeOffset (dt0, new TimeSpan (3, 15, 0)),
- TS = ts,
- Dec = 1239871,
- Guid = guid
- };
- Console.WriteLine ($"MethodWithLocalsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}");
- if (call_other)
- await MethodWithArgumentsForToStringTestAsync (call_other, dt0, dt1, ts, dto, dec, guid, dts, obj, sst);
- }
-
- static async Task MethodWithArgumentsForToStringTestAsync (
- bool call_other, // not really used, just to help with using common code in the tests
- DateTime dt0, DateTime dt1, TimeSpan ts, DateTimeOffset dto, decimal dec,
- Guid guid, DateTime[] dts, ClassForToStringTests obj, StructForToStringTests sst)
- {
- Console.WriteLine ($"MethodWithArgumentsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}");
- }
-
- public static void MethodUpdatingValueTypeMembers ()
- {
- var obj = new ClassForToStringTests {
- DT = new DateTime (1, 2, 3, 4, 5, 6)
- };
- var vt = new StructForToStringTests {
- DT = new DateTime (4, 5, 6, 7, 8, 9)
- };
- Console.WriteLine ($"#1");
- obj.DT = new DateTime (9, 8, 7, 6, 5, 4);
- vt.DT = new DateTime (5, 1, 3, 7, 9, 10);
- Console.WriteLine ($"#2");
- }
-
- public static async Task MethodUpdatingValueTypeLocalsAsync ()
- {
- var dt = new DateTime (1, 2, 3, 4, 5, 6);
- Console.WriteLine ($"#1");
- dt = new DateTime (9, 8, 7, 6, 5, 4);
- Console.WriteLine ($"#2");
- }
-
- public static void MethodUpdatingVTArrayMembers ()
- {
- var ssta = new [] {
- new StructForToStringTests { DT = new DateTime (1, 2, 3, 4, 5, 6) }
- };
- Console.WriteLine ($"#1");
- ssta [0].DT = new DateTime (9, 8, 7, 6, 5, 4);
- Console.WriteLine ($"#2");
- }
- }
-
- class ClassForToStringTests
- {
- public DateTime DT;
- public DateTimeOffset DTO;
- public TimeSpan TS;
- public decimal Dec;
- public Guid Guid;
- }
-
- struct StructForToStringTests
- {
- public DateTime DT;
- public DateTimeOffset DTO;
- public TimeSpan TS;
- public decimal Dec;
- public Guid Guid;
- }
-
- public enum RGB
- {
- Red, Green, Blue
- }
-
- [Flags]
- public enum Options
- {
- None = 0,
- Option1 = 1,
- Option2 = 2,
- Option3 = 4,
-
- All = Option1 | Option3
- }
+namespace DebuggerTests
+{
+ public class ValueTypesTest
+ { //Only append content to this class as the test suite depends on line info
+
+ public static void MethodWithLocalStructs()
+ {
+ var ss_local = new SimpleStruct("set in MethodWithLocalStructs", 1, DateTimeKind.Utc);
+ var gs_local = new GenericStruct<ValueTypesTest> { StringField = "gs_local#GenericStruct<ValueTypesTest>#StringField" };
+
+ ValueTypesTest vt_local = new ValueTypesTest
+ {
+ StringField = "string#0",
+ SimpleStructField = new SimpleStruct("SimpleStructField#string#0", 5, DateTimeKind.Local),
+ SimpleStructProperty = new SimpleStruct("SimpleStructProperty#string#0", 2, DateTimeKind.Utc),
+ DT = new DateTime(2020, 1, 2, 3, 4, 5),
+ RGB = RGB.Blue
+ };
+ Console.WriteLine($"Using the struct: {ss_local.gs.StringField}, gs: {gs_local.StringField}, {vt_local.StringField}");
+ }
+
+ public static void TestStructsAsMethodArgs()
+ {
+ var ss_local = new SimpleStruct("ss_local#SimpleStruct#string#0", 5, DateTimeKind.Local);
+ var ss_ret = MethodWithStructArgs("TestStructsAsMethodArgs#label", ss_local, 3);
+ Console.WriteLine($"got back ss_local: {ss_local.gs.StringField}, ss_ret: {ss_ret.gs.StringField}");
+ }
+
+ static SimpleStruct MethodWithStructArgs(string label, SimpleStruct ss_arg, int x)
+ {
+ Console.WriteLine($"- ss_arg: {ss_arg.str_member}");
+ ss_arg.Kind = DateTimeKind.Utc;
+ ss_arg.str_member = $"ValueTypesTest#MethodWithStructArgs#updated#ss_arg#str_member";
+ ss_arg.gs.StringField = $"ValueTypesTest#MethodWithStructArgs#updated#gs#StringField#{x}";
+ return ss_arg;
+ }
+
+ public static async Task<bool> MethodWithLocalStructsStaticAsync()
+ {
+ var ss_local = new SimpleStruct("set in MethodWithLocalStructsStaticAsync", 1, DateTimeKind.Utc);
+ var gs_local = new GenericStruct<int>
+ {
+ StringField = "gs_local#GenericStruct<ValueTypesTest>#StringField",
+ List = new System.Collections.Generic.List<int> { 5, 3 },
+ Options = Options.Option2
+
+ };
+
+ var result = await ss_local.AsyncMethodWithStructArgs(gs_local);
+ Console.WriteLine($"Using the struct: {ss_local.gs.StringField}, result: {result}");
+
+ return result;
+ }
+
+ public string StringField;
+ public SimpleStruct SimpleStructProperty { get; set; }
+ public SimpleStruct SimpleStructField;
+
+ public struct SimpleStruct
+ {
+ public uint V { get { return 0xDEADBEEF + (uint)dt.Month; } set { } }
+ public string str_member;
+ public DateTime dt;
+ public GenericStruct<DateTime> gs;
+ public DateTimeKind Kind;
+
+ public SimpleStruct(string str, int f, DateTimeKind kind)
+ {
+ str_member = $"{str}#SimpleStruct#str_member";
+ dt = new DateTime(2020 + f, 1 + f, 2 + f, 3 + f, 5 + f, 6 + f);
+ gs = new GenericStruct<DateTime>
+ {
+ StringField = $"{str}#SimpleStruct#gs#StringField",
+ List = new System.Collections.Generic.List<DateTime> { new DateTime(2010 + f, 2 + f, 3 + f, 10 + f, 2 + f, 3 + f) },
+ Options = Options.Option1
+ };
+ Kind = kind;
+ }
+
+ public Task<bool> AsyncMethodWithStructArgs(GenericStruct<int> gs)
+ {
+ Console.WriteLine($"placeholder line for a breakpoint");
+ if (gs.List.Count > 0)
+ return Task.FromResult(true);
+
+ return Task.FromResult(false);
+ }
+ }
+
+ public struct GenericStruct<T>
+ {
+ public System.Collections.Generic.List<T> List;
+ public string StringField;
+
+ public Options Options { get; set; }
+ }
+
+ public DateTime DT { get; set; }
+ public RGB RGB;
+
+ public static void MethodWithLocalsForToStringTest(bool call_other)
+ {
+ var dt0 = new DateTime(2020, 1, 2, 3, 4, 5);
+ var dt1 = new DateTime(2010, 5, 4, 3, 2, 1);
+ var ts = dt0 - dt1;
+ var dto = new DateTimeOffset(dt0, new TimeSpan(4, 5, 0));
+ decimal dec = 123987123;
+ var guid = new Guid("3d36e07e-ac90-48c6-b7ec-a481e289d014");
+
+ var dts = new DateTime[]
+ {
+ new DateTime(1983, 6, 7, 5, 6, 10),
+ new DateTime(1999, 10, 15, 1, 2, 3)
+ };
+
+ var obj = new ClassForToStringTests
+ {
+ DT = new DateTime(2004, 10, 15, 1, 2, 3),
+ DTO = new DateTimeOffset(dt0, new TimeSpan(2, 14, 0)),
+ TS = ts,
+ Dec = 1239871,
+ Guid = guid
+ };
+
+ var sst = new StructForToStringTests
+ {
+ DT = new DateTime(2004, 10, 15, 1, 2, 3),
+ DTO = new DateTimeOffset(dt0, new TimeSpan(3, 15, 0)),
+ TS = ts,
+ Dec = 1239871,
+ Guid = guid
+ };
+ Console.WriteLine($"MethodWithLocalsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}");
+ if (call_other)
+ MethodWithArgumentsForToStringTest(call_other, dt0, dt1, ts, dto, dec, guid, dts, obj, sst);
+ }
+
+ static void MethodWithArgumentsForToStringTest(
+ bool call_other, // not really used, just to help with using common code in the tests
+ DateTime dt0, DateTime dt1, TimeSpan ts, DateTimeOffset dto, decimal dec,
+ Guid guid, DateTime[] dts, ClassForToStringTests obj, StructForToStringTests sst)
+ {
+ Console.WriteLine($"MethodWithArgumentsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}");
+ }
+
+ public static async Task MethodWithLocalsForToStringTestAsync(bool call_other)
+ {
+ var dt0 = new DateTime(2020, 1, 2, 3, 4, 5);
+ var dt1 = new DateTime(2010, 5, 4, 3, 2, 1);
+ var ts = dt0 - dt1;
+ var dto = new DateTimeOffset(dt0, new TimeSpan(4, 5, 0));
+ decimal dec = 123987123;
+ var guid = new Guid("3d36e07e-ac90-48c6-b7ec-a481e289d014");
+
+ var dts = new DateTime[]
+ {
+ new DateTime(1983, 6, 7, 5, 6, 10),
+ new DateTime(1999, 10, 15, 1, 2, 3)
+ };
+
+ var obj = new ClassForToStringTests
+ {
+ DT = new DateTime(2004, 10, 15, 1, 2, 3),
+ DTO = new DateTimeOffset(dt0, new TimeSpan(2, 14, 0)),
+ TS = ts,
+ Dec = 1239871,
+ Guid = guid
+ };
+
+ var sst = new StructForToStringTests
+ {
+ DT = new DateTime(2004, 10, 15, 1, 2, 3),
+ DTO = new DateTimeOffset(dt0, new TimeSpan(3, 15, 0)),
+ TS = ts,
+ Dec = 1239871,
+ Guid = guid
+ };
+ Console.WriteLine($"MethodWithLocalsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}");
+ if (call_other)
+ await MethodWithArgumentsForToStringTestAsync(call_other, dt0, dt1, ts, dto, dec, guid, dts, obj, sst);
+ }
+
+ static async Task MethodWithArgumentsForToStringTestAsync(
+ bool call_other, // not really used, just to help with using common code in the tests
+ DateTime dt0, DateTime dt1, TimeSpan ts, DateTimeOffset dto, decimal dec,
+ Guid guid, DateTime[] dts, ClassForToStringTests obj, StructForToStringTests sst)
+ {
+ Console.WriteLine($"MethodWithArgumentsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}");
+ }
+
+ public static void MethodUpdatingValueTypeMembers()
+ {
+ var obj = new ClassForToStringTests
+ {
+ DT = new DateTime(1, 2, 3, 4, 5, 6)
+ };
+ var vt = new StructForToStringTests
+ {
+ DT = new DateTime(4, 5, 6, 7, 8, 9)
+ };
+ Console.WriteLine($"#1");
+ obj.DT = new DateTime(9, 8, 7, 6, 5, 4);
+ vt.DT = new DateTime(5, 1, 3, 7, 9, 10);
+ Console.WriteLine($"#2");
+ }
+
+ public static async Task MethodUpdatingValueTypeLocalsAsync()
+ {
+ var dt = new DateTime(1, 2, 3, 4, 5, 6);
+ Console.WriteLine($"#1");
+ dt = new DateTime(9, 8, 7, 6, 5, 4);
+ Console.WriteLine($"#2");
+ }
+
+ public static void MethodUpdatingVTArrayMembers()
+ {
+ var ssta = new[]
+ {
+ new StructForToStringTests { DT = new DateTime(1, 2, 3, 4, 5, 6) }
+ };
+ Console.WriteLine($"#1");
+ ssta[0].DT = new DateTime(9, 8, 7, 6, 5, 4);
+ Console.WriteLine($"#2");
+ }
+ }
+
+ class ClassForToStringTests
+ {
+ public DateTime DT;
+ public DateTimeOffset DTO;
+ public TimeSpan TS;
+ public decimal Dec;
+ public Guid Guid;
+ }
+
+ struct StructForToStringTests
+ {
+ public DateTime DT;
+ public DateTimeOffset DTO;
+ public TimeSpan TS;
+ public decimal Dec;
+ public Guid Guid;
+ }
+
+ public enum RGB
+ {
+ Red,
+ Green,
+ Blue
+ }
+
+ [Flags]
+ public enum Options
+ {
+ None = 0,
+ Option1 = 1,
+ Option2 = 2,
+ Option3 = 4,
+
+ All = Option1 | Option3
+ }
}
diff --git a/sdks/wasm/tests/debugger/dependency.cs b/sdks/wasm/tests/debugger/dependency.cs
new file mode 100644
index 00000000000..05925f75aa8
--- /dev/null
+++ b/sdks/wasm/tests/debugger/dependency.cs
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Simple
+{
+ public class Complex
+ {
+ public int A { get; set; }
+ public string B { get; set; }
+ object c;
+
+ public Complex(int a, string b)
+ {
+ A = a;
+ B = b;
+ this.c = this;
+ }
+
+ public int DoStuff()
+ {
+ return DoOtherStuff();
+ }
+
+ public int DoOtherStuff()
+ {
+ return DoEvenMoreStuff() - 1;
+ }
+
+ public int DoEvenMoreStuff()
+ {
+ return 1 + BreakOnThisMethod();
+ }
+
+ public int BreakOnThisMethod()
+ {
+ var x = A + 10;
+ c = $"{x}_{B}";
+
+ return x;
+ }
+ }
+}