From 70219cc2b54ebc52a54d24f8973802370bfdcc76 Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Sat, 15 Aug 2020 14:03:15 -0300 Subject: [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. --- sdks/wasm/BrowserDebugHost/BrowserDebugHost.csproj | 6 +- sdks/wasm/BrowserDebugHost/Program.cs | 101 +- sdks/wasm/BrowserDebugHost/Startup.cs | 292 +- sdks/wasm/BrowserDebugHost/TestHarnessStartup.cs | 225 -- sdks/wasm/BrowserDebugProxy/.editorconfig | 69 - sdks/wasm/BrowserDebugProxy/AssemblyInfo.cs | 3 - .../BrowserDebugProxy/BrowserDebugProxy.csproj | 2 +- sdks/wasm/BrowserDebugProxy/DebugStore.cs | 1693 ++++++------ sdks/wasm/BrowserDebugProxy/DebuggerProxy.cs | 31 +- sdks/wasm/BrowserDebugProxy/DevToolsHelper.cs | 546 ++-- sdks/wasm/BrowserDebugProxy/DevToolsProxy.cs | 706 ++--- sdks/wasm/BrowserDebugProxy/EvaluateExpression.cs | 379 +-- sdks/wasm/BrowserDebugProxy/MonoProxy.cs | 1894 +++++++------ sdks/wasm/DebuggerTestSuite/ArrayTests.cs | 1231 +++++---- sdks/wasm/DebuggerTestSuite/CallFunctionOnTests.cs | 1754 ++++++------ sdks/wasm/DebuggerTestSuite/DateTimeTests.cs | 110 +- sdks/wasm/DebuggerTestSuite/DelegateTests.cs | 568 ++-- sdks/wasm/DebuggerTestSuite/DevToolsClient.cs | 297 +- .../DebuggerTestSuite/EvaluateOnCallFrameTests.cs | 396 +-- sdks/wasm/DebuggerTestSuite/ExceptionTests.cs | 264 ++ sdks/wasm/DebuggerTestSuite/InspectorClient.cs | 125 +- sdks/wasm/DebuggerTestSuite/PointerTests.cs | 1060 ++++---- sdks/wasm/DebuggerTestSuite/Support.cs | 1934 ++++++------- sdks/wasm/DebuggerTestSuite/TestHarnessOptions.cs | 15 + sdks/wasm/DebuggerTestSuite/TestHarnessProxy.cs | 59 + sdks/wasm/DebuggerTestSuite/TestHarnessStartup.cs | 255 ++ sdks/wasm/DebuggerTestSuite/Tests.cs | 2840 +++++++++++--------- sdks/wasm/Makefile | 6 +- sdks/wasm/other.js | 37 +- sdks/wasm/packager.cs | 6 +- sdks/wasm/tests/debugger/debugger-array-test.cs | 583 ++-- sdks/wasm/tests/debugger/debugger-cfo-test.cs | 125 +- sdks/wasm/tests/debugger/debugger-datetime-test.cs | 43 +- sdks/wasm/tests/debugger/debugger-driver.html | 2 +- sdks/wasm/tests/debugger/debugger-evaluate-test.cs | 157 +- .../wasm/tests/debugger/debugger-exception-test.cs | 55 + sdks/wasm/tests/debugger/debugger-pointers-test.cs | 211 +- sdks/wasm/tests/debugger/debugger-test.cs | 618 ++--- sdks/wasm/tests/debugger/debugger-test2.cs | 85 +- .../tests/debugger/debugger-valuetypes-test.cs | 506 ++-- sdks/wasm/tests/debugger/dependency.cs | 44 + 41 files changed, 10404 insertions(+), 8929 deletions(-) delete mode 100644 sdks/wasm/BrowserDebugHost/TestHarnessStartup.cs delete mode 100644 sdks/wasm/BrowserDebugProxy/.editorconfig delete mode 100644 sdks/wasm/BrowserDebugProxy/AssemblyInfo.cs create mode 100644 sdks/wasm/DebuggerTestSuite/ExceptionTests.cs create mode 100644 sdks/wasm/DebuggerTestSuite/TestHarnessOptions.cs create mode 100644 sdks/wasm/DebuggerTestSuite/TestHarnessProxy.cs create mode 100644 sdks/wasm/DebuggerTestSuite/TestHarnessStartup.cs create mode 100644 sdks/wasm/tests/debugger/debugger-exception-test.cs create mode 100644 sdks/wasm/tests/debugger/dependency.cs 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 @@ -4,12 +4,10 @@ netcoreapp3.0 - - - - + + 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 () - .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 (ctx.Configuration); - services.Configure (options => { - options.ChromePath = options.ChromePath ?? chromePath; - options.AppPath = appPath; - options.PagePath = pagePath; - options.DevToolsUrl = new Uri ("http://localhost:0"); - }); - }) - .UseStartup () - .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() + .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 (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 optionsAccessor, IWebHostEnvironment env) - { - var options = optionsAccessor.CurrentValue; - app.UseDeveloperExceptionPage () - .UseWebSockets () - .UseDebugProxy (options); - } - } - - static class DebugExtensions { - public static Dictionary MapValues (Dictionary response, HttpContext context, Uri debuggerHost) - { - var filtered = new Dictionary (); - 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, HttpContext, Uri, Dictionary> 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> (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 []> (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 ProxyGetJsonAsync (string url) - { - using (var httpClient = new HttpClient ()) { - var response = await httpClient.GetAsync (url); - return await JsonSerializer.DeserializeAsync (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(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 optionsAccessor, IWebHostEnvironment env) + { + var options = optionsAccessor.CurrentValue; + app.UseDeveloperExceptionPage() + .UseWebSockets() + .UseDebugProxy(options); + } + } + + static class DebugExtensions + { + public static Dictionary MapValues(Dictionary response, HttpContext context, Uri debuggerHost) + { + var filtered = new Dictionary(); + 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, HttpContext, Uri, Dictionary> 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>(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[]>(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 ProxyGetJsonAsync(string url) + { + using (var httpClient = new HttpClient()) + { + var response = await httpClient.GetAsync(url); + return await JsonSerializer.DeserializeAsync(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 (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> extract_conn_url) - { - - if (!context.WebSockets.IsWebSocketRequest) { - context.Response.StatusCode = 400; - return; - } - - var tcs = new TaskCompletionSource (); - - 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 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 (); - 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 @@  - netstandard2.1 + netcoreapp3.0 true 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 Locations { get; } = new List (); - - public override string ToString () - => $"BreakpointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}"; - - public object AsSetBreakpointByUrlResponse (IEnumerable 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 (); - if (url == null) { - var urlRegex = request?["urlRegex"].Value(); - 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 (); - var column = request? ["columnNumber"]?.Value (); - - 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 (), out var id)) - return null; - - var line = obj ["lineNumber"]?.Value (); - var column = obj ["columnNumber"]?.Value (); - if (id == null || line == null || column == null) - return null; - - return new SourceLocation (id, line.Value, column.Value); - } - - - internal class LocationComparer : EqualityComparer - { - 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 (); - - 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 methods; - - public TypeInfo (AssemblyInfo assembly, TypeDefinition type) { - this.assembly = assembly; - this.type = type; - methods = new List (); - } - - public string Name => type.Name; - public string FullName => type.FullName; - public List Methods => methods; - - public override string ToString () => "TypeInfo('" + FullName + "')"; - } - - class AssemblyInfo { - static int next_id; - ModuleDefinition image; - readonly int id; - readonly ILogger logger; - Dictionary methods = new Dictionary (); - Dictionary sourceLinkMappings = new Dictionary(); - Dictionary typesByName = new Dictionary (); - readonly List sources = new List(); - 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 (); - - 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> (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 Sources - => this.sources; - - public Dictionary 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 methods; - AssemblyInfo assembly; - int id; - Document doc; - - internal SourceFile (AssemblyInfo assembly, int id, Document doc, Uri sourceLinkUri) - { - this.methods = new Dictionary (); - 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 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 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 (); - } - - public async Task 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 assemblies = new List (); - 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 Data { get; set; } - } - - public async IAsyncEnumerable 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 (); - var pdb_files = new List (); - 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 steps = new List (); - 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 (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 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 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 (); - 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 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 Locations { get; } = new List(); + + public override string ToString() => $"BreakpointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}"; + + public object AsSetBreakpointByUrlResponse(IEnumerable 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(); + if (url == null) + { + var urlRegex = request?["urlRegex"].Value(); + 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(); + var column = request?["columnNumber"]?.Value(); + + 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(), out var id)) + return null; + + var line = obj["lineNumber"]?.Value(); + var column = obj["columnNumber"]?.Value(); + if (id == null || line == null || column == null) + return null; + + return new SourceLocation(id, line.Value, column.Value); + } + + internal class LocationComparer : EqualityComparer + { + 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(); + + 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 methods; + + public TypeInfo(AssemblyInfo assembly, TypeDefinition type) + { + this.assembly = assembly; + this.type = type; + methods = new List(); + } + + public string Name => type.Name; + public string FullName => type.FullName; + public List Methods => methods; + + public override string ToString() => "TypeInfo('" + FullName + "')"; + } + + class AssemblyInfo + { + static int next_id; + ModuleDefinition image; + readonly int id; + readonly ILogger logger; + Dictionary methods = new Dictionary(); + Dictionary sourceLinkMappings = new Dictionary(); + Dictionary typesByName = new Dictionary(); + readonly List sources = new List(); + 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(); + + 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>(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 Sources => this.sources; + + public Dictionary 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 methods; + AssemblyInfo assembly; + int id; + Document doc; + + internal SourceFile(AssemblyInfo assembly, int id, Document doc, Uri sourceLinkUri) + { + this.methods = new Dictionary(); + 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 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 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(); + } + + public async Task 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 assemblies = new List(); + 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 Data { get; set; } + } + + public async IAsyncEnumerable 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(); + var pdb_files = new List(); + 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 steps = new List(); + 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(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 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 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(); + 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 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(), 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(), 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(), "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 (), "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 BreakpointRequests { get; } = new Dictionary(); - 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 ready = null; + public bool IsRuntimeReady => ready != null && ready.Task.IsCompleted; - internal class ExecutionContext { - public string DebuggerId { get; set; } - public Dictionary BreakpointRequests { get; } = new Dictionary (); + public int Id { get; set; } + public object AuxData { get; set; } - public TaskCompletionSource ready = null; - public bool IsRuntimeReady => ready != null && ready.Task.IsCompleted; - - public int Id { get; set; } - public object AuxData { get; set; } - - public List CallStack { get; set; } - - public string[] LoadedFiles { get; set; } - internal DebugStore store; - public TaskCompletionSource Source { get; } = new TaskCompletionSource (); - - public Dictionary LocalsCache = new Dictionary (); - - public DebugStore Store { - get { - if (store == null || !Source.Task.IsCompleted) - return null; - - return store; - } - } - - public void ClearState () - { - CallStack = null; - LocalsCache.Clear (); - } - - } + public List CallStack { get; set; } + + public string[] LoadedFiles { get; set; } + internal DebugStore store; + public TaskCompletionSource Source { get; } = new TaskCompletionSource(); + + public Dictionary LocalsCache = new Dictionary(); + + 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 pending; - - public WebSocket Ws { get; private set; } - public Task CurrentSend { get { return current_send; } } - public DevToolsQueue (WebSocket sock) - { - this.Ws = sock; - pending = new List (); - } - - 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 (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 (pending [0]), WebSocketMessageType.Text, true, token); - return current_send; - } - return null; - } - } - - internal class DevToolsProxy { - TaskCompletionSource side_exception = new TaskCompletionSource (); - TaskCompletionSource client_initiated_close = new TaskCompletionSource (); - Dictionary> pending_cmds = new Dictionary> (); - ClientWebSocket browser; - WebSocket ide; - int next_cmd_id; - List pending_ops = new List (); - List queues = new List (); - - protected readonly ILogger logger; - - public DevToolsProxy (ILoggerFactory loggerFactory) - { - logger = loggerFactory.CreateLogger(); - } - - protected virtual Task AcceptEvent (SessionId sessionId, string method, JObject args, CancellationToken token) - { - return Task.FromResult (false); - } - - protected virtual Task AcceptCommand (MessageId id, string method, JObject args, CancellationToken token) - { - return Task.FromResult (false); - } - - async Task 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 (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 ()), res ["method"].Value (), res ["params"] as JObject, token)); - else - OnResponse (new MessageId (res ["sessionId"]?.Value (), res ["id"].Value ()), 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 (), res ["id"].Value ()), - res ["method"].Value (), - res ["params"] as JObject, token)); - } - } - - internal async Task SendCommand (SessionId id, string method, JObject args, CancellationToken token) { - //Log ("verbose", $"sending command {method}: {args}"); - return await SendCommandInternal (id, method, args, token); - } - - Task 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 (); - - 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)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)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)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)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 pending; + + public WebSocket Ws { get; private set; } + public Task CurrentSend { get { return current_send; } } + public DevToolsQueue(WebSocket sock) + { + this.Ws = sock; + pending = new List(); + } + + 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(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(pending[0]), WebSocketMessageType.Text, true, token); + return current_send; + } + return null; + } + } + + internal class DevToolsProxy + { + TaskCompletionSource side_exception = new TaskCompletionSource(); + TaskCompletionSource client_initiated_close = new TaskCompletionSource(); + Dictionary> pending_cmds = new Dictionary>(); + ClientWebSocket browser; + WebSocket ide; + int next_cmd_id; + List pending_ops = new List(); + List queues = new List(); + + protected readonly ILogger logger; + + public DevToolsProxy(ILoggerFactory loggerFactory) + { + logger = loggerFactory.CreateLogger(); + } + + protected virtual Task AcceptEvent(SessionId sessionId, string method, JObject args, CancellationToken token) + { + return Task.FromResult(false); + } + + protected virtual Task AcceptCommand(MessageId id, string method, JObject args, CancellationToken token) + { + return Task.FromResult(false); + } + + async Task 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(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()), res["method"].Value(), res["params"] as JObject, token)); + else + OnResponse(new MessageId(res["sessionId"]?.Value(), res["id"].Value()), 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(), res["id"].Value()), + res["method"].Value(), + res["params"] as JObject, token)); + } + } + + internal async Task SendCommand(SessionId id, string method, JObject args, CancellationToken token) + { + //Log ("verbose", $"sending command {method}: {args}"); + return await SendCommandInternal(id, method, args, token); + } + + Task 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(); + + 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)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)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)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)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 thisExpressions = new List (); - public SyntaxTree syntaxTree; - public FindThisExpression (SyntaxTree syntax) - { - syntaxTree = syntax; - } - public override void Visit (SyntaxNode node) - { - if (node is ThisExpressionSyntax) { - if (node.Parent is MemberAccessExpressionSyntax thisParent && thisParent.Name is IdentifierNameSyntax) { - IdentifierNameSyntax var = thisParent.Name as IdentifierNameSyntax; - thisExpressions.Add(var.Identifier.Text); - var newRoot = syntaxTree.GetRoot ().ReplaceNode (node.Parent, thisParent.Name); - syntaxTree = syntaxTree.WithRootAndOptions (newRoot, syntaxTree.Options); - this.Visit (GetExpressionFromSyntaxTree(syntaxTree)); - } - } - else - base.Visit (node); - } - - public async Task CheckIfIsProperty (MonoProxy proxy, MessageId msg_id, int scope_id, CancellationToken token) - { - foreach (var var in thisExpressions) { - JToken value = await proxy.TryGetVariableValue (msg_id, scope_id, var, true, token); - if (value == null) - throw new Exception ($"The property {var} does not exist in the current context"); - } - } - } - - class FindVariableNMethodCall : CSharpSyntaxWalker { - public List variables = new List (); - public List thisList = new List (); - public List methodCall = new List (); - public List values = new List (); - - 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 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(); - var subType = variable["subtype"]?.Value(); - - switch (type) { - case "string": - return value?.Value (); - case "number": - return value?.Value (); - case "boolean": - return value?.Value (); - 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(); - object value = ConvertJSToCSharpType (variable); - - switch (type) { - case "object": { - if (subType == "null") - return variable["className"].Value(); - 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 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 thisExpressions = new List(); + public SyntaxTree syntaxTree; + public FindThisExpression(SyntaxTree syntax) + { + syntaxTree = syntax; + } + public override void Visit(SyntaxNode node) + { + if (node is ThisExpressionSyntax) + { + if (node.Parent is MemberAccessExpressionSyntax thisParent && thisParent.Name is IdentifierNameSyntax) + { + IdentifierNameSyntax var = thisParent.Name as IdentifierNameSyntax; + thisExpressions.Add(var.Identifier.Text); + var newRoot = syntaxTree.GetRoot().ReplaceNode(node.Parent, thisParent.Name); + syntaxTree = syntaxTree.WithRootAndOptions(newRoot, syntaxTree.Options); + this.Visit(GetExpressionFromSyntaxTree(syntaxTree)); + } + } + else + base.Visit(node); + } + + public async Task CheckIfIsProperty(MonoProxy proxy, MessageId msg_id, int scope_id, CancellationToken token) + { + foreach (var var in thisExpressions) + { + JToken value = await proxy.TryGetVariableValue(msg_id, scope_id, var, true, token); + if (value == null) + throw new Exception($"The property {var} does not exist in the current context"); + } + } + } + + class FindVariableNMethodCall : CSharpSyntaxWalker + { + public List variables = new List(); + public List thisList = new List(); + public List methodCall = new List(); + public List values = new List(); + + 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 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(); + var subType = variable["subtype"]?.Value(); + + switch (type) + { + case "string": + return value?.Value(); + case "number": + return value?.Value(); + case "boolean": + return value?.Value(); + 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(); + object value = ConvertJSToCSharpType(variable); + + switch (type) + { + case "object": + { + if (subType == "null") + return variable["className"].Value(); + 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 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 sessions = new HashSet (); - Dictionary contexts = new Dictionary (); - - 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 SendMonoCommand (SessionId id, MonoCommands cmd, CancellationToken token) - => SendCommand (id, "Runtime.evaluate", JObject.FromObject (cmd), token); - - protected override async Task 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 (); - } 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 (); - if (aux_data != null) { - var is_default = aux_data ["isDefault"]?.Value (); - 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 (); - - 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 () ?? ""; - - 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 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 () ?? false; - } - - static int bpIdGenerator; - - protected override async Task 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 (); - return await OnGetScriptSource (id, script, token); - } - - case "Runtime.compileScript": { - var exp = args? ["expression"]?.Value (); - 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(); - 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 (), 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 typeName = args ["typeName"]?.Value (); - string methodName = args ["methodName"]?.Value (); - 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}/::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 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 (); - 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 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 (); - 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 (); - 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 (); - foreach (var frame in orig_callframes) { - var function_name = frame ["functionName"]?.Value (); - var url = frame ["url"]?.Value (); - if ("mono_wasm_fire_bp" == function_name || "_mono_wasm_fire_bp" == function_name) { - var frames = new List (); - int frame_id = 0; - var the_mono_frames = res.Value? ["result"]? ["value"]? ["frames"]?.Values (); - - foreach (var mono_frame in the_mono_frames) { - ++frame_id; - var il_pos = mono_frame ["il_pos"].Value (); - var method_token = mono_frame ["method_token"].Value (); - var assembly_name = mono_frame ["assembly_name"].Value (); - - // This can be different than `method.Name`, like in case of generic methods - var method_name = mono_frame ["method_name"]?.Value (); - - 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 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 (); - - 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 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 ()?.ToArray (); - thisValue = scope_values?.FirstOrDefault (v => v ["name"]?.Value () == "this"); - - if (!only_search_on_this) { - if (thisValue != null && expression == "this") - return thisValue; - - var value = scope_values.SingleOrDefault (sv => sv ["name"]?.Value () == 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 ().ToArray (); - var foundValue = scope_values.FirstOrDefault (v => v ["name"].Value () == expression); - if (foundValue != null) { - foundValue["fromThis"] = true; - context.LocalsCache[foundValue ["name"].Value ()] = foundValue; - return foundValue; - } - } - return null; - } - - async Task 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 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 ().ToArray (); - - if(values == null || values.Length == 0) - return Result.OkFromObject (new { result = Array.Empty () }); - - foreach (var value in values) - ctx.LocalsCache [value ["name"]?.Value ()] = value; - - return Result.OkFromObject (new { result = values }); - } catch (Exception exception) { - Log ("verbose", $"Error resolving scope properties {exception.Message}"); - return Result.Exception (exception); - } - } - - async Task 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 (); - - 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 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 (); - } - - 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 RuntimeReady (SessionId sessionId, CancellationToken token) - { - var context = GetContext (sessionId); - if (Interlocked.CompareExchange (ref context.ready, new TaskCompletionSource (), 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 (); - - 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 (); - - 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 (); - - 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 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 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 sessions = new HashSet(); + Dictionary contexts = new Dictionary(); + + 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 SendMonoCommand(SessionId id, MonoCommands cmd, CancellationToken token) => SendCommand(id, "Runtime.evaluate", JObject.FromObject(cmd), token); + + protected override async Task 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(); + } + 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(); + if (aux_data != null) + { + var is_default = aux_data["isDefault"]?.Value(); + 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(); + + 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() ?? ""; + + 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 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() ?? false; + } + + protected override async Task 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(); + return await OnGetScriptSource(id, script, token); + } + + case "Runtime.compileScript": + { + var exp = args?["expression"]?.Value(); + 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(); + 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(), 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(); + 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 typeName = args["typeName"]?.Value(); + string methodName = args["methodName"]?.Value(); + 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}/::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 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(); + 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 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(); + 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(); + 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(); + foreach (var frame in orig_callframes) + { + var function_name = frame["functionName"]?.Value(); + var url = frame["url"]?.Value(); + 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()); + data = JObject.FromObject(new + { + type = "object", + subtype = "error", + className = res_val?["class_name"]?.Value(), + uncaught = res_val?["uncaught"]?.Value(), + description = res_val?["message"]?.Value() + "\n", + objectId = exception_dotnet_obj_id.ToString() + }); + reason = "exception"; + } + + var frames = new List(); + int frame_id = 0; + var the_mono_frames = res.Value?["result"]?["value"]?["frames"]?.Values(); + + foreach (var mono_frame in the_mono_frames) + { + ++frame_id; + var il_pos = mono_frame["il_pos"].Value(); + var method_token = mono_frame["method_token"].Value(); + var assembly_name = mono_frame["assembly_name"].Value(); + + // This can be different than `method.Name`, like in case of generic methods + var method_name = mono_frame["method_name"]?.Value(); + + 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 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(); + + 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 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()?.ToArray(); + thisValue = scope_values?.FirstOrDefault(v => v["name"]?.Value() == "this"); + + if (!only_search_on_this) + { + if (thisValue != null && expression == "this") + return thisValue; + + var value = scope_values.SingleOrDefault(sv => sv["name"]?.Value() == 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().ToArray(); + var foundValue = scope_values.FirstOrDefault(v => v["name"].Value() == expression); + if (foundValue != null) + { + foundValue["fromThis"] = true; + context.LocalsCache[foundValue["name"].Value()] = foundValue; + return foundValue; + } + } + return null; + } + + async Task 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 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().ToArray(); + + if (values == null || values.Length == 0) + return Result.OkFromObject(new { result = Array.Empty() }); + + foreach (var value in values) + ctx.LocalsCache[value["name"]?.Value()] = value; + + return Result.OkFromObject(new { result = values }); + } + catch (Exception exception) + { + Log("verbose", $"Error resolving scope properties {exception.Message}"); + return Result.Exception(exception); + } + } + + async Task 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(); + + 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 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(); + } + + 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 RuntimeReady(SessionId sessionId, CancellationToken token) + { + var context = GetContext(sessionId); + if (Interlocked.CompareExchange(ref context.ready, new TaskCompletionSource(), 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(); + + 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(); + + 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(); + + 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 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 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", - local_var_name_prefix: "gclass", - array: new [] { - TObject ("DebuggerTests.GenericClass", is_null: true), - TObject ("DebuggerTests.GenericClass"), - TObject ("DebuggerTests.GenericClass") - }, - 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", - local_var_name_prefix: "gvclass", - array: new [] { - TValueType ("DebuggerTests.SimpleGenericStruct"), - TValueType ("DebuggerTests.SimpleGenericStruct") - }, - 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", - local_var_name_prefix: "gvclass", - array: new [] { - TValueType ("DebuggerTests.SimpleGenericStruct"), - TValueType ("DebuggerTests.SimpleGenericStruct") - }, - 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 ()); - 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 ()); - var l_obj = GetAndAssertObjectWithName (locals, name); - var l_objectId = l_obj ["value"]["objectId"]?.Value (); - - 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 () == 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 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 (), 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 ()); - 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 ()); - await CheckProps (frame_locals, new { - call_other = TBool (false), - gvclass_arr = TArray ("DebuggerTests.SimpleGenericStruct[]", 2), - gvclass_arr_empty = TArray ("DebuggerTests.SimpleGenericStruct[]"), - gvclass_arr_null = TObject ("DebuggerTests.SimpleGenericStruct[]", is_null: true), - gvclass = TValueType ("DebuggerTests.SimpleGenericStruct"), - // 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 ()); - await CheckProps (frame_locals, new { - t1 = TObject ("DebuggerTests.SimpleGenericStruct"), - @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 ()); - 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 ()); - await CheckProps (frame_locals, new { - sc_arg = TObject ("DebuggerTests.SimpleClass"), - @this = TValueType ("DebuggerTests.Point"), - local_gs = TValueType ("DebuggerTests.SimpleGenericStruct") - }, - "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", + local_var_name_prefix: "gclass", + array: new[] + { + TObject("DebuggerTests.GenericClass", is_null : true), + TObject("DebuggerTests.GenericClass"), + TObject("DebuggerTests.GenericClass") + }, + 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", + local_var_name_prefix: "gvclass", + array: new[] + { + TValueType("DebuggerTests.SimpleGenericStruct"), + TValueType("DebuggerTests.SimpleGenericStruct") + }, + 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", + local_var_name_prefix: "gvclass", + array: new[] + { + TValueType("DebuggerTests.SimpleGenericStruct"), + TValueType("DebuggerTests.SimpleGenericStruct") + }, + 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()); + 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()); + var l_obj = GetAndAssertObjectWithName(locals, name); + var l_objectId = l_obj["value"]["objectId"]?.Value(); + + 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() == 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 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(), 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()); + 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()); + await CheckProps(frame_locals, new + { + call_other = TBool(false), + gvclass_arr = TArray("DebuggerTests.SimpleGenericStruct[]", 2), + gvclass_arr_empty = TArray("DebuggerTests.SimpleGenericStruct[]"), + gvclass_arr_null = TObject("DebuggerTests.SimpleGenericStruct[]", is_null: true), + gvclass = TValueType("DebuggerTests.SimpleGenericStruct"), + // 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()); + await CheckProps(frame_locals, new + { + t1 = TObject("DebuggerTests.SimpleGenericStruct"), + @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()); + 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()); + await CheckProps(frame_locals, new + { + sc_arg = TObject("DebuggerTests.SimpleClass"), + @this = TValueType("DebuggerTests.Point"), + local_gs = TValueType("DebuggerTests.SimpleGenericStruct") + }, + "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()); + var c_obj = GetAndAssertObjectWithName(frame_locals, "c"); + var c_obj_id = c_obj["value"]?["objectId"]?.Value(); + 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()), "c"); + var c_obj_id = c_obj["value"]?["objectId"]?.Value(); + 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()); + + if (!DotnetObjectId.TryParse(pf_arr_elems[0]["value"]?["objectId"]?.Value(), 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()), "c"); + var c_obj_id = c_obj["value"]?["objectId"]?.Value(); + 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>>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 (), - 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 (), - 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 (), $"{label}-type"); - AssertEqual (className, actual ["className"]?.Value (), $"{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 { - - 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 (), - 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 (), - 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 (), - 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 (), - 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 (), - 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 (), - 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 (), - 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 (), - 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 ()); - 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") - }, "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", 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 (), - 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 (), - 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") - }, $"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", 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 (), - 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 (), - 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 (), "cfo-res-type"); - - AssertEqual (JTokenType.Array, result.Value ["result"] ["value"].Type, "cfo-res-value-jsontype"); - var actual = result.Value ["result"]?["value"].Values ().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 (), $"[{i}]"); - else { - AssertEqual ("number", actual [i]?["type"]?.Value (), $"[{i}]-type"); - AssertEqual (exp_num.ToString (), actual [i]?["description"]?.Value (), $"[{i}]-description"); - AssertEqual (exp_num, actual [i]?["value"]?.Value (), $"[{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 (), "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 ()); - var obj = GetAndAssertObjectWithName (frame_locals, "big"); - var obj_id = obj ["value"]["objectId"].Value (); - - 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 SilentErrorsTestData (bool? silent) - => new TheoryData { - { "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 ()); - var obj = GetAndAssertObjectWithName (frame_locals, "big"); - var big_obj_id = obj ["value"]["objectId"].Value (); - 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 ()?.Contains (error_msg); - Assert.True ((hasErrorMessage ?? false), "Exception message not found"); - }); - } - - public static TheoryData, bool> GettersTestData (bool use_cfo) - => new TheoryData, 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 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 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 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()); - 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 ()); - 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 ()); - 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 ()); - 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()); - 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 ()); - 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 (); - 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 () == "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 () == "StringField"), "StringField should be returned for `accessorPropertiesOnly=false`"); - }); - - async Task 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 InvokeGetter (JToken obj, string fn, object arguments) - => await ctx.cli.SendCommand ( - "Runtime.callFunctionOn", - JObject.FromObject (new { - functionDeclaration = fn, - objectId = obj ["value"]?["objectId"]?.Value (), - 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 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 ()); - var obj = GetAndAssertObjectWithName (frame_locals, local_name); - var obj_id = obj ["value"]["objectId"].Value (); - - 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 () - }); - - 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>>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(), + 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(), + 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(), $"{label}-type"); + AssertEqual(className, actual["className"]?.Value(), $"{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 + { + + 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(), + 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(), + 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(), + 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(), + 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(), + 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(), + 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(), + 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(), + 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()); + 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") + }, "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", 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(), + 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(), + 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") + }, $"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", 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(), + 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(), + 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(), "cfo-res-type"); + + AssertEqual(JTokenType.Array, result.Value["result"]["value"].Type, "cfo-res-value-jsontype"); + var actual = result.Value["result"]?["value"].Values().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(), $"[{i}]"); + else + { + AssertEqual("number", actual[i]?["type"]?.Value(), $"[{i}]-type"); + AssertEqual(exp_num.ToString(), actual[i]?["description"]?.Value(), $"[{i}]-description"); + AssertEqual(exp_num, actual[i]?["value"]?.Value(), $"[{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(), "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()); + var obj = GetAndAssertObjectWithName(frame_locals, "big"); + var obj_id = obj["value"]["objectId"].Value(); + + 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 SilentErrorsTestData(bool? silent) => new TheoryData + { { "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()); + var obj = GetAndAssertObjectWithName(frame_locals, "big"); + var big_obj_id = obj["value"]["objectId"].Value(); + 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()?.Contains(error_msg); + Assert.True((hasErrorMessage ?? false), "Exception message not found"); + }); + } + + public static TheoryData, string, bool> GettersTestData(string local_name, bool use_cfo) => new TheoryData, 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 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 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 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()); + + 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()); + 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()); + 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()); + 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(); + 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() == "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() == "StringField"), "StringField should be returned for `accessorPropertiesOnly=false`"); + }); + + async Task 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 NegativeTestsData(bool use_cfo = false) => new TheoryData + { { "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(); + + 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()); + var ptd = GetAndAssertObjectWithName(frame_locals, "ptd"); + var ptd_id = ptd["value"]["objectId"].Value(); + + 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()); + var ptd = GetAndAssertObjectWithName(frame_locals, "ptd"); + var ptd_id = ptd["value"]["objectId"].Value(); + + 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() } }); + + 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 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()); + var obj = GetAndAssertObjectWithName(frame_locals, local_name); + var obj_id = obj["value"]["objectId"].Value(); + + 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() + }); + + 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()); - - await CheckProps (locals, new { - fn_func = TDelegate ("System.Func", "bool |(Math)"), - fn_func_null = TObject ("System.Func", is_null: true), - fn_func_arr = TArray ("System.Func[]", 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", "bool |(Math)"), - fn_func_null_unused = TObject ("System.Func", is_null: true), - fn_func_arr_unused = TArray ("System.Func[]", 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", - "bool |(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", - "bool |(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()); - - await CheckProps (locals, new { - fn_func = TDelegate ("System.Func>, Math.GenericStruct>", - "Math.GenericStruct |(Math,Math.GenericStruct>)"), - - fn_func_del = TDelegate ("System.Func>, Math.GenericStruct>", - "Math.GenericStruct DelegateTargetForSignatureTest (Math,Math.GenericStruct>)"), - - fn_func_null = TObject ("System.Func>, Math.GenericStruct>", is_null: true), - fn_func_only_ret= TDelegate ("System.Func", "bool |()"), - fn_func_arr = TArray ("System.Func>, Math.GenericStruct>[]", 1), - - fn_del = TDelegate ("Math.DelegateForSignatureTest", - "Math.GenericStruct DelegateTargetForSignatureTest (Math,Math.GenericStruct>)"), - - fn_del_l = TDelegate ("Math.DelegateForSignatureTest", - "Math.GenericStruct |(Math,Math.GenericStruct>)"), - - 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>"), - fn_void_del = TDelegate ("Math.DelegateWithVoidReturn", - "void DelegateTargetWithVoidReturn (Math.GenericStruct)"), - - fn_void_del_arr = TArray ("Math.DelegateWithVoidReturn[]", 1), - fn_void_del_null= TObject ("Math.DelegateWithVoidReturn", is_null: true), - gs = TValueType ("Math.GenericStruct"), - rets = TArray ("Math.GenericStruct[]", 6) - }, "locals"); - - await CompareObjectPropertiesFor (locals, "fn_func_arr", new [] { - TDelegate ( - "System.Func>, Math.GenericStruct>", - "Math.GenericStruct |(Math,Math.GenericStruct>)"), - }, "locals#fn_func_arr"); - - await CompareObjectPropertiesFor (locals, "fn_del_arr", new [] { - TDelegate ( - "Math.DelegateForSignatureTest", - "Math.GenericStruct DelegateTargetForSignatureTest (Math,Math.GenericStruct>)"), - TDelegate ( - "Math.DelegateForSignatureTest", - "Math.GenericStruct |(Math,Math.GenericStruct>)") - }, "locals#fn_del_arr"); - - await CompareObjectPropertiesFor (locals, "fn_void_del_arr", new [] { - TDelegate ( - "Math.DelegateWithVoidReturn", - "void DelegateTargetWithVoidReturn (Math.GenericStruct)") - }, "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()); - - await CheckProps (locals, new - { - fn_action = TDelegate ("System.Action>", - "void |(Math.GenericStruct)"), - fn_action_del = TDelegate ("System.Action>", - "void DelegateTargetWithVoidReturn (Math.GenericStruct)"), - fn_action_bare = TDelegate ("System.Action", - "void|()"), - - fn_action_null = TObject ("System.Action>", is_null: true), - - fn_action_arr = TArray ("System.Action>[]", 3), - - gs = TValueType ("Math.GenericStruct"), - }, "locals"); - - await CompareObjectPropertiesFor (locals, "fn_action_arr", new [] { - TDelegate ( - "System.Action>", - "void |(Math.GenericStruct)"), - TDelegate ( - "System.Action>", - "void DelegateTargetWithVoidReturn (Math.GenericStruct)"), - TObject ("System.Action>", 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()); - - await CheckProps (locals, new { - fn_func = TDelegate ("System.Func, bool>", - "bool |(Func)"), - fn_func_null = TObject ("System.Func, bool>", is_null: true), - fn_func_arr = TArray ("System.Func, bool>[]", 1), - fn_del_arr = TArray ("System.Func, bool>[]", 1), - - m_obj = TObject ("Math"), - fn_del_null = TObject ("System.Func, bool>", is_null: true), - fs = TDelegate ("System.Func", - "bool |(int)") - }, "locals"); - - await CompareObjectPropertiesFor (locals, "fn_func_arr", new [] { - TDelegate ( - "System.Func, bool>", - "bool |(System.Func)") - }, "locals#fn_func_arr"); - - await CompareObjectPropertiesFor (locals, "fn_del_arr", new [] { - TDelegate ( - "System.Func, bool>", - "bool DelegateTargetForNestedFunc (Func)") - }, "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()); - - await CheckProps (locals, new { - @this = TObject ("Math"), - dst_arr = TArray ("Math.DelegateForSignatureTest[]", 2), - fn_func = TDelegate ("System.Func", - "bool |(char[])"), - fn_action = TDelegate ("System.Action[]>", - "void |(Math.GenericStruct[])") - }, "locals"); - - await CompareObjectPropertiesFor (locals, "dst_arr", new [] { - TDelegate ("Math.DelegateForSignatureTest", - "Math.GenericStruct DelegateTargetForSignatureTest (Math,Math.GenericStruct>)"), - TDelegate ("Math.DelegateForSignatureTest", - "Math.GenericStruct |(Math,Math.GenericStruct>)"), - }, "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()); - - await CheckProps (locals, new { - @this = TObject ("Math"), - _dst_arr = TArray ("Math.DelegateForSignatureTest[]", 2), - _fn_func = TDelegate ("System.Func", - "bool |(char[])"), - _fn_action = TDelegate ("System.Action[]>", - "void |(Math.GenericStruct[])") - }, "locals"); - - await CompareObjectPropertiesFor (locals, "_dst_arr", new [] { - TDelegate ( - "Math.DelegateForSignatureTest", - "Math.GenericStruct DelegateTargetForSignatureTest (Math,Math.GenericStruct>)"), - TDelegate ( - "Math.DelegateForSignatureTest", - "Math.GenericStruct |(Math,Math.GenericStruct>)"), - }, "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()); + + await CheckProps(locals, new + { + fn_func = TDelegate("System.Func", "bool |(Math)"), + fn_func_null = TObject("System.Func", is_null: true), + fn_func_arr = TArray("System.Func[]", 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", "bool |(Math)"), + fn_func_null_unused = TObject("System.Func", is_null: true), + fn_func_arr_unused = TArray("System.Func[]", 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", + "bool |(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", + "bool |(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()); + + await CheckProps(locals, new + { + fn_func = TDelegate("System.Func>, Math.GenericStruct>", + "Math.GenericStruct |(Math,Math.GenericStruct>)"), + + fn_func_del = TDelegate("System.Func>, Math.GenericStruct>", + "Math.GenericStruct DelegateTargetForSignatureTest (Math,Math.GenericStruct>)"), + + fn_func_null = TObject("System.Func>, Math.GenericStruct>", is_null: true), + fn_func_only_ret = TDelegate("System.Func", "bool |()"), + fn_func_arr = TArray("System.Func>, Math.GenericStruct>[]", 1), + + fn_del = TDelegate("Math.DelegateForSignatureTest", + "Math.GenericStruct DelegateTargetForSignatureTest (Math,Math.GenericStruct>)"), + + fn_del_l = TDelegate("Math.DelegateForSignatureTest", + "Math.GenericStruct |(Math,Math.GenericStruct>)"), + + 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>"), + fn_void_del = TDelegate("Math.DelegateWithVoidReturn", + "void DelegateTargetWithVoidReturn (Math.GenericStruct)"), + + fn_void_del_arr = TArray("Math.DelegateWithVoidReturn[]", 1), + fn_void_del_null = TObject("Math.DelegateWithVoidReturn", is_null: true), + gs = TValueType("Math.GenericStruct"), + rets = TArray("Math.GenericStruct[]", 6) + }, "locals"); + + await CompareObjectPropertiesFor(locals, "fn_func_arr", new[] + { + TDelegate( + "System.Func>, Math.GenericStruct>", + "Math.GenericStruct |(Math,Math.GenericStruct>)"), + }, "locals#fn_func_arr"); + + await CompareObjectPropertiesFor(locals, "fn_del_arr", new[] + { + TDelegate( + "Math.DelegateForSignatureTest", + "Math.GenericStruct DelegateTargetForSignatureTest (Math,Math.GenericStruct>)"), + TDelegate( + "Math.DelegateForSignatureTest", + "Math.GenericStruct |(Math,Math.GenericStruct>)") + }, "locals#fn_del_arr"); + + await CompareObjectPropertiesFor(locals, "fn_void_del_arr", new[] + { + TDelegate( + "Math.DelegateWithVoidReturn", + "void DelegateTargetWithVoidReturn (Math.GenericStruct)") + }, "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()); + + await CheckProps(locals, new + { + fn_action = TDelegate("System.Action>", + "void |(Math.GenericStruct)"), + fn_action_del = TDelegate("System.Action>", + "void DelegateTargetWithVoidReturn (Math.GenericStruct)"), + fn_action_bare = TDelegate("System.Action", + "void|()"), + + fn_action_null = TObject("System.Action>", is_null: true), + + fn_action_arr = TArray("System.Action>[]", 3), + + gs = TValueType("Math.GenericStruct"), + }, "locals"); + + await CompareObjectPropertiesFor(locals, "fn_action_arr", new[] + { + TDelegate( + "System.Action>", + "void |(Math.GenericStruct)"), + TDelegate( + "System.Action>", + "void DelegateTargetWithVoidReturn (Math.GenericStruct)"), + TObject("System.Action>", 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()); + + await CheckProps(locals, new + { + fn_func = TDelegate("System.Func, bool>", + "bool |(Func)"), + fn_func_null = TObject("System.Func, bool>", is_null: true), + fn_func_arr = TArray("System.Func, bool>[]", 1), + fn_del_arr = TArray("System.Func, bool>[]", 1), + + m_obj = TObject("Math"), + fn_del_null = TObject("System.Func, bool>", is_null: true), + fs = TDelegate("System.Func", + "bool |(int)") + }, "locals"); + + await CompareObjectPropertiesFor(locals, "fn_func_arr", new[] + { + TDelegate( + "System.Func, bool>", + "bool |(System.Func)") + }, "locals#fn_func_arr"); + + await CompareObjectPropertiesFor(locals, "fn_del_arr", new[] + { + TDelegate( + "System.Func, bool>", + "bool DelegateTargetForNestedFunc (Func)") + }, "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()); + + await CheckProps(locals, new + { + @this = TObject("Math"), + dst_arr = TArray("Math.DelegateForSignatureTest[]", 2), + fn_func = TDelegate("System.Func", + "bool |(char[])"), + fn_action = TDelegate("System.Action[]>", + "void |(Math.GenericStruct[])") + }, "locals"); + + await CompareObjectPropertiesFor(locals, "dst_arr", new[] + { + TDelegate("Math.DelegateForSignatureTest", + "Math.GenericStruct DelegateTargetForSignatureTest (Math,Math.GenericStruct>)"), + TDelegate("Math.DelegateForSignatureTest", + "Math.GenericStruct |(Math,Math.GenericStruct>)"), + }, "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()); + + await CheckProps(locals, new + { + @this = TObject("Math"), + _dst_arr = TArray("Math.DelegateForSignatureTest[]", 2), + _fn_func = TDelegate("System.Func", + "bool |(char[])"), + _fn_action = TDelegate("System.Action[]>", + "void |(Math.GenericStruct[])") + }, "locals"); + + await CompareObjectPropertiesFor(locals, "_dst_arr", new[] + { + TDelegate( + "Math.DelegateForSignatureTest", + "Math.GenericStruct DelegateTargetForSignatureTest (Math,Math.GenericStruct>)"), + TDelegate( + "Math.DelegateForSignatureTest", + "Math.GenericStruct |(Math,Math.GenericStruct>)"), + }, "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 pending_ops = new List (); - TaskCompletionSource side_exit = new TaskCompletionSource (); - List pending_writes = new List (); - 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 (pending_writes [0]), WebSocketMessageType.Text, true, token); - return current_write; - } - return null; - } - - async Task ReadOne (CancellationToken token) - { - byte [] buff = new byte [4000]; - var mem = new MemoryStream (); - while (true) { - var result = await this.socket.ReceiveAsync (new ArraySegment (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 (bytes), WebSocketMessageType.Text, true, token); - pending_ops.Add (current_write); - } - } - - async Task MarkCompleteAfterward (Func send, CancellationToken token) - { - try { - await send(token); - side_exit.SetResult (true); - } catch (Exception e) { - side_exit.SetException (e); - } - } - - protected async Task ConnectWithMainLoops( - Uri uri, - Func receive, - Func 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)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)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 pending_ops = new List(); + TaskCompletionSource side_exit = new TaskCompletionSource(); + List pending_writes = new List(); + 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(pending_writes[0]), WebSocketMessageType.Text, true, token); + return current_write; + } + return null; + } + + async Task ReadOne(CancellationToken token) + { + byte[] buff = new byte[4000]; + var mem = new MemoryStream(); + while (true) + { + var result = await this.socket.ReceiveAsync(new ArraySegment(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(bytes), WebSocketMessageType.Text, true, token); + pending_ops.Add(current_write); + } + } + + async Task MarkCompleteAfterward(Func send, CancellationToken token) + { + try + { + await send(token); + side_exit.SetResult(true); + } + catch (Exception e) + { + side_exit.SetException(e); + } + } + + protected async Task ConnectWithMainLoops( + Uri uri, + Func receive, + Func 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)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)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 ()); - var evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "a"); - CheckContentValue (evaluate, "1"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "b"); - CheckContentValue (evaluate, "2"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "c"); - CheckContentValue (evaluate, "3"); - - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "dt"); - await CheckDateTimeValue (evaluate, new DateTime (2000, 5, 4, 3, 2, 1)); - }); - - [Theory] - [InlineData (58, 3, "EvaluateTestsStructInstanceMethod")] - [InlineData (74, 3, "GenericInstanceMethodOnStruct")] - [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 (), "a"); - CheckContentValue (evaluate, "1"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "b"); - CheckContentValue (evaluate, "2"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "c"); - CheckContentValue (evaluate, "3"); - - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "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 ()); - var evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "g"); - CheckContentValue (evaluate, "100"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "h"); - CheckContentValue (evaluate, "200"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "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 ()); - var evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "d"); - CheckContentValue (evaluate, "101"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "e"); - CheckContentValue (evaluate, "102"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "f"); - CheckContentValue (evaluate, "103"); - - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "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 ()); - - // sc_arg - { - var sc_arg = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "sc_arg"); - await CheckValue (sc_arg, TObject ("DebuggerTests.SimpleClass"), "sc_arg#1"); - - var sc_arg_props = await GetProperties (sc_arg ["objectId"]?.Value ()); - 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 (), "local_gs"); - await CheckValue (local_gs, TValueType ("DebuggerTests.SimpleGenericStruct"), "local_gs#1"); - - var local_gs_props = await GetProperties (local_gs ["objectId"]?.Value ()); - 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 (), "local_gs"); - await CheckValue (local_gs, TValueType ("DebuggerTests.SimpleGenericStruct"), "local_gs#2"); - - var local_gs_props = await GetProperties (local_gs ["objectId"]?.Value ()); - 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 (), "sc_arg"); - await CheckValue (sc_arg, TObject ("DebuggerTests.SimpleClass"), "sc_arg#2"); - - var sc_arg_props = await GetProperties (sc_arg ["objectId"]?.Value ()); - 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 ()); - var evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "d + e"); - CheckContentValue (evaluate, "203"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "e + 10"); - CheckContentValue (evaluate, "112"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "a + a"); - CheckContentValue (evaluate, "2"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "this.a + this.b"); - CheckContentValue (evaluate, "3"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "\"test\" + \"test\""); - CheckContentValue (evaluate, "testtest"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "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 ()); - var evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "this.a"); - CheckContentValue (evaluate, "1"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "this.b"); - CheckContentValue (evaluate, "2"); - evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "this.c"); - CheckContentValue (evaluate, "3"); - - // FIXME: not supported yet - // evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "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()); + var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "a"); + CheckContentValue(evaluate, "1"); + evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "b"); + CheckContentValue(evaluate, "2"); + evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "c"); + CheckContentValue(evaluate, "3"); + + evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "dt"); + await CheckDateTimeValue(evaluate, new DateTime(2000, 5, 4, 3, 2, 1)); + }); + + [Theory] + [InlineData(63, 12, "EvaluateTestsStructInstanceMethod")] + [InlineData(79, 12, "GenericInstanceMethodOnStruct")] + [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(), "a"); + CheckContentValue(evaluate, "1"); + evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "b"); + CheckContentValue(evaluate, "2"); + evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "c"); + CheckContentValue(evaluate, "3"); + + evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "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()); + var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "g"); + CheckContentValue(evaluate, "100"); + evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "h"); + CheckContentValue(evaluate, "200"); + evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "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()); + var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "d"); + CheckContentValue(evaluate, "101"); + evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "e"); + CheckContentValue(evaluate, "102"); + evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "f"); + CheckContentValue(evaluate, "103"); + + evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "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()); + + // sc_arg + { + var sc_arg = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "sc_arg"); + await CheckValue(sc_arg, TObject("DebuggerTests.SimpleClass"), "sc_arg#1"); + + var sc_arg_props = await GetProperties(sc_arg["objectId"]?.Value()); + 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(), "local_gs"); + await CheckValue(local_gs, TValueType("DebuggerTests.SimpleGenericStruct"), "local_gs#1"); + + var local_gs_props = await GetProperties(local_gs["objectId"]?.Value()); + 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(), "local_gs"); + await CheckValue(local_gs, TValueType("DebuggerTests.SimpleGenericStruct"), "local_gs#2"); + + var local_gs_props = await GetProperties(local_gs["objectId"]?.Value()); + 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(), "sc_arg"); + await CheckValue(sc_arg, TObject("DebuggerTests.SimpleClass"), "sc_arg#2"); + + var sc_arg_props = await GetProperties(sc_arg["objectId"]?.Value()); + 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()); + var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "d + e"); + CheckContentValue(evaluate, "203"); + evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "e + 10"); + CheckContentValue(evaluate, "112"); + evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "a + a"); + CheckContentValue(evaluate, "2"); + evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "this.a + this.b"); + CheckContentValue(evaluate, "3"); + evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "\"test\" + \"test\""); + CheckContentValue(evaluate, "testtest"); + evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "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()); + var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "this.a"); + CheckContentValue(evaluate, "1"); + evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "this.b"); + CheckContentValue(evaluate, "2"); + evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value(), "this.c"); + CheckContentValue(evaluate, "3"); + + // FIXME: not supported yet + // evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value (), "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(), "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()); + CheckString(exception_members, "message", "not implemented caught"); + + pause_location = await WaitForManagedException(null); + AssertEqual("run", pause_location["callFrames"]?[0]?["functionName"]?.Value(), "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()); + 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()); + 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()); + 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 (), "lineNumber"); + AssertEqual("Uncaught", eo["exceptionDetails"]?["text"]?.Value(), "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(), "lineNumber"); + AssertEqual("Uncaught", eo["exceptionDetails"]?["text"]?.Value(), "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()); + CheckString(exception_members, "message", exception_message); + }); + } + + async Task WaitForManagedException(JObject pause_location) + { + while (true) + { + if (pause_location != null) + { + AssertEqual("exception", pause_location["reason"]?.Value(), $"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()?.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)> pending_cmds = new List<(int, TaskCompletionSource)> (); - Func onEvent; - int next_cmd_id; +namespace Microsoft.WebAssembly.Diagnostics +{ + internal class InspectorClient : DevToolsClient + { + List<(int, TaskCompletionSource)> pending_cmds = new List<(int, TaskCompletionSource)>(); + Func 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 (), res ["params"] as JObject, token); - var id = res ["id"].Value (); - 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(), res["params"] as JObject, token); + var id = res["id"].Value(); + 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 onEvent, - Func send, - CancellationToken token) { + public async Task Connect( + Uri uri, + Func onEvent, + Func send, + CancellationToken token) + { - this.onEvent = onEvent; - await ConnectWithMainLoops (uri, HandleMessage, send, token); - } + this.onEvent = onEvent; + await ConnectWithMainLoops(uri, HandleMessage, send, token); + } - public Task SendCommand (string method, JObject args, CancellationToken token) - { - int id = ++next_cmd_id; - if (args == null) - args = new JObject (); + public Task 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 (); - pending_cmds.Add ((id, tcs)); + var tcs = new TaskCompletionSource(); + 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 PointersTestData => - new TheoryData { - {$"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()); - - 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()); - - 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()); - - 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()); - - 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*"), - gsp_null = TPointer ("DebuggerTests.GenericStructWithUnmanagedT*") - }, "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"), "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"), "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()); - - 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()); - - var dt = new DateTime (5, 6, 7, 8, 9, 10); - await CheckProps (locals, new { - gspa = TArray ("DebuggerTests.GenericStructWithUnmanagedT*[]", 3), - }, "locals", num_fields: 26); - - // dtpa - var gspa_elems = await CompareObjectPropertiesFor (locals, "gspa", new [] { - TPointer ("DebuggerTests.GenericStructWithUnmanagedT*", is_null: true), - TPointer ("DebuggerTests.GenericStructWithUnmanagedT*"), - TPointer ("DebuggerTests.GenericStructWithUnmanagedT*"), - }); - { - var gs_dt = new DateTime (1, 2, 3, 4, 5, 6); - var actual_elems = await CheckArrayElements (gspa_elems, new [] { - null, - TValueType ("DebuggerTests.GenericStructWithUnmanagedT"), - TValueType ("DebuggerTests.GenericStructWithUnmanagedT") - }); - - // *[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()); - - 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()); - - var dt = new DateTime (5, 6, 7, 8, 9, 10); - await CheckProps (locals, new { - cwp = TObject ("DebuggerTests.GenericClassWithPointers"), - cwp_null = TObject ("DebuggerTests.GenericClassWithPointers") - }, "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 PointersAsMethodArgsTestData => - new TheoryData { - {$"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()); - - 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()); - - 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()); - var complex = GetAndAssertObjectWithName (locals, "complex"); - - // try to deref the non-pointer object, as a pointer - var props = await GetProperties (complex ["value"]["objectId"].Value ().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 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 PointersTestData => + new TheoryData + { { $"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()); + + 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()); + + 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()); + + 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()); + + 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*"), + gsp_null = TPointer("DebuggerTests.GenericStructWithUnmanagedT*") + }, "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"), "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"), "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()); + + 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()); + + var dt = new DateTime(5, 6, 7, 8, 9, 10); + await CheckProps(locals, new + { + gspa = TArray("DebuggerTests.GenericStructWithUnmanagedT*[]", 3), + }, "locals", num_fields: 26); + + // dtpa + var gspa_elems = await CompareObjectPropertiesFor(locals, "gspa", new[] + { + TPointer("DebuggerTests.GenericStructWithUnmanagedT*", is_null : true), + TPointer("DebuggerTests.GenericStructWithUnmanagedT*"), + TPointer("DebuggerTests.GenericStructWithUnmanagedT*"), + }); + { + var gs_dt = new DateTime(1, 2, 3, 4, 5, 6); + var actual_elems = await CheckArrayElements(gspa_elems, new[] + { + null, + TValueType("DebuggerTests.GenericStructWithUnmanagedT"), + TValueType("DebuggerTests.GenericStructWithUnmanagedT") + }); + + // *[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()); + + 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()); + + var dt = new DateTime(5, 6, 7, 8, 9, 10); + await CheckProps(locals, new + { + cwp = TObject("DebuggerTests.GenericClassWithPointers"), + cwp_null = TObject("DebuggerTests.GenericClassWithPointers") + }, "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 PointersAsMethodArgsTestData => + new TheoryData + { { $"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()); + + 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()); + + 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()); + var complex = GetAndAssertObjectWithName(locals, "complex"); + + // try to deref the non-pointer object, as a pointer + await GetProperties(complex["value"]["objectId"].Value().Replace(":object:", ":pointer:"), expect_ok: false); + + // try to deref an invalid pointer id + await GetProperties("dotnet:pointer:123897", expect_ok: false); + }); + + async Task 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> notifications = new Dictionary> (); - Dictionary> eventListeners = new Dictionary> (); - - public const string PAUSE = "pause"; - public const string READY = "ready"; - - public Task 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 (); - 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 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 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())) { - 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 dicScriptsIdToUrl; - internal Dictionary dicFileToUrl; - internal Dictionary SubscribeToScripts (Inspector insp) { - dicScriptsIdToUrl = new Dictionary (); - dicFileToUrl = new Dictionary(); - insp.On("Debugger.scriptParsed", async (args, c) => { - var script_id = args? ["scriptId"]?.Value (); - var url = args["url"]?.Value (); - if (script_id.StartsWith("dotnet://")) - { - var dbgUrl = args["dotNetUrl"]?.Value(); - var arrStr = dbgUrl.Split("/"); - dbgUrl = arrStr[0] + "/" + arrStr[1] + "/" + arrStr[2] + "/" + arrStr[arrStr.Length - 1]; - dicScriptsIdToUrl[script_id] = dbgUrl; - dicFileToUrl[dbgUrl] = args["url"]?.Value(); - } 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 test_fn = null, Func 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 ()); - - 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 locals_fn = null, Func 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 ()); - - Assert.Equal (bp.Value ["breakpointId"]?.ToString (), pause_location ["hitBreakpoints"]?[0]?.Value ()); - - 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 ()); - locals_fn (locals); - } - }); - } - - internal void CheckLocation (string script_loc, int line, int column, Dictionary scripts, JToken location) - { - var loc_str = $"{ scripts[location["scriptId"].Value()] }" - + $"#{ location ["lineNumber"].Value () }" - + $"#{ location ["columnNumber"].Value () }"; - - var expected_loc_str = $"{script_loc}#{line}#{column}"; - Assert.Equal (expected_loc_str, loc_str); - } - - internal void CheckNumber (JToken locals, string name, T value) { - foreach (var l in locals) { - if (name != l["name"]?.Value ()) - continue; - var val = l["value"]; - Assert.Equal ("number", val ["type"]?.Value ()); - Assert.Equal (value, val["value"].Value ()); - 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 ()) - continue; - var val = l["value"]; - if (value == null) { - Assert.Equal ("object", val ["type"]?.Value ()); - Assert.Equal ("null", val["subtype"]?.Value ()); - } else { - Assert.Equal ("string", val ["type"]?.Value ()); - Assert.Equal (value, val["value"]?.Value ()); - } - 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 ()); - Assert.Equal (value, val ["value"]?.Value ()); - 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 ()); - Assert.True (val ["isValueType"] == null || !val ["isValueType"].Value ()); - Assert.Equal (class_name, val ["className"]?.Value ()); - - var has_null_subtype = val ["subtype"] != null && val ["subtype"]?.Value () == "null"; - Assert.Equal (is_null, has_null_subtype); - if (subtype != null) - Assert.Equal (subtype, val ["subtype"]?.Value ()); - - return l; - } - - internal async Task 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 (), "className"); - AssertEqual (expected.ToString (), value ["description"]?.Value (), "description"); - - var members = await GetProperties (value ["objectId"]?.Value ()); - - // 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 ()); - if (val ["value"] == null) - Assert.True (false, "expected bool value not found for variable named {name}"); - Assert.Equal (expected, val ["value"]?.Value ()); - - return l; - } - - internal void CheckContentValue (JToken token, string value) { - var val = token["value"].Value (); - 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 ()); - Assert.True (val ["isValueType"] != null && val ["isValueType"].Value ()); - Assert.Equal (class_name, val ["className"]?.Value ()); - 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 ()); - Assert.True (val ["isEnum"] != null && val ["isEnum"].Value ()); - Assert.Equal (class_name, val ["className"]?.Value ()); - Assert.Equal (descr, val ["description"]?.Value ()); - return l; - } - - internal void CheckArray (JToken locals, string name, string class_name) { - foreach (var l in locals) { - if (name != l["name"]?.Value ()) - continue; - - var val = l["value"]; - Assert.Equal ("object", val ["type"]?.Value ()); - Assert.Equal ("array", val ["subtype"]?.Value ()); - Assert.Equal (class_name, val ["className"]?.Value ()); - - //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 () == name); - if (l == null) - Assert.True (false, $"Could not find variable '{name}'"); - return l; - } - - internal async Task 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 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 ()); - } - - // Place a breakpoint in the given method and run until its hit - // Return the Debugger.paused data - internal async Task 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 StepAndCheck (StepKind kind, string script_loc, int line, int column, string function_name, - Func wait_for_event_fn = null, Action 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 EvaluateAndCheck (string expression, string script_loc, int line, int column, string function_name, - Func wait_for_event_fn = null, Action 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 SendCommandAndCheck (JObject args, string method, string script_loc, int line, int column, string function_name, - Func wait_for_event_fn = null, Action 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 ()); - - 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 ()); - 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(), $"{label}-type"); - AssertEqual (exp_val ["className"]?.Value (), actual_val ["className"]?.Value(), $"{label}-className"); - - var actual_target = actual_val["description"]?.Value(); - Assert.True(actual_target != null, $"${label}-description"); - var exp_target = exp_val["target"].Value(); - - CheckDelegateTarget (actual_target, exp_target); - - var del_props = await GetProperties(actual_val["objectId"]?.Value()); - AssertEqual (1, del_props.Count(), $"${label}-delegate-properties-count"); - - var obj = del_props.Where (jt => jt ["name"]?.Value () == "Target").FirstOrDefault (); - Assert.True (obj != null, $"[{label}] Property named 'Target' found found in delegate properties"); - - AssertEqual("symbol", obj ["value"]?["type"]?.Value(), $"{label}#Target#type"); - CheckDelegateTarget(obj ["value"]?["value"]?.Value(), 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(); - switch (ctype) { - case "delegate": - await CheckDelegate (actual_val, exp_val, label); - break; - - case "pointer": { - - if (exp_val ["is_null"]?.Value() == true) { - AssertEqual ("symbol", actual_val ["type"]?.Value(), $"{label}-type"); - - var exp_val_str = $"({exp_val ["type_name"]?.Value()}) 0"; - AssertEqual (exp_val_str, actual_val ["value"]?.Value (), $"{label}-value"); - AssertEqual (exp_val_str, actual_val ["description"]?.Value (), $"{label}-description"); - } else if (exp_val ["is_void"]?.Value () == true) { - AssertEqual ("symbol", actual_val ["type"]?.Value(), $"{label}-type"); - - var exp_val_str = $"({exp_val ["type_name"]?.Value()})"; - AssertStartsWith (exp_val_str, actual_val ["value"]?.Value (), $"{label}-value"); - AssertStartsWith (exp_val_str, actual_val ["description"]?.Value (), $"{label}-description"); - } else { - AssertEqual ("object", actual_val ["type"]?.Value(), $"{label}-type"); - - var exp_prefix = $"({exp_val ["type_name"]?.Value()})"; - AssertStartsWith (exp_prefix, actual_val ["className"]?.Value (), $"{label}-className"); - AssertStartsWith (exp_prefix, actual_val ["description"]?.Value (), $"{label}-description"); - Assert.False (actual_val ["className"]?.Value () == $"{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 (), $"{label}-className"); - AssertStartsWith ($"get {exp_val ["type_name"]?.Value ()} ()", get ["description"]?.Value (), $"{label}-description"); - AssertEqual ("function", get ["type"]?.Value (), $"{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 (), $"{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().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() == 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()); - await CheckProps (actual_props, exp_val, $"{label}-{exp_name}"); - } else if (exp_val ["__custom_type"] != null && exp_val ["__custom_type"]?.Value () == "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 ()); - await CheckProps (new_val, exp_val, $"{label}-{actual_val["objectId"]?.Value()}"); - return; - } - - foreach (var jp in exp_val.Values ()) { - if (jp.Value.Type == JTokenType.Object) { - var new_val = await GetProperties (actual_val ["objectId"].Value ()); - await CheckProps (new_val, jp.Value, $"{label}-{actual_val["objectId"]?.Value()}"); - - continue; - } - - var exp_val_str = jp.Value.Value (); - bool null_or_empty_exp_val = String.IsNullOrEmpty (exp_val_str); - - var actual_field_val = actual_val.Values ().FirstOrDefault (a_jp => a_jp.Name == jp.Name); - var actual_field_val_str = actual_field_val?.Value?.Value (); - 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 ()}\n" + - $"Actual: {actual_field_val.Value.Value ()}"); - } - } - - internal async Task 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 ()); - - return await GetProperties (frame ["callFrameId"].Value ()); - } - - internal async Task GetObjectOnFrame (JToken frame, string name) - { - var locals = await GetProperties (frame ["callFrameId"].Value ()); - return await GetObjectOnLocals (locals, name); - } - - // Find an object with @name, *fetch* the object, and check against @o - internal async Task 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 GetObjectOnLocals (JToken locals, string name) - { - var obj = GetAndAssertObjectWithName (locals, name); - var objectId = obj ["value"]["objectId"]?.Value (); - 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 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 (); - } - - 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 () == "length" && p ["enumerable"]?.Value () != true) { - p.Remove (); - break; - } - } - } - - return locals; - } - - internal async Task 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 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 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 (); - var m_line = res.Value ["result"]["line"].Value (); - - 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 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 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 b__11_0 (Math.GenericStruct)` - - .. pass target "as `target: "void |(Math.GenericStruct)"` - */ - 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 scripts; - - public bool UseCallFunctionOnBeforeGetProperties; - - public DebugTestContext (InspectorClient cli, Inspector insp, CancellationToken token, Dictionary scripts) - { - this.cli = cli; - this.insp = insp; - this.token = token; - this.scripts = scripts; - } - } - - enum StepKind - { - Into, - Over, - Out - } -} + class Inspector + { + // InspectorClient client; + Dictionary> notifications = new Dictionary>(); + Dictionary> eventListeners = new Dictionary>(); + + public const string PAUSE = "pause"; + public const string READY = "ready"; + + public Task 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(); + 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 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 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())) + { + 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 dicScriptsIdToUrl; + internal Dictionary dicFileToUrl; + internal Dictionary SubscribeToScripts(Inspector insp) + { + dicScriptsIdToUrl = new Dictionary(); + dicFileToUrl = new Dictionary(); + insp.On("Debugger.scriptParsed", async (args, c) => + { + var script_id = args?["scriptId"]?.Value(); + var url = args["url"]?.Value(); + if (script_id.StartsWith("dotnet://")) + { + var dbgUrl = args["dotNetUrl"]?.Value(); + var arrStr = dbgUrl.Split("/"); + dbgUrl = arrStr[0] + "/" + arrStr[1] + "/" + arrStr[2] + "/" + arrStr[arrStr.Length - 1]; + dicScriptsIdToUrl[script_id] = dbgUrl; + dicFileToUrl[dbgUrl] = args["url"]?.Value(); + } + 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 test_fn = null, Func 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()); + + 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 locals_fn = null, Func 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()); + + Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); + + 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()); + locals_fn(locals); + } + }); + } + + internal void CheckLocation(string script_loc, int line, int column, Dictionary scripts, JToken location) + { + var loc_str = $"{ scripts[location["scriptId"].Value()] }" + + $"#{ location["lineNumber"].Value() }" + + $"#{ location["columnNumber"].Value() }"; + + var expected_loc_str = $"{script_loc}#{line}#{column}"; + Assert.Equal(expected_loc_str, loc_str); + } + + internal void CheckNumber(JToken locals, string name, T value) + { + foreach (var l in locals) + { + if (name != l["name"]?.Value()) + continue; + var val = l["value"]; + Assert.Equal("number", val["type"]?.Value()); + Assert.Equal(value, val["value"].Value()); + Assert.Equal(value.ToString(), val["description"].Value().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()); + + return l; + } + + internal async Task 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(), "className"); + AssertEqual(exp_dt.ToString(), v["description"]?.Value(), "description"); + + var members = await GetProperties(v["objectId"]?.Value()); + + // 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(); + 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() == name); + if (l == null) + Assert.True(false, $"Could not find variable '{name}'"); + return l; + } + + internal async Task 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 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()); + } + + // Place a breakpoint in the given method and run until its hit + // Return the Debugger.paused data + internal async Task 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 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(), + 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 StepAndCheck(StepKind kind, string script_loc, int line, int column, string function_name, + Func wait_for_event_fn = null, Action 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 EvaluateAndCheck(string expression, string script_loc, int line, int column, string function_name, + Func wait_for_event_fn = null, Action 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 SendCommandAndCheck(JObject args, string method, string script_loc, int line, int column, string function_name, + Func wait_for_event_fn = null, Action 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()); + + 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()); + 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(), $"{label}-type"); + AssertEqual(exp_val["className"]?.Value(), actual_val["className"]?.Value(), $"{label}-className"); + + var actual_target = actual_val["description"]?.Value(); + Assert.True(actual_target != null, $"${label}-description"); + var exp_target = exp_val["target"].Value(); + + CheckDelegateTarget(actual_target, exp_target); + + var del_props = await GetProperties(actual_val["objectId"]?.Value()); + AssertEqual(1, del_props.Count(), $"${label}-delegate-properties-count"); + + var obj = del_props.Where(jt => jt["name"]?.Value() == "Target").FirstOrDefault(); + Assert.True(obj != null, $"[{label}] Property named 'Target' found found in delegate properties"); + + AssertEqual("symbol", obj["value"]?["type"]?.Value(), $"{label}#Target#type"); + CheckDelegateTarget(obj["value"]?["value"]?.Value(), 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(); + switch (ctype) + { + case "delegate": + await CheckDelegate(actual_val, exp_val, label); + break; + + case "pointer": + { + + if (exp_val["is_null"]?.Value() == true) + { + AssertEqual("symbol", actual_val["type"]?.Value(), $"{label}-type"); + + var exp_val_str = $"({exp_val["type_name"]?.Value()}) 0"; + AssertEqual(exp_val_str, actual_val["value"]?.Value(), $"{label}-value"); + AssertEqual(exp_val_str, actual_val["description"]?.Value(), $"{label}-description"); + } + else if (exp_val["is_void"]?.Value() == true) + { + AssertEqual("symbol", actual_val["type"]?.Value(), $"{label}-type"); + + var exp_val_str = $"({exp_val["type_name"]?.Value()})"; + AssertStartsWith(exp_val_str, actual_val["value"]?.Value(), $"{label}-value"); + AssertStartsWith(exp_val_str, actual_val["description"]?.Value(), $"{label}-description"); + } + else + { + AssertEqual("object", actual_val["type"]?.Value(), $"{label}-type"); + + var exp_prefix = $"({exp_val["type_name"]?.Value()})"; + AssertStartsWith(exp_prefix, actual_val["className"]?.Value(), $"{label}-className"); + AssertStartsWith(exp_prefix, actual_val["description"]?.Value(), $"{label}-description"); + Assert.False(actual_val["className"]?.Value() == $"{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(), $"{label}-className"); + AssertStartsWith($"get {exp_val["type_name"]?.Value()} ()", get["description"]?.Value(), $"{label}-description"); + AssertEqual("function", get["type"]?.Value(), $"{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(), $"{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().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() == 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()); + await CheckProps(actual_props, exp_val, $"{label}-{exp_name}"); + } + else if (exp_val["__custom_type"] != null && exp_val["__custom_type"]?.Value() == "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()); + await CheckProps(new_val, exp_val, $"{label}-{actual_val["objectId"]?.Value()}"); + return; + } + + foreach (var jp in exp_val.Values()) + { + if (jp.Value.Type == JTokenType.Object) + { + var new_val = await GetProperties(actual_val["objectId"].Value()); + await CheckProps(new_val, jp.Value, $"{label}-{actual_val["objectId"]?.Value()}"); + + continue; + } + + var exp_val_str = jp.Value.Value(); + bool null_or_empty_exp_val = String.IsNullOrEmpty(exp_val_str); + + var actual_field_val = actual_val.Values().FirstOrDefault(a_jp => a_jp.Name == jp.Name); + var actual_field_val_str = actual_field_val?.Value?.Value(); + 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()}\n" + + $"Actual: {actual_field_val.Value.Value()}"); + } + } + + internal async Task 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()); + + return await GetProperties(frame["callFrameId"].Value()); + } + + internal async Task GetObjectOnFrame(JToken frame, string name) + { + var locals = await GetProperties(frame["callFrameId"].Value()); + return await GetObjectOnLocals(locals, name); + } + + // Find an object with @name, *fetch* the object, and check against @o + internal async Task 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 GetObjectOnLocals(JToken locals, string name) + { + var obj = GetAndAssertObjectWithName(locals, name); + var objectId = obj["value"]["objectId"]?.Value(); + 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 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(); + } + + 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() == "length" && p["enumerable"]?.Value() != true) + { + p.Remove(); + break; + } + } + } + + return locals; + } + + internal async Task 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 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 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 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(); + var m_line = res.Value["result"]["line"].Value(); + + 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 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 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 b__11_0 (Math.GenericStruct)` + + .. pass target "as `target: "void |(Math.GenericStruct)"` + */ + 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 scripts; + + public bool UseCallFunctionOnBeforeGetProperties; + + public DebugTestContext(InspectorClient cli, Inspector insp, CancellationToken token, Dictionary 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(), 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(ctx.Configuration); + services.Configure(options => + { + options.ChromePath = options.ChromePath ?? chromePath; + options.AppPath = appPath; + options.PagePath = pagePath; + options.DevToolsUrl = new Uri("http://localhost:0"); + }); + }) + .UseStartup() + .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(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> extract_conn_url) + { + + if (!context.WebSockets.IsWebSocketRequest) + { + context.Response.StatusCode = 400; + return; + } + + var tcs = new TaskCompletionSource(); + + 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 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(); + 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 ()?.Count); - - var loc = bp1_res.Value ["locations"]?.Value ()[0]; - - Assert.NotNull (loc ["scriptId"]); - Assert.Equal("dotnet://debugger-test.dll/debugger-test.cs", scripts [loc["scriptId"]?.Value ()]); - 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 ()?.Count); - - var loc = bp1_res.Value ["locations"]?.Value ()[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 ()?.Count); - - var loc2 = bp2_res.Value ["locations"]?.Value ()[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 ()?.Count); - - var loc = bp1_res.Value ["locations"]?.Value ()[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 ()?.Count); + [Fact] + public async Task CreateGoodBreakpoint() + { + var insp = new Inspector(); - var loc2 = bp2_res.Value ["locations"]?.Value ()[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()?.Count); + + var loc = bp1_res.Value["locations"]?.Value()[0]; - var scripts = SubscribeToScripts(insp); + Assert.NotNull(loc["scriptId"]); + Assert.Equal("dotnet://debugger-test.dll/debugger-test.cs", scripts[loc["scriptId"]?.Value()]); + 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 ()?.Count); + //Collect events + var scripts = SubscribeToScripts(insp); - var loc = bp1_res.Value ["locations"]?.Value ()[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()?.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 ()?.Count); + var loc = bp1_res.Value["locations"]?.Value()[0]; - var loc2 = bp2_res.Value ["locations"]?.Value ()[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()?.Count); - [Fact] - public async Task CreateBadBreakpoint () { - var insp = new Inspector (); + var loc2 = bp2_res.Value["locations"]?.Value()[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()?.Count); + + var loc = bp1_res.Value["locations"]?.Value()[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()?.Count); + + var loc2 = bp2_res.Value["locations"]?.Value()[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()); - //Assert.Equal ((int)MonoErrorCodes.BpNotFound, bp1_res.Error ["code"]?.Value ()); - }); - } + [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()?.Count); + + var loc = bp1_res.Value["locations"]?.Value()[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()?.Count); + + var loc2 = bp2_res.Value["locations"]?.Value()[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()); + //Assert.Equal ((int)MonoErrorCodes.BpNotFound, bp1_res.Error ["code"]?.Value ()); + }); + } + + [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()); + Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); + + var top_frame = pause_location["callFrames"][0]; + Assert.Equal("IntAdd", top_frame["functionName"].Value()); + Assert.Contains("debugger-test.cs", top_frame["url"].Value()); + + 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()); + }); + } + + [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(async () => await insp.WaitFor("Runtime.exceptionThrown")); + var ex_json = JObject.Parse(ex.Message); + Assert.Equal(dicFileToUrl["/debugger-driver.html"], ex_json["exceptionDetails"]?["url"]?.Value()); + }); + + } + + [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"); + CheckObject(locals, "list_null", "System.Collections.Generic.Dictionary", is_null: true); + + CheckArray(locals, "list_arr", "System.Collections.Generic.Dictionary[]", 1); + CheckObject(locals, "list_arr_null", "System.Collections.Generic.Dictionary[]", is_null: true); + + // Unused locals + CheckObject(locals, "list_unused", "System.Collections.Generic.Dictionary"); + CheckObject(locals, "list_null_unused", "System.Collections.Generic.Dictionary", is_null: true); + + CheckArray(locals, "list_arr_unused", "System.Collections.Generic.Dictionary[]", 1); + CheckObject(locals, "list_arr_null_unused", "System.Collections.Generic.Dictionary[]", 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()); + + 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()); + + 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") + }, "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 ()); - Assert.Equal (bp.Value["breakpointId"]?.ToString(), pause_location ["hitBreakpoints"]?[0]?.Value ()); - - var top_frame = pause_location ["callFrames"][0]; - Assert.Equal ("IntAdd", top_frame ["functionName"].Value()); - Assert.Contains ("debugger-test.cs", top_frame ["url"].Value ()); - - 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 ()); - }); - } - - [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 (async () => await insp.WaitFor("Runtime.exceptionThrown")); - var ex_json = JObject.Parse (ex.Message); - Assert.Equal (dicFileToUrl["/debugger-driver.html"], ex_json ["exceptionDetails"]? ["url"]? .Value ()); - }); - - } - - [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"); - CheckObject (locals, "list_null", "System.Collections.Generic.Dictionary", is_null: true); - - CheckArray (locals, "list_arr", "System.Collections.Generic.Dictionary[]"); - CheckObject (locals, "list_arr_null", "System.Collections.Generic.Dictionary[]", is_null: true); - - // Unused locals - CheckObject (locals, "list_unused", "System.Collections.Generic.Dictionary"); - CheckObject (locals, "list_null_unused", "System.Collections.Generic.Dictionary", is_null: true); - - CheckObject (locals, "list_arr_unused", "System.Collections.Generic.Dictionary[]"); - CheckObject (locals, "list_arr_null_unused", "System.Collections.Generic.Dictionary[]", 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 ()); - - 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 ()); - - 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"); - - 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 ()); - 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()); + 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"); - 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"), - 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"); - } - - // 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#StringField"), - List = TObject ("System.Collections.Generic.List", 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"), - 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"), - 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"), - 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"), - 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"), - 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"), - 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"); - } - - // ----------- 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 ()); - 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 ()); - 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 ()); - 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 ()); - 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 ()); - 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 ()); - await CheckProps (locals, new { - ss_local = TObject ("DebuggerTests.ValueTypesTest.SimpleStruct"), - gs_local = TValueType ("DebuggerTests.ValueTypesTest.GenericStruct"), - 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"), - 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"), - 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#StringField"), - List = TObject ("System.Collections.Generic.List"), - 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 ()); - 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 ()); - }); - } - - [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", - "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()); - - await CheckProps (frame_locals, new { - sc_arg = TObject ("DebuggerTests.SimpleClass"), - @this = TValueType ("DebuggerTests.Point"), - local_gs = TValueType ("DebuggerTests.SimpleGenericStruct") - }, - "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 ()); - - pause_location = await SendCommandAndCheck (null, $"Debugger.stepInto", null, -1, -1, null); - var top_frame = pause_location ["callFrames"][0]; - - AssertEqual ("WriteLine", top_frame ["functionName"]?.Value (), "Expected to be in WriteLine method"); - var script_id = top_frame ["functionLocation"]["scriptId"].Value (); - 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()); + await CheckProps(locals, new + { + ss_local = TValueType("DebuggerTests.ValueTypesTest.SimpleStruct"), + gs_local = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + 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"), + 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"); + } + + // 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#StringField"), + List = TObject("System.Collections.Generic.List", 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"), + 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()); + { + 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"), + 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"), + 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()); + { + 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"), + 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"), + 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"); + } + + // ----------- 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()); + 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()); + 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()); + 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()); + 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()); + 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()); + await CheckProps(locals, new + { + ss_local = TObject("DebuggerTests.ValueTypesTest.SimpleStruct"), + gs_local = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + 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"), + 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"), + 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#StringField"), + List = TObject("System.Collections.Generic.List"), + 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()); + 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()); + }); + } + + [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", + "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()); + + await CheckProps(frame_locals, new + { + sc_arg = TObject("DebuggerTests.SimpleClass"), + @this = TValueType("DebuggerTests.Point"), + local_gs = TValueType("DebuggerTests.SimpleGenericStruct") + }, + "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()); + + pause_location = await SendCommandAndCheck(null, $"Debugger.stepInto", null, -1, -1, null); + var top_frame = pause_location["callFrames"][0]; + + AssertEqual("WriteLine", top_frame["functionName"]?.Value(), "Expected to be in WriteLine method"); + var script_id = top_frame["functionLocation"]["scriptId"].Value(); + 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 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(), "Expected Runtime.evaluate to return a string type result"); + return res.Value["result"]?["value"]?.Value(); + } + + async Task _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 (); var profilers = new List (); var native_libs = new List (); @@ -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 [] { - null, - new GenericClass { Id = "gclass_arr#1#Id", Color = RGB.Red, Value = 5 }, - new GenericClass { Id = "gclass_arr#2#Id", Color = RGB.Blue, Value = -12 }, - }; - - var gclass_arr_empty = new GenericClass [0]; - GenericClass [] 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 [] { - new SimpleGenericStruct { 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 { 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 [0]; - SimpleGenericStruct [] 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 ValueTypeLocalsAsync (bool call_other = false) - { - var gvclass_arr = new SimpleGenericStruct [] { - new SimpleGenericStruct { 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 { 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 [0]; - SimpleGenericStruct [] gvclass_arr_null = null; - Console.WriteLine ($"ValueTypeLocalsAsync: call_other: {call_other}"); - SimpleGenericStruct gvclass; - Point[] points = null; - - if (call_other) { - (gvclass, points) = await new ArrayTestsClass ().InstanceMethodValueTypeLocalsAsync> (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 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 [] { - new SimpleGenericStruct { - 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 { - 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 [0]; - SimpleGenericStruct [] 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 { 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 sc_arg) where T: SimpleClass - { - var local_gs = new SimpleGenericStruct { 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 - { - public string Id { get; set; } - public RGB Color { get; set; } - public T Value { get; set; } - } - - public struct SimpleGenericStruct - { - 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[] + { + null, + new GenericClass { Id = "gclass_arr#1#Id", Color = RGB.Red, Value = 5 }, + new GenericClass { Id = "gclass_arr#2#Id", Color = RGB.Blue, Value = -12 }, + }; + + var gclass_arr_empty = new GenericClass[0]; + GenericClass[] 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[] + { + new SimpleGenericStruct { 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 { 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[0]; + SimpleGenericStruct[] 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 ValueTypeLocalsAsync(bool call_other = false) + { + var gvclass_arr = new SimpleGenericStruct[] + { + new SimpleGenericStruct { 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 { 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[0]; + SimpleGenericStruct[] gvclass_arr_null = null; + Console.WriteLine($"ValueTypeLocalsAsync: call_other: {call_other}"); + SimpleGenericStruct gvclass; + Point[] points = null; + + if (call_other) + { + (gvclass, points) = await new ArrayTestsClass().InstanceMethodValueTypeLocalsAsync>(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 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[] + { + new SimpleGenericStruct + { + 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 + { + 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[0]; + SimpleGenericStruct[] 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 { 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 sc_arg) where T : SimpleClass + { + var local_gs = new SimpleGenericStruct { 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 + { + public string Id { get; set; } + public RGB Color { get; set; } + public T Value { get; set; } + } + + public struct SimpleGenericStruct + { + 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 { 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 { 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> 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 { 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 { 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> 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 @@ } - + 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 (100, 200, "test"); + var f_s = new EvaluateTestsStruct(); + f_s.EvaluateTestsStructInstanceMethod(100, 200, "test"); + f_s.GenericInstanceMethodOnStruct(100, 200, "test"); - var f_g_s = new EvaluateTestsGenericStruct (); - f_g_s.EvaluateTestsGenericStructInstanceMethod (100, 200, "test"); - Console.WriteLine ($"a: {f.a}, b: {f.b}, c: {f.c}"); - } + var f_g_s = new EvaluateTestsGenericStruct(); + 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 (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(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 - { - 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 + { + 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 { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp }; - var gs_null = new GenericStructWithUnmanagedT { 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*[] { null, gsp, gsp_null }; - - var cwp = new GenericClassWithPointers { Ptr = dtp }; - var cwp_null = new GenericClassWithPointers(); - 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 { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp }; - var gs_null = new GenericStructWithUnmanagedT { 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*[] { null, gsp, gsp_null }; - - var cwp = new GenericClassWithPointers { Ptr = dtp }; - var cwp_null = new GenericClassWithPointers(); - 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 where T : unmanaged - { - public T Value; - public int IntField; - - public DateTime** DTPP; - } - - public unsafe class GenericClassWithPointers 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 { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp }; + var gs_null = new GenericStructWithUnmanagedT { 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*[] { null, gsp, gsp_null }; + + var cwp = new GenericClassWithPointers { Ptr = dtp }; + var cwp_null = new GenericClassWithPointers(); + 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 { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp }; + var gs_null = new GenericStructWithUnmanagedT { 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*[] { null, gsp, gsp_null }; + + var cwp = new GenericClassWithPointers { Ptr = dtp }; + var cwp_null = new GenericClassWithPointers(); + 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 where T : unmanaged + { + public T Value; + public int IntField; + + public DateTime** DTPP; + } + + public unsafe class GenericClassWithPointers 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 fn_func = (Math m) => m == null; - Func fn_func_null = null; - Func[] fn_func_arr = new Func[] { (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 fn_func_unused = (Math m) => m == null; - Func fn_func_null_unused = null; - Func[] fn_func_arr_unused = new Func[] { (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 (); - System.Collections.Generic.Dictionary list_null = null; - - var list_arr = new System.Collections.Generic.Dictionary[] { new System.Collections.Generic.Dictionary () }; - System.Collections.Generic.Dictionary[] 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 (); - System.Collections.Generic.Dictionary list_null_unused = null; - - var list_arr_unused = new System.Collections.Generic.Dictionary[] { new System.Collections.Generic.Dictionary () }; - System.Collections.Generic.Dictionary[] 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 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 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>, GenericStruct> fn_func = (m, gs) => new GenericStruct(); - Func>, GenericStruct> fn_func_del = GenericStruct.DelegateTargetForSignatureTest; - Func>, GenericStruct> fn_func_null = null; - Func fn_func_only_ret = () => { Console.WriteLine ($"hello"); return true; }; - var fn_func_arr = new Func>, GenericStruct>[] { (m, gs) => new GenericStruct() }; - - Math.DelegateForSignatureTest fn_del = GenericStruct.DelegateTargetForSignatureTest; - Math.DelegateForSignatureTest fn_del_l = (m, gs) => new GenericStruct { StringField = "fn_del_l#lambda" }; - var fn_del_arr = new Math.DelegateForSignatureTest[] { GenericStruct.DelegateTargetForSignatureTest, (m, gs) => new GenericStruct { StringField = "fn_del_arr#1#lambda" } }; - var m_obj = new Math (); - Math.DelegateForSignatureTest fn_del_null = null; - var gs_gs = new GenericStruct> - { - List = new System.Collections.Generic.List> { - new GenericStruct { StringField = "gs#List#0#StringField" }, - new GenericStruct { 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[] { - 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(); - 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> fn_action = (_) => { }; - Action> fn_action_del = Math.DelegateTargetWithVoidReturn; - Action fn_action_bare = () => {}; - Action> fn_action_null = null; - var fn_action_arr = new Action>[] { - (gs) => new GenericStruct(), - Math.DelegateTargetWithVoidReturn, - null - }; - - var gs = new GenericStruct(); - fn_action (gs); - fn_action_del (gs); - fn_action_arr[0](gs); - fn_action_bare (); - OuterMethod (); - return 0; - } - - public static int NestedDelegatesTest () - { - Func, bool> fn_func = (_) => { return true; }; - Func, bool> fn_func_null = null; - var fn_func_arr = new Func, bool>[] { (gs) => { return true; } }; - - var fn_del_arr = new Func, bool>[] { DelegateTargetForNestedFunc> }; - var m_obj = new Math (); - Func, bool> fn_del_null = null; - Func 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.DelegateTargetForSignatureTest, - (m, gs) => new GenericStruct () - }; - Func _fn_func = (cs) => cs.Length == 0; - Action[]> _fn_action = (gss) => { }; - - new Math ().MethodWithDelegateArgs (_dst_arr, _fn_func, _fn_action); - } - - void MethodWithDelegateArgs (Math.DelegateForSignatureTest[] dst_arr, Func fn_func, - Action[]> 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.DelegateTargetForSignatureTest, - (m, gs) => new GenericStruct () - }; - Func _fn_func = (cs) => cs.Length == 0; - Action[]> _fn_action = (gss) => { }; - - Console.WriteLine ($"Placeholder for breakpoint"); - await System.Threading.Tasks.Task.CompletedTask; - } - - public delegate void DelegateWithVoidReturn (GenericStruct gs); - public static void DelegateTargetWithVoidReturn (GenericStruct gs) { } - - delegate GenericStruct DelegateForSignatureTest (Math m, GenericStruct> gs); - static bool DelegateTargetForNestedFunc(T arg) => true; - - public struct SimpleStruct - { - public DateTime dt; - public GenericStruct gs; - } - - public struct GenericStruct - { - public System.Collections.Generic.List List; - public string StringField; - - public static GenericStruct DelegateTargetForSignatureTest (Math m, GenericStruct> gs) - => new GenericStruct (); - } +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 fn_func = (Math m) => m == null; + Func fn_func_null = null; + Func[] fn_func_arr = new Func[] { + (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 fn_func_unused = (Math m) => m == null; + Func fn_func_null_unused = null; + Func[] fn_func_arr_unused = new Func[] { (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(); + System.Collections.Generic.Dictionary list_null = null; + + var list_arr = new System.Collections.Generic.Dictionary[] { new System.Collections.Generic.Dictionary() }; + System.Collections.Generic.Dictionary[] 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(); + System.Collections.Generic.Dictionary list_null_unused = null; + + var list_arr_unused = new System.Collections.Generic.Dictionary[] { new System.Collections.Generic.Dictionary() }; + System.Collections.Generic.Dictionary[] 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 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 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>, GenericStruct> fn_func = (m, gs) => new GenericStruct(); + Func>, GenericStruct> fn_func_del = GenericStruct.DelegateTargetForSignatureTest; + Func>, GenericStruct> fn_func_null = null; + Func fn_func_only_ret = () => { Console.WriteLine($"hello"); return true; }; + var fn_func_arr = new Func>, GenericStruct>[] { + (m, gs) => new GenericStruct () }; + + Math.DelegateForSignatureTest fn_del = GenericStruct.DelegateTargetForSignatureTest; + Math.DelegateForSignatureTest fn_del_l = (m, gs) => new GenericStruct { StringField = "fn_del_l#lambda" }; + var fn_del_arr = new Math.DelegateForSignatureTest[] { GenericStruct.DelegateTargetForSignatureTest, (m, gs) => new GenericStruct { StringField = "fn_del_arr#1#lambda" } }; + var m_obj = new Math(); + Math.DelegateForSignatureTest fn_del_null = null; + var gs_gs = new GenericStruct> + { + List = new System.Collections.Generic.List> + { + new GenericStruct { StringField = "gs#List#0#StringField" }, + new GenericStruct { 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[] + { + 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(); + 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> fn_action = (_) => { }; + Action> fn_action_del = Math.DelegateTargetWithVoidReturn; + Action fn_action_bare = () => { }; + Action> fn_action_null = null; + var fn_action_arr = new Action>[] + { + (gs) => new GenericStruct(), + Math.DelegateTargetWithVoidReturn, + null + }; + + var gs = new GenericStruct(); + fn_action(gs); + fn_action_del(gs); + fn_action_arr[0](gs); + fn_action_bare(); + OuterMethod(); + return 0; + } + + public static int NestedDelegatesTest() + { + Func, bool> fn_func = (_) => { return true; }; + Func, bool> fn_func_null = null; + var fn_func_arr = new Func, bool>[] { + (gs) => { return true; } }; + + var fn_del_arr = new Func, bool>[] { DelegateTargetForNestedFunc> }; + var m_obj = new Math(); + Func, bool> fn_del_null = null; + Func 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.DelegateTargetForSignatureTest, + (m, gs) => new GenericStruct() + }; + Func _fn_func = (cs) => cs.Length == 0; + Action[]> _fn_action = (gss) => { }; + + new Math().MethodWithDelegateArgs(_dst_arr, _fn_func, _fn_action); + } + + void MethodWithDelegateArgs(Math.DelegateForSignatureTest[] dst_arr, Func fn_func, + Action[]> 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.DelegateTargetForSignatureTest, + (m, gs) => new GenericStruct() + }; + Func _fn_func = (cs) => cs.Length == 0; + Action[]> _fn_action = (gss) => { }; + + Console.WriteLine($"Placeholder for breakpoint"); + await System.Threading.Tasks.Task.CompletedTask; + } + + public delegate void DelegateWithVoidReturn(GenericStruct gs); + public static void DelegateTargetWithVoidReturn(GenericStruct gs) { } + + delegate GenericStruct DelegateForSignatureTest(Math m, GenericStruct> gs); + static bool DelegateTargetForNestedFunc(T arg) => true; + + public struct SimpleStruct + { + public DateTime dt; + public GenericStruct gs; + } + + public struct GenericStruct + { + public System.Collections.Generic.List List; + public string StringField; + + public static GenericStruct DelegateTargetForSignatureTest(Math m, GenericStruct> gs) => new GenericStruct(); + } } 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 { StringField = "gs_local#GenericStruct#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 MethodWithLocalStructsStaticAsync () - { - var ss_local = new SimpleStruct ("set in MethodWithLocalStructsStaticAsync", 1, DateTimeKind.Utc); - var gs_local = new GenericStruct { - StringField = "gs_local#GenericStruct#StringField", - List = new System.Collections.Generic.List { 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 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 { - StringField = $"{str}#SimpleStruct#gs#StringField", - List = new System.Collections.Generic.List { new DateTime (2010+f, 2+f, 3+f, 10+f, 2+f, 3+f) }, - Options = Options.Option1 - }; - Kind = kind; - } - - public Task AsyncMethodWithStructArgs (GenericStruct gs) - { - Console.WriteLine ($"placeholder line for a breakpoint"); - if (gs.List.Count > 0) - return Task.FromResult (true); - - return Task.FromResult (false); - } - } - - public struct GenericStruct - { - public System.Collections.Generic.List 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 { StringField = "gs_local#GenericStruct#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 MethodWithLocalStructsStaticAsync() + { + var ss_local = new SimpleStruct("set in MethodWithLocalStructsStaticAsync", 1, DateTimeKind.Utc); + var gs_local = new GenericStruct + { + StringField = "gs_local#GenericStruct#StringField", + List = new System.Collections.Generic.List { 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 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 + { + StringField = $"{str}#SimpleStruct#gs#StringField", + List = new System.Collections.Generic.List { new DateTime(2010 + f, 2 + f, 3 + f, 10 + f, 2 + f, 3 + f) }, + Options = Options.Option1 + }; + Kind = kind; + } + + public Task AsyncMethodWithStructArgs(GenericStruct gs) + { + Console.WriteLine($"placeholder line for a breakpoint"); + if (gs.List.Count > 0) + return Task.FromResult(true); + + return Task.FromResult(false); + } + } + + public struct GenericStruct + { + public System.Collections.Generic.List 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; + } + } +} -- cgit v1.2.3