зеркало из https://github.com/CryptoPro/runtime.git
[wasm] Moving wasm debugger to dotnet/runtime (#40146)
* Moving Wasm debugger to dotnet/runtime The build is working. The tests are running. - make -C src/mono/wasm/ run-debugger-tests
This commit is contained in:
Родитель
8140826a72
Коммит
03d2e48212
|
@ -139,3 +139,27 @@ run-tests-jsc-%:
|
|||
|
||||
run-tests-%:
|
||||
PATH="$(JSVU):$(PATH)" $(DOTNET) build $(TOP)/src/libraries/$*/tests/ /t:Test /p:TargetOS=Browser /p:TargetArchitecture=wasm /p:Configuration=$(CONFIG) $(MSBUILD_ARGS)
|
||||
|
||||
build-debugger-test-app:
|
||||
$(DOTNET) build --configuration debug --nologo /p:TargetArchitecture=wasm /p:TargetOS=Browser /p:Configuration=Debug /p:RuntimeConfiguration=$(CONFIG) $(TOP)/src/mono/wasm/debugger/tests
|
||||
cp $(TOP)/src/mono/wasm/debugger/tests/debugger-driver.html $(TOP)/src/mono/wasm/debugger/tests/bin/Debug/publish
|
||||
cp $(TOP)/src/mono/wasm/debugger/tests/other.js $(TOP)/src/mono/wasm/debugger/tests/bin/Debug/publish
|
||||
cp $(TOP)/src/mono/wasm/debugger/tests/runtime-debugger.js $(TOP)/src/mono/wasm/debugger/tests/bin/Debug/publish
|
||||
|
||||
run-debugger-tests: build-debugger-test-app build-dbg-testsuite
|
||||
if [ ! -z "$(TEST_FILTER)" ]; then \
|
||||
export TEST_SUITE_PATH=$(TOP)/src/mono/wasm/debugger/tests/bin/Debug/publish; \
|
||||
export LC_ALL=en_US.UTF-8; \
|
||||
$(DOTNET) test $(TOP)/src/mono/wasm/debugger/DebuggerTestSuite --filter FullyQualifiedName~$(TEST_FILTER); \
|
||||
unset TEST_SUITE_PATH LC_ALL; \
|
||||
else \
|
||||
export TEST_SUITE_PATH=$(TOP)/src/mono/wasm/debugger/tests/bin/Debug/publish; \
|
||||
export LC_ALL=en_US.UTF-8; \
|
||||
$(DOTNET) test $(TOP)/src/mono/wasm/debugger/DebuggerTestSuite $(TEST_ARGS); \
|
||||
unset TEST_SUITE_PATH LC_ALL; \
|
||||
fi
|
||||
|
||||
build-dbg-proxy:
|
||||
$(DOTNET) build $(TOP)/src/mono/wasm/debugger/BrowserDebugHost
|
||||
build-dbg-testsuite:
|
||||
$(DOTNET) build $(TOP)/src/mono/wasm/debugger/DebuggerTestSuite
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BrowserDebugProxy\BrowserDebugProxy.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,92 @@
|
|||
// 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 ProxyOptions
|
||||
{
|
||||
public Uri DevToolsUrl { get; set; } = new Uri("http://localhost:9222");
|
||||
}
|
||||
|
||||
public class TestHarnessOptions : ProxyOptions
|
||||
{
|
||||
public string ChromePath { get; set; }
|
||||
public string AppPath { get; set; }
|
||||
public string PagePath { get; set; }
|
||||
public string NodeApp { get; set; }
|
||||
}
|
||||
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var host = new WebHostBuilder()
|
||||
.UseSetting("UseIISIntegration", false.ToString())
|
||||
.UseKestrel()
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.UseStartup<Startup>()
|
||||
.ConfigureAppConfiguration((hostingContext, config) =>
|
||||
{
|
||||
config.AddCommandLine(args);
|
||||
})
|
||||
.UseUrls("http://localhost:9300")
|
||||
.Build();
|
||||
|
||||
host.Run();
|
||||
}
|
||||
}
|
||||
|
||||
public class TestHarnessProxy
|
||||
{
|
||||
static IWebHost host;
|
||||
static Task hostTask;
|
||||
static CancellationTokenSource cts = new CancellationTokenSource();
|
||||
static object proxyLock = new object();
|
||||
|
||||
public static readonly Uri Endpoint = new Uri("http://localhost:9400");
|
||||
|
||||
public static Task Start(string chromePath, string appPath, string pagePath)
|
||||
{
|
||||
lock(proxyLock)
|
||||
{
|
||||
if (host != null)
|
||||
return hostTask;
|
||||
|
||||
host = WebHost.CreateDefaultBuilder()
|
||||
.UseSetting("UseIISIntegration", false.ToString())
|
||||
.ConfigureAppConfiguration((hostingContext, config) =>
|
||||
{
|
||||
config.AddEnvironmentVariables(prefix: "WASM_TESTS_");
|
||||
})
|
||||
.ConfigureServices((ctx, services) =>
|
||||
{
|
||||
services.Configure<TestHarnessOptions>(ctx.Configuration);
|
||||
services.Configure<TestHarnessOptions>(options =>
|
||||
{
|
||||
options.ChromePath = options.ChromePath ?? chromePath;
|
||||
options.AppPath = appPath;
|
||||
options.PagePath = pagePath;
|
||||
options.DevToolsUrl = new Uri("http://localhost:0");
|
||||
});
|
||||
})
|
||||
.UseStartup<TestHarnessStartup>()
|
||||
.UseUrls(Endpoint.ToString())
|
||||
.Build();
|
||||
hostTask = host.StartAsync(cts.Token);
|
||||
}
|
||||
|
||||
Console.WriteLine("WebServer Ready!");
|
||||
return hostTask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +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.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.WebAssembly.Diagnostics
|
||||
{
|
||||
internal class Startup
|
||||
{
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
|
||||
public void ConfigureServices(IServiceCollection services) =>
|
||||
services.AddRouting()
|
||||
.Configure<ProxyOptions>(Configuration);
|
||||
|
||||
public Startup(IConfiguration configuration) =>
|
||||
Configuration = configuration;
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IOptionsMonitor<ProxyOptions> optionsAccessor, IWebHostEnvironment env)
|
||||
{
|
||||
var options = optionsAccessor.CurrentValue;
|
||||
app.UseDeveloperExceptionPage()
|
||||
.UseWebSockets()
|
||||
.UseDebugProxy(options);
|
||||
}
|
||||
}
|
||||
|
||||
static class DebugExtensions
|
||||
{
|
||||
public static Dictionary<string, string> MapValues(Dictionary<string, string> response, HttpContext context, Uri debuggerHost)
|
||||
{
|
||||
var filtered = new Dictionary<string, string>();
|
||||
var request = context.Request;
|
||||
|
||||
foreach (var key in response.Keys)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case "devtoolsFrontendUrl":
|
||||
var front = response[key];
|
||||
filtered[key] = $"{debuggerHost.Scheme}://{debuggerHost.Authority}{front.Replace ($"ws={debuggerHost.Authority}", $"ws={request.Host}")}";
|
||||
break;
|
||||
case "webSocketDebuggerUrl":
|
||||
var page = new Uri(response[key]);
|
||||
filtered[key] = $"{page.Scheme}://{request.Host}{page.PathAndQuery}";
|
||||
break;
|
||||
default:
|
||||
filtered[key] = response[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseDebugProxy(this IApplicationBuilder app, ProxyOptions options) =>
|
||||
UseDebugProxy(app, options, MapValues);
|
||||
|
||||
public static IApplicationBuilder UseDebugProxy(
|
||||
this IApplicationBuilder app,
|
||||
ProxyOptions options,
|
||||
Func<Dictionary<string, string>, HttpContext, Uri, Dictionary<string, string>> mapFunc)
|
||||
{
|
||||
var devToolsHost = options.DevToolsUrl;
|
||||
app.UseRouter(router =>
|
||||
{
|
||||
router.MapGet("/", Copy);
|
||||
router.MapGet("/favicon.ico", Copy);
|
||||
router.MapGet("json", RewriteArray);
|
||||
router.MapGet("json/list", RewriteArray);
|
||||
router.MapGet("json/version", RewriteSingle);
|
||||
router.MapGet("json/new", RewriteSingle);
|
||||
router.MapGet("devtools/page/{pageId}", ConnectProxy);
|
||||
router.MapGet("devtools/browser/{pageId}", ConnectProxy);
|
||||
|
||||
string GetEndpoint(HttpContext context)
|
||||
{
|
||||
var request = context.Request;
|
||||
var requestPath = request.Path;
|
||||
return $"{devToolsHost.Scheme}://{devToolsHost.Authority}{request.Path}{request.QueryString}";
|
||||
}
|
||||
|
||||
async Task Copy(HttpContext context)
|
||||
{
|
||||
using(var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) })
|
||||
{
|
||||
var response = await httpClient.GetAsync(GetEndpoint(context));
|
||||
context.Response.ContentType = response.Content.Headers.ContentType.ToString();
|
||||
if ((response.Content.Headers.ContentLength ?? 0) > 0)
|
||||
context.Response.ContentLength = response.Content.Headers.ContentLength;
|
||||
var bytes = await response.Content.ReadAsByteArrayAsync();
|
||||
await context.Response.Body.WriteAsync(bytes);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
async Task RewriteSingle(HttpContext context)
|
||||
{
|
||||
var version = await ProxyGetJsonAsync<Dictionary<string, string>>(GetEndpoint(context));
|
||||
context.Response.ContentType = "application/json";
|
||||
await context.Response.WriteAsync(
|
||||
JsonSerializer.Serialize(mapFunc(version, context, devToolsHost)));
|
||||
}
|
||||
|
||||
async Task RewriteArray(HttpContext context)
|
||||
{
|
||||
var tabs = await ProxyGetJsonAsync<Dictionary<string, string>[]>(GetEndpoint(context));
|
||||
var alteredTabs = tabs.Select(t => mapFunc(t, context, devToolsHost)).ToArray();
|
||||
context.Response.ContentType = "application/json";
|
||||
await context.Response.WriteAsync(JsonSerializer.Serialize(alteredTabs));
|
||||
}
|
||||
|
||||
async Task ConnectProxy(HttpContext context)
|
||||
{
|
||||
if (!context.WebSockets.IsWebSocketRequest)
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return;
|
||||
}
|
||||
|
||||
var endpoint = new Uri($"ws://{devToolsHost.Authority}{context.Request.Path.ToString ()}");
|
||||
try
|
||||
{
|
||||
using var loggerFactory = LoggerFactory.Create(
|
||||
builder => builder.AddConsole().AddFilter(null, LogLevel.Information));
|
||||
var proxy = new DebuggerProxy(loggerFactory);
|
||||
var ideSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
|
||||
await proxy.Run(endpoint, ideSocket);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("got exception {0}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
return app;
|
||||
}
|
||||
|
||||
static async Task<T> ProxyGetJsonAsync<T>(string url)
|
||||
{
|
||||
using(var httpClient = new HttpClient())
|
||||
{
|
||||
var response = await httpClient.GetAsync(url);
|
||||
return await JsonSerializer.DeserializeAsync<T>(await response.Content.ReadAsStreamAsync());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,255 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.WebAssembly.Diagnostics
|
||||
{
|
||||
public class TestHarnessStartup
|
||||
{
|
||||
static Regex parseConnection = new Regex(@"listening on (ws?s://[^\s]*)");
|
||||
public TestHarnessStartup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; set; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddRouting()
|
||||
.Configure<TestHarnessOptions>(Configuration);
|
||||
}
|
||||
|
||||
async Task SendNodeVersion(HttpContext context)
|
||||
{
|
||||
Console.WriteLine("hello chrome! json/version");
|
||||
var resp_obj = new JObject();
|
||||
resp_obj["Browser"] = "node.js/v9.11.1";
|
||||
resp_obj["Protocol-Version"] = "1.1";
|
||||
|
||||
var response = resp_obj.ToString();
|
||||
await context.Response.WriteAsync(response, new CancellationTokenSource().Token);
|
||||
}
|
||||
|
||||
async Task SendNodeList(HttpContext context)
|
||||
{
|
||||
Console.WriteLine("hello chrome! json/list");
|
||||
try
|
||||
{
|
||||
var response = new JArray(JObject.FromObject(new
|
||||
{
|
||||
description = "node.js instance",
|
||||
devtoolsFrontendUrl = "chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=localhost:9300/91d87807-8a81-4f49-878c-a5604103b0a4",
|
||||
faviconUrl = "https://nodejs.org/static/favicon.ico",
|
||||
id = "91d87807-8a81-4f49-878c-a5604103b0a4",
|
||||
title = "foo.js",
|
||||
type = "node",
|
||||
webSocketDebuggerUrl = "ws://localhost:9300/91d87807-8a81-4f49-878c-a5604103b0a4"
|
||||
})).ToString();
|
||||
|
||||
Console.WriteLine($"sending: {response}");
|
||||
await context.Response.WriteAsync(response, new CancellationTokenSource().Token);
|
||||
}
|
||||
catch (Exception e) { Console.WriteLine(e); }
|
||||
}
|
||||
|
||||
public async Task LaunchAndServe(ProcessStartInfo psi, HttpContext context, Func<string, Task<string>> extract_conn_url)
|
||||
{
|
||||
|
||||
if (!context.WebSockets.IsWebSocketRequest)
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return;
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<string>();
|
||||
|
||||
var proc = Process.Start(psi);
|
||||
try
|
||||
{
|
||||
proc.ErrorDataReceived += (sender, e) =>
|
||||
{
|
||||
var str = e.Data;
|
||||
Console.WriteLine($"stderr: {str}");
|
||||
|
||||
if (tcs.Task.IsCompleted)
|
||||
return;
|
||||
|
||||
var match = parseConnection.Match(str);
|
||||
if (match.Success)
|
||||
{
|
||||
tcs.TrySetResult(match.Groups[1].Captures[0].Value);
|
||||
}
|
||||
};
|
||||
|
||||
proc.OutputDataReceived += (sender, e) =>
|
||||
{
|
||||
Console.WriteLine($"stdout: {e.Data}");
|
||||
};
|
||||
|
||||
proc.BeginErrorReadLine();
|
||||
proc.BeginOutputReadLine();
|
||||
|
||||
if (await Task.WhenAny(tcs.Task, Task.Delay(5000)) != tcs.Task)
|
||||
{
|
||||
Console.WriteLine("Didnt get the con string after 5s.");
|
||||
throw new Exception("node.js timedout");
|
||||
}
|
||||
var line = await tcs.Task;
|
||||
var con_str = extract_conn_url != null ? await extract_conn_url(line) : line;
|
||||
|
||||
Console.WriteLine($"launching proxy for {con_str}");
|
||||
|
||||
using var loggerFactory = LoggerFactory.Create(
|
||||
builder => builder.AddConsole().AddFilter(null, LogLevel.Information));
|
||||
var proxy = new DebuggerProxy(loggerFactory);
|
||||
var browserUri = new Uri(con_str);
|
||||
var ideSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
|
||||
await proxy.Run(browserUri, ideSocket);
|
||||
Console.WriteLine("Proxy done");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("got exception {0}", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
proc.CancelErrorRead();
|
||||
proc.CancelOutputRead();
|
||||
proc.Kill();
|
||||
proc.WaitForExit();
|
||||
proc.Close();
|
||||
}
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IOptionsMonitor<TestHarnessOptions> optionsAccessor, IWebHostEnvironment env)
|
||||
{
|
||||
app.UseWebSockets();
|
||||
app.UseStaticFiles();
|
||||
|
||||
TestHarnessOptions options = optionsAccessor.CurrentValue;
|
||||
|
||||
var provider = new FileExtensionContentTypeProvider();
|
||||
provider.Mappings[".wasm"] = "application/wasm";
|
||||
|
||||
app.UseStaticFiles(new StaticFileOptions
|
||||
{
|
||||
FileProvider = new PhysicalFileProvider(options.AppPath),
|
||||
ServeUnknownFileTypes = true, //Cuz .wasm is not a known file type :cry:
|
||||
RequestPath = "",
|
||||
ContentTypeProvider = provider
|
||||
});
|
||||
|
||||
var devToolsUrl = options.DevToolsUrl;
|
||||
app.UseRouter(router =>
|
||||
{
|
||||
router.MapGet("launch-chrome-and-connect", async context =>
|
||||
{
|
||||
Console.WriteLine("New test request");
|
||||
try
|
||||
{
|
||||
var client = new HttpClient();
|
||||
var psi = new ProcessStartInfo();
|
||||
|
||||
psi.Arguments = $"--headless --disable-gpu --lang=en-US --incognito --remote-debugging-port={devToolsUrl.Port} http://{TestHarnessProxy.Endpoint.Authority}/{options.PagePath}";
|
||||
psi.UseShellExecute = false;
|
||||
psi.FileName = options.ChromePath;
|
||||
psi.RedirectStandardError = true;
|
||||
psi.RedirectStandardOutput = true;
|
||||
|
||||
await LaunchAndServe(psi, context, async(str) =>
|
||||
{
|
||||
var start = DateTime.Now;
|
||||
JArray obj = null;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Unfortunately it does look like we have to wait
|
||||
// for a bit after getting the response but before
|
||||
// making the list request. We get an empty result
|
||||
// if we make the request too soon.
|
||||
await Task.Delay(100);
|
||||
|
||||
var res = await client.GetStringAsync(new Uri(new Uri(str), "/json/list"));
|
||||
Console.WriteLine("res is {0}", res);
|
||||
|
||||
if (!String.IsNullOrEmpty(res))
|
||||
{
|
||||
// Sometimes we seem to get an empty array `[ ]`
|
||||
obj = JArray.Parse(res);
|
||||
if (obj != null && obj.Count >= 1)
|
||||
break;
|
||||
}
|
||||
|
||||
var elapsed = DateTime.Now - start;
|
||||
if (elapsed.Milliseconds > 5000)
|
||||
{
|
||||
Console.WriteLine($"Unable to get DevTools /json/list response in {elapsed.Seconds} seconds, stopping");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var wsURl = obj[0] ? ["webSocketDebuggerUrl"]?.Value<string>();
|
||||
Console.WriteLine(">>> {0}", wsURl);
|
||||
|
||||
return wsURl;
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"launch-chrome-and-connect failed with {ex.ToString ()}");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (options.NodeApp != null)
|
||||
{
|
||||
Console.WriteLine($"Doing the nodejs: {options.NodeApp}");
|
||||
var nodeFullPath = Path.GetFullPath(options.NodeApp);
|
||||
Console.WriteLine(nodeFullPath);
|
||||
var psi = new ProcessStartInfo();
|
||||
|
||||
psi.UseShellExecute = false;
|
||||
psi.RedirectStandardError = true;
|
||||
psi.RedirectStandardOutput = true;
|
||||
|
||||
psi.Arguments = $"--inspect-brk=localhost:0 {nodeFullPath}";
|
||||
psi.FileName = "node";
|
||||
|
||||
app.UseRouter(router =>
|
||||
{
|
||||
//Inspector API for using chrome devtools directly
|
||||
router.MapGet("json", SendNodeList);
|
||||
router.MapGet("json/list", SendNodeList);
|
||||
router.MapGet("json/version", SendNodeVersion);
|
||||
router.MapGet("launch-done-and-connect", async context =>
|
||||
{
|
||||
await LaunchAndServe(psi, context, null);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
|
@ -0,0 +1,15 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.2" />
|
||||
<PackageReference Include="Mono.Cecil" Version="0.11.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.28407.52
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BrowserDebugHost", "..\BrowserDebugHost\BrowserDebugHost.csproj", "{954F768A-23E6-4B14-90E0-27EA6B41FBCC}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BrowserDebugProxy", "BrowserDebugProxy.csproj", "{490128B6-9F21-46CA-878A-F22BCF51EF3C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{954F768A-23E6-4B14-90E0-27EA6B41FBCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{954F768A-23E6-4B14-90E0-27EA6B41FBCC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{954F768A-23E6-4B14-90E0-27EA6B41FBCC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{954F768A-23E6-4B14-90E0-27EA6B41FBCC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{490128B6-9F21-46CA-878A-F22BCF51EF3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{490128B6-9F21-46CA-878A-F22BCF51EF3C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{490128B6-9F21-46CA-878A-F22BCF51EF3C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{490128B6-9F21-46CA-878A-F22BCF51EF3C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {F8BA2C2D-8F28-4F9E-9C54-51E394EF941E}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Mono.Cecil.Pdb;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.WebAssembly.Diagnostics
|
||||
{
|
||||
internal class BreakpointRequest
|
||||
{
|
||||
public string Id { get; private set; }
|
||||
public string Assembly { get; private set; }
|
||||
public string File { get; private set; }
|
||||
public int Line { get; private set; }
|
||||
public int Column { get; private set; }
|
||||
public MethodInfo Method { get; private set; }
|
||||
|
||||
JObject request;
|
||||
|
||||
public bool IsResolved => Assembly != null;
|
||||
public List<Breakpoint> Locations { get; } = new List<Breakpoint>();
|
||||
|
||||
public override string ToString() => $"BreakpointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}";
|
||||
|
||||
public object AsSetBreakpointByUrlResponse(IEnumerable<object> jsloc) => new { breakpointId = Id, locations = Locations.Select(l => l.Location.AsLocation()).Concat(jsloc) };
|
||||
|
||||
public BreakpointRequest()
|
||||
{ }
|
||||
|
||||
public BreakpointRequest(string id, MethodInfo method)
|
||||
{
|
||||
Id = id;
|
||||
Method = method;
|
||||
}
|
||||
|
||||
public BreakpointRequest(string id, JObject request)
|
||||
{
|
||||
Id = id;
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
public static BreakpointRequest Parse(string id, JObject args)
|
||||
{
|
||||
return new BreakpointRequest(id, args);
|
||||
}
|
||||
|
||||
public BreakpointRequest Clone() => new BreakpointRequest { Id = Id, request = request };
|
||||
|
||||
public bool IsMatch(SourceFile sourceFile)
|
||||
{
|
||||
var url = request?["url"]?.Value<string>();
|
||||
if (url == null)
|
||||
{
|
||||
var urlRegex = request?["urlRegex"].Value<string>();
|
||||
var regex = new Regex(urlRegex);
|
||||
return regex.IsMatch(sourceFile.Url.ToString()) || regex.IsMatch(sourceFile.DocUrl);
|
||||
}
|
||||
|
||||
return sourceFile.Url.ToString() == url || sourceFile.DotNetUrl == url;
|
||||
}
|
||||
|
||||
public bool TryResolve(SourceFile sourceFile)
|
||||
{
|
||||
if (!IsMatch(sourceFile))
|
||||
return false;
|
||||
|
||||
var line = request?["lineNumber"]?.Value<int>();
|
||||
var column = request?["columnNumber"]?.Value<int>();
|
||||
|
||||
if (line == null || column == null)
|
||||
return false;
|
||||
|
||||
Assembly = sourceFile.AssemblyName;
|
||||
File = sourceFile.DebuggerFileName;
|
||||
Line = line.Value;
|
||||
Column = column.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryResolve(DebugStore store)
|
||||
{
|
||||
if (request == null || store == null)
|
||||
return false;
|
||||
|
||||
return store.AllSources().FirstOrDefault(source => TryResolve(source)) != null;
|
||||
}
|
||||
}
|
||||
|
||||
internal class VarInfo
|
||||
{
|
||||
public VarInfo(VariableDebugInformation v)
|
||||
{
|
||||
this.Name = v.Name;
|
||||
this.Index = v.Index;
|
||||
}
|
||||
|
||||
public VarInfo(ParameterDefinition p)
|
||||
{
|
||||
this.Name = p.Name;
|
||||
this.Index = (p.Index + 1) * -1;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public int Index { get; }
|
||||
|
||||
public override string ToString() => $"(var-info [{Index}] '{Name}')";
|
||||
}
|
||||
|
||||
internal class CliLocation
|
||||
{
|
||||
public CliLocation(MethodInfo method, int offset)
|
||||
{
|
||||
Method = method;
|
||||
Offset = offset;
|
||||
}
|
||||
|
||||
public MethodInfo Method { get; }
|
||||
public int Offset { get; }
|
||||
}
|
||||
|
||||
internal class SourceLocation
|
||||
{
|
||||
SourceId id;
|
||||
int line;
|
||||
int column;
|
||||
CliLocation cliLoc;
|
||||
|
||||
public SourceLocation(SourceId id, int line, int column)
|
||||
{
|
||||
this.id = id;
|
||||
this.line = line;
|
||||
this.column = column;
|
||||
}
|
||||
|
||||
public SourceLocation(MethodInfo mi, SequencePoint sp)
|
||||
{
|
||||
this.id = mi.SourceId;
|
||||
this.line = sp.StartLine - 1;
|
||||
this.column = sp.StartColumn - 1;
|
||||
this.cliLoc = new CliLocation(mi, sp.Offset);
|
||||
}
|
||||
|
||||
public SourceId Id { get => id; }
|
||||
public int Line { get => line; }
|
||||
public int Column { get => column; }
|
||||
public CliLocation CliLocation => this.cliLoc;
|
||||
|
||||
public override string ToString() => $"{id}:{Line}:{Column}";
|
||||
|
||||
public static SourceLocation Parse(JObject obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return null;
|
||||
|
||||
if (!SourceId.TryParse(obj["scriptId"]?.Value<string>(), out var id))
|
||||
return null;
|
||||
|
||||
var line = obj["lineNumber"]?.Value<int>();
|
||||
var column = obj["columnNumber"]?.Value<int>();
|
||||
if (id == null || line == null || column == null)
|
||||
return null;
|
||||
|
||||
return new SourceLocation(id, line.Value, column.Value);
|
||||
}
|
||||
|
||||
internal class LocationComparer : EqualityComparer<SourceLocation>
|
||||
{
|
||||
public override bool Equals(SourceLocation l1, SourceLocation l2)
|
||||
{
|
||||
if (l1 == null && l2 == null)
|
||||
return true;
|
||||
else if (l1 == null || l2 == null)
|
||||
return false;
|
||||
|
||||
return (l1.Line == l2.Line &&
|
||||
l1.Column == l2.Column &&
|
||||
l1.Id == l2.Id);
|
||||
}
|
||||
|
||||
public override int GetHashCode(SourceLocation loc)
|
||||
{
|
||||
int hCode = loc.Line ^ loc.Column;
|
||||
return loc.Id.GetHashCode() ^ hCode.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
internal object AsLocation() => new
|
||||
{
|
||||
scriptId = id.ToString(),
|
||||
lineNumber = line,
|
||||
columnNumber = column
|
||||
};
|
||||
}
|
||||
|
||||
internal class SourceId
|
||||
{
|
||||
const string Scheme = "dotnet://";
|
||||
|
||||
readonly int assembly, document;
|
||||
|
||||
public int Assembly => assembly;
|
||||
public int Document => document;
|
||||
|
||||
internal SourceId(int assembly, int document)
|
||||
{
|
||||
this.assembly = assembly;
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
public SourceId(string id)
|
||||
{
|
||||
if (!TryParse(id, out assembly, out document))
|
||||
throw new ArgumentException("invalid source identifier", nameof(id));
|
||||
}
|
||||
|
||||
public static bool TryParse(string id, out SourceId source)
|
||||
{
|
||||
source = null;
|
||||
if (!TryParse(id, out var assembly, out var document))
|
||||
return false;
|
||||
|
||||
source = new SourceId(assembly, document);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool TryParse(string id, out int assembly, out int document)
|
||||
{
|
||||
assembly = document = 0;
|
||||
if (id == null || !id.StartsWith(Scheme, StringComparison.Ordinal))
|
||||
return false;
|
||||
|
||||
var sp = id.Substring(Scheme.Length).Split('_');
|
||||
if (sp.Length != 2)
|
||||
return false;
|
||||
|
||||
if (!int.TryParse(sp[0], out assembly))
|
||||
return false;
|
||||
|
||||
if (!int.TryParse(sp[1], out document))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString() => $"{Scheme}{assembly}_{document}";
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return false;
|
||||
SourceId that = obj as SourceId;
|
||||
return that.assembly == this.assembly && that.document == this.document;
|
||||
}
|
||||
|
||||
public override int GetHashCode() => assembly.GetHashCode() ^ document.GetHashCode();
|
||||
|
||||
public static bool operator ==(SourceId a, SourceId b) => ((object) a == null) ? (object) b == null : a.Equals(b);
|
||||
|
||||
public static bool operator !=(SourceId a, SourceId b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
internal class MethodInfo
|
||||
{
|
||||
MethodDefinition methodDef;
|
||||
SourceFile source;
|
||||
|
||||
public SourceId SourceId => source.SourceId;
|
||||
|
||||
public string Name => methodDef.Name;
|
||||
public MethodDebugInformation DebugInformation => methodDef.DebugInformation;
|
||||
|
||||
public SourceLocation StartLocation { get; }
|
||||
public SourceLocation EndLocation { get; }
|
||||
public AssemblyInfo Assembly { get; }
|
||||
public uint Token => methodDef.MetadataToken.RID;
|
||||
|
||||
public MethodInfo(AssemblyInfo assembly, MethodDefinition methodDef, SourceFile source)
|
||||
{
|
||||
this.Assembly = assembly;
|
||||
this.methodDef = methodDef;
|
||||
this.source = source;
|
||||
|
||||
var sps = DebugInformation.SequencePoints;
|
||||
if (sps == null || sps.Count() < 1)
|
||||
return;
|
||||
|
||||
SequencePoint start = sps[0];
|
||||
SequencePoint end = sps[0];
|
||||
|
||||
foreach (var sp in sps)
|
||||
{
|
||||
if (sp.StartLine < start.StartLine)
|
||||
start = sp;
|
||||
else if (sp.StartLine == start.StartLine && sp.StartColumn < start.StartColumn)
|
||||
start = sp;
|
||||
|
||||
if (sp.EndLine > end.EndLine)
|
||||
end = sp;
|
||||
else if (sp.EndLine == end.EndLine && sp.EndColumn > end.EndColumn)
|
||||
end = sp;
|
||||
}
|
||||
|
||||
StartLocation = new SourceLocation(this, start);
|
||||
EndLocation = new SourceLocation(this, end);
|
||||
}
|
||||
|
||||
public SourceLocation GetLocationByIl(int pos)
|
||||
{
|
||||
SequencePoint prev = null;
|
||||
foreach (var sp in DebugInformation.SequencePoints)
|
||||
{
|
||||
if (sp.Offset > pos)
|
||||
break;
|
||||
prev = sp;
|
||||
}
|
||||
|
||||
if (prev != null)
|
||||
return new SourceLocation(this, prev);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public VarInfo[] GetLiveVarsAt(int offset)
|
||||
{
|
||||
var res = new List<VarInfo>();
|
||||
|
||||
res.AddRange(methodDef.Parameters.Select(p => new VarInfo(p)));
|
||||
res.AddRange(methodDef.DebugInformation.GetScopes()
|
||||
.Where(s => s.Start.Offset <= offset && (s.End.IsEndOfMethod || s.End.Offset > offset))
|
||||
.SelectMany(s => s.Variables)
|
||||
.Where(v => !v.IsDebuggerHidden)
|
||||
.Select(v => new VarInfo(v)));
|
||||
|
||||
return res.ToArray();
|
||||
}
|
||||
|
||||
public override string ToString() => "MethodInfo(" + methodDef.FullName + ")";
|
||||
}
|
||||
|
||||
internal class TypeInfo
|
||||
{
|
||||
AssemblyInfo assembly;
|
||||
TypeDefinition type;
|
||||
List<MethodInfo> methods;
|
||||
|
||||
public TypeInfo(AssemblyInfo assembly, TypeDefinition type)
|
||||
{
|
||||
this.assembly = assembly;
|
||||
this.type = type;
|
||||
methods = new List<MethodInfo>();
|
||||
}
|
||||
|
||||
public string Name => type.Name;
|
||||
public string FullName => type.FullName;
|
||||
public List<MethodInfo> Methods => methods;
|
||||
|
||||
public override string ToString() => "TypeInfo('" + FullName + "')";
|
||||
}
|
||||
|
||||
class AssemblyInfo
|
||||
{
|
||||
static int next_id;
|
||||
ModuleDefinition image;
|
||||
readonly int id;
|
||||
readonly ILogger logger;
|
||||
Dictionary<uint, MethodInfo> methods = new Dictionary<uint, MethodInfo>();
|
||||
Dictionary<string, string> sourceLinkMappings = new Dictionary<string, string>();
|
||||
Dictionary<string, TypeInfo> typesByName = new Dictionary<string, TypeInfo>();
|
||||
readonly List<SourceFile> sources = new List<SourceFile>();
|
||||
internal string Url { get; }
|
||||
|
||||
public AssemblyInfo(IAssemblyResolver resolver, string url, byte[] assembly, byte[] pdb)
|
||||
{
|
||||
this.id = Interlocked.Increment(ref next_id);
|
||||
|
||||
try
|
||||
{
|
||||
Url = url;
|
||||
ReaderParameters rp = new ReaderParameters( /*ReadingMode.Immediate*/ );
|
||||
rp.AssemblyResolver = resolver;
|
||||
// set ReadSymbols = true unconditionally in case there
|
||||
// is an embedded pdb then handle ArgumentException
|
||||
// and assume that if pdb == null that is the cause
|
||||
rp.ReadSymbols = true;
|
||||
rp.SymbolReaderProvider = new PdbReaderProvider();
|
||||
if (pdb != null)
|
||||
rp.SymbolStream = new MemoryStream(pdb);
|
||||
rp.ReadingMode = ReadingMode.Immediate;
|
||||
|
||||
this.image = ModuleDefinition.ReadModule(new MemoryStream(assembly), rp);
|
||||
}
|
||||
catch (BadImageFormatException ex)
|
||||
{
|
||||
logger.LogWarning($"Failed to read assembly as portable PDB: {ex.Message}");
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// if pdb == null this is expected and we
|
||||
// read the assembly without symbols below
|
||||
if (pdb != null)
|
||||
throw;
|
||||
}
|
||||
|
||||
if (this.image == null)
|
||||
{
|
||||
ReaderParameters rp = new ReaderParameters( /*ReadingMode.Immediate*/ );
|
||||
rp.AssemblyResolver = resolver;
|
||||
if (pdb != null)
|
||||
{
|
||||
rp.ReadSymbols = true;
|
||||
rp.SymbolReaderProvider = new PdbReaderProvider();
|
||||
rp.SymbolStream = new MemoryStream(pdb);
|
||||
}
|
||||
|
||||
rp.ReadingMode = ReadingMode.Immediate;
|
||||
|
||||
this.image = ModuleDefinition.ReadModule(new MemoryStream(assembly), rp);
|
||||
}
|
||||
|
||||
Populate();
|
||||
}
|
||||
|
||||
public AssemblyInfo(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
void Populate()
|
||||
{
|
||||
ProcessSourceLink();
|
||||
|
||||
var d2s = new Dictionary<Document, SourceFile>();
|
||||
|
||||
SourceFile FindSource(Document doc)
|
||||
{
|
||||
if (doc == null)
|
||||
return null;
|
||||
|
||||
if (d2s.TryGetValue(doc, out var source))
|
||||
return source;
|
||||
|
||||
var src = new SourceFile(this, sources.Count, doc, GetSourceLinkUrl(doc.Url));
|
||||
sources.Add(src);
|
||||
d2s[doc] = src;
|
||||
return src;
|
||||
};
|
||||
|
||||
foreach (var type in image.GetTypes())
|
||||
{
|
||||
var typeInfo = new TypeInfo(this, type);
|
||||
typesByName[type.FullName] = typeInfo;
|
||||
|
||||
foreach (var method in type.Methods)
|
||||
{
|
||||
foreach (var sp in method.DebugInformation.SequencePoints)
|
||||
{
|
||||
var source = FindSource(sp.Document);
|
||||
var methodInfo = new MethodInfo(this, method, source);
|
||||
methods[method.MetadataToken.RID] = methodInfo;
|
||||
if (source != null)
|
||||
source.AddMethod(methodInfo);
|
||||
|
||||
typeInfo.Methods.Add(methodInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessSourceLink()
|
||||
{
|
||||
var sourceLinkDebugInfo = image.CustomDebugInformations.FirstOrDefault(i => i.Kind == CustomDebugInformationKind.SourceLink);
|
||||
|
||||
if (sourceLinkDebugInfo != null)
|
||||
{
|
||||
var sourceLinkContent = ((SourceLinkDebugInformation) sourceLinkDebugInfo).Content;
|
||||
|
||||
if (sourceLinkContent != null)
|
||||
{
|
||||
var jObject = JObject.Parse(sourceLinkContent) ["documents"];
|
||||
sourceLinkMappings = JsonConvert.DeserializeObject<Dictionary<string, string>>(jObject.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Uri GetSourceLinkUrl(string document)
|
||||
{
|
||||
if (sourceLinkMappings.TryGetValue(document, out string url))
|
||||
return new Uri(url);
|
||||
|
||||
foreach (var sourceLinkDocument in sourceLinkMappings)
|
||||
{
|
||||
string key = sourceLinkDocument.Key;
|
||||
|
||||
if (Path.GetFileName(key) != "*")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var keyTrim = key.TrimEnd('*');
|
||||
|
||||
if (document.StartsWith(keyTrim, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var docUrlPart = document.Replace(keyTrim, "");
|
||||
return new Uri(sourceLinkDocument.Value.TrimEnd('*') + docUrlPart);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IEnumerable<SourceFile> Sources => this.sources;
|
||||
|
||||
public Dictionary<string, TypeInfo> TypesByName => this.typesByName;
|
||||
public int Id => id;
|
||||
public string Name => image.Name;
|
||||
|
||||
public SourceFile GetDocById(int document)
|
||||
{
|
||||
return sources.FirstOrDefault(s => s.SourceId.Document == document);
|
||||
}
|
||||
|
||||
public MethodInfo GetMethodByToken(uint token)
|
||||
{
|
||||
methods.TryGetValue(token, out var value);
|
||||
return value;
|
||||
}
|
||||
|
||||
public TypeInfo GetTypeByName(string name)
|
||||
{
|
||||
typesByName.TryGetValue(name, out var res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
internal class SourceFile
|
||||
{
|
||||
Dictionary<uint, MethodInfo> methods;
|
||||
AssemblyInfo assembly;
|
||||
int id;
|
||||
Document doc;
|
||||
|
||||
internal SourceFile(AssemblyInfo assembly, int id, Document doc, Uri sourceLinkUri)
|
||||
{
|
||||
this.methods = new Dictionary<uint, MethodInfo>();
|
||||
this.SourceLinkUri = sourceLinkUri;
|
||||
this.assembly = assembly;
|
||||
this.id = id;
|
||||
this.doc = doc;
|
||||
this.DebuggerFileName = doc.Url.Replace("\\", "/").Replace(":", "");
|
||||
|
||||
this.SourceUri = new Uri((Path.IsPathRooted(doc.Url) ? "file://" : "") + doc.Url, UriKind.RelativeOrAbsolute);
|
||||
if (SourceUri.IsFile && File.Exists(SourceUri.LocalPath))
|
||||
{
|
||||
this.Url = this.SourceUri.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Url = DotNetUrl;
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddMethod(MethodInfo mi)
|
||||
{
|
||||
if (!this.methods.ContainsKey(mi.Token))
|
||||
this.methods[mi.Token] = mi;
|
||||
}
|
||||
|
||||
public string DebuggerFileName { get; }
|
||||
public string Url { get; }
|
||||
public string AssemblyName => assembly.Name;
|
||||
public string DotNetUrl => $"dotnet://{assembly.Name}/{DebuggerFileName}";
|
||||
|
||||
public SourceId SourceId => new SourceId(assembly.Id, this.id);
|
||||
public Uri SourceLinkUri { get; }
|
||||
public Uri SourceUri { get; }
|
||||
|
||||
public IEnumerable<MethodInfo> Methods => this.methods.Values;
|
||||
|
||||
public string DocUrl => doc.Url;
|
||||
|
||||
public(int startLine, int startColumn, int endLine, int endColumn) GetExtents()
|
||||
{
|
||||
var start = Methods.OrderBy(m => m.StartLocation.Line).ThenBy(m => m.StartLocation.Column).First();
|
||||
var end = Methods.OrderByDescending(m => m.EndLocation.Line).ThenByDescending(m => m.EndLocation.Column).First();
|
||||
return (start.StartLocation.Line, start.StartLocation.Column, end.EndLocation.Line, end.EndLocation.Column);
|
||||
}
|
||||
|
||||
async Task<MemoryStream> GetDataAsync(Uri uri, CancellationToken token)
|
||||
{
|
||||
var mem = new MemoryStream();
|
||||
try
|
||||
{
|
||||
if (uri.IsFile && File.Exists(uri.LocalPath))
|
||||
{
|
||||
using(var file = File.Open(SourceUri.LocalPath, FileMode.Open))
|
||||
{
|
||||
await file.CopyToAsync(mem, token).ConfigureAwait(false);
|
||||
mem.Position = 0;
|
||||
}
|
||||
}
|
||||
else if (uri.Scheme == "http" || uri.Scheme == "https")
|
||||
{
|
||||
var client = new HttpClient();
|
||||
using(var stream = await client.GetStreamAsync(uri))
|
||||
{
|
||||
await stream.CopyToAsync(mem, token).ConfigureAwait(false);
|
||||
mem.Position = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return mem;
|
||||
}
|
||||
|
||||
static HashAlgorithm GetHashAlgorithm(DocumentHashAlgorithm algorithm)
|
||||
{
|
||||
switch (algorithm)
|
||||
{
|
||||
case DocumentHashAlgorithm.SHA1:
|
||||
return SHA1.Create();
|
||||
case DocumentHashAlgorithm.SHA256:
|
||||
return SHA256.Create();
|
||||
case DocumentHashAlgorithm.MD5:
|
||||
return MD5.Create();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
bool CheckPdbHash(byte[] computedHash)
|
||||
{
|
||||
if (computedHash.Length != doc.Hash.Length)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < computedHash.Length; i++)
|
||||
if (computedHash[i] != doc.Hash[i])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
byte[] ComputePdbHash(Stream sourceStream)
|
||||
{
|
||||
var algorithm = GetHashAlgorithm(doc.HashAlgorithm);
|
||||
if (algorithm != null)
|
||||
using(algorithm)
|
||||
return algorithm.ComputeHash(sourceStream);
|
||||
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
public async Task<Stream> GetSourceAsync(bool checkHash, CancellationToken token = default(CancellationToken))
|
||||
{
|
||||
if (doc.EmbeddedSource.Length > 0)
|
||||
return new MemoryStream(doc.EmbeddedSource, false);
|
||||
|
||||
foreach (var url in new [] { SourceUri, SourceLinkUri })
|
||||
{
|
||||
var mem = await GetDataAsync(url, token).ConfigureAwait(false);
|
||||
if (mem != null && (!checkHash || CheckPdbHash(ComputePdbHash(mem))))
|
||||
{
|
||||
mem.Position = 0;
|
||||
return mem;
|
||||
}
|
||||
}
|
||||
|
||||
return MemoryStream.Null;
|
||||
}
|
||||
|
||||
public object ToScriptSource(int executionContextId, object executionContextAuxData)
|
||||
{
|
||||
return new
|
||||
{
|
||||
scriptId = SourceId.ToString(),
|
||||
url = Url,
|
||||
executionContextId,
|
||||
executionContextAuxData,
|
||||
//hash: should be the v8 hash algo, managed implementation is pending
|
||||
dotNetUrl = DotNetUrl,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal class DebugStore
|
||||
{
|
||||
List<AssemblyInfo> assemblies = new List<AssemblyInfo>();
|
||||
readonly HttpClient client;
|
||||
readonly ILogger logger;
|
||||
|
||||
public DebugStore(ILogger logger, HttpClient client)
|
||||
{
|
||||
this.client = client;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public DebugStore(ILogger logger) : this(logger, new HttpClient())
|
||||
{ }
|
||||
|
||||
class DebugItem
|
||||
{
|
||||
public string Url { get; set; }
|
||||
public Task<byte[][]> Data { get; set; }
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<SourceFile> Load(SessionId sessionId, string[] loaded_files, [EnumeratorCancellation] CancellationToken token)
|
||||
{
|
||||
static bool MatchPdb(string asm, string pdb) => Path.ChangeExtension(asm, "pdb") == pdb;
|
||||
|
||||
var asm_files = new List<string>();
|
||||
var pdb_files = new List<string>();
|
||||
foreach (var file_name in loaded_files)
|
||||
{
|
||||
if (file_name.EndsWith(".pdb", StringComparison.OrdinalIgnoreCase))
|
||||
pdb_files.Add(file_name);
|
||||
else
|
||||
asm_files.Add(file_name);
|
||||
}
|
||||
|
||||
List<DebugItem> steps = new List<DebugItem>();
|
||||
foreach (var url in asm_files)
|
||||
{
|
||||
try
|
||||
{
|
||||
var pdb = pdb_files.FirstOrDefault(n => MatchPdb(url, n));
|
||||
steps.Add(
|
||||
new DebugItem
|
||||
{
|
||||
Url = url,
|
||||
Data = Task.WhenAll(client.GetByteArrayAsync(url), pdb != null ? client.GetByteArrayAsync(pdb) : Task.FromResult<byte[]>(null))
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogDebug($"Failed to read {url} ({e.Message})");
|
||||
}
|
||||
}
|
||||
|
||||
var resolver = new DefaultAssemblyResolver();
|
||||
foreach (var step in steps)
|
||||
{
|
||||
AssemblyInfo assembly = null;
|
||||
try
|
||||
{
|
||||
var bytes = await step.Data.ConfigureAwait(false);
|
||||
assembly = new AssemblyInfo(resolver, step.Url, bytes[0], bytes[1]);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogDebug($"Failed to load {step.Url} ({e.Message})");
|
||||
}
|
||||
if (assembly == null)
|
||||
continue;
|
||||
|
||||
assemblies.Add(assembly);
|
||||
foreach (var source in assembly.Sources)
|
||||
yield return source;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<SourceFile> AllSources() => assemblies.SelectMany(a => a.Sources);
|
||||
|
||||
public SourceFile GetFileById(SourceId id) => AllSources().SingleOrDefault(f => f.SourceId.Equals(id));
|
||||
|
||||
public AssemblyInfo GetAssemblyByName(string name) => assemblies.FirstOrDefault(a => a.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
/*
|
||||
V8 uses zero based indexing for both line and column.
|
||||
PPDBs uses one based indexing for both line and column.
|
||||
*/
|
||||
static bool Match(SequencePoint sp, SourceLocation start, SourceLocation end)
|
||||
{
|
||||
var spStart = (Line: sp.StartLine - 1, Column: sp.StartColumn - 1);
|
||||
var spEnd = (Line: sp.EndLine - 1, Column: sp.EndColumn - 1);
|
||||
|
||||
if (start.Line > spEnd.Line)
|
||||
return false;
|
||||
|
||||
if (start.Column > spEnd.Column && start.Line == spEnd.Line)
|
||||
return false;
|
||||
|
||||
if (end.Line < spStart.Line)
|
||||
return false;
|
||||
|
||||
if (end.Column < spStart.Column && end.Line == spStart.Line)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<SourceLocation> FindPossibleBreakpoints(SourceLocation start, SourceLocation end)
|
||||
{
|
||||
//XXX FIXME no idea what todo with locations on different files
|
||||
if (start.Id != end.Id)
|
||||
{
|
||||
logger.LogDebug($"FindPossibleBreakpoints: documents differ (start: {start.Id}) (end {end.Id}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var sourceId = start.Id;
|
||||
|
||||
var doc = GetFileById(sourceId);
|
||||
|
||||
var res = new List<SourceLocation>();
|
||||
if (doc == null)
|
||||
{
|
||||
logger.LogDebug($"Could not find document {sourceId}");
|
||||
return res;
|
||||
}
|
||||
|
||||
foreach (var method in doc.Methods)
|
||||
{
|
||||
foreach (var sequencePoint in method.DebugInformation.SequencePoints)
|
||||
{
|
||||
if (!sequencePoint.IsHidden && Match(sequencePoint, start, end))
|
||||
res.Add(new SourceLocation(method, sequencePoint));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
V8 uses zero based indexing for both line and column.
|
||||
PPDBs uses one based indexing for both line and column.
|
||||
*/
|
||||
static bool Match(SequencePoint sp, int line, int column)
|
||||
{
|
||||
var bp = (line: line + 1, column: column + 1);
|
||||
|
||||
if (sp.StartLine > bp.line || sp.EndLine < bp.line)
|
||||
return false;
|
||||
|
||||
//Chrome sends a zero column even if getPossibleBreakpoints say something else
|
||||
if (column == 0)
|
||||
return true;
|
||||
|
||||
if (sp.StartColumn > bp.column && sp.StartLine == bp.line)
|
||||
return false;
|
||||
|
||||
if (sp.EndColumn < bp.column && sp.EndLine == bp.line)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public IEnumerable<SourceLocation> FindBreakpointLocations(BreakpointRequest request)
|
||||
{
|
||||
request.TryResolve(this);
|
||||
|
||||
var asm = assemblies.FirstOrDefault(a => a.Name.Equals(request.Assembly, StringComparison.OrdinalIgnoreCase));
|
||||
var sourceFile = asm?.Sources?.SingleOrDefault(s => s.DebuggerFileName.Equals(request.File, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (sourceFile == null)
|
||||
yield break;
|
||||
|
||||
foreach (var method in sourceFile.Methods)
|
||||
{
|
||||
foreach (var sequencePoint in method.DebugInformation.SequencePoints)
|
||||
{
|
||||
if (!sequencePoint.IsHidden && Match(sequencePoint, request.Line, request.Column))
|
||||
yield return new SourceLocation(method, sequencePoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string ToUrl(SourceLocation location) => location != null ? GetFileById(location.Id).Url : "";
|
||||
}
|
||||
}
|
|
@ -0,0 +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 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.
|
||||
|
||||
public class DebuggerProxy
|
||||
{
|
||||
private readonly MonoProxy proxy;
|
||||
|
||||
public DebuggerProxy(ILoggerFactory loggerFactory)
|
||||
{
|
||||
proxy = new MonoProxy(loggerFactory);
|
||||
}
|
||||
|
||||
public Task Run(Uri browserUri, WebSocket ideSocket)
|
||||
{
|
||||
return proxy.Run(browserUri, ideSocket);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
// 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;
|
||||
|
||||
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}";
|
||||
|
||||
public override int GetHashCode() => (sessionId?.GetHashCode() ?? 0) ^ id.GetHashCode();
|
||||
|
||||
public override bool Equals(object obj) => (obj is MessageId) ? ((MessageId) obj).sessionId == sessionId && ((MessageId) obj).id == id : false;
|
||||
}
|
||||
|
||||
internal class DotnetObjectId
|
||||
{
|
||||
public string Scheme { get; }
|
||||
public string Value { get; }
|
||||
|
||||
public static bool TryParse(JToken jToken, out DotnetObjectId objectId) => TryParse(jToken?.Value<string>(), out objectId);
|
||||
|
||||
public static bool TryParse(string id, out DotnetObjectId objectId)
|
||||
{
|
||||
objectId = null;
|
||||
if (id == null)
|
||||
return false;
|
||||
|
||||
if (!id.StartsWith("dotnet:"))
|
||||
return false;
|
||||
|
||||
var parts = id.Split(":", 3);
|
||||
|
||||
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}";
|
||||
}
|
||||
|
||||
public struct Result
|
||||
{
|
||||
public JObject Value { get; private set; }
|
||||
public JObject Error { get; private set; }
|
||||
|
||||
public bool IsOk => Value != null;
|
||||
public bool IsErr => Error != null;
|
||||
|
||||
Result(JObject result, JObject error)
|
||||
{
|
||||
if (result != null && error != null)
|
||||
throw new ArgumentException($"Both {nameof(result)} and {nameof(error)} arguments cannot be non-null.");
|
||||
|
||||
bool resultHasError = String.Compare((result?["result"] as JObject) ? ["subtype"]?.Value<string>(), "error") == 0;
|
||||
if (result != null && resultHasError)
|
||||
{
|
||||
this.Value = null;
|
||||
this.Error = result;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Value = result;
|
||||
this.Error = error;
|
||||
}
|
||||
}
|
||||
|
||||
public static Result FromJson(JObject obj)
|
||||
{
|
||||
//Log ("protocol", $"from result: {obj}");
|
||||
return new Result(obj["result"] as JObject, obj["error"] as JObject);
|
||||
}
|
||||
|
||||
public static Result Ok(JObject ok) => new Result(ok, null);
|
||||
|
||||
public static Result OkFromObject(object ok) => Ok(JObject.FromObject(ok));
|
||||
|
||||
public static Result Err(JObject err) => new Result(null, err);
|
||||
|
||||
public static Result 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
|
||||
}
|
||||
|
||||
internal class ExecutionContext
|
||||
{
|
||||
public string DebuggerId { get; set; }
|
||||
public Dictionary<string, BreakpointRequest> BreakpointRequests { get; } = new Dictionary<string, BreakpointRequest>();
|
||||
|
||||
public TaskCompletionSource<DebugStore> ready = null;
|
||||
public bool IsRuntimeReady => ready != null && ready.Task.IsCompleted;
|
||||
|
||||
public int Id { get; set; }
|
||||
public object AuxData { get; set; }
|
||||
|
||||
public List<Frame> CallStack { get; set; }
|
||||
|
||||
public string[] LoadedFiles { get; set; }
|
||||
internal DebugStore store;
|
||||
public TaskCompletionSource<DebugStore> Source { get; } = new TaskCompletionSource<DebugStore>();
|
||||
|
||||
public Dictionary<string, JToken> LocalsCache = new Dictionary<string, JToken>();
|
||||
|
||||
public DebugStore Store
|
||||
{
|
||||
get
|
||||
{
|
||||
if (store == null || !Source.Task.IsCompleted)
|
||||
return null;
|
||||
|
||||
return store;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearState()
|
||||
{
|
||||
CallStack = null;
|
||||
LocalsCache.Clear();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,381 @@
|
|||
// 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.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.WebAssembly.Diagnostics
|
||||
{
|
||||
|
||||
class DevToolsQueue
|
||||
{
|
||||
Task current_send;
|
||||
List<byte[]> pending;
|
||||
|
||||
public WebSocket Ws { get; private set; }
|
||||
public Task CurrentSend { get { return current_send; } }
|
||||
public DevToolsQueue(WebSocket sock)
|
||||
{
|
||||
this.Ws = sock;
|
||||
pending = new List<byte[]>();
|
||||
}
|
||||
|
||||
public Task Send(byte[] bytes, CancellationToken token)
|
||||
{
|
||||
pending.Add(bytes);
|
||||
if (pending.Count == 1)
|
||||
{
|
||||
if (current_send != null)
|
||||
throw new Exception("current_send MUST BE NULL IF THERE'S no pending send");
|
||||
//logger.LogTrace ("sending {0} bytes", bytes.Length);
|
||||
current_send = Ws.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, token);
|
||||
return current_send;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Task Pump(CancellationToken token)
|
||||
{
|
||||
current_send = null;
|
||||
pending.RemoveAt(0);
|
||||
|
||||
if (pending.Count > 0)
|
||||
{
|
||||
if (current_send != null)
|
||||
throw new Exception("current_send MUST BE NULL IF THERE'S no pending send");
|
||||
|
||||
current_send = Ws.SendAsync(new ArraySegment<byte>(pending[0]), WebSocketMessageType.Text, true, token);
|
||||
return current_send;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal class DevToolsProxy
|
||||
{
|
||||
TaskCompletionSource<bool> side_exception = new TaskCompletionSource<bool>();
|
||||
TaskCompletionSource<bool> client_initiated_close = new TaskCompletionSource<bool>();
|
||||
Dictionary<MessageId, TaskCompletionSource<Result>> pending_cmds = new Dictionary<MessageId, TaskCompletionSource<Result>>();
|
||||
ClientWebSocket browser;
|
||||
WebSocket ide;
|
||||
int next_cmd_id;
|
||||
List<Task> pending_ops = new List<Task>();
|
||||
List<DevToolsQueue> queues = new List<DevToolsQueue>();
|
||||
|
||||
protected readonly ILogger logger;
|
||||
|
||||
public DevToolsProxy(ILoggerFactory loggerFactory)
|
||||
{
|
||||
logger = loggerFactory.CreateLogger<DevToolsProxy>();
|
||||
}
|
||||
|
||||
protected virtual Task<bool> AcceptEvent(SessionId sessionId, string method, JObject args, CancellationToken token)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
protected virtual Task<bool> AcceptCommand(MessageId id, string method, JObject args, CancellationToken token)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
async Task<string> ReadOne(WebSocket socket, CancellationToken token)
|
||||
{
|
||||
byte[] buff = new byte[4000];
|
||||
var mem = new MemoryStream();
|
||||
while (true)
|
||||
{
|
||||
|
||||
if (socket.State != WebSocketState.Open)
|
||||
{
|
||||
Log("error", $"DevToolsProxy: Socket is no longer open.");
|
||||
client_initiated_close.TrySetResult(true);
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = await socket.ReceiveAsync(new ArraySegment<byte>(buff), token);
|
||||
if (result.MessageType == WebSocketMessageType.Close)
|
||||
{
|
||||
client_initiated_close.TrySetResult(true);
|
||||
return null;
|
||||
}
|
||||
|
||||
mem.Write(buff, 0, result.Count);
|
||||
|
||||
if (result.EndOfMessage)
|
||||
return Encoding.UTF8.GetString(mem.GetBuffer(), 0, (int) mem.Length);
|
||||
}
|
||||
}
|
||||
|
||||
DevToolsQueue GetQueueForSocket(WebSocket ws)
|
||||
{
|
||||
return queues.FirstOrDefault(q => q.Ws == ws);
|
||||
}
|
||||
|
||||
DevToolsQueue GetQueueForTask(Task task)
|
||||
{
|
||||
return queues.FirstOrDefault(q => q.CurrentSend == task);
|
||||
}
|
||||
|
||||
void Send(WebSocket to, JObject o, CancellationToken token)
|
||||
{
|
||||
var sender = browser == to ? "Send-browser" : "Send-ide";
|
||||
|
||||
var method = o["method"]?.ToString();
|
||||
//if (method != "Debugger.scriptParsed" && method != "Runtime.consoleAPICalled")
|
||||
Log("protocol", $"{sender}: " + JsonConvert.SerializeObject(o));
|
||||
var bytes = Encoding.UTF8.GetBytes(o.ToString());
|
||||
|
||||
var queue = GetQueueForSocket(to);
|
||||
|
||||
var task = queue.Send(bytes, token);
|
||||
if (task != null)
|
||||
pending_ops.Add(task);
|
||||
}
|
||||
|
||||
async Task OnEvent(SessionId sessionId, string method, JObject args, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!await AcceptEvent(sessionId, method, args, token))
|
||||
{
|
||||
//logger.LogDebug ("proxy browser: {0}::{1}",method, args);
|
||||
SendEventInternal(sessionId, method, args, token);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
side_exception.TrySetException(e);
|
||||
}
|
||||
}
|
||||
|
||||
async Task OnCommand(MessageId id, string method, JObject args, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!await AcceptCommand(id, method, args, token))
|
||||
{
|
||||
var res = await SendCommandInternal(id, method, args, token);
|
||||
SendResponseInternal(id, res, token);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
side_exception.TrySetException(e);
|
||||
}
|
||||
}
|
||||
|
||||
void OnResponse(MessageId id, Result result)
|
||||
{
|
||||
//logger.LogTrace ("got id {0} res {1}", id, result);
|
||||
// Fixme
|
||||
if (pending_cmds.Remove(id, out var task))
|
||||
{
|
||||
task.SetResult(result);
|
||||
return;
|
||||
}
|
||||
logger.LogError("Cannot respond to command: {id} with result: {result} - command is not pending", id, result);
|
||||
}
|
||||
|
||||
void ProcessBrowserMessage(string msg, CancellationToken token)
|
||||
{
|
||||
var res = JObject.Parse(msg);
|
||||
|
||||
var method = res["method"]?.ToString();
|
||||
//if (method != "Debugger.scriptParsed" && method != "Runtime.consoleAPICalled")
|
||||
Log("protocol", $"browser: {msg}");
|
||||
|
||||
if (res["id"] == null)
|
||||
pending_ops.Add(OnEvent(new SessionId(res["sessionId"]?.Value<string>()), res["method"].Value<string>(), res["params"] as JObject, token));
|
||||
else
|
||||
OnResponse(new MessageId(res["sessionId"]?.Value<string>(), res["id"].Value<int>()), Result.FromJson(res));
|
||||
}
|
||||
|
||||
void ProcessIdeMessage(string msg, CancellationToken token)
|
||||
{
|
||||
Log("protocol", $"ide: {msg}");
|
||||
if (!string.IsNullOrEmpty(msg))
|
||||
{
|
||||
var res = JObject.Parse(msg);
|
||||
pending_ops.Add(OnCommand(
|
||||
new MessageId(res["sessionId"]?.Value<string>(), res["id"].Value<int>()),
|
||||
res["method"].Value<string>(),
|
||||
res["params"] as JObject, token));
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<Result> SendCommand(SessionId id, string method, JObject args, CancellationToken token)
|
||||
{
|
||||
//Log ("verbose", $"sending command {method}: {args}");
|
||||
return await SendCommandInternal(id, method, args, token);
|
||||
}
|
||||
|
||||
Task<Result> SendCommandInternal(SessionId sessionId, string method, JObject args, CancellationToken token)
|
||||
{
|
||||
int id = Interlocked.Increment(ref next_cmd_id);
|
||||
|
||||
var o = JObject.FromObject(new
|
||||
{
|
||||
id,
|
||||
method,
|
||||
@params = args
|
||||
});
|
||||
if (sessionId.sessionId != null)
|
||||
o["sessionId"] = sessionId.sessionId;
|
||||
var tcs = new TaskCompletionSource<Result>();
|
||||
|
||||
var msgId = new MessageId(sessionId.sessionId, id);
|
||||
//Log ("verbose", $"add cmd id {sessionId}-{id}");
|
||||
pending_cmds[msgId] = tcs;
|
||||
|
||||
Send(this.browser, o, token);
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public void SendEvent(SessionId sessionId, string method, JObject args, CancellationToken token)
|
||||
{
|
||||
//Log ("verbose", $"sending event {method}: {args}");
|
||||
SendEventInternal(sessionId, method, args, token);
|
||||
}
|
||||
|
||||
void SendEventInternal(SessionId sessionId, string method, JObject args, CancellationToken token)
|
||||
{
|
||||
var o = JObject.FromObject(new
|
||||
{
|
||||
method,
|
||||
@params = args
|
||||
});
|
||||
if (sessionId.sessionId != null)
|
||||
o["sessionId"] = sessionId.sessionId;
|
||||
|
||||
Send(this.ide, o, token);
|
||||
}
|
||||
|
||||
internal void SendResponse(MessageId id, Result result, CancellationToken token)
|
||||
{
|
||||
SendResponseInternal(id, result, token);
|
||||
}
|
||||
|
||||
void SendResponseInternal(MessageId id, Result result, CancellationToken token)
|
||||
{
|
||||
JObject o = result.ToJObject(id);
|
||||
if (result.IsErr)
|
||||
logger.LogError($"sending error response for id: {id} -> {result}");
|
||||
|
||||
Send(this.ide, o, token);
|
||||
}
|
||||
|
||||
// , HttpContext context)
|
||||
public async Task Run(Uri browserUri, WebSocket ideSocket)
|
||||
{
|
||||
Log("info", $"DevToolsProxy: Starting on {browserUri}");
|
||||
using(this.ide = ideSocket)
|
||||
{
|
||||
Log("verbose", $"DevToolsProxy: IDE waiting for connection on {browserUri}");
|
||||
queues.Add(new DevToolsQueue(this.ide));
|
||||
using(this.browser = new ClientWebSocket())
|
||||
{
|
||||
this.browser.Options.KeepAliveInterval = Timeout.InfiniteTimeSpan;
|
||||
await this.browser.ConnectAsync(browserUri, CancellationToken.None);
|
||||
queues.Add(new DevToolsQueue(this.browser));
|
||||
|
||||
Log("verbose", $"DevToolsProxy: Client connected on {browserUri}");
|
||||
var x = new CancellationTokenSource();
|
||||
|
||||
pending_ops.Add(ReadOne(browser, x.Token));
|
||||
pending_ops.Add(ReadOne(ide, x.Token));
|
||||
pending_ops.Add(side_exception.Task);
|
||||
pending_ops.Add(client_initiated_close.Task);
|
||||
|
||||
try
|
||||
{
|
||||
while (!x.IsCancellationRequested)
|
||||
{
|
||||
var task = await Task.WhenAny(pending_ops.ToArray());
|
||||
//logger.LogTrace ("pump {0} {1}", task, pending_ops.IndexOf (task));
|
||||
if (task == pending_ops[0])
|
||||
{
|
||||
var msg = ((Task<string>) task).Result;
|
||||
if (msg != null)
|
||||
{
|
||||
pending_ops[0] = ReadOne(browser, x.Token); //queue next read
|
||||
ProcessBrowserMessage(msg, x.Token);
|
||||
}
|
||||
}
|
||||
else if (task == pending_ops[1])
|
||||
{
|
||||
var msg = ((Task<string>) task).Result;
|
||||
if (msg != null)
|
||||
{
|
||||
pending_ops[1] = ReadOne(ide, x.Token); //queue next read
|
||||
ProcessIdeMessage(msg, x.Token);
|
||||
}
|
||||
}
|
||||
else if (task == pending_ops[2])
|
||||
{
|
||||
var res = ((Task<bool>) task).Result;
|
||||
throw new Exception("side task must always complete with an exception, what's going on???");
|
||||
}
|
||||
else if (task == pending_ops[3])
|
||||
{
|
||||
var res = ((Task<bool>) task).Result;
|
||||
Log("verbose", $"DevToolsProxy: Client initiated close from {browserUri}");
|
||||
x.Cancel();
|
||||
}
|
||||
else
|
||||
{
|
||||
//must be a background task
|
||||
pending_ops.Remove(task);
|
||||
var queue = GetQueueForTask(task);
|
||||
if (queue != null)
|
||||
{
|
||||
var tsk = queue.Pump(x.Token);
|
||||
if (tsk != null)
|
||||
pending_ops.Add(tsk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log("error", $"DevToolsProxy::Run: Exception {e}");
|
||||
//throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!x.IsCancellationRequested)
|
||||
x.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void Log(string priority, string msg)
|
||||
{
|
||||
switch (priority)
|
||||
{
|
||||
case "protocol":
|
||||
logger.LogTrace(msg);
|
||||
break;
|
||||
case "verbose":
|
||||
logger.LogDebug(msg);
|
||||
break;
|
||||
case "info":
|
||||
case "warning":
|
||||
case "error":
|
||||
default:
|
||||
logger.LogDebug(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
// 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.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Emit;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.WebAssembly.Diagnostics
|
||||
{
|
||||
|
||||
internal class EvaluateExpression
|
||||
{
|
||||
|
||||
class FindThisExpression : CSharpSyntaxWalker
|
||||
{
|
||||
public List<string> thisExpressions = new List<string>();
|
||||
public SyntaxTree syntaxTree;
|
||||
public FindThisExpression(SyntaxTree syntax)
|
||||
{
|
||||
syntaxTree = syntax;
|
||||
}
|
||||
public override void Visit(SyntaxNode node)
|
||||
{
|
||||
if (node is ThisExpressionSyntax)
|
||||
{
|
||||
if (node.Parent is MemberAccessExpressionSyntax thisParent && thisParent.Name is IdentifierNameSyntax)
|
||||
{
|
||||
IdentifierNameSyntax var = thisParent.Name as IdentifierNameSyntax;
|
||||
thisExpressions.Add(var.Identifier.Text);
|
||||
var newRoot = syntaxTree.GetRoot().ReplaceNode(node.Parent, thisParent.Name);
|
||||
syntaxTree = syntaxTree.WithRootAndOptions(newRoot, syntaxTree.Options);
|
||||
this.Visit(GetExpressionFromSyntaxTree(syntaxTree));
|
||||
}
|
||||
}
|
||||
else
|
||||
base.Visit(node);
|
||||
}
|
||||
|
||||
public async Task CheckIfIsProperty(MonoProxy proxy, MessageId msg_id, int scope_id, CancellationToken token)
|
||||
{
|
||||
foreach (var var in thisExpressions)
|
||||
{
|
||||
JToken value = await proxy.TryGetVariableValue(msg_id, scope_id, var, true, token);
|
||||
if (value == null)
|
||||
throw new Exception($"The property {var} does not exist in the current context");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FindVariableNMethodCall : CSharpSyntaxWalker
|
||||
{
|
||||
public List<IdentifierNameSyntax> variables = new List<IdentifierNameSyntax>();
|
||||
public List<ThisExpressionSyntax> thisList = new List<ThisExpressionSyntax>();
|
||||
public List<InvocationExpressionSyntax> methodCall = new List<InvocationExpressionSyntax>();
|
||||
public List<object> values = new List<Object>();
|
||||
|
||||
public override void Visit(SyntaxNode node)
|
||||
{
|
||||
if (node is IdentifierNameSyntax identifier && !variables.Any(x => x.Identifier.Text == identifier.Identifier.Text))
|
||||
variables.Add(identifier);
|
||||
if (node is InvocationExpressionSyntax)
|
||||
{
|
||||
methodCall.Add(node as InvocationExpressionSyntax);
|
||||
throw new Exception("Method Call is not implemented yet");
|
||||
}
|
||||
if (node is AssignmentExpressionSyntax)
|
||||
throw new Exception("Assignment is not implemented yet");
|
||||
base.Visit(node);
|
||||
}
|
||||
public async Task<SyntaxTree> ReplaceVars(SyntaxTree syntaxTree, MonoProxy proxy, MessageId msg_id, int scope_id, CancellationToken token)
|
||||
{
|
||||
CompilationUnitSyntax root = syntaxTree.GetCompilationUnitRoot();
|
||||
foreach (var var in variables)
|
||||
{
|
||||
ClassDeclarationSyntax classDeclaration = root.Members.ElementAt(0) as ClassDeclarationSyntax;
|
||||
MethodDeclarationSyntax method = classDeclaration.Members.ElementAt(0) as MethodDeclarationSyntax;
|
||||
|
||||
JToken value = await proxy.TryGetVariableValue(msg_id, scope_id, var.Identifier.Text, false, token);
|
||||
|
||||
if (value == null)
|
||||
throw new Exception($"The name {var.Identifier.Text} does not exist in the current context");
|
||||
|
||||
values.Add(ConvertJSToCSharpType(value["value"]));
|
||||
|
||||
var updatedMethod = method.AddParameterListParameters(
|
||||
SyntaxFactory.Parameter(
|
||||
SyntaxFactory.Identifier(var.Identifier.Text))
|
||||
.WithType(SyntaxFactory.ParseTypeName(GetTypeFullName(value["value"]))));
|
||||
root = root.ReplaceNode(method, updatedMethod);
|
||||
}
|
||||
syntaxTree = syntaxTree.WithRootAndOptions(root, syntaxTree.Options);
|
||||
return syntaxTree;
|
||||
}
|
||||
|
||||
private object ConvertJSToCSharpType(JToken variable)
|
||||
{
|
||||
var value = variable["value"];
|
||||
var type = variable["type"].Value<string>();
|
||||
var subType = variable["subtype"]?.Value<string>();
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case "string":
|
||||
return value?.Value<string>();
|
||||
case "number":
|
||||
return value?.Value<double>();
|
||||
case "boolean":
|
||||
return value?.Value<bool>();
|
||||
case "object":
|
||||
if (subType == "null")
|
||||
return null;
|
||||
break;
|
||||
}
|
||||
throw new Exception($"Evaluate of this datatype {type} not implemented yet");
|
||||
}
|
||||
|
||||
private string GetTypeFullName(JToken variable)
|
||||
{
|
||||
var type = variable["type"].ToString();
|
||||
var subType = variable["subtype"]?.Value<string>();
|
||||
object value = ConvertJSToCSharpType(variable);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case "object":
|
||||
{
|
||||
if (subType == "null")
|
||||
return variable["className"].Value<string>();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return value.GetType().FullName;
|
||||
}
|
||||
throw new Exception($"Evaluate of this datatype {type} not implemented yet");
|
||||
}
|
||||
}
|
||||
|
||||
static SyntaxNode GetExpressionFromSyntaxTree(SyntaxTree syntaxTree)
|
||||
{
|
||||
CompilationUnitSyntax root = syntaxTree.GetCompilationUnitRoot();
|
||||
ClassDeclarationSyntax classDeclaration = root.Members.ElementAt(0) as ClassDeclarationSyntax;
|
||||
MethodDeclarationSyntax methodDeclaration = classDeclaration.Members.ElementAt(0) as MethodDeclarationSyntax;
|
||||
BlockSyntax blockValue = methodDeclaration.Body;
|
||||
ReturnStatementSyntax returnValue = blockValue.Statements.ElementAt(0) as ReturnStatementSyntax;
|
||||
InvocationExpressionSyntax expressionInvocation = returnValue.Expression as InvocationExpressionSyntax;
|
||||
MemberAccessExpressionSyntax expressionMember = expressionInvocation.Expression as MemberAccessExpressionSyntax;
|
||||
ParenthesizedExpressionSyntax expressionParenthesized = expressionMember.Expression as ParenthesizedExpressionSyntax;
|
||||
return expressionParenthesized.Expression;
|
||||
}
|
||||
|
||||
internal static async Task<string> CompileAndRunTheExpression(MonoProxy proxy, MessageId msg_id, int scope_id, string expression, CancellationToken token)
|
||||
{
|
||||
FindVariableNMethodCall findVarNMethodCall = new FindVariableNMethodCall();
|
||||
string retString;
|
||||
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(@"
|
||||
using System;
|
||||
public class CompileAndRunTheExpression
|
||||
{
|
||||
public string Evaluate()
|
||||
{
|
||||
return (" + expression + @").ToString();
|
||||
}
|
||||
}");
|
||||
|
||||
FindThisExpression findThisExpression = new FindThisExpression(syntaxTree);
|
||||
var expressionTree = GetExpressionFromSyntaxTree(syntaxTree);
|
||||
findThisExpression.Visit(expressionTree);
|
||||
await findThisExpression.CheckIfIsProperty(proxy, msg_id, scope_id, token);
|
||||
syntaxTree = findThisExpression.syntaxTree;
|
||||
|
||||
expressionTree = GetExpressionFromSyntaxTree(syntaxTree);
|
||||
findVarNMethodCall.Visit(expressionTree);
|
||||
|
||||
syntaxTree = await findVarNMethodCall.ReplaceVars(syntaxTree, proxy, msg_id, scope_id, token);
|
||||
|
||||
MetadataReference[] references = new MetadataReference[]
|
||||
{
|
||||
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
|
||||
MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location)
|
||||
};
|
||||
|
||||
CSharpCompilation compilation = CSharpCompilation.Create(
|
||||
"compileAndRunTheExpression",
|
||||
syntaxTrees : new [] { syntaxTree },
|
||||
references : references,
|
||||
options : new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
||||
using(var ms = new MemoryStream())
|
||||
{
|
||||
EmitResult result = compilation.Emit(ms);
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
Assembly assembly = Assembly.Load(ms.ToArray());
|
||||
Type type = assembly.GetType("CompileAndRunTheExpression");
|
||||
object obj = Activator.CreateInstance(type);
|
||||
var ret = type.InvokeMember("Evaluate",
|
||||
BindingFlags.Default | BindingFlags.InvokeMethod,
|
||||
null,
|
||||
obj,
|
||||
findVarNMethodCall.values.ToArray());
|
||||
retString = ret.ToString();
|
||||
}
|
||||
return retString;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,998 @@
|
|||
// 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.CodeAnalysis;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.WebAssembly.Diagnostics
|
||||
{
|
||||
|
||||
internal class MonoProxy : DevToolsProxy
|
||||
{
|
||||
HashSet<SessionId> sessions = new HashSet<SessionId>();
|
||||
Dictionary<SessionId, ExecutionContext> contexts = new Dictionary<SessionId, ExecutionContext>();
|
||||
|
||||
public MonoProxy(ILoggerFactory loggerFactory, bool hideWebDriver = true) : base(loggerFactory) { hideWebDriver = true; }
|
||||
|
||||
readonly bool hideWebDriver;
|
||||
|
||||
internal ExecutionContext GetContext(SessionId sessionId)
|
||||
{
|
||||
if (contexts.TryGetValue(sessionId, out var context))
|
||||
return context;
|
||||
|
||||
throw new ArgumentException($"Invalid Session: \"{sessionId}\"", nameof(sessionId));
|
||||
}
|
||||
|
||||
bool UpdateContext(SessionId sessionId, ExecutionContext executionContext, out ExecutionContext previousExecutionContext)
|
||||
{
|
||||
var previous = contexts.TryGetValue(sessionId, out previousExecutionContext);
|
||||
contexts[sessionId] = executionContext;
|
||||
return previous;
|
||||
}
|
||||
|
||||
internal Task<Result> SendMonoCommand(SessionId id, MonoCommands cmd, CancellationToken token) => SendCommand(id, "Runtime.evaluate", JObject.FromObject(cmd), token);
|
||||
|
||||
protected override async Task<bool> AcceptEvent(SessionId sessionId, string method, JObject args, CancellationToken token)
|
||||
{
|
||||
switch (method)
|
||||
{
|
||||
case "Runtime.consoleAPICalled":
|
||||
{
|
||||
var type = args["type"]?.ToString();
|
||||
if (type == "debug")
|
||||
{
|
||||
var a = args["args"];
|
||||
if (a?[0] ? ["value"]?.ToString() == MonoConstants.RUNTIME_IS_READY &&
|
||||
a?[1] ? ["value"]?.ToString() == "fe00e07a-5519-4dfe-b35a-f867dbaf2e28")
|
||||
{
|
||||
if (a.Count() > 2)
|
||||
{
|
||||
try
|
||||
{
|
||||
// The optional 3rd argument is the stringified assembly
|
||||
// list so that we don't have to make more round trips
|
||||
var context = GetContext(sessionId);
|
||||
var loaded = a?[2] ? ["value"]?.ToString();
|
||||
if (loaded != null)
|
||||
context.LoadedFiles = JToken.Parse(loaded).ToObject<string[]>();
|
||||
}
|
||||
catch (InvalidCastException ice)
|
||||
{
|
||||
Log("verbose", ice.ToString());
|
||||
}
|
||||
}
|
||||
await RuntimeReady(sessionId, token);
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "Runtime.executionContextCreated":
|
||||
{
|
||||
SendEvent(sessionId, method, args, token);
|
||||
var ctx = args?["context"];
|
||||
var aux_data = ctx?["auxData"] as JObject;
|
||||
var id = ctx["id"].Value<int>();
|
||||
if (aux_data != null)
|
||||
{
|
||||
var is_default = aux_data["isDefault"]?.Value<bool>();
|
||||
if (is_default == true)
|
||||
{
|
||||
await OnDefaultContext(sessionId, new ExecutionContext { Id = id, AuxData = aux_data }, token);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case "Debugger.paused":
|
||||
{
|
||||
//TODO figure out how to stich out more frames and, in particular what happens when real wasm is on the stack
|
||||
var top_func = args?["callFrames"] ? [0] ? ["functionName"]?.Value<string>();
|
||||
|
||||
if (top_func == "mono_wasm_fire_bp" || top_func == "_mono_wasm_fire_bp")
|
||||
{
|
||||
return await OnBreakpointHit(sessionId, args, token);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "Debugger.breakpointResolved":
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
case "Debugger.scriptParsed":
|
||||
{
|
||||
var url = args?["url"]?.Value<string>() ?? "";
|
||||
|
||||
switch (url)
|
||||
{
|
||||
case var _ when url == "":
|
||||
case var _ when url.StartsWith("wasm://", StringComparison.Ordinal):
|
||||
{
|
||||
Log("verbose", $"ignoring wasm: Debugger.scriptParsed {url}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Log("verbose", $"proxying Debugger.scriptParsed ({sessionId.sessionId}) {url} {args}");
|
||||
break;
|
||||
}
|
||||
|
||||
case "Target.attachedToTarget":
|
||||
{
|
||||
if (args["targetInfo"]["type"]?.ToString() == "page")
|
||||
await DeleteWebDriver(new SessionId(args["sessionId"]?.ToString()), token);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async Task<bool> IsRuntimeAlreadyReadyAlready(SessionId sessionId, CancellationToken token)
|
||||
{
|
||||
if (contexts.TryGetValue(sessionId, out var context) && context.IsRuntimeReady)
|
||||
return true;
|
||||
|
||||
var res = await SendMonoCommand(sessionId, MonoCommands.IsRuntimeReady(), token);
|
||||
return res.Value?["result"] ? ["value"]?.Value<bool>() ?? false;
|
||||
}
|
||||
|
||||
static int bpIdGenerator;
|
||||
|
||||
protected override async Task<bool> AcceptCommand(MessageId id, string method, JObject args, CancellationToken token)
|
||||
{
|
||||
// Inspector doesn't use the Target domain or sessions
|
||||
// so we try to init immediately
|
||||
if (hideWebDriver && id == SessionId.Null)
|
||||
await DeleteWebDriver(id, token);
|
||||
|
||||
if (!contexts.TryGetValue(id, out var context))
|
||||
return false;
|
||||
|
||||
switch (method)
|
||||
{
|
||||
case "Target.attachToTarget":
|
||||
{
|
||||
var resp = await SendCommand(id, method, args, token);
|
||||
await DeleteWebDriver(new SessionId(resp.Value["sessionId"]?.ToString()), token);
|
||||
break;
|
||||
}
|
||||
|
||||
case "Debugger.enable":
|
||||
{
|
||||
System.Console.WriteLine("recebi o Debugger.enable");
|
||||
var resp = await SendCommand(id, method, args, token);
|
||||
|
||||
context.DebuggerId = resp.Value["debuggerId"]?.ToString();
|
||||
|
||||
if (await IsRuntimeAlreadyReadyAlready(id, token))
|
||||
await RuntimeReady(id, token);
|
||||
|
||||
SendResponse(id, resp, token);
|
||||
return true;
|
||||
}
|
||||
|
||||
case "Debugger.getScriptSource":
|
||||
{
|
||||
var script = args?["scriptId"]?.Value<string>();
|
||||
return await OnGetScriptSource(id, script, token);
|
||||
}
|
||||
|
||||
case "Runtime.compileScript":
|
||||
{
|
||||
var exp = args?["expression"]?.Value<string>();
|
||||
if (exp.StartsWith("//dotnet:", StringComparison.Ordinal))
|
||||
{
|
||||
OnCompileDotnetScript(id, token);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "Debugger.getPossibleBreakpoints":
|
||||
{
|
||||
var resp = await SendCommand(id, method, args, token);
|
||||
if (resp.IsOk && resp.Value["locations"].HasValues)
|
||||
{
|
||||
SendResponse(id, resp, token);
|
||||
return true;
|
||||
}
|
||||
|
||||
var start = SourceLocation.Parse(args?["start"] as JObject);
|
||||
//FIXME support variant where restrictToFunction=true and end is omitted
|
||||
var end = SourceLocation.Parse(args?["end"] as JObject);
|
||||
if (start != null && end != null && await GetPossibleBreakpoints(id, start, end, token))
|
||||
return true;
|
||||
|
||||
SendResponse(id, resp, token);
|
||||
return true;
|
||||
}
|
||||
|
||||
case "Debugger.setBreakpoint":
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
case "Debugger.setBreakpointByUrl":
|
||||
{
|
||||
var resp = await SendCommand(id, method, args, token);
|
||||
if (!resp.IsOk)
|
||||
{
|
||||
SendResponse(id, resp, token);
|
||||
return true;
|
||||
}
|
||||
|
||||
var bpid = resp.Value["breakpointId"]?.ToString();
|
||||
var locations = resp.Value["locations"]?.Values<object>();
|
||||
var request = BreakpointRequest.Parse(bpid, args);
|
||||
|
||||
// is the store done loading?
|
||||
var loaded = context.Source.Task.IsCompleted;
|
||||
if (!loaded)
|
||||
{
|
||||
// Send and empty response immediately if not
|
||||
// and register the breakpoint for resolution
|
||||
context.BreakpointRequests[bpid] = request;
|
||||
SendResponse(id, resp, token);
|
||||
}
|
||||
|
||||
if (await IsRuntimeAlreadyReadyAlready(id, token))
|
||||
{
|
||||
var store = await RuntimeReady(id, token);
|
||||
|
||||
Log("verbose", $"BP req {args}");
|
||||
await SetBreakpoint(id, store, request, !loaded, token);
|
||||
}
|
||||
|
||||
if (loaded)
|
||||
{
|
||||
// we were already loaded so we should send a response
|
||||
// with the locations included and register the request
|
||||
context.BreakpointRequests[bpid] = request;
|
||||
var result = Result.OkFromObject(request.AsSetBreakpointByUrlResponse(locations));
|
||||
SendResponse(id, result, token);
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case "Debugger.removeBreakpoint":
|
||||
{
|
||||
await RemoveBreakpoint(id, args, token);
|
||||
break;
|
||||
}
|
||||
|
||||
case "Debugger.resume":
|
||||
{
|
||||
await OnResume(id, token);
|
||||
break;
|
||||
}
|
||||
|
||||
case "Debugger.stepInto":
|
||||
{
|
||||
return await Step(id, StepKind.Into, token);
|
||||
}
|
||||
|
||||
case "Debugger.stepOut":
|
||||
{
|
||||
return await Step(id, StepKind.Out, token);
|
||||
}
|
||||
|
||||
case "Debugger.stepOver":
|
||||
{
|
||||
return await Step(id, StepKind.Over, token);
|
||||
}
|
||||
|
||||
case "Debugger.evaluateOnCallFrame":
|
||||
{
|
||||
if (!DotnetObjectId.TryParse(args?["callFrameId"], out var objectId))
|
||||
return false;
|
||||
|
||||
switch (objectId.Scheme)
|
||||
{
|
||||
case "scope":
|
||||
return await OnEvaluateOnCallFrame(id,
|
||||
int.Parse(objectId.Value),
|
||||
args?["expression"]?.Value<string>(), token);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
case "Runtime.getProperties":
|
||||
{
|
||||
if (!DotnetObjectId.TryParse(args?["objectId"], out var objectId))
|
||||
break;
|
||||
|
||||
var result = await RuntimeGetProperties(id, objectId, args, token);
|
||||
SendResponse(id, result, token);
|
||||
return true;
|
||||
}
|
||||
|
||||
case "Runtime.releaseObject":
|
||||
{
|
||||
if (!(DotnetObjectId.TryParse(args["objectId"], out var objectId) && objectId.Scheme == "cfo_res"))
|
||||
break;
|
||||
|
||||
await SendMonoCommand(id, MonoCommands.ReleaseObject(objectId), token);
|
||||
SendResponse(id, Result.OkFromObject(new { }), token);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Protocol extensions
|
||||
case "DotnetDebugger.getMethodLocation":
|
||||
{
|
||||
Console.WriteLine("set-breakpoint-by-method: " + id + " " + args);
|
||||
|
||||
var store = await RuntimeReady(id, token);
|
||||
string aname = args["assemblyName"]?.Value<string>();
|
||||
string typeName = args["typeName"]?.Value<string>();
|
||||
string methodName = args["methodName"]?.Value<string>();
|
||||
if (aname == null || typeName == null || methodName == null)
|
||||
{
|
||||
SendResponse(id, Result.Err("Invalid protocol message '" + args + "'."), token);
|
||||
return true;
|
||||
}
|
||||
|
||||
// GetAssemblyByName seems to work on file names
|
||||
var assembly = store.GetAssemblyByName(aname);
|
||||
if (assembly == null)
|
||||
assembly = store.GetAssemblyByName(aname + ".exe");
|
||||
if (assembly == null)
|
||||
assembly = store.GetAssemblyByName(aname + ".dll");
|
||||
if (assembly == null)
|
||||
{
|
||||
SendResponse(id, Result.Err("Assembly '" + aname + "' not found."), token);
|
||||
return true;
|
||||
}
|
||||
|
||||
var type = assembly.GetTypeByName(typeName);
|
||||
if (type == null)
|
||||
{
|
||||
SendResponse(id, Result.Err($"Type '{typeName}' not found."), token);
|
||||
return true;
|
||||
}
|
||||
|
||||
var methodInfo = type.Methods.FirstOrDefault(m => m.Name == methodName);
|
||||
if (methodInfo == null)
|
||||
{
|
||||
// Maybe this is an async method, in which case the debug info is attached
|
||||
// to the async method implementation, in class named:
|
||||
// `{type_name}/<method_name>::MoveNext`
|
||||
methodInfo = assembly.TypesByName.Values.SingleOrDefault(t => t.FullName.StartsWith($"{typeName}/<{methodName}>")) ?
|
||||
.Methods.FirstOrDefault(mi => mi.Name == "MoveNext");
|
||||
}
|
||||
|
||||
if (methodInfo == null)
|
||||
{
|
||||
SendResponse(id, Result.Err($"Method '{typeName}:{methodName}' not found."), token);
|
||||
return true;
|
||||
}
|
||||
|
||||
var src_url = methodInfo.Assembly.Sources.Single(sf => sf.SourceId == methodInfo.SourceId).Url;
|
||||
SendResponse(id, Result.OkFromObject(new
|
||||
{
|
||||
result = new { line = methodInfo.StartLocation.Line, column = methodInfo.StartLocation.Column, url = src_url }
|
||||
}), token);
|
||||
|
||||
return true;
|
||||
}
|
||||
case "Runtime.callFunctionOn":
|
||||
{
|
||||
if (!DotnetObjectId.TryParse(args["objectId"], out var objectId))
|
||||
return false;
|
||||
|
||||
if (objectId.Scheme == "scope")
|
||||
{
|
||||
SendResponse(id,
|
||||
Result.Exception(new ArgumentException(
|
||||
$"Runtime.callFunctionOn not supported with scope ({objectId}).")),
|
||||
token);
|
||||
return true;
|
||||
}
|
||||
|
||||
var res = await SendMonoCommand(id, MonoCommands.CallFunctionOn(args), token);
|
||||
var res_value_type = res.Value?["result"] ? ["value"]?.Type;
|
||||
|
||||
if (res.IsOk && res_value_type == JTokenType.Object || res_value_type == JTokenType.Object)
|
||||
res = Result.OkFromObject(new { result = res.Value["result"]["value"] });
|
||||
|
||||
SendResponse(id, res, token);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async Task<Result> RuntimeGetProperties(MessageId id, DotnetObjectId objectId, JToken args, CancellationToken token)
|
||||
{
|
||||
if (objectId.Scheme == "scope")
|
||||
return await GetScopeProperties(id, int.Parse(objectId.Value), token);
|
||||
|
||||
var res = await SendMonoCommand(id, MonoCommands.GetDetails(objectId, args), token);
|
||||
if (res.IsErr)
|
||||
return res;
|
||||
|
||||
if (objectId.Scheme == "cfo_res")
|
||||
{
|
||||
// Runtime.callFunctionOn result object
|
||||
var value_json_str = res.Value["result"] ? ["value"] ? ["__value_as_json_string__"]?.Value<string>();
|
||||
if (value_json_str != null)
|
||||
{
|
||||
res = Result.OkFromObject(new
|
||||
{
|
||||
result = JArray.Parse(value_json_str)
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
res = Result.OkFromObject(new { result = new { } });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
res = Result.Ok(JObject.FromObject(new { result = res.Value["result"]["value"] }));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
//static int frame_id=0;
|
||||
async Task<bool> OnBreakpointHit(SessionId sessionId, JObject args, CancellationToken token)
|
||||
{
|
||||
//FIXME we should send release objects every now and then? Or intercept those we inject and deal in the runtime
|
||||
var res = await SendMonoCommand(sessionId, MonoCommands.GetCallStack(), token);
|
||||
var orig_callframes = args?["callFrames"]?.Values<JObject>();
|
||||
var context = GetContext(sessionId);
|
||||
|
||||
if (res.IsErr)
|
||||
{
|
||||
//Give up and send the original call stack
|
||||
return false;
|
||||
}
|
||||
|
||||
//step one, figure out where did we hit
|
||||
var res_value = res.Value?["result"] ? ["value"];
|
||||
if (res_value == null || res_value is JValue)
|
||||
{
|
||||
//Give up and send the original call stack
|
||||
return false;
|
||||
}
|
||||
|
||||
Log("verbose", $"call stack (err is {res.Error} value is:\n{res.Value}");
|
||||
var bp_id = res_value?["breakpoint_id"]?.Value<int>();
|
||||
Log("verbose", $"We just hit bp {bp_id}");
|
||||
if (!bp_id.HasValue)
|
||||
{
|
||||
//Give up and send the original call stack
|
||||
return false;
|
||||
}
|
||||
|
||||
var bp = context.BreakpointRequests.Values.SelectMany(v => v.Locations).FirstOrDefault(b => b.RemoteId == bp_id.Value);
|
||||
|
||||
var callFrames = new List<object>();
|
||||
foreach (var frame in orig_callframes)
|
||||
{
|
||||
var function_name = frame["functionName"]?.Value<string>();
|
||||
var url = frame["url"]?.Value<string>();
|
||||
if ("mono_wasm_fire_bp" == function_name ||"_mono_wasm_fire_bp" == function_name)
|
||||
{
|
||||
var frames = new List<Frame>();
|
||||
int frame_id = 0;
|
||||
var the_mono_frames = res.Value?["result"] ? ["value"] ? ["frames"]?.Values<JObject>();
|
||||
|
||||
foreach (var mono_frame in the_mono_frames)
|
||||
{
|
||||
++frame_id;
|
||||
var il_pos = mono_frame["il_pos"].Value<int>();
|
||||
var method_token = mono_frame["method_token"].Value<uint>();
|
||||
var assembly_name = mono_frame["assembly_name"].Value<string>();
|
||||
|
||||
// This can be different than `method.Name`, like in case of generic methods
|
||||
var method_name = mono_frame["method_name"]?.Value<string>();
|
||||
|
||||
var store = await LoadStore(sessionId, token);
|
||||
var asm = store.GetAssemblyByName(assembly_name);
|
||||
if (asm == null)
|
||||
{
|
||||
Log("info", $"Unable to find assembly: {assembly_name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var method = asm.GetMethodByToken(method_token);
|
||||
|
||||
if (method == null)
|
||||
{
|
||||
Log("info", $"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var location = method?.GetLocationByIl(il_pos);
|
||||
|
||||
// When hitting a breakpoint on the "IncrementCount" method in the standard
|
||||
// Blazor project template, one of the stack frames is inside mscorlib.dll
|
||||
// and we get location==null for it. It will trigger a NullReferenceException
|
||||
// if we don't skip over that stack frame.
|
||||
if (location == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Log("info", $"frame il offset: {il_pos} method token: {method_token} assembly name: {assembly_name}");
|
||||
Log("info", $"\tmethod {method_name} location: {location}");
|
||||
frames.Add(new Frame(method, location, frame_id - 1));
|
||||
|
||||
callFrames.Add(new
|
||||
{
|
||||
functionName = method_name,
|
||||
callFrameId = $"dotnet:scope:{frame_id-1}",
|
||||
functionLocation = method.StartLocation.AsLocation(),
|
||||
|
||||
location = location.AsLocation(),
|
||||
|
||||
url = store.ToUrl(location),
|
||||
|
||||
scopeChain = new []
|
||||
{
|
||||
new
|
||||
{
|
||||
type = "local",
|
||||
@object = new
|
||||
{
|
||||
@type = "object",
|
||||
className = "Object",
|
||||
description = "Object",
|
||||
objectId = $"dotnet:scope:{frame_id-1}",
|
||||
},
|
||||
name = method_name,
|
||||
startLocation = method.StartLocation.AsLocation(),
|
||||
endLocation = method.EndLocation.AsLocation(),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
context.CallStack = frames;
|
||||
|
||||
}
|
||||
}
|
||||
else if (!(function_name.StartsWith("wasm-function", StringComparison.Ordinal) ||
|
||||
url.StartsWith("wasm://wasm/", StringComparison.Ordinal)))
|
||||
{
|
||||
callFrames.Add(frame);
|
||||
}
|
||||
}
|
||||
|
||||
var bp_list = new string[bp == null ? 0 : 1];
|
||||
if (bp != null)
|
||||
bp_list[0] = bp.StackId;
|
||||
|
||||
var o = JObject.FromObject(new
|
||||
{
|
||||
callFrames,
|
||||
reason = "other", //other means breakpoint
|
||||
hitBreakpoints = bp_list,
|
||||
});
|
||||
|
||||
SendEvent(sessionId, "Debugger.paused", o, token);
|
||||
return true;
|
||||
}
|
||||
|
||||
async Task OnDefaultContext(SessionId sessionId, ExecutionContext context, CancellationToken token)
|
||||
{
|
||||
Log("verbose", "Default context created, clearing state and sending events");
|
||||
if (UpdateContext(sessionId, context, out var previousContext))
|
||||
{
|
||||
foreach (var kvp in previousContext.BreakpointRequests)
|
||||
{
|
||||
context.BreakpointRequests[kvp.Key] = kvp.Value.Clone();
|
||||
}
|
||||
}
|
||||
|
||||
if (await IsRuntimeAlreadyReadyAlready(sessionId, token))
|
||||
await RuntimeReady(sessionId, token);
|
||||
}
|
||||
|
||||
async Task OnResume(MessageId msg_id, CancellationToken token)
|
||||
{
|
||||
var ctx = GetContext(msg_id);
|
||||
if (ctx.CallStack != null)
|
||||
{
|
||||
// Stopped on managed code
|
||||
await SendMonoCommand(msg_id, MonoCommands.Resume(), token);
|
||||
}
|
||||
|
||||
//discard managed frames
|
||||
GetContext(msg_id).ClearState();
|
||||
}
|
||||
|
||||
async Task<bool> Step(MessageId msg_id, StepKind kind, CancellationToken token)
|
||||
{
|
||||
var context = GetContext(msg_id);
|
||||
if (context.CallStack == null)
|
||||
return false;
|
||||
|
||||
if (context.CallStack.Count <= 1 && kind == StepKind.Out)
|
||||
return false;
|
||||
|
||||
var res = await SendMonoCommand(msg_id, MonoCommands.StartSingleStepping(kind), token);
|
||||
|
||||
var ret_code = res.Value?["result"] ? ["value"]?.Value<int>();
|
||||
|
||||
if (ret_code.HasValue && ret_code.Value == 0)
|
||||
{
|
||||
context.ClearState();
|
||||
await SendCommand(msg_id, "Debugger.stepOut", new JObject(), token);
|
||||
return false;
|
||||
}
|
||||
|
||||
SendResponse(msg_id, Result.Ok(new JObject()), token);
|
||||
|
||||
context.ClearState();
|
||||
|
||||
await SendCommand(msg_id, "Debugger.resume", new JObject(), token);
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool TryFindVariableValueInCache(ExecutionContext ctx, string expression, bool only_search_on_this, out JToken obj)
|
||||
{
|
||||
if (ctx.LocalsCache.TryGetValue(expression, out obj))
|
||||
{
|
||||
if (only_search_on_this && obj["fromThis"] == null)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal async Task<JToken> TryGetVariableValue(MessageId msg_id, int scope_id, string expression, bool only_search_on_this, CancellationToken token)
|
||||
{
|
||||
JToken thisValue = null;
|
||||
var context = GetContext(msg_id);
|
||||
if (context.CallStack == null)
|
||||
return null;
|
||||
|
||||
if (TryFindVariableValueInCache(context, expression, only_search_on_this, out JToken obj))
|
||||
return obj;
|
||||
|
||||
var scope = context.CallStack.FirstOrDefault(s => s.Id == scope_id);
|
||||
var live_vars = scope.Method.GetLiveVarsAt(scope.Location.CliLocation.Offset);
|
||||
//get_this
|
||||
var res = await SendMonoCommand(msg_id, MonoCommands.GetScopeVariables(scope.Id, live_vars), token);
|
||||
|
||||
var scope_values = res.Value?["result"] ? ["value"]?.Values<JObject>()?.ToArray();
|
||||
thisValue = scope_values?.FirstOrDefault(v => v["name"]?.Value<string>() == "this");
|
||||
|
||||
if (!only_search_on_this)
|
||||
{
|
||||
if (thisValue != null && expression == "this")
|
||||
return thisValue;
|
||||
|
||||
var value = scope_values.SingleOrDefault(sv => sv["name"]?.Value<string>() == expression);
|
||||
if (value != null)
|
||||
return value;
|
||||
}
|
||||
|
||||
//search in scope
|
||||
if (thisValue != null)
|
||||
{
|
||||
if (!DotnetObjectId.TryParse(thisValue["value"]["objectId"], out var objectId))
|
||||
return null;
|
||||
|
||||
res = await SendMonoCommand(msg_id, MonoCommands.GetDetails(objectId), token);
|
||||
scope_values = res.Value?["result"] ? ["value"]?.Values<JObject>().ToArray();
|
||||
var foundValue = scope_values.FirstOrDefault(v => v["name"].Value<string>() == expression);
|
||||
if (foundValue != null)
|
||||
{
|
||||
foundValue["fromThis"] = true;
|
||||
context.LocalsCache[foundValue["name"].Value<string>()] = foundValue;
|
||||
return foundValue;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async Task<bool> OnEvaluateOnCallFrame(MessageId msg_id, int scope_id, string expression, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
var context = GetContext(msg_id);
|
||||
if (context.CallStack == null)
|
||||
return false;
|
||||
|
||||
var varValue = await TryGetVariableValue(msg_id, scope_id, expression, false, token);
|
||||
|
||||
if (varValue != null)
|
||||
{
|
||||
SendResponse(msg_id, Result.OkFromObject(new
|
||||
{
|
||||
result = varValue["value"]
|
||||
}), token);
|
||||
return true;
|
||||
}
|
||||
|
||||
string retValue = await EvaluateExpression.CompileAndRunTheExpression(this, msg_id, scope_id, expression, token);
|
||||
SendResponse(msg_id, Result.OkFromObject(new
|
||||
{
|
||||
result = new
|
||||
{
|
||||
value = retValue
|
||||
}
|
||||
}), token);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogDebug(e, $"Error in EvaluateOnCallFrame for expression '{expression}.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async Task<Result> GetScopeProperties(MessageId msg_id, int scope_id, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ctx = GetContext(msg_id);
|
||||
var scope = ctx.CallStack.FirstOrDefault(s => s.Id == scope_id);
|
||||
if (scope == null)
|
||||
return Result.Err(JObject.FromObject(new { message = $"Could not find scope with id #{scope_id}" }));
|
||||
|
||||
var var_ids = scope.Method.GetLiveVarsAt(scope.Location.CliLocation.Offset);
|
||||
var res = await SendMonoCommand(msg_id, MonoCommands.GetScopeVariables(scope.Id, var_ids), token);
|
||||
|
||||
//if we fail we just buble that to the IDE (and let it panic over it)
|
||||
if (res.IsErr)
|
||||
return res;
|
||||
|
||||
var values = res.Value?["result"] ? ["value"]?.Values<JObject>().ToArray();
|
||||
|
||||
if (values == null || values.Length == 0)
|
||||
return Result.OkFromObject(new { result = Array.Empty<object>() });
|
||||
|
||||
foreach (var value in values)
|
||||
ctx.LocalsCache[value["name"]?.Value<string>()] = value;
|
||||
|
||||
return Result.OkFromObject(new { result = values });
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log("verbose", $"Error resolving scope properties {exception.Message}");
|
||||
return Result.Exception(exception);
|
||||
}
|
||||
}
|
||||
|
||||
async Task<Breakpoint> SetMonoBreakpoint(SessionId sessionId, string reqId, SourceLocation location, CancellationToken token)
|
||||
{
|
||||
var bp = new Breakpoint(reqId, location, BreakpointState.Pending);
|
||||
var asm_name = bp.Location.CliLocation.Method.Assembly.Name;
|
||||
var method_token = bp.Location.CliLocation.Method.Token;
|
||||
var il_offset = bp.Location.CliLocation.Offset;
|
||||
|
||||
var res = await SendMonoCommand(sessionId, MonoCommands.SetBreakpoint(asm_name, method_token, il_offset), token);
|
||||
var ret_code = res.Value?["result"] ? ["value"]?.Value<int>();
|
||||
|
||||
if (ret_code.HasValue)
|
||||
{
|
||||
bp.RemoteId = ret_code.Value;
|
||||
bp.State = BreakpointState.Active;
|
||||
//Log ("verbose", $"BP local id {bp.LocalId} enabled with remote id {bp.RemoteId}");
|
||||
}
|
||||
|
||||
return bp;
|
||||
}
|
||||
|
||||
async Task<DebugStore> LoadStore(SessionId sessionId, CancellationToken token)
|
||||
{
|
||||
var context = GetContext(sessionId);
|
||||
|
||||
if (Interlocked.CompareExchange(ref context.store, new DebugStore(logger), null) != null)
|
||||
return await context.Source.Task;
|
||||
|
||||
try
|
||||
{
|
||||
var loaded_files = context.LoadedFiles;
|
||||
|
||||
if (loaded_files == null)
|
||||
{
|
||||
var loaded = await SendMonoCommand(sessionId, MonoCommands.GetLoadedFiles(), token);
|
||||
loaded_files = loaded.Value?["result"] ? ["value"]?.ToObject<string[]>();
|
||||
}
|
||||
|
||||
await
|
||||
foreach (var source in context.store.Load(sessionId, loaded_files, token).WithCancellation(token))
|
||||
{
|
||||
var scriptSource = JObject.FromObject(source.ToScriptSource(context.Id, context.AuxData));
|
||||
Log("verbose", $"\tsending {source.Url} {context.Id} {sessionId.sessionId}");
|
||||
|
||||
SendEvent(sessionId, "Debugger.scriptParsed", scriptSource, token);
|
||||
|
||||
foreach (var req in context.BreakpointRequests.Values)
|
||||
{
|
||||
if (req.TryResolve(source))
|
||||
{
|
||||
await SetBreakpoint(sessionId, context.store, req, true, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
context.Source.SetException(e);
|
||||
}
|
||||
|
||||
if (!context.Source.Task.IsCompleted)
|
||||
context.Source.SetResult(context.store);
|
||||
return context.store;
|
||||
}
|
||||
|
||||
async Task<DebugStore> RuntimeReady(SessionId sessionId, CancellationToken token)
|
||||
{
|
||||
var context = GetContext(sessionId);
|
||||
if (Interlocked.CompareExchange(ref context.ready, new TaskCompletionSource<DebugStore>(), null) != null)
|
||||
return await context.ready.Task;
|
||||
|
||||
var clear_result = await SendMonoCommand(sessionId, MonoCommands.ClearAllBreakpoints(), token);
|
||||
if (clear_result.IsErr)
|
||||
{
|
||||
Log("verbose", $"Failed to clear breakpoints due to {clear_result}");
|
||||
}
|
||||
|
||||
var store = await LoadStore(sessionId, token);
|
||||
|
||||
context.ready.SetResult(store);
|
||||
SendEvent(sessionId, "Mono.runtimeReady", new JObject(), token);
|
||||
return store;
|
||||
}
|
||||
|
||||
async Task RemoveBreakpoint(MessageId msg_id, JObject args, CancellationToken token)
|
||||
{
|
||||
var bpid = args?["breakpointId"]?.Value<string>();
|
||||
|
||||
var context = GetContext(msg_id);
|
||||
if (!context.BreakpointRequests.TryGetValue(bpid, out var breakpointRequest))
|
||||
return;
|
||||
|
||||
foreach (var bp in breakpointRequest.Locations)
|
||||
{
|
||||
var res = await SendMonoCommand(msg_id, MonoCommands.RemoveBreakpoint(bp.RemoteId), token);
|
||||
var ret_code = res.Value?["result"] ? ["value"]?.Value<int>();
|
||||
|
||||
if (ret_code.HasValue)
|
||||
{
|
||||
bp.RemoteId = -1;
|
||||
bp.State = BreakpointState.Disabled;
|
||||
}
|
||||
}
|
||||
breakpointRequest.Locations.Clear();
|
||||
}
|
||||
|
||||
async Task SetBreakpoint(SessionId sessionId, DebugStore store, BreakpointRequest req, bool sendResolvedEvent, CancellationToken token)
|
||||
{
|
||||
var context = GetContext(sessionId);
|
||||
if (req.Locations.Any())
|
||||
{
|
||||
Log("debug", $"locations already loaded for {req.Id}");
|
||||
return;
|
||||
}
|
||||
|
||||
var comparer = new SourceLocation.LocationComparer();
|
||||
// if column is specified the frontend wants the exact matches
|
||||
// and will clear the bp if it isn't close enoug
|
||||
var locations = store.FindBreakpointLocations(req)
|
||||
.Distinct(comparer)
|
||||
.Where(l => l.Line == req.Line && (req.Column == 0 || l.Column == req.Column))
|
||||
.OrderBy(l => l.Column)
|
||||
.GroupBy(l => l.Id);
|
||||
|
||||
logger.LogDebug("BP request for '{req}' runtime ready {context.RuntimeReady}", req, GetContext(sessionId).IsRuntimeReady);
|
||||
|
||||
var breakpoints = new List<Breakpoint>();
|
||||
|
||||
foreach (var sourceId in locations)
|
||||
{
|
||||
var loc = sourceId.First();
|
||||
var bp = await SetMonoBreakpoint(sessionId, req.Id, loc, token);
|
||||
|
||||
// If we didn't successfully enable the breakpoint
|
||||
// don't add it to the list of locations for this id
|
||||
if (bp.State != BreakpointState.Active)
|
||||
continue;
|
||||
|
||||
breakpoints.Add(bp);
|
||||
|
||||
var resolvedLocation = new
|
||||
{
|
||||
breakpointId = req.Id,
|
||||
location = loc.AsLocation()
|
||||
};
|
||||
|
||||
if (sendResolvedEvent)
|
||||
SendEvent(sessionId, "Debugger.breakpointResolved", JObject.FromObject(resolvedLocation), token);
|
||||
}
|
||||
|
||||
req.Locations.AddRange(breakpoints);
|
||||
return;
|
||||
}
|
||||
|
||||
async Task<bool> GetPossibleBreakpoints(MessageId msg, SourceLocation start, SourceLocation end, CancellationToken token)
|
||||
{
|
||||
var bps = (await RuntimeReady(msg, token)).FindPossibleBreakpoints(start, end);
|
||||
|
||||
if (bps == null)
|
||||
return false;
|
||||
|
||||
var response = new { locations = bps.Select(b => b.AsLocation()) };
|
||||
|
||||
SendResponse(msg, Result.OkFromObject(response), token);
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnCompileDotnetScript(MessageId msg_id, CancellationToken token)
|
||||
{
|
||||
SendResponse(msg_id, Result.OkFromObject(new { }), token);
|
||||
}
|
||||
|
||||
async Task<bool> OnGetScriptSource(MessageId msg_id, string script_id, CancellationToken token)
|
||||
{
|
||||
if (!SourceId.TryParse(script_id, out var id))
|
||||
return false;
|
||||
|
||||
var src_file = (await LoadStore(msg_id, token)).GetFileById(id);
|
||||
|
||||
try
|
||||
{
|
||||
var uri = new Uri(src_file.Url);
|
||||
string source = $"// Unable to find document {src_file.SourceUri}";
|
||||
|
||||
using(var data = await src_file.GetSourceAsync(checkHash: false, token: token))
|
||||
{
|
||||
if (data.Length == 0)
|
||||
return false;
|
||||
|
||||
using(var reader = new StreamReader(data))
|
||||
source = await reader.ReadToEndAsync();
|
||||
}
|
||||
SendResponse(msg_id, Result.OkFromObject(new { scriptSource = source }), token);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var o = new
|
||||
{
|
||||
scriptSource = $"// Unable to read document ({e.Message})\n" +
|
||||
$"Local path: {src_file?.SourceUri}\n" +
|
||||
$"SourceLink path: {src_file?.SourceLinkUri}\n"
|
||||
};
|
||||
|
||||
SendResponse(msg_id, Result.OkFromObject(o), token);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async Task DeleteWebDriver(SessionId sessionId, CancellationToken token)
|
||||
{
|
||||
// see https://github.com/mono/mono/issues/19549 for background
|
||||
if (hideWebDriver && sessions.Add(sessionId))
|
||||
{
|
||||
var res = await SendCommand(sessionId,
|
||||
"Page.addScriptToEvaluateOnNewDocument",
|
||||
JObject.FromObject(new { source = "delete navigator.constructor.prototype.webdriver" }),
|
||||
token);
|
||||
|
||||
if (sessionId != SessionId.Null && !res.IsOk)
|
||||
sessions.Remove(sessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,605 @@
|
|||
// 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 Newtonsoft.Json.Linq;
|
||||
using Microsoft.WebAssembly.Diagnostics;
|
||||
using Xunit;
|
||||
|
||||
namespace DebuggerTests
|
||||
{
|
||||
|
||||
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_elements : 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_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(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_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(72, 8, "GenericTypeLocals", false, 0, false)]
|
||||
[InlineData(72, 8, "GenericTypeLocals", false, 0, true)]
|
||||
[InlineData(100, 8, "YetAnotherMethod", true, 2, false)]
|
||||
[InlineData(100, 8, "YetAnotherMethod", true, 2, true)]
|
||||
public async Task InspectGenericTypeArrayLocals(int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo) => await TestSimpleArrayLocals(
|
||||
line, col,
|
||||
entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:GenericTypeLocals",
|
||||
method_name : method_name,
|
||||
etype_name: "DebuggerTests.GenericClass<int>",
|
||||
local_var_name_prefix: "gclass",
|
||||
array : new []
|
||||
{
|
||||
TObject("DebuggerTests.GenericClass<int>", is_null : true),
|
||||
TObject("DebuggerTests.GenericClass<int>"),
|
||||
TObject("DebuggerTests.GenericClass<int>")
|
||||
},
|
||||
array_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(89, 8, "GenericValueTypeLocals", false, 0, false)]
|
||||
[InlineData(89, 8, "GenericValueTypeLocals", false, 0, true)]
|
||||
[InlineData(100, 8, "YetAnotherMethod", true, 2, false)]
|
||||
[InlineData(100, 8, "YetAnotherMethod", true, 2, true)]
|
||||
public async Task InspectGenericValueTypeArrayLocals(int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo) => await TestSimpleArrayLocals(
|
||||
line, col,
|
||||
entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:GenericValueTypeLocals",
|
||||
method_name : method_name,
|
||||
etype_name: "DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>",
|
||||
local_var_name_prefix: "gvclass",
|
||||
array : new []
|
||||
{
|
||||
TValueType("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>"),
|
||||
TValueType("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>")
|
||||
},
|
||||
array_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(213, 8, "GenericValueTypeLocals2", false, 0, false)]
|
||||
[InlineData(213, 8, "GenericValueTypeLocals2", false, 0, true)]
|
||||
[InlineData(100, 8, "YetAnotherMethod", true, 2, false)]
|
||||
[InlineData(100, 8, "YetAnotherMethod", true, 2, true)]
|
||||
public async Task InspectGenericValueTypeArrayLocals2(int line, int col, string method_name, bool test_prev_frame, int frame_idx, bool use_cfo) => await TestSimpleArrayLocals(
|
||||
line, col,
|
||||
entry_method_name: "[debugger-test] DebuggerTests.ArrayTestsClass:GenericValueTypeLocals2",
|
||||
method_name : method_name,
|
||||
etype_name: "DebuggerTests.SimpleGenericStruct<DebuggerTests.Point[]>",
|
||||
local_var_name_prefix: "gvclass",
|
||||
array : new []
|
||||
{
|
||||
TValueType("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point[]>"),
|
||||
TValueType("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point[]>")
|
||||
},
|
||||
array_elements : new []
|
||||
{
|
||||
new
|
||||
{
|
||||
Id = TString("gvclass_arr#0#Id"),
|
||||
Color = TEnum("DebuggerTests.RGB", "Red"),
|
||||
Value = new []
|
||||
{
|
||||
TPoint(100, 200, "gvclass_arr#0#0#Value#Id", "Red"),
|
||||
TPoint(100, 200, "gvclass_arr#0#1#Value#Id", "Green")
|
||||
}
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = TString("gvclass_arr#1#Id"),
|
||||
Color = TEnum("DebuggerTests.RGB", "Blue"),
|
||||
Value = new []
|
||||
{
|
||||
TPoint(100, 200, "gvclass_arr#1#0#Value#Id", "Green"),
|
||||
TPoint(100, 200, "gvclass_arr#1#1#Value#Id", "Blue")
|
||||
}
|
||||
}
|
||||
},
|
||||
test_prev_frame : test_prev_frame,
|
||||
frame_idx : frame_idx,
|
||||
use_cfo : use_cfo);
|
||||
|
||||
async Task TestSimpleArrayLocals(int line, int col, string entry_method_name, string method_name, string etype_name,
|
||||
string local_var_name_prefix, object[] array, object[] array_elements,
|
||||
bool test_prev_frame = false, int frame_idx = 0, bool use_cfo = false)
|
||||
{
|
||||
var insp = new Inspector();
|
||||
//Collect events
|
||||
var scripts = SubscribeToScripts(insp);
|
||||
|
||||
await Ready();
|
||||
await insp.Ready(async(cli, token) =>
|
||||
{
|
||||
ctx = new DebugTestContext(cli, insp, token, scripts);
|
||||
var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
|
||||
ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
|
||||
|
||||
await SetBreakpoint(debugger_test_loc, line, col);
|
||||
|
||||
var eval_expr = "window.setTimeout(function() { invoke_static_method (" +
|
||||
$"'{entry_method_name}', { (test_prev_frame ? "true" : "false") }" +
|
||||
"); }, 1);";
|
||||
|
||||
var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, method_name);
|
||||
|
||||
var locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value<string>());
|
||||
Assert.Equal(4, locals.Count());
|
||||
CheckArray(locals, $"{local_var_name_prefix}_arr", $"{etype_name}[]");
|
||||
CheckArray(locals, $"{local_var_name_prefix}_arr_empty", $"{etype_name}[]");
|
||||
CheckObject(locals, $"{local_var_name_prefix}_arr_null", $"{etype_name}[]", is_null : true);
|
||||
CheckBool(locals, "call_other", test_prev_frame);
|
||||
|
||||
var local_arr_name = $"{local_var_name_prefix}_arr";
|
||||
|
||||
JToken prefix_arr;
|
||||
if (use_cfo)
|
||||
{ // Use `Runtime.callFunctionOn` to get the properties
|
||||
var frame = pause_location["callFrames"][frame_idx];
|
||||
var name = local_arr_name;
|
||||
var fl = await GetProperties(frame["callFrameId"].Value<string>());
|
||||
var l_obj = GetAndAssertObjectWithName(locals, name);
|
||||
var l_objectId = l_obj["value"]["objectId"]?.Value<string>();
|
||||
|
||||
Assert.True(!String.IsNullOrEmpty(l_objectId), $"No objectId found for {name}");
|
||||
|
||||
prefix_arr = await GetObjectWithCFO(l_objectId);
|
||||
}
|
||||
else
|
||||
{
|
||||
prefix_arr = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], local_arr_name);
|
||||
}
|
||||
|
||||
await CheckProps(prefix_arr, array, local_arr_name);
|
||||
|
||||
if (array_elements?.Length > 0)
|
||||
{
|
||||
for (int i = 0; i < array_elements.Length; i++)
|
||||
{
|
||||
var i_str = i.ToString();
|
||||
var label = $"{local_var_name_prefix}_arr[{i}]";
|
||||
if (array_elements[i] == null)
|
||||
{
|
||||
var act_i = prefix_arr.FirstOrDefault(jt => jt["name"]?.Value<string>() == i_str);
|
||||
Assert.True(act_i != null, $"[{label}] Couldn't find array element [{i_str}]");
|
||||
|
||||
await CheckValue(act_i["value"], TObject(etype_name, is_null : true), label);
|
||||
}
|
||||
else
|
||||
{
|
||||
await CompareObjectPropertiesFor(prefix_arr, i_str, array_elements[i], label : label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var props = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], $"{local_var_name_prefix}_arr_empty");
|
||||
await CheckProps(props, new object[0], "${local_var_name_prefix}_arr_empty");
|
||||
});
|
||||
|
||||
async Task<JToken> GetObjectWithCFO(string objectId, JObject fn_args = null)
|
||||
{
|
||||
var fn_decl = "function () { return this; }";
|
||||
var cfo_args = JObject.FromObject(new
|
||||
{
|
||||
functionDeclaration = fn_decl,
|
||||
objectId = objectId
|
||||
});
|
||||
|
||||
if (fn_args != null)
|
||||
cfo_args["arguments"] = fn_args;
|
||||
|
||||
// callFunctionOn
|
||||
var result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token);
|
||||
|
||||
return await GetProperties(result.Value["result"]["objectId"]?.Value<string>(), fn_args);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task InspectObjectArrayMembers(bool use_cfo)
|
||||
{
|
||||
var insp = new Inspector();
|
||||
//Collect events
|
||||
var scripts = SubscribeToScripts(insp);
|
||||
int line = 227;
|
||||
int col = 12;
|
||||
string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:ObjectArrayMembers";
|
||||
string method_name = "PlaceholderMethod";
|
||||
int frame_idx = 1;
|
||||
|
||||
await Ready();
|
||||
await insp.Ready(async(cli, token) =>
|
||||
{
|
||||
ctx = new DebugTestContext(cli, insp, token, scripts);
|
||||
ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
|
||||
var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
|
||||
|
||||
await SetBreakpoint(debugger_test_loc, line, col);
|
||||
|
||||
var eval_expr = "window.setTimeout(function() { invoke_static_method (" +
|
||||
$"'{entry_method_name}'" +
|
||||
"); }, 1);";
|
||||
|
||||
var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, method_name);
|
||||
var locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value<string>());
|
||||
Assert.Single(locals);
|
||||
CheckObject(locals, "c", "DebuggerTests.Container");
|
||||
|
||||
var c_props = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], "c");
|
||||
await CheckProps(c_props, new
|
||||
{
|
||||
id = TString("c#id"),
|
||||
ClassArrayProperty = TArray("DebuggerTests.SimpleClass[]", 3),
|
||||
ClassArrayField = TArray("DebuggerTests.SimpleClass[]", 3),
|
||||
PointsProperty = TArray("DebuggerTests.Point[]", 2),
|
||||
PointsField = TArray("DebuggerTests.Point[]", 2)
|
||||
},
|
||||
"c"
|
||||
);
|
||||
|
||||
await CompareObjectPropertiesFor(c_props, "ClassArrayProperty",
|
||||
new []
|
||||
{
|
||||
TSimpleClass(5, -2, "ClassArrayProperty#Id#0", "Green"),
|
||||
TSimpleClass(30, 1293, "ClassArrayProperty#Id#1", "Green"),
|
||||
TObject("DebuggerTests.SimpleClass", is_null : true)
|
||||
},
|
||||
label: "InspectLocalsWithStructsStaticAsync");
|
||||
|
||||
await CompareObjectPropertiesFor(c_props, "ClassArrayField",
|
||||
new []
|
||||
{
|
||||
TObject("DebuggerTests.SimpleClass", is_null : true),
|
||||
TSimpleClass(5, -2, "ClassArrayField#Id#1", "Blue"),
|
||||
TSimpleClass(30, 1293, "ClassArrayField#Id#2", "Green")
|
||||
},
|
||||
label: "c#ClassArrayField");
|
||||
|
||||
await CompareObjectPropertiesFor(c_props, "PointsProperty",
|
||||
new []
|
||||
{
|
||||
TPoint(5, -2, "PointsProperty#Id#0", "Green"),
|
||||
TPoint(123, 0, "PointsProperty#Id#1", "Blue"),
|
||||
},
|
||||
label: "c#PointsProperty");
|
||||
|
||||
await CompareObjectPropertiesFor(c_props, "PointsField",
|
||||
new []
|
||||
{
|
||||
TPoint(5, -2, "PointsField#Id#0", "Green"),
|
||||
TPoint(123, 0, "PointsField#Id#1", "Blue"),
|
||||
},
|
||||
label: "c#PointsField");
|
||||
});
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task InspectValueTypeArrayLocalsStaticAsync(bool use_cfo)
|
||||
{
|
||||
var insp = new Inspector();
|
||||
//Collect events
|
||||
var scripts = SubscribeToScripts(insp);
|
||||
int line = 157;
|
||||
int col = 12;
|
||||
string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:ValueTypeLocalsAsync";
|
||||
string method_name = "MoveNext"; // BUG: this should be ValueTypeLocalsAsync
|
||||
int frame_idx = 0;
|
||||
|
||||
await Ready();
|
||||
await insp.Ready(async(cli, token) =>
|
||||
{
|
||||
ctx = new DebugTestContext(cli, insp, token, scripts);
|
||||
ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
|
||||
var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
|
||||
|
||||
await SetBreakpoint(debugger_test_loc, line, col);
|
||||
|
||||
var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" +
|
||||
$"'{entry_method_name}', false" // *false* here keeps us only in the static method
|
||||
+
|
||||
"); }, 1);";
|
||||
|
||||
var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, method_name);
|
||||
var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value<string>());
|
||||
await CheckProps(frame_locals, new
|
||||
{
|
||||
call_other = TBool(false),
|
||||
gvclass_arr = TArray("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[]", 2),
|
||||
gvclass_arr_empty = TArray("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[]"),
|
||||
gvclass_arr_null = TObject("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>[]", is_null : true),
|
||||
gvclass = TValueType("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>"),
|
||||
// BUG: this shouldn't be null!
|
||||
points = TObject("DebuggerTests.Point[]", is_null : true)
|
||||
}, "ValueTypeLocalsAsync#locals");
|
||||
|
||||
var local_var_name_prefix = "gvclass";
|
||||
await CompareObjectPropertiesFor(frame_locals, local_var_name_prefix, new
|
||||
{
|
||||
Id = TString(null),
|
||||
Color = TEnum("DebuggerTests.RGB", "Red"),
|
||||
Value = TPoint(0, 0, null, "Red")
|
||||
});
|
||||
|
||||
await CompareObjectPropertiesFor(frame_locals, $"{local_var_name_prefix}_arr",
|
||||
new []
|
||||
{
|
||||
new
|
||||
{
|
||||
Id = TString("gvclass_arr#1#Id"),
|
||||
Color = TEnum("DebuggerTests.RGB", "Red"),
|
||||
Value = TPoint(100, 200, "gvclass_arr#1#Value#Id", "Red")
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = TString("gvclass_arr#2#Id"),
|
||||
Color = TEnum("DebuggerTests.RGB", "Blue"),
|
||||
Value = TPoint(10, 20, "gvclass_arr#2#Value#Id", "Green")
|
||||
}
|
||||
}
|
||||
);
|
||||
await CompareObjectPropertiesFor(frame_locals, $"{local_var_name_prefix}_arr_empty",
|
||||
new object[0]);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Check previous frame too
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task InspectValueTypeArrayLocalsInstanceAsync(bool use_cfo)
|
||||
{
|
||||
var insp = new Inspector();
|
||||
//Collect events
|
||||
var scripts = SubscribeToScripts(insp);
|
||||
int line = 170;
|
||||
int col = 12;
|
||||
string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:ValueTypeLocalsAsync";
|
||||
int frame_idx = 0;
|
||||
|
||||
await Ready();
|
||||
await insp.Ready(async(cli, token) =>
|
||||
{
|
||||
ctx = new DebugTestContext(cli, insp, token, scripts);
|
||||
ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
|
||||
var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
|
||||
|
||||
await SetBreakpoint(debugger_test_loc, line, col);
|
||||
|
||||
var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" +
|
||||
$"'{entry_method_name}', true" +
|
||||
"); }, 1);";
|
||||
|
||||
// BUG: Should be InspectValueTypeArrayLocalsInstanceAsync
|
||||
var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, "MoveNext");
|
||||
|
||||
var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value<string>());
|
||||
await CheckProps(frame_locals, new
|
||||
{
|
||||
t1 = TObject("DebuggerTests.SimpleGenericStruct<DebuggerTests.Point>"),
|
||||
@this = TObject("DebuggerTests.ArrayTestsClass"),
|
||||
point_arr = TArray("DebuggerTests.Point[]", 2),
|
||||
point = TValueType("DebuggerTests.Point")
|
||||
}, "InspectValueTypeArrayLocalsInstanceAsync#locals");
|
||||
|
||||
await CompareObjectPropertiesFor(frame_locals, "t1",
|
||||
new
|
||||
{
|
||||
Id = TString("gvclass_arr#1#Id"),
|
||||
Color = TEnum("DebuggerTests.RGB", "Red"),
|
||||
Value = TPoint(100, 200, "gvclass_arr#1#Value#Id", "Red")
|
||||
});
|
||||
|
||||
await CompareObjectPropertiesFor(frame_locals, "point_arr",
|
||||
new []
|
||||
{
|
||||
TPoint(5, -2, "point_arr#Id#0", "Red"),
|
||||
TPoint(123, 0, "point_arr#Id#1", "Blue"),
|
||||
}
|
||||
);
|
||||
|
||||
await CompareObjectPropertiesFor(frame_locals, "point",
|
||||
TPoint(45, 51, "point#Id", "Green"));
|
||||
});
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task InspectValueTypeArrayLocalsInAsyncStaticStructMethod(bool use_cfo)
|
||||
{
|
||||
var insp = new Inspector();
|
||||
//Collect events
|
||||
var scripts = SubscribeToScripts(insp);
|
||||
int line = 244;
|
||||
int col = 12;
|
||||
string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:EntryPointForStructMethod";
|
||||
int frame_idx = 0;
|
||||
|
||||
await Ready();
|
||||
await insp.Ready(async(cli, token) =>
|
||||
{
|
||||
ctx = new DebugTestContext(cli, insp, token, scripts);
|
||||
ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
|
||||
var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
|
||||
|
||||
await SetBreakpoint(debugger_test_loc, line, col);
|
||||
//await SetBreakpoint (debugger_test_loc, 143, 3);
|
||||
|
||||
var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" +
|
||||
$"'{entry_method_name}', false" +
|
||||
"); }, 1);";
|
||||
|
||||
// BUG: Should be InspectValueTypeArrayLocalsInstanceAsync
|
||||
var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, "MoveNext");
|
||||
|
||||
var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value<string>());
|
||||
await CheckProps(frame_locals, new
|
||||
{
|
||||
call_other = TBool(false),
|
||||
local_i = TNumber(5),
|
||||
sc = TSimpleClass(10, 45, "sc#Id", "Blue")
|
||||
}, "InspectValueTypeArrayLocalsInAsyncStaticStructMethod#locals");
|
||||
});
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task InspectValueTypeArrayLocalsInAsyncInstanceStructMethod(bool use_cfo)
|
||||
{
|
||||
var insp = new Inspector();
|
||||
//Collect events
|
||||
var scripts = SubscribeToScripts(insp);
|
||||
int line = 251;
|
||||
int col = 12;
|
||||
string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:EntryPointForStructMethod";
|
||||
int frame_idx = 0;
|
||||
|
||||
await Ready();
|
||||
await insp.Ready(async(cli, token) =>
|
||||
{
|
||||
ctx = new DebugTestContext(cli, insp, token, scripts);
|
||||
ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
|
||||
var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
|
||||
|
||||
await SetBreakpoint(debugger_test_loc, line, col);
|
||||
|
||||
var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" +
|
||||
$"'{entry_method_name}', true" +
|
||||
"); }, 1);";
|
||||
|
||||
// BUG: Should be InspectValueTypeArrayLocalsInstanceAsync
|
||||
var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, "MoveNext");
|
||||
|
||||
var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value<string>());
|
||||
await CheckProps(frame_locals, new
|
||||
{
|
||||
sc_arg = TObject("DebuggerTests.SimpleClass"),
|
||||
@this = TValueType("DebuggerTests.Point"),
|
||||
local_gs = TValueType("DebuggerTests.SimpleGenericStruct<int>")
|
||||
},
|
||||
"locals#0");
|
||||
|
||||
await CompareObjectPropertiesFor(frame_locals, "local_gs",
|
||||
new
|
||||
{
|
||||
Id = TString("local_gs#Id"),
|
||||
Color = TEnum("DebuggerTests.RGB", "Green"),
|
||||
Value = TNumber(4)
|
||||
},
|
||||
label: "local_gs#0");
|
||||
|
||||
await CompareObjectPropertiesFor(frame_locals, "sc_arg",
|
||||
TSimpleClass(10, 45, "sc_arg#Id", "Blue"),
|
||||
label: "sc_arg#0");
|
||||
|
||||
await CompareObjectPropertiesFor(frame_locals, "this",
|
||||
TPoint(90, -4, "point#Id", "Green"),
|
||||
label: "this#0");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,857 @@
|
|||
// 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 Newtonsoft.Json.Linq;
|
||||
using Microsoft.WebAssembly.Diagnostics;
|
||||
using Xunit;
|
||||
|
||||
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", 8, 1, 10, false)]
|
||||
[InlineData("big_array_js_test (0);", "/other.js", 8, 1, 0, true)]
|
||||
[InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, 10, false)]
|
||||
[InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 0);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, 0, true)]
|
||||
public async Task CheckVSCodeTestFunction1(string eval_fn, string bp_loc, int line, int col, int len, bool roundtrip)
|
||||
{
|
||||
string vscode_fn0 = "function(){const e={__proto__:this.__proto__},t=Object.getOwnPropertyNames(this);for(let r=0;r<t.length;++r){const n=t[r],i=n>>>0;if(String(i>>>0)===n&&i>>>0!=4294967295)continue;const a=Object.getOwnPropertyDescriptor(this,n);a&&Object.defineProperty(e,n,a)}return e}";
|
||||
|
||||
await RunCallFunctionOn(eval_fn, vscode_fn0, "big", bp_loc, line, col, res_array_len : len, roundtrip : roundtrip,
|
||||
test_fn : async(result) =>
|
||||
{
|
||||
|
||||
var is_js = bp_loc.EndsWith(".js", StringComparison.Ordinal);
|
||||
var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
|
||||
{
|
||||
objectId = result.Value["result"]["objectId"].Value<string>(),
|
||||
accessorPropertiesOnly = true,
|
||||
ownProperties = false
|
||||
}), ctx.token);
|
||||
if (is_js)
|
||||
await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors");
|
||||
else
|
||||
AssertEqual(0, obj_accessors.Value["result"]?.Count(), "obj_accessors-count");
|
||||
|
||||
// Check for a __proto__ object
|
||||
// isOwn = true, accessorPropertiesOnly = false
|
||||
var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
|
||||
{
|
||||
objectId = result.Value["result"]["objectId"].Value<string>(),
|
||||
accessorPropertiesOnly = false,
|
||||
ownProperties = true
|
||||
}), ctx.token);
|
||||
|
||||
await CheckProps(obj_own.Value["result"], new
|
||||
{
|
||||
length = TNumber(len),
|
||||
// __proto__ = TArray (type, 0) // Is this one really required?
|
||||
}, $"obj_own", num_fields : is_js ? 2 : 1);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
void CheckJFunction(JToken actual, string className, string label)
|
||||
{
|
||||
AssertEqual("function", actual["type"]?.Value<string>(), $"{label}-type");
|
||||
AssertEqual(className, actual["className"]?.Value<string>(), $"{label}-className");
|
||||
}
|
||||
|
||||
// This tests `callFunctionOn` with a function that the vscode-js-debug extension uses
|
||||
// Using this here as a non-trivial test case
|
||||
[Theory]
|
||||
[InlineData("big_array_js_test (10);", "/other.js", 8, 1, 10)]
|
||||
[InlineData("big_array_js_test (0);", "/other.js", 8, 1, 0)]
|
||||
[InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, 10)]
|
||||
[InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 0);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, 0)]
|
||||
public async Task CheckVSCodeTestFunction2(string eval_fn, string bp_loc, int line, int col, int len)
|
||||
{
|
||||
var fetch_start_idx = 2;
|
||||
var num_elems_fetch = 3;
|
||||
string vscode_fn1 = "function(e,t){const r={},n=-1===e?0:e,i=-1===t?this.length:e+t;for(let e=n;e<i&&e<this.length;++e){const t=Object.getOwnPropertyDescriptor(this,e);t&&Object.defineProperty(r,e,t)}return r}";
|
||||
|
||||
await RunCallFunctionOn(eval_fn, vscode_fn1, "big", bp_loc, line, col,
|
||||
fn_args : JArray.FromObject(new []
|
||||
{
|
||||
new { @value = fetch_start_idx },
|
||||
new { @value = num_elems_fetch }
|
||||
}),
|
||||
test_fn : async(result) =>
|
||||
{
|
||||
|
||||
var is_js = bp_loc.EndsWith(".js", StringComparison.Ordinal);
|
||||
|
||||
// isOwn = false, accessorPropertiesOnly = true
|
||||
var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
|
||||
{
|
||||
objectId = result.Value["result"]["objectId"].Value<string>(),
|
||||
accessorPropertiesOnly = true,
|
||||
ownProperties = false
|
||||
}), ctx.token);
|
||||
if (is_js)
|
||||
await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors");
|
||||
else
|
||||
AssertEqual(0, obj_accessors.Value["result"]?.Count(), "obj_accessors-count");
|
||||
|
||||
// Ignoring the __proto__ property
|
||||
|
||||
// isOwn = true, accessorPropertiesOnly = false
|
||||
var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
|
||||
{
|
||||
objectId = result.Value["result"]["objectId"].Value<string>(),
|
||||
accessorPropertiesOnly = false,
|
||||
ownProperties = true
|
||||
}), ctx.token);
|
||||
|
||||
var obj_own_val = obj_own.Value["result"];
|
||||
var num_elems_recd = len == 0 ? 0 : num_elems_fetch;
|
||||
AssertEqual(is_js ? num_elems_recd + 1 : num_elems_recd, obj_own_val.Count(), $"obj_own-count");
|
||||
|
||||
if (is_js)
|
||||
CheckObject(obj_own_val, "__proto__", "Object");
|
||||
|
||||
for (int i = fetch_start_idx; i < fetch_start_idx + num_elems_recd; i++)
|
||||
CheckNumber(obj_own_val, i.ToString(), 1000 + i);
|
||||
});
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("big_array_js_test (10);", "/other.js", 8, 1, false)]
|
||||
[InlineData("big_array_js_test (10);", "/other.js", 8, 1, true)]
|
||||
[InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, false)]
|
||||
[InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, true)]
|
||||
public async Task RunOnArrayReturnEmptyArray(string eval_fn, string bp_loc, int line, int col, bool roundtrip)
|
||||
{
|
||||
var ret_len = 0;
|
||||
|
||||
await RunCallFunctionOn(eval_fn,
|
||||
"function () { return []; }",
|
||||
"big", bp_loc, line, col,
|
||||
res_array_len : ret_len,
|
||||
roundtrip : roundtrip,
|
||||
test_fn : async(result) =>
|
||||
{
|
||||
var is_js = bp_loc.EndsWith(".js", StringComparison.Ordinal);
|
||||
|
||||
// getProperties (isOwn = false, accessorPropertiesOnly = true)
|
||||
var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
|
||||
{
|
||||
objectId = result.Value["result"]["objectId"].Value<string>(),
|
||||
accessorPropertiesOnly = true,
|
||||
ownProperties = false
|
||||
}), ctx.token);
|
||||
if (is_js)
|
||||
await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors");
|
||||
else
|
||||
AssertEqual(0, obj_accessors.Value["result"]?.Count(), "obj_accessors-count");
|
||||
|
||||
// getProperties (isOwn = true, accessorPropertiesOnly = false)
|
||||
var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
|
||||
{
|
||||
objectId = result.Value["result"]["objectId"].Value<string>(),
|
||||
accessorPropertiesOnly = false,
|
||||
ownProperties = true
|
||||
}), ctx.token);
|
||||
|
||||
await CheckProps(obj_own.Value["result"], new
|
||||
{
|
||||
length = TNumber(ret_len),
|
||||
// __proto__ returned by js
|
||||
}, $"obj_own", num_fields : is_js ? 2 : 1);
|
||||
});
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("big_array_js_test (10);", "/other.js", 8, 1, false)]
|
||||
[InlineData("big_array_js_test (10);", "/other.js", 8, 1, true)]
|
||||
[InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, false)]
|
||||
[InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, true)]
|
||||
public async Task RunOnArrayReturnArray(string eval_fn, string bp_loc, int line, int col, bool roundtrip)
|
||||
{
|
||||
var ret_len = 5;
|
||||
await RunCallFunctionOn(eval_fn,
|
||||
"function (m) { return Object.values (this).filter ((k, i) => i%m == 0); }",
|
||||
"big", bp_loc, line, col,
|
||||
fn_args : JArray.FromObject(new [] { new { value = 2 } }),
|
||||
res_array_len : ret_len,
|
||||
roundtrip : roundtrip,
|
||||
test_fn : async(result) =>
|
||||
{
|
||||
var is_js = bp_loc.EndsWith(".js");
|
||||
|
||||
// getProperties (own=false)
|
||||
var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
|
||||
{
|
||||
objectId = result.Value["result"]["objectId"].Value<string>(),
|
||||
accessorPropertiesOnly = true,
|
||||
ownProperties = false
|
||||
}), ctx.token);
|
||||
|
||||
if (is_js)
|
||||
await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors");
|
||||
else
|
||||
AssertEqual(0, obj_accessors.Value["result"]?.Count(), "obj_accessors-count");
|
||||
|
||||
// getProperties (own=true)
|
||||
// isOwn = true, accessorPropertiesOnly = false
|
||||
var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
|
||||
{
|
||||
objectId = result.Value["result"]["objectId"].Value<string>(),
|
||||
accessorPropertiesOnly = false,
|
||||
ownProperties = true
|
||||
}), ctx.token);
|
||||
|
||||
// AssertEqual (2, obj_own.Value ["result"].Count (), $"{label}-obj_own.count");
|
||||
|
||||
var obj_own_val = obj_own.Value["result"];
|
||||
await CheckProps(obj_own_val, new
|
||||
{
|
||||
length = TNumber(ret_len),
|
||||
// __proto__ returned by JS
|
||||
}, $"obj_own", num_fields: (is_js ? ret_len + 2 : ret_len + 1));
|
||||
|
||||
for (int i = 0; i < ret_len; i++)
|
||||
CheckNumber(obj_own_val, i.ToString(), i * 2 + 1000);
|
||||
});
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task RunOnVTArray(bool roundtrip) => await RunCallFunctionOn(
|
||||
"invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);",
|
||||
"function (m) { return Object.values (this).filter ((k, i) => i%m == 0); }",
|
||||
"ss_arr",
|
||||
"dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12,
|
||||
fn_args : JArray.FromObject(new [] { new { value = 2 } }),
|
||||
res_array_len : 5,
|
||||
roundtrip : roundtrip,
|
||||
test_fn : async(result) =>
|
||||
{
|
||||
var ret_len = 5;
|
||||
|
||||
// getProperties (own=false)
|
||||
var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
|
||||
{
|
||||
objectId = result.Value["result"]["objectId"].Value<string>(),
|
||||
accessorPropertiesOnly = true,
|
||||
ownProperties = false
|
||||
}), ctx.token);
|
||||
|
||||
AssertEqual(0, obj_accessors.Value["result"]?.Count(), "obj_accessors-count");
|
||||
|
||||
// getProperties (own=true)
|
||||
// isOwn = true, accessorPropertiesOnly = false
|
||||
var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
|
||||
{
|
||||
objectId = result.Value["result"]["objectId"].Value<string>(),
|
||||
accessorPropertiesOnly = false,
|
||||
ownProperties = true
|
||||
}), ctx.token);
|
||||
|
||||
var obj_own_val = obj_own.Value["result"];
|
||||
await CheckProps(obj_own_val, new
|
||||
{
|
||||
length = TNumber(ret_len),
|
||||
// __proto__ returned by JS
|
||||
}, "obj_own", num_fields : ret_len + 1);
|
||||
|
||||
for (int i = 0; i < ret_len; i++)
|
||||
{
|
||||
var act_i = CheckValueType(obj_own_val, i.ToString(), "Math.SimpleStruct");
|
||||
|
||||
// Valuetypes can get sent as part of the container's getProperties, so ensure that we can access it
|
||||
var act_i_props = await GetProperties(act_i["value"]["objectId"]?.Value<string>());
|
||||
await CheckProps(act_i_props, new
|
||||
{
|
||||
dt = TValueType("System.DateTime", new DateTime(2020 + (i * 2), 1, 2, 3, 4, 5).ToString()),
|
||||
gs = TValueType("Math.GenericStruct<System.DateTime>")
|
||||
}, "obj_own ss_arr[{i}]");
|
||||
|
||||
var gs_props = await GetObjectOnLocals(act_i_props, "gs");
|
||||
await CheckProps(gs_props, new
|
||||
{
|
||||
List = TObject("System.Collections.Generic.List<System.DateTime>", is_null : true),
|
||||
StringField = TString($"ss_arr # {i*2} # gs # StringField")
|
||||
}, "obj_own ss_arr[{i}].gs");
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task RunOnCFOValueTypeResult(bool roundtrip) => await RunCallFunctionOn(
|
||||
eval_fn: "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);",
|
||||
fn_decl: "function () { return this; }",
|
||||
local_name: "simple_struct",
|
||||
bp_loc: "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12,
|
||||
roundtrip : roundtrip,
|
||||
test_fn : async(result) =>
|
||||
{
|
||||
|
||||
// getProperties (own=false)
|
||||
var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
|
||||
{
|
||||
objectId = result.Value["result"]["objectId"].Value<string>(),
|
||||
accessorPropertiesOnly = true,
|
||||
ownProperties = false
|
||||
}), ctx.token);
|
||||
AssertEqual(0, obj_accessors.Value["result"].Count(), "obj_accessors-count");
|
||||
|
||||
// getProperties (own=true)
|
||||
// isOwn = true, accessorPropertiesOnly = false
|
||||
var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
|
||||
{
|
||||
objectId = result.Value["result"]["objectId"].Value<string>(),
|
||||
accessorPropertiesOnly = false,
|
||||
ownProperties = true
|
||||
}), ctx.token);
|
||||
|
||||
var obj_own_val = obj_own.Value["result"];
|
||||
var dt = new DateTime(2020, 1, 2, 3, 4, 5);
|
||||
await CheckProps(obj_own_val, new
|
||||
{
|
||||
dt = TValueType("System.DateTime", dt.ToString()),
|
||||
gs = TValueType("Math.GenericStruct<System.DateTime>")
|
||||
}, $"obj_own-props");
|
||||
|
||||
await CheckDateTime(obj_own_val, "dt", dt);
|
||||
|
||||
var gs_props = await GetObjectOnLocals(obj_own_val, "gs");
|
||||
await CheckProps(gs_props, new
|
||||
{
|
||||
List = TObject("System.Collections.Generic.List<System.DateTime>", is_null : true),
|
||||
StringField = TString($"simple_struct # gs # StringField")
|
||||
}, "simple_struct.gs-props");
|
||||
});
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task RunOnJSObject(bool roundtrip) => await RunCallFunctionOn(
|
||||
"object_js_test ();",
|
||||
"function () { return this; }",
|
||||
"obj", "/other.js", 17, 1,
|
||||
fn_args : JArray.FromObject(new [] { new { value = 2 } }),
|
||||
roundtrip : roundtrip,
|
||||
test_fn : async(result) =>
|
||||
{
|
||||
|
||||
// getProperties (own=false)
|
||||
var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
|
||||
{
|
||||
objectId = result.Value["result"]["objectId"].Value<string>(),
|
||||
accessorPropertiesOnly = true,
|
||||
ownProperties = false
|
||||
}), ctx.token);
|
||||
|
||||
await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors");
|
||||
|
||||
// getProperties (own=true)
|
||||
// isOwn = true, accessorPropertiesOnly = false
|
||||
var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new
|
||||
{
|
||||
objectId = result.Value["result"]["objectId"].Value<string>(),
|
||||
accessorPropertiesOnly = false,
|
||||
ownProperties = true
|
||||
}), ctx.token);
|
||||
|
||||
var obj_own_val = obj_own.Value["result"];
|
||||
await CheckProps(obj_own_val, new
|
||||
{
|
||||
a_obj = TObject("Object"),
|
||||
b_arr = TArray("Array", 2)
|
||||
}, "obj_own", num_fields : 3);
|
||||
});
|
||||
|
||||
[Theory]
|
||||
[InlineData("big_array_js_test (10);", "/other.js", 8, 1, false)]
|
||||
[InlineData("big_array_js_test (10);", "/other.js", 8, 1, true)]
|
||||
[InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, false)]
|
||||
[InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, true)]
|
||||
public async Task RunOnArrayReturnObjectArrayByValue(string eval_fn, string bp_loc, int line, int col, bool roundtrip)
|
||||
{
|
||||
var ret_len = 5;
|
||||
await RunCallFunctionOn(eval_fn,
|
||||
"function () { return Object.values (this).filter ((k, i) => i%2 == 0); }",
|
||||
"big", bp_loc, line, col, returnByValue : true, roundtrip : roundtrip,
|
||||
test_fn : async(result) =>
|
||||
{
|
||||
// Check cfo result
|
||||
AssertEqual(JTokenType.Object, result.Value["result"].Type, "cfo-result-jsontype");
|
||||
AssertEqual("object", result.Value["result"]["type"]?.Value<string>(), "cfo-res-type");
|
||||
|
||||
AssertEqual(JTokenType.Array, result.Value["result"]["value"].Type, "cfo-res-value-jsontype");
|
||||
var actual = result.Value["result"] ? ["value"].Values<JToken>().ToArray();
|
||||
AssertEqual(ret_len, actual.Length, "cfo-res-value-length");
|
||||
|
||||
for (int i = 0; i < ret_len; i++)
|
||||
{
|
||||
var exp_num = i * 2 + 1000;
|
||||
if (bp_loc.EndsWith(".js", StringComparison.Ordinal))
|
||||
AssertEqual(exp_num, actual[i].Value<int>(), $"[{i}]");
|
||||
else
|
||||
{
|
||||
AssertEqual("number", actual[i] ? ["type"]?.Value<string>(), $"[{i}]-type");
|
||||
AssertEqual(exp_num.ToString(), actual[i] ? ["description"]?.Value<string>(), $"[{i}]-description");
|
||||
AssertEqual(exp_num, actual[i] ? ["value"]?.Value<int>(), $"[{i}]-value");
|
||||
}
|
||||
}
|
||||
await Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("big_array_js_test (10);", "/other.js", 8, 1, false)]
|
||||
[InlineData("big_array_js_test (10);", "/other.js", 8, 1, true)]
|
||||
[InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, false)]
|
||||
[InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, true)]
|
||||
public async Task RunOnArrayReturnArrayByValue(string eval_fn, string bp_loc, int line, int col, bool roundtrip) => await RunCallFunctionOn(eval_fn,
|
||||
"function () { return Object.getOwnPropertyNames (this); }",
|
||||
"big", bp_loc, line, col, returnByValue : true,
|
||||
roundtrip : roundtrip,
|
||||
test_fn : async(result) =>
|
||||
{
|
||||
// Check cfo result
|
||||
AssertEqual("object", result.Value["result"]["type"]?.Value<string>(), "cfo-res-type");
|
||||
|
||||
var exp = new JArray();
|
||||
for (int i = 0; i < 10; i++)
|
||||
exp.Add(i.ToString());
|
||||
exp.Add("length");
|
||||
|
||||
var actual = result.Value["result"] ? ["value"];
|
||||
if (!JObject.DeepEquals(exp, actual))
|
||||
{
|
||||
Assert.True(false, $"Results don't match.\nExpected: {exp}\nActual: {actual}");
|
||||
}
|
||||
await Task.CompletedTask;
|
||||
});
|
||||
|
||||
[Theory]
|
||||
[InlineData("big_array_js_test (10);", "/other.js", 8, 1, false)]
|
||||
[InlineData("big_array_js_test (10);", "/other.js", 8, 1, true)]
|
||||
[InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, false)]
|
||||
[InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, true)]
|
||||
public async Task RunOnArrayReturnPrimitive(string eval_fn, string bp_loc, int line, int col, bool return_by_val)
|
||||
{
|
||||
var insp = new Inspector();
|
||||
//Collect events
|
||||
var scripts = SubscribeToScripts(insp);
|
||||
|
||||
await Ready();
|
||||
await insp.Ready(async(cli, token) =>
|
||||
{
|
||||
ctx = new DebugTestContext(cli, insp, token, scripts);
|
||||
await SetBreakpoint(bp_loc, line, col);
|
||||
|
||||
// callFunctionOn
|
||||
var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);";
|
||||
var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token);
|
||||
var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE);
|
||||
|
||||
// Um for js we get "scriptId": "6"
|
||||
// CheckLocation (bp_loc, line, col, ctx.scripts, pause_location ["callFrames"][0]["location"]);
|
||||
|
||||
// Check the object at the bp
|
||||
var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value<string>());
|
||||
var obj = GetAndAssertObjectWithName(frame_locals, "big");
|
||||
var obj_id = obj["value"]["objectId"].Value<string>();
|
||||
|
||||
var cfo_args = JObject.FromObject(new
|
||||
{
|
||||
functionDeclaration = "function () { return 5; }",
|
||||
objectId = obj_id
|
||||
});
|
||||
|
||||
// value of @returnByValue doesn't matter, as the returned value
|
||||
// is a primitive
|
||||
if (return_by_val)
|
||||
cfo_args["returnByValue"] = return_by_val;
|
||||
|
||||
// callFunctionOn
|
||||
result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token);
|
||||
await CheckValue(result.Value["result"], TNumber(5), "cfo-res");
|
||||
});
|
||||
}
|
||||
|
||||
public static TheoryData<string, string, int, int, bool?> SilentErrorsTestData(bool? silent) => new TheoryData<string, string, int, int, bool?>
|
||||
{ { "invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, silent },
|
||||
{ "big_array_js_test (10);", "/other.js", 8, 1, silent }
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(SilentErrorsTestData), null)]
|
||||
[MemberData(nameof(SilentErrorsTestData), false)]
|
||||
[MemberData(nameof(SilentErrorsTestData), true)]
|
||||
public async Task CFOWithSilentReturnsErrors(string eval_fn, string bp_loc, int line, int col, bool? silent)
|
||||
{
|
||||
var insp = new Inspector();
|
||||
//Collect events
|
||||
var scripts = SubscribeToScripts(insp);
|
||||
|
||||
await Ready();
|
||||
await insp.Ready(async(cli, token) =>
|
||||
{
|
||||
ctx = new DebugTestContext(cli, insp, token, scripts);
|
||||
await SetBreakpoint(bp_loc, line, col);
|
||||
|
||||
// callFunctionOn
|
||||
var eval_expr = "window.setTimeout(function() { " + eval_fn + " }, 1);";
|
||||
var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token);
|
||||
var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE);
|
||||
|
||||
var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value<string>());
|
||||
var obj = GetAndAssertObjectWithName(frame_locals, "big");
|
||||
var big_obj_id = obj["value"]["objectId"].Value<string>();
|
||||
var error_msg = "#This is an error message#";
|
||||
|
||||
// Check the object at the bp
|
||||
var cfo_args = JObject.FromObject(new
|
||||
{
|
||||
functionDeclaration = $"function () {{ throw Error ('{error_msg}'); }}",
|
||||
objectId = big_obj_id
|
||||
});
|
||||
|
||||
if (silent.HasValue)
|
||||
cfo_args["silent"] = silent;
|
||||
|
||||
// callFunctionOn, Silent does not change the result, except that the error
|
||||
// doesn't get reported, and the execution is NOT paused even with setPauseOnException=true
|
||||
result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token);
|
||||
Assert.False(result.IsOk, "result.IsOk");
|
||||
Assert.True(result.IsErr, "result.IsErr");
|
||||
|
||||
var hasErrorMessage = result.Error["exceptionDetails"] ? ["exception"] ? ["description"]?.Value<string>()?.Contains(error_msg);
|
||||
Assert.True((hasErrorMessage ?? false), "Exception message not found");
|
||||
});
|
||||
}
|
||||
|
||||
public static TheoryData<string, string, int, int, string, Func<string[], object>, bool> GettersTestData(bool use_cfo) => new TheoryData<string, string, int, int, string, Func<string[], object>, bool>
|
||||
{
|
||||
// Chrome sends this one
|
||||
{
|
||||
"invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');",
|
||||
"PropertyGettersTest",
|
||||
30,
|
||||
12,
|
||||
"function invokeGetter(arrayStr){ let result=this; const properties=JSON.parse(arrayStr); for(let i=0,n=properties.length;i<n;++i){ result=result[properties[i]]; } return result; }",
|
||||
(arg_strs) => JArray.FromObject(arg_strs).ToString(),
|
||||
use_cfo
|
||||
},
|
||||
{
|
||||
"invoke_static_method_async ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTestAsync');",
|
||||
"MoveNext",
|
||||
38,
|
||||
12,
|
||||
"function invokeGetter(arrayStr){ let result=this; const properties=JSON.parse(arrayStr); for(let i=0,n=properties.length;i<n;++i){ result=result[properties[i]]; } return result; }",
|
||||
(arg_strs) => JArray.FromObject(arg_strs).ToString(),
|
||||
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,
|
||||
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,
|
||||
use_cfo
|
||||
}
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(GettersTestData), parameters : false)]
|
||||
[MemberData(nameof(GettersTestData), parameters : true)]
|
||||
public async Task PropertyGettersOnObjectsTest(string eval_fn, string method_name, int line, int col, string cfo_fn, Func<string[], object> get_args_fn, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
|
||||
"dotnet://debugger-test.dll/debugger-cfo-test.cs", line, col,
|
||||
method_name,
|
||||
$"window.setTimeout(function() {{ {eval_fn} }}, 1);",
|
||||
use_cfo : use_cfo,
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var frame_locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
|
||||
var dt = new DateTime(10, 9, 8, 7, 6, 5);
|
||||
|
||||
await CheckProps(frame_locals, new
|
||||
{
|
||||
ptd = TObject("DebuggerTests.ClassWithProperties"),
|
||||
swp = TObject("DebuggerTests.StructWithProperties"),
|
||||
}, "locals#0");
|
||||
|
||||
var ptd = GetAndAssertObjectWithName(frame_locals, "ptd");
|
||||
|
||||
var ptd_props = await GetProperties(ptd?["value"] ? ["objectId"]?.Value<string>());
|
||||
await CheckProps(ptd_props, new
|
||||
{
|
||||
Int = TGetter("Int"),
|
||||
String = TGetter("String"),
|
||||
DT = TGetter("DT"),
|
||||
IntArray = TGetter("IntArray"),
|
||||
DTArray = TGetter("DTArray")
|
||||
}, "ptd", num_fields : 7);
|
||||
|
||||
// Automatic properties don't have invokable getters, because we can get their
|
||||
// value from the backing field directly
|
||||
{
|
||||
dt = new DateTime(4, 5, 6, 7, 8, 9);
|
||||
var dt_auto_props = await GetObjectOnLocals(ptd_props, "DTAutoProperty");
|
||||
await CheckDateTime(ptd_props, "DTAutoProperty", dt);
|
||||
}
|
||||
|
||||
// Invoke getters, and check values
|
||||
|
||||
var res = await InvokeGetter(ptd, cfo_fn, get_args_fn(new [] { "Int" }));
|
||||
Assert.True(res.IsOk, $"InvokeGetter failed with : {res}");
|
||||
await CheckValue(res.Value["result"], JObject.FromObject(new { type = "number", value = 5 }), "ptd.Int");
|
||||
|
||||
res = await InvokeGetter(ptd, cfo_fn, get_args_fn(new [] { "String" }));
|
||||
Assert.True(res.IsOk, $"InvokeGetter failed with : {res}");
|
||||
await CheckValue(res.Value["result"], JObject.FromObject(new { type = "string", value = "foobar" }), "ptd.String");
|
||||
|
||||
dt = new DateTime(3, 4, 5, 6, 7, 8);
|
||||
res = await InvokeGetter(ptd, cfo_fn, get_args_fn(new [] { "DT" }));
|
||||
Assert.True(res.IsOk, $"InvokeGetter failed with : {res}");
|
||||
await CheckValue(res.Value["result"], TValueType("System.DateTime", dt.ToString()), "ptd.DT");
|
||||
await CheckDateTimeValue(res.Value["result"], dt);
|
||||
|
||||
// Check arrays through getters
|
||||
|
||||
res = await InvokeGetter(ptd, cfo_fn, get_args_fn(new [] { "IntArray" }));
|
||||
Assert.True(res.IsOk, $"InvokeGetter failed with : {res}");
|
||||
await CheckValue(res.Value["result"], TArray("int[]", 2), "ptd.IntArray");
|
||||
{
|
||||
var arr_elems = await GetProperties(res.Value["result"] ? ["objectId"]?.Value<string>());
|
||||
var exp_elems = new []
|
||||
{
|
||||
TNumber(10),
|
||||
TNumber(20)
|
||||
};
|
||||
|
||||
await CheckProps(arr_elems, exp_elems, "ptd.IntArray");
|
||||
}
|
||||
|
||||
res = await InvokeGetter(ptd, cfo_fn, get_args_fn(new [] { "DTArray" }));
|
||||
Assert.True(res.IsOk, $"InvokeGetter failed with : {res}");
|
||||
await CheckValue(res.Value["result"], TArray("System.DateTime[]", 2), "ptd.DTArray");
|
||||
{
|
||||
var dt0 = new DateTime(6, 7, 8, 9, 10, 11);
|
||||
var dt1 = new DateTime(1, 2, 3, 4, 5, 6);
|
||||
|
||||
var arr_elems = await GetProperties(res.Value["result"] ? ["objectId"]?.Value<string>());
|
||||
var exp_elems = new []
|
||||
{
|
||||
TValueType("System.DateTime", dt0.ToString()),
|
||||
TValueType("System.DateTime", dt1.ToString()),
|
||||
};
|
||||
|
||||
await CheckProps(arr_elems, exp_elems, "ptd.DTArray");
|
||||
}
|
||||
});
|
||||
|
||||
[Theory]
|
||||
[InlineData("invoke_static_method_async ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTestAsync');", "MoveNext", 38, 12)]
|
||||
[InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');", "PropertyGettersTest", 30, 12)]
|
||||
public async Task PropertyGettersOnStructsTest(string eval_fn, string method_name, int line, int col) => await CheckInspectLocalsAtBreakpointSite(
|
||||
"dotnet://debugger-test.dll/debugger-cfo-test.cs", line, col,
|
||||
method_name,
|
||||
$"window.setTimeout(function() {{ {eval_fn} }}, 1);",
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var frame_locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
|
||||
await CheckProps(frame_locals, new
|
||||
{
|
||||
ptd = TObject("DebuggerTests.ClassWithProperties"),
|
||||
swp = TObject("DebuggerTests.StructWithProperties"),
|
||||
}, "locals#0");
|
||||
|
||||
var swp = GetAndAssertObjectWithName(frame_locals, "swp");
|
||||
|
||||
var swp_props = await GetProperties(swp?["value"] ? ["objectId"]?.Value<string>());
|
||||
await CheckProps(swp_props, new
|
||||
{
|
||||
Int = TSymbol("int { get; }"),
|
||||
String = TSymbol("string { get; }"),
|
||||
DT = TSymbol("System.DateTime { get; }"),
|
||||
IntArray = TSymbol("int[] { get; }"),
|
||||
DTArray = TSymbol("System.DateTime[] { get; }")
|
||||
}, "swp");
|
||||
});
|
||||
|
||||
[Theory]
|
||||
[InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 30, 12, false)]
|
||||
[InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:PropertyGettersTest');", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 30, 12, true)]
|
||||
[InlineData("invoke_getters_js_test ();", "/other.js", 29, 1, false)]
|
||||
[InlineData("invoke_getters_js_test ();", "/other.js", 29, 1, true)]
|
||||
public async Task CheckAccessorsOnObjectsWithCFO(string eval_fn, string bp_loc, int line, int col, bool roundtrip)
|
||||
{
|
||||
await RunCallFunctionOn(
|
||||
eval_fn, "function() { return this; }", "ptd",
|
||||
bp_loc, line, col,
|
||||
roundtrip : roundtrip,
|
||||
test_fn : async(result) =>
|
||||
{
|
||||
|
||||
var is_js = bp_loc.EndsWith(".js");
|
||||
|
||||
// Check with `accessorPropertiesOnly=true`
|
||||
|
||||
var id = result.Value?["result"] ? ["objectId"]?.Value<string>();
|
||||
var get_prop_req = JObject.FromObject(new
|
||||
{
|
||||
objectId = id,
|
||||
accessorPropertiesOnly = true
|
||||
});
|
||||
|
||||
var res = await GetPropertiesAndCheckAccessors(get_prop_req, is_js ? 6 : 5); // js returns extra `__proto__` member also
|
||||
Assert.False(res.Value["result"].Any(jt => jt["name"]?.Value<string>() == "StringField"), "StringField shouldn't be returned for `accessorPropertiesOnly`");
|
||||
|
||||
// Check with `accessorPropertiesOnly` unset, == false
|
||||
get_prop_req = JObject.FromObject(new
|
||||
{
|
||||
objectId = id,
|
||||
});
|
||||
|
||||
res = await GetPropertiesAndCheckAccessors(get_prop_req, is_js ? 8 : 7); // js returns a `__proto__` member also
|
||||
Assert.True(res.Value["result"].Any(jt => jt["name"]?.Value<string>() == "StringField"), "StringField should be returned for `accessorPropertiesOnly=false`");
|
||||
});
|
||||
|
||||
async Task<Result> GetPropertiesAndCheckAccessors(JObject get_prop_req, int num_fields)
|
||||
{
|
||||
var res = await ctx.cli.SendCommand("Runtime.getProperties", get_prop_req, ctx.token);
|
||||
if (!res.IsOk)
|
||||
Assert.True(false, $"Runtime.getProperties failed for {get_prop_req.ToString ()}, with Result: {res}");
|
||||
|
||||
var accessors = new string[] { "Int", "String", "DT", "IntArray", "DTArray" };
|
||||
foreach (var name in accessors)
|
||||
{
|
||||
var prop = GetAndAssertObjectWithName(res.Value["result"], name);
|
||||
Assert.True(prop["value"] == null, $"{name} shouldn't have a `value`");
|
||||
|
||||
await CheckValue(prop, TGetter(name), $"{name}");
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
async Task<Result> InvokeGetter(JToken obj, string fn, object arguments) => await ctx.cli.SendCommand(
|
||||
"Runtime.callFunctionOn",
|
||||
JObject.FromObject(new
|
||||
{
|
||||
functionDeclaration = fn,
|
||||
objectId = obj["value"] ? ["objectId"]?.Value<string>(),
|
||||
arguments = new [] { new { value = arguments } }
|
||||
}), ctx.token);
|
||||
|
||||
/*
|
||||
* 1. runs `Runtime.callFunctionOn` on the objectId,
|
||||
* if @roundtrip == false, then
|
||||
* -> calls @test_fn for that result (new objectId)
|
||||
* else
|
||||
* -> runs it again on the *result's* objectId.
|
||||
* -> calls @test_fn on the *new* result objectId
|
||||
*
|
||||
* Returns: result of `Runtime.callFunctionOn`
|
||||
*/
|
||||
async Task RunCallFunctionOn(string eval_fn, string fn_decl, string local_name, string bp_loc, int line, int col, int res_array_len = -1,
|
||||
Func<Result, Task> test_fn = null, bool returnByValue = false, JArray fn_args = null, bool roundtrip = false)
|
||||
{
|
||||
var insp = new Inspector();
|
||||
//Collect events
|
||||
var scripts = SubscribeToScripts(insp);
|
||||
|
||||
await Ready();
|
||||
await insp.Ready(async(cli, token) =>
|
||||
{
|
||||
ctx = new DebugTestContext(cli, insp, token, scripts);
|
||||
await SetBreakpoint(bp_loc, line, col);
|
||||
|
||||
// callFunctionOn
|
||||
var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);";
|
||||
var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token);
|
||||
var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE);
|
||||
|
||||
// Um for js we get "scriptId": "6"
|
||||
// CheckLocation (bp_loc, line, col, ctx.scripts, pause_location ["callFrames"][0]["location"]);
|
||||
|
||||
// Check the object at the bp
|
||||
var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value<string>());
|
||||
var obj = GetAndAssertObjectWithName(frame_locals, local_name);
|
||||
var obj_id = obj["value"]["objectId"].Value<string>();
|
||||
|
||||
var cfo_args = JObject.FromObject(new
|
||||
{
|
||||
functionDeclaration = fn_decl,
|
||||
objectId = obj_id
|
||||
});
|
||||
|
||||
if (fn_args != null)
|
||||
cfo_args["arguments"] = fn_args;
|
||||
|
||||
if (returnByValue)
|
||||
cfo_args["returnByValue"] = returnByValue;
|
||||
|
||||
// callFunctionOn
|
||||
result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token);
|
||||
await CheckCFOResult(result);
|
||||
|
||||
// If it wasn't `returnByValue`, then try to run a new function
|
||||
// on that *returned* object
|
||||
// This second function, just returns the object as-is, so the same
|
||||
// test_fn is re-usable.
|
||||
if (!returnByValue && roundtrip)
|
||||
{
|
||||
cfo_args = JObject.FromObject(new
|
||||
{
|
||||
functionDeclaration = "function () { return this; }",
|
||||
objectId = result.Value["result"]["objectId"]?.Value<string>()
|
||||
});
|
||||
|
||||
if (fn_args != null)
|
||||
cfo_args["arguments"] = fn_args;
|
||||
|
||||
result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token);
|
||||
|
||||
await CheckCFOResult(result);
|
||||
}
|
||||
|
||||
if (test_fn != null)
|
||||
await test_fn(result);
|
||||
|
||||
return;
|
||||
|
||||
async Task CheckCFOResult(Result result)
|
||||
{
|
||||
if (returnByValue)
|
||||
return;
|
||||
|
||||
if (res_array_len < 0)
|
||||
await CheckValue(result.Value["result"], TObject("Object"), $"cfo-res");
|
||||
else
|
||||
await CheckValue(result.Value["result"], TArray("Array", res_array_len), $"cfo-res");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +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;
|
||||
|
||||
namespace DebuggerTests
|
||||
{
|
||||
public class DateTimeList : DebuggerTestBase
|
||||
{
|
||||
|
||||
[Theory]
|
||||
[InlineData("en-US")]
|
||||
|
||||
// Currently not passing tests. Issue #19743
|
||||
// [InlineData ("ja-JP")]
|
||||
// [InlineData ("es-ES")]
|
||||
//[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);
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="xunit" Version="2.4.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
|
||||
|
||||
<Content Include="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BrowserDebugHost\BrowserDebugHost.csproj" />
|
||||
<ProjectReference Include="..\BrowserDebugProxy\BrowserDebugProxy.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +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 Newtonsoft.Json.Linq;
|
||||
using Microsoft.WebAssembly.Diagnostics;
|
||||
using Xunit;
|
||||
|
||||
namespace DebuggerTests
|
||||
{
|
||||
|
||||
public class DelegateTests : DebuggerTestBase
|
||||
{
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 53, 8, "DelegatesTest", false)]
|
||||
[InlineData(0, 53, 8, "DelegatesTest", true)]
|
||||
[InlineData(2, 99, 8, "InnerMethod2", false)]
|
||||
[InlineData(2, 99, 8, "InnerMethod2", true)]
|
||||
public async Task InspectLocalsWithDelegatesAtBreakpointSite(int frame, int line, int col, string method_name, bool use_cfo) =>
|
||||
await CheckInspectLocalsAtBreakpointSite(
|
||||
"dotnet://debugger-test.dll/debugger-test.cs", line, col, method_name,
|
||||
"window.setTimeout(function() { invoke_delegates_test (); }, 1);",
|
||||
use_cfo : use_cfo,
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][frame]["callFrameId"].Value<string>());
|
||||
|
||||
await CheckProps(locals, new
|
||||
{
|
||||
fn_func = TDelegate("System.Func<Math, bool>", "bool <DelegatesTest>|(Math)"),
|
||||
fn_func_null = TObject("System.Func<Math, bool>", is_null : true),
|
||||
fn_func_arr = TArray("System.Func<Math, bool>[]", 1),
|
||||
fn_del = TDelegate("Math.IsMathNull", "bool IsMathNullDelegateTarget (Math)"),
|
||||
fn_del_null = TObject("Math.IsMathNull", is_null : true),
|
||||
fn_del_arr = TArray("Math.IsMathNull[]", 1),
|
||||
|
||||
// Unused locals
|
||||
fn_func_unused = TDelegate("System.Func<Math, bool>", "bool <DelegatesTest>|(Math)"),
|
||||
fn_func_null_unused = TObject("System.Func<Math, bool>", is_null : true),
|
||||
fn_func_arr_unused = TArray("System.Func<Math, bool>[]", 1),
|
||||
|
||||
fn_del_unused = TDelegate("Math.IsMathNull", "bool IsMathNullDelegateTarget (Math)"),
|
||||
fn_del_null_unused = TObject("Math.IsMathNull", is_null : true),
|
||||
fn_del_arr_unused = TArray("Math.IsMathNull[]", 1),
|
||||
|
||||
res = TBool(false),
|
||||
m_obj = TObject("Math")
|
||||
}, "locals");
|
||||
|
||||
await CompareObjectPropertiesFor(locals, "fn_func_arr", new []
|
||||
{
|
||||
TDelegate(
|
||||
"System.Func<Math, bool>",
|
||||
"bool <DelegatesTest>|(Math)")
|
||||
}, "locals#fn_func_arr");
|
||||
|
||||
await CompareObjectPropertiesFor(locals, "fn_del_arr", new []
|
||||
{
|
||||
TDelegate(
|
||||
"Math.IsMathNull",
|
||||
"bool IsMathNullDelegateTarget (Math)")
|
||||
}, "locals#fn_del_arr");
|
||||
|
||||
await CompareObjectPropertiesFor(locals, "fn_func_arr_unused", new []
|
||||
{
|
||||
TDelegate(
|
||||
"System.Func<Math, bool>",
|
||||
"bool <DelegatesTest>|(Math)")
|
||||
}, "locals#fn_func_arr_unused");
|
||||
|
||||
await CompareObjectPropertiesFor(locals, "fn_del_arr_unused", new []
|
||||
{
|
||||
TDelegate(
|
||||
"Math.IsMathNull",
|
||||
"bool IsMathNullDelegateTarget (Math)")
|
||||
}, "locals#fn_del_arr_unused");
|
||||
}
|
||||
);
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 202, 8, "DelegatesSignatureTest", false)]
|
||||
[InlineData(0, 202, 8, "DelegatesSignatureTest", true)]
|
||||
[InlineData(2, 99, 8, "InnerMethod2", false)]
|
||||
[InlineData(2, 99, 8, "InnerMethod2", true)]
|
||||
public async Task InspectDelegateSignaturesWithFunc(int frame, int line, int col, string bp_method, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
|
||||
"dotnet://debugger-test.dll/debugger-test.cs",
|
||||
line, col,
|
||||
bp_method,
|
||||
"window.setTimeout (function () { invoke_static_method ('[debugger-test] Math:DelegatesSignatureTest'); }, 1)",
|
||||
use_cfo : use_cfo,
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][frame]["callFrameId"].Value<string>());
|
||||
|
||||
await CheckProps(locals, new
|
||||
{
|
||||
fn_func = TDelegate("System.Func<Math, Math.GenericStruct<Math.GenericStruct<int[]>>, Math.GenericStruct<bool[]>>",
|
||||
"Math.GenericStruct<bool[]> <DelegatesSignatureTest>|(Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
|
||||
|
||||
fn_func_del = TDelegate("System.Func<Math, Math.GenericStruct<Math.GenericStruct<int[]>>, Math.GenericStruct<bool[]>>",
|
||||
"Math.GenericStruct<bool[]> DelegateTargetForSignatureTest (Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
|
||||
|
||||
fn_func_null = TObject("System.Func<Math, Math.GenericStruct<Math.GenericStruct<int[]>>, Math.GenericStruct<bool[]>>", is_null : true),
|
||||
fn_func_only_ret = TDelegate("System.Func<bool>", "bool <DelegatesSignatureTest>|()"),
|
||||
fn_func_arr = TArray("System.Func<Math, Math.GenericStruct<Math.GenericStruct<int[]>>, Math.GenericStruct<bool[]>>[]", 1),
|
||||
|
||||
fn_del = TDelegate("Math.DelegateForSignatureTest",
|
||||
"Math.GenericStruct<bool[]> DelegateTargetForSignatureTest (Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
|
||||
|
||||
fn_del_l = TDelegate("Math.DelegateForSignatureTest",
|
||||
"Math.GenericStruct<bool[]> <DelegatesSignatureTest>|(Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
|
||||
|
||||
fn_del_null = TObject("Math.DelegateForSignatureTest", is_null : true),
|
||||
fn_del_arr = TArray("Math.DelegateForSignatureTest[]", 2),
|
||||
m_obj = TObject("Math"),
|
||||
gs_gs = TValueType("Math.GenericStruct<Math.GenericStruct<int[]>>"),
|
||||
fn_void_del = TDelegate("Math.DelegateWithVoidReturn",
|
||||
"void DelegateTargetWithVoidReturn (Math.GenericStruct<int[]>)"),
|
||||
|
||||
fn_void_del_arr = TArray("Math.DelegateWithVoidReturn[]", 1),
|
||||
fn_void_del_null = TObject("Math.DelegateWithVoidReturn", is_null : true),
|
||||
gs = TValueType("Math.GenericStruct<int[]>"),
|
||||
rets = TArray("Math.GenericStruct<bool[]>[]", 6)
|
||||
}, "locals");
|
||||
|
||||
await CompareObjectPropertiesFor(locals, "fn_func_arr", new []
|
||||
{
|
||||
TDelegate(
|
||||
"System.Func<Math, Math.GenericStruct<Math.GenericStruct<int[]>>, Math.GenericStruct<bool[]>>",
|
||||
"Math.GenericStruct<bool[]> <DelegatesSignatureTest>|(Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
|
||||
}, "locals#fn_func_arr");
|
||||
|
||||
await CompareObjectPropertiesFor(locals, "fn_del_arr", new []
|
||||
{
|
||||
TDelegate(
|
||||
"Math.DelegateForSignatureTest",
|
||||
"Math.GenericStruct<bool[]> DelegateTargetForSignatureTest (Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
|
||||
TDelegate(
|
||||
"Math.DelegateForSignatureTest",
|
||||
"Math.GenericStruct<bool[]> <DelegatesSignatureTest>|(Math,Math.GenericStruct<Math.GenericStruct<int[]>>)")
|
||||
}, "locals#fn_del_arr");
|
||||
|
||||
await CompareObjectPropertiesFor(locals, "fn_void_del_arr", new []
|
||||
{
|
||||
TDelegate(
|
||||
"Math.DelegateWithVoidReturn",
|
||||
"void DelegateTargetWithVoidReturn (Math.GenericStruct<int[]>)")
|
||||
}, "locals#fn_void_del_arr");
|
||||
});
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 224, 8, "ActionTSignatureTest", false)]
|
||||
[InlineData(0, 224, 8, "ActionTSignatureTest", true)]
|
||||
[InlineData(2, 99, 8, "InnerMethod2", false)]
|
||||
[InlineData(2, 99, 8, "InnerMethod2", true)]
|
||||
public async Task ActionTSignatureTest(int frame, int line, int col, string bp_method, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
|
||||
"dotnet://debugger-test.dll/debugger-test.cs", line, col,
|
||||
bp_method,
|
||||
"window.setTimeout (function () { invoke_static_method ('[debugger-test] Math:ActionTSignatureTest'); }, 1)",
|
||||
use_cfo : use_cfo,
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][frame]["callFrameId"].Value<string>());
|
||||
|
||||
await CheckProps(locals, new
|
||||
{
|
||||
fn_action = TDelegate("System.Action<Math.GenericStruct<int[]>>",
|
||||
"void <ActionTSignatureTest>|(Math.GenericStruct<int[]>)"),
|
||||
fn_action_del = TDelegate("System.Action<Math.GenericStruct<int[]>>",
|
||||
"void DelegateTargetWithVoidReturn (Math.GenericStruct<int[]>)"),
|
||||
fn_action_bare = TDelegate("System.Action",
|
||||
"void|()"),
|
||||
|
||||
fn_action_null = TObject("System.Action<Math.GenericStruct<int[]>>", is_null : true),
|
||||
|
||||
fn_action_arr = TArray("System.Action<Math.GenericStruct<int[]>>[]", 3),
|
||||
|
||||
gs = TValueType("Math.GenericStruct<int[]>"),
|
||||
}, "locals");
|
||||
|
||||
await CompareObjectPropertiesFor(locals, "fn_action_arr", new []
|
||||
{
|
||||
TDelegate(
|
||||
"System.Action<Math.GenericStruct<int[]>>",
|
||||
"void <ActionTSignatureTest>|(Math.GenericStruct<int[]>)"),
|
||||
TDelegate(
|
||||
"System.Action<Math.GenericStruct<int[]>>",
|
||||
"void DelegateTargetWithVoidReturn (Math.GenericStruct<int[]>)"),
|
||||
TObject("System.Action<Math.GenericStruct<int[]>>", is_null : true)
|
||||
}, "locals#fn_action_arr");
|
||||
});
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 242, 8, "NestedDelegatesTest", false)]
|
||||
[InlineData(0, 242, 8, "NestedDelegatesTest", true)]
|
||||
[InlineData(2, 99, 8, "InnerMethod2", false)]
|
||||
[InlineData(2, 99, 8, "InnerMethod2", true)]
|
||||
public async Task NestedDelegatesTest(int frame, int line, int col, string bp_method, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
|
||||
"dotnet://debugger-test.dll/debugger-test.cs", line, col,
|
||||
bp_method,
|
||||
"window.setTimeout (function () { invoke_static_method ('[debugger-test] Math:NestedDelegatesTest'); }, 1)",
|
||||
use_cfo : use_cfo,
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][frame]["callFrameId"].Value<string>());
|
||||
|
||||
await CheckProps(locals, new
|
||||
{
|
||||
fn_func = TDelegate("System.Func<System.Func<int, bool>, bool>",
|
||||
"bool <NestedDelegatesTest>|(Func<int, bool>)"),
|
||||
fn_func_null = TObject("System.Func<System.Func<int, bool>, bool>", is_null : true),
|
||||
fn_func_arr = TArray("System.Func<System.Func<int, bool>, bool>[]", 1),
|
||||
fn_del_arr = TArray("System.Func<System.Func<int, bool>, bool>[]", 1),
|
||||
|
||||
m_obj = TObject("Math"),
|
||||
fn_del_null = TObject("System.Func<System.Func<int, bool>, bool>", is_null : true),
|
||||
fs = TDelegate("System.Func<int, bool>",
|
||||
"bool <NestedDelegatesTest>|(int)")
|
||||
}, "locals");
|
||||
|
||||
await CompareObjectPropertiesFor(locals, "fn_func_arr", new []
|
||||
{
|
||||
TDelegate(
|
||||
"System.Func<System.Func<int, bool>, bool>",
|
||||
"bool <NestedDelegatesTest>|(System.Func<int, bool>)")
|
||||
}, "locals#fn_func_arr");
|
||||
|
||||
await CompareObjectPropertiesFor(locals, "fn_del_arr", new []
|
||||
{
|
||||
TDelegate(
|
||||
"System.Func<System.Func<int, bool>, bool>",
|
||||
"bool DelegateTargetForNestedFunc (Func<int, bool>)")
|
||||
}, "locals#fn_del_arr");
|
||||
});
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 262, 8, "MethodWithDelegateArgs", false)]
|
||||
[InlineData(0, 262, 8, "MethodWithDelegateArgs", true)]
|
||||
[InlineData(2, 99, 8, "InnerMethod2", false)]
|
||||
[InlineData(2, 99, 8, "InnerMethod2", true)]
|
||||
public async Task DelegatesAsMethodArgsTest(int frame, int line, int col, string bp_method, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
|
||||
"dotnet://debugger-test.dll/debugger-test.cs", line, col,
|
||||
bp_method,
|
||||
"window.setTimeout (function () { invoke_static_method ('[debugger-test] Math:DelegatesAsMethodArgsTest'); }, 1)",
|
||||
use_cfo : use_cfo,
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][frame]["callFrameId"].Value<string>());
|
||||
|
||||
await CheckProps(locals, new
|
||||
{
|
||||
@this = TObject("Math"),
|
||||
dst_arr = TArray("Math.DelegateForSignatureTest[]", 2),
|
||||
fn_func = TDelegate("System.Func<char[], bool>",
|
||||
"bool <DelegatesAsMethodArgsTest>|(char[])"),
|
||||
fn_action = TDelegate("System.Action<Math.GenericStruct<int>[]>",
|
||||
"void <DelegatesAsMethodArgsTest>|(Math.GenericStruct<int>[])")
|
||||
}, "locals");
|
||||
|
||||
await CompareObjectPropertiesFor(locals, "dst_arr", new []
|
||||
{
|
||||
TDelegate("Math.DelegateForSignatureTest",
|
||||
"Math.GenericStruct<bool[]> DelegateTargetForSignatureTest (Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
|
||||
TDelegate("Math.DelegateForSignatureTest",
|
||||
"Math.GenericStruct<bool[]> <DelegatesAsMethodArgsTest>|(Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
|
||||
}, "locals#dst_arr");
|
||||
});
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task MethodWithDelegatesAsyncTest(bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
|
||||
"dotnet://debugger-test.dll/debugger-test.cs", 281, 8,
|
||||
"MoveNext", //"DelegatesAsMethodArgsTestAsync"
|
||||
"window.setTimeout (function () { invoke_static_method_async ('[debugger-test] Math:MethodWithDelegatesAsyncTest'); }, 1)",
|
||||
use_cfo : use_cfo,
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
|
||||
|
||||
await CheckProps(locals, new
|
||||
{
|
||||
@this = TObject("Math"),
|
||||
_dst_arr = TArray("Math.DelegateForSignatureTest[]", 2),
|
||||
_fn_func = TDelegate("System.Func<char[], bool>",
|
||||
"bool <MethodWithDelegatesAsync>|(char[])"),
|
||||
_fn_action = TDelegate("System.Action<Math.GenericStruct<int>[]>",
|
||||
"void <MethodWithDelegatesAsync>|(Math.GenericStruct<int>[])")
|
||||
}, "locals");
|
||||
|
||||
await CompareObjectPropertiesFor(locals, "_dst_arr", new []
|
||||
{
|
||||
TDelegate(
|
||||
"Math.DelegateForSignatureTest",
|
||||
"Math.GenericStruct<bool[]> DelegateTargetForSignatureTest (Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
|
||||
TDelegate(
|
||||
"Math.DelegateForSignatureTest",
|
||||
"Math.GenericStruct<bool[]> <MethodWithDelegatesAsync>|(Math,Math.GenericStruct<Math.GenericStruct<int[]>>)"),
|
||||
}, "locals#dst_arr");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
// 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.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.WebAssembly.Diagnostics
|
||||
{
|
||||
internal class DevToolsClient : IDisposable
|
||||
{
|
||||
ClientWebSocket socket;
|
||||
List<Task> pending_ops = new List<Task>();
|
||||
TaskCompletionSource<bool> side_exit = new TaskCompletionSource<bool>();
|
||||
List<byte[]> pending_writes = new List<byte[]>();
|
||||
Task current_write;
|
||||
readonly ILogger logger;
|
||||
|
||||
public DevToolsClient(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
~DevToolsClient()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
public async Task Close(CancellationToken cancellationToken)
|
||||
{
|
||||
if (socket.State == WebSocketState.Open)
|
||||
await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Closing", cancellationToken);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
socket.Dispose();
|
||||
}
|
||||
|
||||
Task Pump(Task task, CancellationToken token)
|
||||
{
|
||||
if (task != current_write)
|
||||
return null;
|
||||
current_write = null;
|
||||
|
||||
pending_writes.RemoveAt(0);
|
||||
|
||||
if (pending_writes.Count > 0)
|
||||
{
|
||||
current_write = socket.SendAsync(new ArraySegment<byte>(pending_writes[0]), WebSocketMessageType.Text, true, token);
|
||||
return current_write;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async Task<string> ReadOne(CancellationToken token)
|
||||
{
|
||||
byte[] buff = new byte[4000];
|
||||
var mem = new MemoryStream();
|
||||
while (true)
|
||||
{
|
||||
var result = await this.socket.ReceiveAsync(new ArraySegment<byte>(buff), token);
|
||||
if (result.MessageType == WebSocketMessageType.Close)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (result.EndOfMessage)
|
||||
{
|
||||
mem.Write(buff, 0, result.Count);
|
||||
return Encoding.UTF8.GetString(mem.GetBuffer(), 0, (int) mem.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
mem.Write(buff, 0, result.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void Send(byte[] bytes, CancellationToken token)
|
||||
{
|
||||
pending_writes.Add(bytes);
|
||||
if (pending_writes.Count == 1)
|
||||
{
|
||||
if (current_write != null)
|
||||
throw new Exception("Internal state is bad. current_write must be null if there are no pending writes");
|
||||
|
||||
current_write = socket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, token);
|
||||
pending_ops.Add(current_write);
|
||||
}
|
||||
}
|
||||
|
||||
async Task MarkCompleteAfterward(Func<CancellationToken, Task> send, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
await send(token);
|
||||
side_exit.SetResult(true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
side_exit.SetException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task<bool> ConnectWithMainLoops(
|
||||
Uri uri,
|
||||
Func<string, CancellationToken, Task> receive,
|
||||
Func<CancellationToken, Task> send,
|
||||
CancellationToken token)
|
||||
{
|
||||
|
||||
logger.LogDebug("connecting to {0}", uri);
|
||||
this.socket = new ClientWebSocket();
|
||||
this.socket.Options.KeepAliveInterval = Timeout.InfiniteTimeSpan;
|
||||
|
||||
await this.socket.ConnectAsync(uri, token);
|
||||
pending_ops.Add(ReadOne(token));
|
||||
pending_ops.Add(side_exit.Task);
|
||||
pending_ops.Add(MarkCompleteAfterward(send, token));
|
||||
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
var task = await Task.WhenAny(pending_ops);
|
||||
if (task == pending_ops[0])
|
||||
{ //pending_ops[0] is for message reading
|
||||
var msg = ((Task<string>) task).Result;
|
||||
pending_ops[0] = ReadOne(token);
|
||||
Task tsk = receive(msg, token);
|
||||
if (tsk != null)
|
||||
pending_ops.Add(tsk);
|
||||
}
|
||||
else if (task == pending_ops[1])
|
||||
{
|
||||
var res = ((Task<bool>) task).Result;
|
||||
//it might not throw if exiting successfull
|
||||
return res;
|
||||
}
|
||||
else
|
||||
{ //must be a background task
|
||||
pending_ops.Remove(task);
|
||||
var tsk = Pump(task, token);
|
||||
if (tsk != null)
|
||||
pending_ops.Add(tsk);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual void Log(string priority, string msg)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +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 Newtonsoft.Json.Linq;
|
||||
using Microsoft.WebAssembly.Diagnostics;
|
||||
using Xunit;
|
||||
|
||||
namespace DebuggerTests
|
||||
{
|
||||
|
||||
public class EvaluateOnCallFrameTests : DebuggerTestBase
|
||||
{
|
||||
|
||||
[Fact]
|
||||
public async Task EvaluateThisProperties() => await CheckInspectLocalsAtBreakpointSite(
|
||||
"dotnet://debugger-test.dll/debugger-evaluate-test.cs", 25, 16,
|
||||
"run",
|
||||
"window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
|
||||
var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "a");
|
||||
CheckContentValue(evaluate, "1");
|
||||
evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "b");
|
||||
CheckContentValue(evaluate, "2");
|
||||
evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "c");
|
||||
CheckContentValue(evaluate, "3");
|
||||
|
||||
evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "dt");
|
||||
await CheckDateTimeValue(evaluate, new DateTime(2000, 5, 4, 3, 2, 1));
|
||||
});
|
||||
|
||||
[Theory]
|
||||
[InlineData(63, 12, "EvaluateTestsStructInstanceMethod")]
|
||||
[InlineData(79, 12, "GenericInstanceMethodOnStruct<int>")]
|
||||
[InlineData(102, 12, "EvaluateTestsGenericStructInstanceMethod")]
|
||||
public async Task EvaluateThisPropertiesOnStruct(int line, int col, string method_name) => await CheckInspectLocalsAtBreakpointSite(
|
||||
"dotnet://debugger-test.dll/debugger-evaluate-test.cs", line, col,
|
||||
method_name,
|
||||
"window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "a");
|
||||
CheckContentValue(evaluate, "1");
|
||||
evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "b");
|
||||
CheckContentValue(evaluate, "2");
|
||||
evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "c");
|
||||
CheckContentValue(evaluate, "3");
|
||||
|
||||
evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "dateTime");
|
||||
await CheckDateTimeValue(evaluate, new DateTime(2020, 1, 2, 3, 4, 5));
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public async Task EvaluateParameters() => await CheckInspectLocalsAtBreakpointSite(
|
||||
"dotnet://debugger-test.dll/debugger-evaluate-test.cs", 25, 16,
|
||||
"run",
|
||||
"window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
|
||||
var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "g");
|
||||
CheckContentValue(evaluate, "100");
|
||||
evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "h");
|
||||
CheckContentValue(evaluate, "200");
|
||||
evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "valString");
|
||||
CheckContentValue(evaluate, "test");
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public async Task EvaluateLocals() => await CheckInspectLocalsAtBreakpointSite(
|
||||
"dotnet://debugger-test.dll/debugger-evaluate-test.cs", 25, 16,
|
||||
"run",
|
||||
"window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
|
||||
var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "d");
|
||||
CheckContentValue(evaluate, "101");
|
||||
evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "e");
|
||||
CheckContentValue(evaluate, "102");
|
||||
evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "f");
|
||||
CheckContentValue(evaluate, "103");
|
||||
|
||||
evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "local_dt");
|
||||
await CheckDateTimeValue(evaluate, new DateTime(2010, 9, 8, 7, 6, 5));
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public async Task EvaluateLocalsAsync()
|
||||
{
|
||||
var bp_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
|
||||
int line = 249;
|
||||
int col = 12;
|
||||
var function_name = "MoveNext";
|
||||
await CheckInspectLocalsAtBreakpointSite(
|
||||
bp_loc, line, col,
|
||||
function_name,
|
||||
"window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.ArrayTestsClass:EntryPointForStructMethod', true); })",
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
|
||||
|
||||
// sc_arg
|
||||
{
|
||||
var sc_arg = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "sc_arg");
|
||||
await CheckValue(sc_arg, TObject("DebuggerTests.SimpleClass"), "sc_arg#1");
|
||||
|
||||
var sc_arg_props = await GetProperties(sc_arg["objectId"]?.Value<string>());
|
||||
await CheckProps(sc_arg_props, new
|
||||
{
|
||||
X = TNumber(10),
|
||||
Y = TNumber(45),
|
||||
Id = TString("sc#Id"),
|
||||
Color = TEnum("DebuggerTests.RGB", "Blue"),
|
||||
PointWithCustomGetter = TGetter("PointWithCustomGetter")
|
||||
}, "sc_arg_props#1");
|
||||
}
|
||||
|
||||
// local_gs
|
||||
{
|
||||
var local_gs = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "local_gs");
|
||||
await CheckValue(local_gs, TValueType("DebuggerTests.SimpleGenericStruct<int>"), "local_gs#1");
|
||||
|
||||
var local_gs_props = await GetProperties(local_gs["objectId"]?.Value<string>());
|
||||
await CheckProps(local_gs_props, new
|
||||
{
|
||||
Id = TObject("string", is_null : true),
|
||||
Color = TEnum("DebuggerTests.RGB", "Red"),
|
||||
Value = TNumber(0)
|
||||
}, "local_gs_props#1");
|
||||
}
|
||||
|
||||
// step, check local_gs
|
||||
pause_location = await StepAndCheck(StepKind.Over, bp_loc, line + 1, col, function_name);
|
||||
{
|
||||
var local_gs = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "local_gs");
|
||||
await CheckValue(local_gs, TValueType("DebuggerTests.SimpleGenericStruct<int>"), "local_gs#2");
|
||||
|
||||
var local_gs_props = await GetProperties(local_gs["objectId"]?.Value<string>());
|
||||
await CheckProps(local_gs_props, new
|
||||
{
|
||||
Id = TString("local_gs#Id"),
|
||||
Color = TEnum("DebuggerTests.RGB", "Green"),
|
||||
Value = TNumber(4)
|
||||
}, "local_gs_props#2");
|
||||
}
|
||||
|
||||
// step check sc_arg.Id
|
||||
pause_location = await StepAndCheck(StepKind.Over, bp_loc, line + 2, col, function_name);
|
||||
{
|
||||
var sc_arg = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "sc_arg");
|
||||
await CheckValue(sc_arg, TObject("DebuggerTests.SimpleClass"), "sc_arg#2");
|
||||
|
||||
var sc_arg_props = await GetProperties(sc_arg["objectId"]?.Value<string>());
|
||||
await CheckProps(sc_arg_props, new
|
||||
{
|
||||
X = TNumber(10),
|
||||
Y = TNumber(45),
|
||||
Id = TString("sc_arg#Id"), // <------- This changed
|
||||
Color = TEnum("DebuggerTests.RGB", "Blue"),
|
||||
PointWithCustomGetter = TGetter("PointWithCustomGetter")
|
||||
}, "sc_arg_props#2");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EvaluateExpressions() => await CheckInspectLocalsAtBreakpointSite(
|
||||
"dotnet://debugger-test.dll/debugger-evaluate-test.cs", 25, 16,
|
||||
"run",
|
||||
"window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
|
||||
var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "d + e");
|
||||
CheckContentValue(evaluate, "203");
|
||||
evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "e + 10");
|
||||
CheckContentValue(evaluate, "112");
|
||||
evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "a + a");
|
||||
CheckContentValue(evaluate, "2");
|
||||
evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "this.a + this.b");
|
||||
CheckContentValue(evaluate, "3");
|
||||
evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "\"test\" + \"test\"");
|
||||
CheckContentValue(evaluate, "testtest");
|
||||
evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "5 + 5");
|
||||
CheckContentValue(evaluate, "10");
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public async Task EvaluateThisExpressions() => await CheckInspectLocalsAtBreakpointSite(
|
||||
"dotnet://debugger-test.dll/debugger-evaluate-test.cs", 25, 16,
|
||||
"run",
|
||||
"window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
|
||||
var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "this.a");
|
||||
CheckContentValue(evaluate, "1");
|
||||
evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "this.b");
|
||||
CheckContentValue(evaluate, "2");
|
||||
evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "this.c");
|
||||
CheckContentValue(evaluate, "3");
|
||||
|
||||
// FIXME: not supported yet
|
||||
// evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "this.dt");
|
||||
// await CheckDateTimeValue (evaluate, new DateTime (2000, 5, 4, 3, 2, 1));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
// 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.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.WebAssembly.Diagnostics
|
||||
{
|
||||
internal class InspectorClient : DevToolsClient
|
||||
{
|
||||
List < (int, TaskCompletionSource<Result>) > pending_cmds = new List < (int, TaskCompletionSource<Result>) > ();
|
||||
Func<string, JObject, CancellationToken, Task> onEvent;
|
||||
int next_cmd_id;
|
||||
|
||||
public InspectorClient(ILogger logger) : base(logger) { }
|
||||
|
||||
Task HandleMessage(string msg, CancellationToken token)
|
||||
{
|
||||
var res = JObject.Parse(msg);
|
||||
if (res["id"] == null)
|
||||
DumpProtocol(string.Format("Event method: {0} params: {1}", res["method"], res["params"]));
|
||||
else
|
||||
DumpProtocol(string.Format("Response id: {0} res: {1}", res["id"], res));
|
||||
|
||||
if (res["id"] == null)
|
||||
return onEvent(res["method"].Value<string>(), res["params"] as JObject, token);
|
||||
var id = res["id"].Value<int>();
|
||||
var idx = pending_cmds.FindIndex(e => e.Item1 == id);
|
||||
var item = pending_cmds[idx];
|
||||
pending_cmds.RemoveAt(idx);
|
||||
item.Item2.SetResult(Result.FromJson(res));
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task Connect(
|
||||
Uri uri,
|
||||
Func<string, JObject, CancellationToken, Task> onEvent,
|
||||
Func<CancellationToken, Task> send,
|
||||
CancellationToken token)
|
||||
{
|
||||
|
||||
this.onEvent = onEvent;
|
||||
await ConnectWithMainLoops(uri, HandleMessage, send, token);
|
||||
}
|
||||
|
||||
public Task<Result> SendCommand(string method, JObject args, CancellationToken token)
|
||||
{
|
||||
int id = ++next_cmd_id;
|
||||
if (args == null)
|
||||
args = new JObject();
|
||||
|
||||
var o = JObject.FromObject(new
|
||||
{
|
||||
id = id,
|
||||
method = method,
|
||||
@params = args
|
||||
});
|
||||
|
||||
var tcs = new TaskCompletionSource<Result>();
|
||||
pending_cmds.Add((id, tcs));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
protected virtual void DumpProtocol(string msg)
|
||||
{
|
||||
// Console.WriteLine (msg);
|
||||
//XXX make logging not stupid
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,560 @@
|
|||
// 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 Newtonsoft.Json.Linq;
|
||||
using Microsoft.WebAssembly.Diagnostics;
|
||||
using Xunit;
|
||||
|
||||
namespace DebuggerTests
|
||||
{
|
||||
|
||||
public class PointerTests : DebuggerTestBase
|
||||
{
|
||||
|
||||
public static TheoryData<string, string, string, int, string, bool> PointersTestData =>
|
||||
new TheoryData<string, string, string, int, string, bool>
|
||||
{ { $"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "LocalPointers", 32, "LocalPointers", false },
|
||||
{ $"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "LocalPointers", 32, "LocalPointers", true },
|
||||
{ $"invoke_static_method_async ('[debugger-test] DebuggerTests.PointerTests:LocalPointersAsync');", "DebuggerTests.PointerTests", "LocalPointersAsync", 32, "MoveNext", false },
|
||||
{ $"invoke_static_method_async ('[debugger-test] DebuggerTests.PointerTests:LocalPointersAsync');", "DebuggerTests.PointerTests", "LocalPointersAsync", 32, "MoveNext", true }
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberDataAttribute(nameof(PointersTestData))]
|
||||
public async Task InspectLocalPointersToPrimitiveTypes(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
|
||||
type, method, line_offset, bp_function_name,
|
||||
"window.setTimeout(function() { " + eval_fn + " })",
|
||||
use_cfo : use_cfo,
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
|
||||
|
||||
var dt = new DateTime(5, 6, 7, 8, 9, 10);
|
||||
await CheckProps(locals, new
|
||||
{
|
||||
ip = TPointer("int*"),
|
||||
ip_null = TPointer("int*", is_null : true),
|
||||
ipp = TPointer("int**"),
|
||||
ipp_null = TPointer("int**"),
|
||||
|
||||
cvalue0 = TSymbol("113 'q'"),
|
||||
cp = TPointer("char*"),
|
||||
|
||||
vp = TPointer("void*"),
|
||||
vp_null = TPointer("void*", is_null : true),
|
||||
}, "locals", num_fields : 26);
|
||||
|
||||
var props = await GetObjectOnLocals(locals, "ip");
|
||||
await CheckPointerValue(props, "*ip", TNumber(5), "locals");
|
||||
|
||||
{
|
||||
var ipp_props = await GetObjectOnLocals(locals, "ipp");
|
||||
await CheckPointerValue(ipp_props, "*ipp", TPointer("int*"));
|
||||
|
||||
ipp_props = await GetObjectOnLocals(ipp_props, "*ipp");
|
||||
await CheckPointerValue(ipp_props, "**ipp", TNumber(5));
|
||||
}
|
||||
|
||||
{
|
||||
var ipp_props = await GetObjectOnLocals(locals, "ipp_null");
|
||||
await CheckPointerValue(ipp_props, "*ipp_null", TPointer("int*", is_null : true));
|
||||
}
|
||||
|
||||
// *cp
|
||||
props = await GetObjectOnLocals(locals, "cp");
|
||||
await CheckPointerValue(props, "*cp", TSymbol("113 'q'"));
|
||||
});
|
||||
|
||||
[Theory]
|
||||
[MemberDataAttribute(nameof(PointersTestData))]
|
||||
public async Task InspectLocalPointerArrays(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
|
||||
type, method, line_offset, bp_function_name,
|
||||
"window.setTimeout(function() { " + eval_fn + " })",
|
||||
use_cfo : use_cfo,
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
|
||||
|
||||
var dt = new DateTime(5, 6, 7, 8, 9, 10);
|
||||
await CheckProps(locals, new
|
||||
{
|
||||
ipa = TArray("int*[]", 3)
|
||||
}, "locals", num_fields : 26);
|
||||
|
||||
var ipa_elems = await CompareObjectPropertiesFor(locals, "ipa", new []
|
||||
{
|
||||
TPointer("int*"),
|
||||
TPointer("int*"),
|
||||
TPointer("int*", is_null : true)
|
||||
});
|
||||
|
||||
await CheckArrayElements(ipa_elems, new []
|
||||
{
|
||||
TNumber(5),
|
||||
TNumber(10),
|
||||
null
|
||||
});
|
||||
});
|
||||
|
||||
[Theory]
|
||||
[MemberDataAttribute(nameof(PointersTestData))]
|
||||
public async Task InspectLocalDoublePointerToPrimitiveTypeArrays(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
|
||||
type, method, line_offset, bp_function_name,
|
||||
"window.setTimeout(function() { " + eval_fn + " })",
|
||||
use_cfo : use_cfo,
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
|
||||
|
||||
var dt = new DateTime(5, 6, 7, 8, 9, 10);
|
||||
await CheckProps(locals, new
|
||||
{
|
||||
ippa = TArray("int**[]", 5)
|
||||
}, "locals", num_fields : 26);
|
||||
|
||||
var ippa_elems = await CompareObjectPropertiesFor(locals, "ippa", new []
|
||||
{
|
||||
TPointer("int**"),
|
||||
TPointer("int**"),
|
||||
TPointer("int**"),
|
||||
TPointer("int**"),
|
||||
TPointer("int**", is_null : true)
|
||||
});
|
||||
|
||||
{
|
||||
var actual_elems = await CheckArrayElements(ippa_elems, new []
|
||||
{
|
||||
TPointer("int*"),
|
||||
TPointer("int*", is_null : true),
|
||||
TPointer("int*"),
|
||||
TPointer("int*", is_null : true),
|
||||
null
|
||||
});
|
||||
|
||||
var val = await GetObjectOnLocals(actual_elems[0], "*[0]");
|
||||
await CheckPointerValue(val, "**[0]", TNumber(5));
|
||||
|
||||
val = await GetObjectOnLocals(actual_elems[2], "*[2]");
|
||||
await CheckPointerValue(val, "**[2]", TNumber(5));
|
||||
}
|
||||
});
|
||||
|
||||
[Theory]
|
||||
[MemberDataAttribute(nameof(PointersTestData))]
|
||||
public async Task InspectLocalPointersToValueTypes(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
|
||||
type, method, line_offset, bp_function_name,
|
||||
"window.setTimeout(function() { " + eval_fn + " })",
|
||||
use_cfo : use_cfo,
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
|
||||
|
||||
var dt = new DateTime(5, 6, 7, 8, 9, 10);
|
||||
await CheckProps(locals, new
|
||||
{
|
||||
dt = TValueType("System.DateTime", dt.ToString()),
|
||||
dtp = TPointer("System.DateTime*"),
|
||||
dtp_null = TPointer("System.DateTime*", is_null : true),
|
||||
|
||||
gsp = TPointer("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*"),
|
||||
gsp_null = TPointer("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*")
|
||||
}, "locals", num_fields : 26);
|
||||
|
||||
await CheckDateTime(locals, "dt", dt);
|
||||
|
||||
// *dtp
|
||||
var props = await GetObjectOnLocals(locals, "dtp");
|
||||
await CheckDateTime(props, "*dtp", dt);
|
||||
|
||||
var gsp_props = await GetObjectOnLocals(locals, "gsp");
|
||||
await CheckPointerValue(gsp_props, "*gsp", TValueType("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>"), "locals#gsp");
|
||||
|
||||
{
|
||||
var gs_dt = new DateTime(1, 2, 3, 4, 5, 6);
|
||||
|
||||
var gsp_deref_props = await GetObjectOnLocals(gsp_props, "*gsp");
|
||||
await CheckProps(gsp_deref_props, new
|
||||
{
|
||||
Value = TValueType("System.DateTime", gs_dt.ToString()),
|
||||
IntField = TNumber(4),
|
||||
DTPP = TPointer("System.DateTime**")
|
||||
}, "locals#gsp#deref");
|
||||
{
|
||||
var dtpp_props = await GetObjectOnLocals(gsp_deref_props, "DTPP");
|
||||
await CheckPointerValue(dtpp_props, "*DTPP", TPointer("System.DateTime*"), "locals#*gsp");
|
||||
|
||||
var dtpp_deref_props = await GetObjectOnLocals(dtpp_props, "*DTPP");
|
||||
await CheckDateTime(dtpp_deref_props, "**DTPP", dt);
|
||||
}
|
||||
}
|
||||
|
||||
// gsp_null
|
||||
var gsp_w_n_props = await GetObjectOnLocals(locals, "gsp_null");
|
||||
await CheckPointerValue(gsp_w_n_props, "*gsp_null", TValueType("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>"), "locals#gsp");
|
||||
|
||||
{
|
||||
var gs_dt = new DateTime(1, 2, 3, 4, 5, 6);
|
||||
|
||||
var gsp_deref_props = await GetObjectOnLocals(gsp_w_n_props, "*gsp_null");
|
||||
await CheckProps(gsp_deref_props, new
|
||||
{
|
||||
Value = TValueType("System.DateTime", gs_dt.ToString()),
|
||||
IntField = TNumber(4),
|
||||
DTPP = TPointer("System.DateTime**")
|
||||
}, "locals#gsp#deref");
|
||||
{
|
||||
var dtpp_props = await GetObjectOnLocals(gsp_deref_props, "DTPP");
|
||||
await CheckPointerValue(dtpp_props, "*DTPP", TPointer("System.DateTime*", is_null : true), "locals#*gsp");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
[Theory]
|
||||
[MemberDataAttribute(nameof(PointersTestData))]
|
||||
public async Task InspectLocalPointersToValueTypeArrays(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
|
||||
type, method, line_offset, bp_function_name,
|
||||
"window.setTimeout(function() { " + eval_fn + " })",
|
||||
use_cfo : use_cfo,
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
|
||||
|
||||
var dt = new DateTime(5, 6, 7, 8, 9, 10);
|
||||
await CheckProps(locals, new
|
||||
{
|
||||
dtpa = TArray("System.DateTime*[]", 2)
|
||||
}, "locals", num_fields : 26);
|
||||
|
||||
// dtpa
|
||||
var dtpa_elems = (await CompareObjectPropertiesFor(locals, "dtpa", new []
|
||||
{
|
||||
TPointer("System.DateTime*"),
|
||||
TPointer("System.DateTime*", is_null : true)
|
||||
}));
|
||||
{
|
||||
var actual_elems = await CheckArrayElements(dtpa_elems, new []
|
||||
{
|
||||
TValueType("System.DateTime", dt.ToString()),
|
||||
null
|
||||
});
|
||||
|
||||
await CheckDateTime(actual_elems[0], "*[0]", dt);
|
||||
}
|
||||
});
|
||||
|
||||
[Theory]
|
||||
[MemberDataAttribute(nameof(PointersTestData))]
|
||||
public async Task InspectLocalPointersToGenericValueTypeArrays(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
|
||||
type, method, line_offset, bp_function_name,
|
||||
"window.setTimeout(function() { " + eval_fn + " })",
|
||||
use_cfo : use_cfo,
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
|
||||
|
||||
var dt = new DateTime(5, 6, 7, 8, 9, 10);
|
||||
await CheckProps(locals, new
|
||||
{
|
||||
gspa = TArray("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*[]", 3),
|
||||
}, "locals", num_fields : 26);
|
||||
|
||||
// dtpa
|
||||
var gspa_elems = await CompareObjectPropertiesFor(locals, "gspa", new []
|
||||
{
|
||||
TPointer("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*", is_null : true),
|
||||
TPointer("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*"),
|
||||
TPointer("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>*"),
|
||||
});
|
||||
{
|
||||
var gs_dt = new DateTime(1, 2, 3, 4, 5, 6);
|
||||
var actual_elems = await CheckArrayElements(gspa_elems, new []
|
||||
{
|
||||
null,
|
||||
TValueType("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>"),
|
||||
TValueType("DebuggerTests.GenericStructWithUnmanagedT<System.DateTime>")
|
||||
});
|
||||
|
||||
// *[1]
|
||||
{
|
||||
var gsp_deref_props = await GetObjectOnLocals(actual_elems[1], "*[1]");
|
||||
await CheckProps(gsp_deref_props, new
|
||||
{
|
||||
Value = TValueType("System.DateTime", gs_dt.ToString()),
|
||||
IntField = TNumber(4),
|
||||
DTPP = TPointer("System.DateTime**")
|
||||
}, "locals#gsp#deref");
|
||||
{
|
||||
var dtpp_props = await GetObjectOnLocals(gsp_deref_props, "DTPP");
|
||||
await CheckPointerValue(dtpp_props, "*DTPP", TPointer("System.DateTime*"), "locals#*gsp");
|
||||
|
||||
dtpp_props = await GetObjectOnLocals(dtpp_props, "*DTPP");
|
||||
await CheckDateTime(dtpp_props, "**DTPP", dt);
|
||||
}
|
||||
}
|
||||
|
||||
// *[2]
|
||||
{
|
||||
var gsp_deref_props = await GetObjectOnLocals(actual_elems[2], "*[2]");
|
||||
await CheckProps(gsp_deref_props, new
|
||||
{
|
||||
Value = TValueType("System.DateTime", gs_dt.ToString()),
|
||||
IntField = TNumber(4),
|
||||
DTPP = TPointer("System.DateTime**")
|
||||
}, "locals#gsp#deref");
|
||||
{
|
||||
var dtpp_props = await GetObjectOnLocals(gsp_deref_props, "DTPP");
|
||||
await CheckPointerValue(dtpp_props, "*DTPP", TPointer("System.DateTime*", is_null : true), "locals#*gsp");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
[Theory]
|
||||
[MemberDataAttribute(nameof(PointersTestData))]
|
||||
public async Task InspectLocalDoublePointersToValueTypeArrays(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
|
||||
type, method, line_offset, bp_function_name,
|
||||
"window.setTimeout(function() { " + eval_fn + " })",
|
||||
use_cfo : use_cfo,
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
|
||||
|
||||
var dt = new DateTime(5, 6, 7, 8, 9, 10);
|
||||
await CheckProps(locals, new
|
||||
{
|
||||
dtppa = TArray("System.DateTime**[]", 3),
|
||||
}, "locals", num_fields : 26);
|
||||
|
||||
// DateTime**[] dtppa = new DateTime**[] { &dtp, &dtp_null, null };
|
||||
var dtppa_elems = (await CompareObjectPropertiesFor(locals, "dtppa", new []
|
||||
{
|
||||
TPointer("System.DateTime**"),
|
||||
TPointer("System.DateTime**"),
|
||||
TPointer("System.DateTime**", is_null : true)
|
||||
}));
|
||||
|
||||
var exp_elems = new []
|
||||
{
|
||||
TPointer("System.DateTime*"),
|
||||
TPointer("System.DateTime*", is_null : true),
|
||||
null
|
||||
};
|
||||
|
||||
var actual_elems = new JToken[exp_elems.Length];
|
||||
for (int i = 0; i < exp_elems.Length; i++)
|
||||
{
|
||||
if (exp_elems[i] != null)
|
||||
{
|
||||
actual_elems[i] = await GetObjectOnLocals(dtppa_elems, i.ToString());
|
||||
await CheckPointerValue(actual_elems[i], $"*[{i}]", exp_elems[i], $"dtppa->");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
[Theory]
|
||||
[MemberDataAttribute(nameof(PointersTestData))]
|
||||
public async Task InspectLocalPointersInClasses(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
|
||||
type, method, line_offset, bp_function_name,
|
||||
"window.setTimeout(function() { " + eval_fn + " })",
|
||||
use_cfo : use_cfo,
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
|
||||
|
||||
var dt = new DateTime(5, 6, 7, 8, 9, 10);
|
||||
await CheckProps(locals, new
|
||||
{
|
||||
cwp = TObject("DebuggerTests.GenericClassWithPointers<System.DateTime>"),
|
||||
cwp_null = TObject("DebuggerTests.GenericClassWithPointers<System.DateTime>")
|
||||
}, "locals", num_fields : 26);
|
||||
|
||||
var cwp_props = await GetObjectOnLocals(locals, "cwp");
|
||||
var ptr_props = await GetObjectOnLocals(cwp_props, "Ptr");
|
||||
await CheckDateTime(ptr_props, "*Ptr", dt);
|
||||
});
|
||||
|
||||
public static TheoryData<string, string, string, int, string, bool> PointersAsMethodArgsTestData =>
|
||||
new TheoryData<string, string, string, int, string, bool>
|
||||
{ { $"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "PointersAsArgsTest", 2, "PointersAsArgsTest", false },
|
||||
{ $"invoke_static_method ('[debugger-test] DebuggerTests.PointerTests:LocalPointers');", "DebuggerTests.PointerTests", "PointersAsArgsTest", 2, "PointersAsArgsTest", true },
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberDataAttribute(nameof(PointersAsMethodArgsTestData))]
|
||||
public async Task InspectPrimitiveTypePointersAsMethodArgs(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
|
||||
type, method, line_offset, bp_function_name,
|
||||
"window.setTimeout(function() { " + eval_fn + " })",
|
||||
use_cfo : use_cfo,
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
|
||||
|
||||
var dt = new DateTime(5, 6, 7, 8, 9, 10);
|
||||
await CheckProps(locals, new
|
||||
{
|
||||
ip = TPointer("int*"),
|
||||
ipp = TPointer("int**"),
|
||||
ipa = TArray("int*[]", 3),
|
||||
ippa = TArray("int**[]", 5)
|
||||
}, "locals", num_fields : 8);
|
||||
|
||||
// ip
|
||||
var props = await GetObjectOnLocals(locals, "ip");
|
||||
await CheckPointerValue(props, "*ip", TNumber(5), "locals");
|
||||
|
||||
// ipp
|
||||
var ipp_props = await GetObjectOnLocals(locals, "ipp");
|
||||
await CheckPointerValue(ipp_props, "*ipp", TPointer("int*"));
|
||||
|
||||
ipp_props = await GetObjectOnLocals(ipp_props, "*ipp");
|
||||
await CheckPointerValue(ipp_props, "**ipp", TNumber(5));
|
||||
|
||||
// ipa
|
||||
var ipa_elems = await CompareObjectPropertiesFor(locals, "ipa", new []
|
||||
{
|
||||
TPointer("int*"),
|
||||
TPointer("int*"),
|
||||
TPointer("int*", is_null : true)
|
||||
});
|
||||
|
||||
await CheckArrayElements(ipa_elems, new []
|
||||
{
|
||||
TNumber(5),
|
||||
TNumber(10),
|
||||
null
|
||||
});
|
||||
|
||||
// ippa
|
||||
var ippa_elems = await CompareObjectPropertiesFor(locals, "ippa", new []
|
||||
{
|
||||
TPointer("int**"),
|
||||
TPointer("int**"),
|
||||
TPointer("int**"),
|
||||
TPointer("int**"),
|
||||
TPointer("int**", is_null : true)
|
||||
});
|
||||
|
||||
{
|
||||
var actual_elems = await CheckArrayElements(ippa_elems, new []
|
||||
{
|
||||
TPointer("int*"),
|
||||
TPointer("int*", is_null : true),
|
||||
TPointer("int*"),
|
||||
TPointer("int*", is_null : true),
|
||||
null
|
||||
});
|
||||
|
||||
var val = await GetObjectOnLocals(actual_elems[0], "*[0]");
|
||||
await CheckPointerValue(val, "**[0]", TNumber(5));
|
||||
|
||||
val = await GetObjectOnLocals(actual_elems[2], "*[2]");
|
||||
await CheckPointerValue(val, "**[2]", TNumber(5));
|
||||
}
|
||||
});
|
||||
|
||||
[Theory]
|
||||
[MemberDataAttribute(nameof(PointersAsMethodArgsTestData))]
|
||||
public async Task InspectValueTypePointersAsMethodArgs(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
|
||||
type, method, line_offset, bp_function_name,
|
||||
"window.setTimeout(function() { " + eval_fn + " })",
|
||||
use_cfo : use_cfo,
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
|
||||
|
||||
var dt = new DateTime(5, 6, 7, 8, 9, 10);
|
||||
await CheckProps(locals, new
|
||||
{
|
||||
dtp = TPointer("System.DateTime*"),
|
||||
dtpp = TPointer("System.DateTime**"),
|
||||
dtpa = TArray("System.DateTime*[]", 2),
|
||||
dtppa = TArray("System.DateTime**[]", 3)
|
||||
}, "locals", num_fields : 8);
|
||||
|
||||
// *dtp
|
||||
var dtp_props = await GetObjectOnLocals(locals, "dtp");
|
||||
await CheckDateTime(dtp_props, "*dtp", dt);
|
||||
|
||||
// *dtpp
|
||||
var dtpp_props = await GetObjectOnLocals(locals, "dtpp");
|
||||
await CheckPointerValue(dtpp_props, "*dtpp", TPointer("System.DateTime*"), "locals");
|
||||
|
||||
dtpp_props = await GetObjectOnLocals(dtpp_props, "*dtpp");
|
||||
await CheckDateTime(dtpp_props, "**dtpp", dt);
|
||||
|
||||
// dtpa
|
||||
var dtpa_elems = (await CompareObjectPropertiesFor(locals, "dtpa", new []
|
||||
{
|
||||
TPointer("System.DateTime*"),
|
||||
TPointer("System.DateTime*", is_null : true)
|
||||
}));
|
||||
{
|
||||
var actual_elems = await CheckArrayElements(dtpa_elems, new []
|
||||
{
|
||||
TValueType("System.DateTime", dt.ToString()),
|
||||
null
|
||||
});
|
||||
|
||||
await CheckDateTime(actual_elems[0], "*[0]", dt);
|
||||
}
|
||||
|
||||
// dtppa = new DateTime**[] { &dtp, &dtp_null, null };
|
||||
var dtppa_elems = (await CompareObjectPropertiesFor(locals, "dtppa", new []
|
||||
{
|
||||
TPointer("System.DateTime**"),
|
||||
TPointer("System.DateTime**"),
|
||||
TPointer("System.DateTime**", is_null : true)
|
||||
}));
|
||||
|
||||
var exp_elems = new []
|
||||
{
|
||||
TPointer("System.DateTime*"),
|
||||
TPointer("System.DateTime*", is_null : true),
|
||||
null
|
||||
};
|
||||
|
||||
await CheckArrayElements(dtppa_elems, exp_elems);
|
||||
});
|
||||
|
||||
[Theory]
|
||||
[InlineData("invoke_static_method ('[debugger-test] Math:UseComplex', 0, 0);", "Math", "UseComplex", 3, "UseComplex", false)]
|
||||
[InlineData("invoke_static_method ('[debugger-test] Math:UseComplex', 0, 0);", "Math", "UseComplex", 3, "UseComplex", true)]
|
||||
public async Task DerefNonPointerObject(string eval_fn, string type, string method, int line_offset, string bp_function_name, bool use_cfo) => await CheckInspectLocalsAtBreakpointSite(
|
||||
type, method, line_offset, bp_function_name,
|
||||
"window.setTimeout(function() { " + eval_fn + " })",
|
||||
use_cfo : use_cfo,
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
|
||||
// this will generate the object ids
|
||||
var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
|
||||
var complex = GetAndAssertObjectWithName(locals, "complex");
|
||||
|
||||
// try to deref the non-pointer object, as a pointer
|
||||
var props = await GetProperties(complex["value"]["objectId"].Value<string>().Replace(":object:", ":pointer:"));
|
||||
Assert.Empty(props.Values());
|
||||
|
||||
// try to deref an invalid pointer id
|
||||
props = await GetProperties("dotnet:pointer:123897");
|
||||
Assert.Empty(props.Values());
|
||||
});
|
||||
|
||||
async Task<JToken[]> CheckArrayElements(JToken array, JToken[] exp_elems)
|
||||
{
|
||||
var actual_elems = new JToken[exp_elems.Length];
|
||||
for (int i = 0; i < exp_elems.Length; i++)
|
||||
{
|
||||
if (exp_elems[i] != null)
|
||||
{
|
||||
actual_elems[i] = await GetObjectOnLocals(array, i.ToString());
|
||||
await CheckPointerValue(actual_elems[i], $"*[{i}]", exp_elems[i], $"dtppa->");
|
||||
}
|
||||
}
|
||||
|
||||
return actual_elems;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,990 @@
|
|||
// 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.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Microsoft.WebAssembly.Diagnostics;
|
||||
using Xunit;
|
||||
|
||||
namespace DebuggerTests
|
||||
{
|
||||
class Inspector
|
||||
{
|
||||
// InspectorClient client;
|
||||
Dictionary<string, TaskCompletionSource<JObject>> notifications = new Dictionary<string, TaskCompletionSource<JObject>>();
|
||||
Dictionary<string, Func<JObject, CancellationToken, Task>> eventListeners = new Dictionary<string, Func<JObject, CancellationToken, Task>>();
|
||||
|
||||
public const string PAUSE = "pause";
|
||||
public const string READY = "ready";
|
||||
|
||||
public Task<JObject> WaitFor(string what)
|
||||
{
|
||||
if (notifications.ContainsKey(what))
|
||||
throw new Exception($"Invalid internal state, waiting for {what} while another wait is already setup");
|
||||
var n = new TaskCompletionSource<JObject>();
|
||||
notifications[what] = n;
|
||||
return n.Task;
|
||||
}
|
||||
|
||||
void NotifyOf(string what, JObject args)
|
||||
{
|
||||
if (!notifications.ContainsKey(what))
|
||||
throw new Exception($"Invalid internal state, notifying of {what}, but nobody waiting");
|
||||
notifications[what].SetResult(args);
|
||||
notifications.Remove(what);
|
||||
}
|
||||
|
||||
public void On(string evtName, Func<JObject, CancellationToken, Task> cb)
|
||||
{
|
||||
eventListeners[evtName] = cb;
|
||||
}
|
||||
|
||||
void FailAllWaitersWithException(JObject exception)
|
||||
{
|
||||
foreach (var tcs in notifications.Values)
|
||||
tcs.SetException(new ArgumentException(exception.ToString()));
|
||||
}
|
||||
|
||||
async Task OnMessage(string method, JObject args, CancellationToken token)
|
||||
{
|
||||
//System.Console.WriteLine("OnMessage " + method + args);
|
||||
switch (method)
|
||||
{
|
||||
case "Debugger.paused":
|
||||
NotifyOf(PAUSE, args);
|
||||
break;
|
||||
case "Mono.runtimeReady":
|
||||
NotifyOf(READY, args);
|
||||
break;
|
||||
case "Runtime.consoleAPICalled":
|
||||
Console.WriteLine("CWL: {0}", args?["args"] ? [0] ? ["value"]);
|
||||
break;
|
||||
}
|
||||
if (eventListeners.ContainsKey(method))
|
||||
await eventListeners[method](args, token);
|
||||
else if (String.Compare(method, "Runtime.exceptionThrown") == 0)
|
||||
FailAllWaitersWithException(args);
|
||||
}
|
||||
|
||||
public async Task Ready(Func<InspectorClient, CancellationToken, Task> cb = null, TimeSpan? span = null)
|
||||
{
|
||||
using(var cts = new CancellationTokenSource())
|
||||
{
|
||||
cts.CancelAfter(span?.Milliseconds ?? 60 * 1000); //tests have 1 minute to complete by default
|
||||
var uri = new Uri($"ws://{TestHarnessProxy.Endpoint.Authority}/launch-chrome-and-connect");
|
||||
using var loggerFactory = LoggerFactory.Create(
|
||||
builder => builder.AddConsole().AddFilter(null, LogLevel.Information));
|
||||
using(var client = new InspectorClient(loggerFactory.CreateLogger<Inspector>()))
|
||||
{
|
||||
await client.Connect(uri, OnMessage, async token =>
|
||||
{
|
||||
Task[] init_cmds = {
|
||||
client.SendCommand("Profiler.enable", null, token),
|
||||
client.SendCommand("Runtime.enable", null, token),
|
||||
client.SendCommand("Debugger.enable", null, token),
|
||||
client.SendCommand("Runtime.runIfWaitingForDebugger", null, token),
|
||||
WaitFor(READY),
|
||||
};
|
||||
// await Task.WhenAll (init_cmds);
|
||||
Console.WriteLine("waiting for the runtime to be ready");
|
||||
await init_cmds[4];
|
||||
Console.WriteLine("runtime ready, TEST TIME");
|
||||
if (cb != null)
|
||||
{
|
||||
Console.WriteLine("await cb(client, token)");
|
||||
await cb(client, token);
|
||||
}
|
||||
|
||||
}, cts.Token);
|
||||
await client.Close(cts.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DebuggerTestBase
|
||||
{
|
||||
protected Task startTask;
|
||||
|
||||
static string FindTestPath()
|
||||
{
|
||||
//FIXME how would I locate it otherwise?
|
||||
var test_path = Environment.GetEnvironmentVariable("TEST_SUITE_PATH");
|
||||
//Lets try to guest
|
||||
if (test_path != null && Directory.Exists(test_path))
|
||||
return test_path;
|
||||
|
||||
var cwd = Environment.CurrentDirectory;
|
||||
Console.WriteLine("guessing from {0}", cwd);
|
||||
//tests run from DebuggerTestSuite/bin/Debug/netcoreapp2.1
|
||||
var new_path = Path.Combine(cwd, "../../../../bin/debugger-test-suite");
|
||||
if (File.Exists(Path.Combine(new_path, "debugger-driver.html")))
|
||||
return new_path;
|
||||
|
||||
throw new Exception("Missing TEST_SUITE_PATH env var and could not guess path from CWD");
|
||||
}
|
||||
|
||||
static string[] PROBE_LIST = {
|
||||
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
||||
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
|
||||
"/usr/bin/chromium",
|
||||
"/usr/bin/chromium-browser",
|
||||
};
|
||||
static string chrome_path;
|
||||
|
||||
static string FindChromePath()
|
||||
{
|
||||
if (chrome_path != null)
|
||||
return chrome_path;
|
||||
|
||||
foreach (var s in PROBE_LIST)
|
||||
{
|
||||
if (File.Exists(s))
|
||||
{
|
||||
chrome_path = s;
|
||||
Console.WriteLine($"Using chrome path: ${s}");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
throw new Exception("Could not find an installed Chrome to use");
|
||||
}
|
||||
|
||||
public DebuggerTestBase(string driver = "debugger-driver.html")
|
||||
{
|
||||
startTask = TestHarnessProxy.Start(FindChromePath(), FindTestPath(), driver);
|
||||
}
|
||||
|
||||
public Task Ready() => startTask;
|
||||
|
||||
internal DebugTestContext ctx;
|
||||
internal Dictionary<string, string> dicScriptsIdToUrl;
|
||||
internal Dictionary<string, string> dicFileToUrl;
|
||||
internal Dictionary<string, string> SubscribeToScripts(Inspector insp)
|
||||
{
|
||||
dicScriptsIdToUrl = new Dictionary<string, string>();
|
||||
dicFileToUrl = new Dictionary<string, string>();
|
||||
insp.On("Debugger.scriptParsed", async(args, c) =>
|
||||
{
|
||||
var script_id = args?["scriptId"]?.Value<string>();
|
||||
var url = args["url"]?.Value<string>();
|
||||
if (script_id.StartsWith("dotnet://"))
|
||||
{
|
||||
var dbgUrl = args["dotNetUrl"]?.Value<string>();
|
||||
var arrStr = dbgUrl.Split("/");
|
||||
dbgUrl = arrStr[0] + "/" + arrStr[1] + "/" + arrStr[2] + "/" + arrStr[arrStr.Length - 1];
|
||||
dicScriptsIdToUrl[script_id] = dbgUrl;
|
||||
dicFileToUrl[dbgUrl] = args["url"]?.Value<string>();
|
||||
}
|
||||
else if (!String.IsNullOrEmpty(url))
|
||||
{
|
||||
dicFileToUrl[new Uri(url).AbsolutePath] = url;
|
||||
}
|
||||
await Task.FromResult(0);
|
||||
});
|
||||
return dicScriptsIdToUrl;
|
||||
}
|
||||
|
||||
internal async Task CheckInspectLocalsAtBreakpointSite(string url_key, int line, int column, string function_name, string eval_expression,
|
||||
Action<JToken> test_fn = null, Func<JObject, Task> wait_for_event_fn = null, bool use_cfo = false)
|
||||
{
|
||||
var insp = new Inspector();
|
||||
//Collect events
|
||||
var scripts = SubscribeToScripts(insp);
|
||||
|
||||
await Ready();
|
||||
await insp.Ready(async(cli, token) =>
|
||||
{
|
||||
ctx = new DebugTestContext(cli, insp, token, scripts);
|
||||
ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
|
||||
|
||||
var bp = await SetBreakpoint(url_key, line, column);
|
||||
|
||||
await EvaluateAndCheck(
|
||||
eval_expression, url_key, line, column,
|
||||
function_name,
|
||||
wait_for_event_fn : async(pause_location) =>
|
||||
{
|
||||
//make sure we're on the right bp
|
||||
|
||||
Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"] ? [0]?.Value<string>());
|
||||
|
||||
var top_frame = pause_location["callFrames"][0];
|
||||
|
||||
var scope = top_frame["scopeChain"][0];
|
||||
Assert.Equal("dotnet:scope:0", scope["object"]["objectId"]);
|
||||
if (wait_for_event_fn != null)
|
||||
await wait_for_event_fn(pause_location);
|
||||
else
|
||||
await Task.CompletedTask;
|
||||
},
|
||||
locals_fn: (locals) =>
|
||||
{
|
||||
if (test_fn != null)
|
||||
test_fn(locals);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// sets breakpoint by method name and line offset
|
||||
internal async Task CheckInspectLocalsAtBreakpointSite(string type, string method, int line_offset, string bp_function_name, string eval_expression,
|
||||
Action<JToken> locals_fn = null, Func<JObject, Task> wait_for_event_fn = null, bool use_cfo = false, string assembly = "debugger-test.dll", int col = 0)
|
||||
{
|
||||
var insp = new Inspector();
|
||||
//Collect events
|
||||
var scripts = SubscribeToScripts(insp);
|
||||
|
||||
await Ready();
|
||||
await insp.Ready(async(cli, token) =>
|
||||
{
|
||||
ctx = new DebugTestContext(cli, insp, token, scripts);
|
||||
ctx.UseCallFunctionOnBeforeGetProperties = use_cfo;
|
||||
|
||||
var bp = await SetBreakpointInMethod(assembly, type, method, line_offset, col);
|
||||
|
||||
var args = JObject.FromObject(new { expression = eval_expression });
|
||||
var res = await ctx.cli.SendCommand("Runtime.evaluate", args, ctx.token);
|
||||
if (!res.IsOk)
|
||||
{
|
||||
Console.WriteLine($"Failed to run command {method} with args: {args?.ToString ()}\nresult: {res.Error.ToString ()}");
|
||||
Assert.True(false, $"SendCommand for {method} failed with {res.Error.ToString ()}");
|
||||
}
|
||||
|
||||
var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE);
|
||||
|
||||
if (bp_function_name != null)
|
||||
Assert.Equal(bp_function_name, pause_location["callFrames"] ? [0] ? ["functionName"]?.Value<string>());
|
||||
|
||||
Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"] ? [0]?.Value<string>());
|
||||
|
||||
var top_frame = pause_location["callFrames"][0];
|
||||
|
||||
var scope = top_frame["scopeChain"][0];
|
||||
Assert.Equal("dotnet:scope:0", scope["object"]["objectId"]);
|
||||
|
||||
if (wait_for_event_fn != null)
|
||||
await wait_for_event_fn(pause_location);
|
||||
|
||||
if (locals_fn != null)
|
||||
{
|
||||
var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
|
||||
locals_fn(locals);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
internal void CheckLocation(string script_loc, int line, int column, Dictionary<string, string> scripts, JToken location)
|
||||
{
|
||||
var loc_str = $"{ scripts[location["scriptId"].Value<string>()] }" +
|
||||
$"#{ location ["lineNumber"].Value<int> () }" +
|
||||
$"#{ location ["columnNumber"].Value<int> () }";
|
||||
|
||||
var expected_loc_str = $"{script_loc}#{line}#{column}";
|
||||
Assert.Equal(expected_loc_str, loc_str);
|
||||
}
|
||||
|
||||
internal void CheckNumber<T>(JToken locals, string name, T value)
|
||||
{
|
||||
foreach (var l in locals)
|
||||
{
|
||||
if (name != l["name"]?.Value<string>())
|
||||
continue;
|
||||
var val = l["value"];
|
||||
Assert.Equal("number", val["type"]?.Value<string>());
|
||||
Assert.Equal(value, val["value"].Value<T>());
|
||||
return;
|
||||
}
|
||||
Assert.True(false, $"Could not find variable '{name}'");
|
||||
}
|
||||
|
||||
internal void CheckString(JToken locals, string name, string value)
|
||||
{
|
||||
foreach (var l in locals)
|
||||
{
|
||||
if (name != l["name"]?.Value<string>())
|
||||
continue;
|
||||
var val = l["value"];
|
||||
if (value == null)
|
||||
{
|
||||
Assert.Equal("object", val["type"]?.Value<string>());
|
||||
Assert.Equal("null", val["subtype"]?.Value<string>());
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal("string", val["type"]?.Value<string>());
|
||||
Assert.Equal(value, val["value"]?.Value<string>());
|
||||
}
|
||||
return;
|
||||
}
|
||||
Assert.True(false, $"Could not find variable '{name}'");
|
||||
}
|
||||
|
||||
internal JToken CheckSymbol(JToken locals, string name, string value)
|
||||
{
|
||||
var l = GetAndAssertObjectWithName(locals, name);
|
||||
var val = l["value"];
|
||||
Assert.Equal("symbol", val["type"]?.Value<string>());
|
||||
Assert.Equal(value, val["value"]?.Value<string>());
|
||||
return l;
|
||||
}
|
||||
|
||||
internal JToken CheckObject(JToken locals, string name, string class_name, string subtype = null, bool is_null = false)
|
||||
{
|
||||
var l = GetAndAssertObjectWithName(locals, name);
|
||||
var val = l["value"];
|
||||
Assert.Equal("object", val["type"]?.Value<string>());
|
||||
Assert.True(val["isValueType"] == null || !val["isValueType"].Value<bool>());
|
||||
Assert.Equal(class_name, val["className"]?.Value<string>());
|
||||
|
||||
var has_null_subtype = val["subtype"] != null && val["subtype"]?.Value<string>() == "null";
|
||||
Assert.Equal(is_null, has_null_subtype);
|
||||
if (subtype != null)
|
||||
Assert.Equal(subtype, val["subtype"]?.Value<string>());
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
internal async Task<JToken> CheckPointerValue(JToken locals, string name, JToken expected, string label = null)
|
||||
{
|
||||
var l = GetAndAssertObjectWithName(locals, name);
|
||||
await CheckValue(l["value"], expected, $"{label ?? String.Empty}-{name}");
|
||||
return l;
|
||||
}
|
||||
|
||||
internal async Task CheckDateTime(JToken locals, string name, DateTime expected)
|
||||
{
|
||||
var obj = GetAndAssertObjectWithName(locals, name);
|
||||
await CheckDateTimeValue(obj["value"], expected);
|
||||
}
|
||||
|
||||
internal async Task CheckDateTimeValue(JToken value, DateTime expected)
|
||||
{
|
||||
AssertEqual("System.DateTime", value["className"]?.Value<string>(), "className");
|
||||
AssertEqual(expected.ToString(), value["description"]?.Value<string>(), "description");
|
||||
|
||||
var members = await GetProperties(value["objectId"]?.Value<string>());
|
||||
|
||||
// not checking everything
|
||||
CheckNumber(members, "Year", expected.Year);
|
||||
CheckNumber(members, "Month", expected.Month);
|
||||
CheckNumber(members, "Day", expected.Day);
|
||||
CheckNumber(members, "Hour", expected.Hour);
|
||||
CheckNumber(members, "Minute", expected.Minute);
|
||||
CheckNumber(members, "Second", expected.Second);
|
||||
|
||||
// FIXME: check some float properties too
|
||||
}
|
||||
|
||||
internal JToken CheckBool(JToken locals, string name, bool expected)
|
||||
{
|
||||
var l = GetAndAssertObjectWithName(locals, name);
|
||||
var val = l["value"];
|
||||
Assert.Equal("boolean", val["type"]?.Value<string>());
|
||||
if (val["value"] == null)
|
||||
Assert.True(false, "expected bool value not found for variable named {name}");
|
||||
Assert.Equal(expected, val["value"]?.Value<bool>());
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
internal void CheckContentValue(JToken token, string value)
|
||||
{
|
||||
var val = token["value"].Value<string>();
|
||||
Assert.Equal(value, val);
|
||||
}
|
||||
|
||||
internal JToken CheckValueType(JToken locals, string name, string class_name)
|
||||
{
|
||||
var l = GetAndAssertObjectWithName(locals, name);
|
||||
var val = l["value"];
|
||||
Assert.Equal("object", val["type"]?.Value<string>());
|
||||
Assert.True(val["isValueType"] != null && val["isValueType"].Value<bool>());
|
||||
Assert.Equal(class_name, val["className"]?.Value<string>());
|
||||
return l;
|
||||
}
|
||||
|
||||
internal JToken CheckEnum(JToken locals, string name, string class_name, string descr)
|
||||
{
|
||||
var l = GetAndAssertObjectWithName(locals, name);
|
||||
var val = l["value"];
|
||||
Assert.Equal("object", val["type"]?.Value<string>());
|
||||
Assert.True(val["isEnum"] != null && val["isEnum"].Value<bool>());
|
||||
Assert.Equal(class_name, val["className"]?.Value<string>());
|
||||
Assert.Equal(descr, val["description"]?.Value<string>());
|
||||
return l;
|
||||
}
|
||||
|
||||
internal void CheckArray(JToken locals, string name, string class_name)
|
||||
{
|
||||
foreach (var l in locals)
|
||||
{
|
||||
if (name != l["name"]?.Value<string>())
|
||||
continue;
|
||||
|
||||
var val = l["value"];
|
||||
Assert.Equal("object", val["type"]?.Value<string>());
|
||||
Assert.Equal("array", val["subtype"]?.Value<string>());
|
||||
Assert.Equal(class_name, val["className"]?.Value<string>());
|
||||
|
||||
//FIXME: elements?
|
||||
return;
|
||||
}
|
||||
Assert.True(false, $"Could not find variable '{name}'");
|
||||
}
|
||||
|
||||
internal JToken GetAndAssertObjectWithName(JToken obj, string name)
|
||||
{
|
||||
var l = obj.FirstOrDefault(jt => jt["name"]?.Value<string>() == name);
|
||||
if (l == null)
|
||||
Assert.True(false, $"Could not find variable '{name}'");
|
||||
return l;
|
||||
}
|
||||
|
||||
internal async Task<Result> SendCommand(string method, JObject args)
|
||||
{
|
||||
var res = await ctx.cli.SendCommand(method, args, ctx.token);
|
||||
if (!res.IsOk)
|
||||
{
|
||||
Console.WriteLine($"Failed to run command {method} with args: {args?.ToString ()}\nresult: {res.Error.ToString ()}");
|
||||
Assert.True(false, $"SendCommand for {method} failed with {res.Error.ToString ()}");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
internal async Task<Result> Evaluate(string expression)
|
||||
{
|
||||
return await SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = expression }));
|
||||
}
|
||||
|
||||
internal void AssertLocation(JObject args, string methodName)
|
||||
{
|
||||
Assert.Equal(methodName, args["callFrames"] ? [0] ? ["functionName"]?.Value<string>());
|
||||
}
|
||||
|
||||
// Place a breakpoint in the given method and run until its hit
|
||||
// Return the Debugger.paused data
|
||||
internal async Task<JObject> RunUntil(string methodName)
|
||||
{
|
||||
await SetBreakpointInMethod("debugger-test", "DebuggerTest", methodName);
|
||||
// This will run all the tests until it hits the bp
|
||||
await Evaluate("window.setTimeout(function() { invoke_run_all (); }, 1);");
|
||||
var wait_res = await ctx.insp.WaitFor(Inspector.PAUSE);
|
||||
AssertLocation(wait_res, "locals_inner");
|
||||
return wait_res;
|
||||
}
|
||||
|
||||
internal async Task<JObject> StepAndCheck(StepKind kind, string script_loc, int line, int column, string function_name,
|
||||
Func<JObject, Task> wait_for_event_fn = null, Action<JToken> locals_fn = null, int times = 1)
|
||||
{
|
||||
for (int i = 0; i < times - 1; i++)
|
||||
{
|
||||
await SendCommandAndCheck(null, $"Debugger.step{kind.ToString ()}", null, -1, -1, null);
|
||||
}
|
||||
|
||||
// Check for method/line etc only at the last step
|
||||
return await SendCommandAndCheck(
|
||||
null, $"Debugger.step{kind.ToString ()}", script_loc, line, column, function_name,
|
||||
wait_for_event_fn : wait_for_event_fn,
|
||||
locals_fn : locals_fn);
|
||||
}
|
||||
|
||||
internal async Task<JObject> EvaluateAndCheck(string expression, string script_loc, int line, int column, string function_name,
|
||||
Func<JObject, Task> wait_for_event_fn = null, Action<JToken> locals_fn = null) => await SendCommandAndCheck(
|
||||
JObject.FromObject(new { expression = expression }),
|
||||
"Runtime.evaluate", script_loc, line, column, function_name,
|
||||
wait_for_event_fn : wait_for_event_fn,
|
||||
locals_fn : locals_fn);
|
||||
|
||||
internal async Task<JObject> SendCommandAndCheck(JObject args, string method, string script_loc, int line, int column, string function_name,
|
||||
Func<JObject, Task> wait_for_event_fn = null, Action<JToken> locals_fn = null, string waitForEvent = Inspector.PAUSE)
|
||||
{
|
||||
var res = await ctx.cli.SendCommand(method, args, ctx.token);
|
||||
if (!res.IsOk)
|
||||
{
|
||||
Console.WriteLine($"Failed to run command {method} with args: {args?.ToString ()}\nresult: {res.Error.ToString ()}");
|
||||
Assert.True(false, $"SendCommand for {method} failed with {res.Error.ToString ()}");
|
||||
}
|
||||
|
||||
var wait_res = await ctx.insp.WaitFor(waitForEvent);
|
||||
|
||||
if (function_name != null)
|
||||
Assert.Equal(function_name, wait_res["callFrames"] ? [0] ? ["functionName"]?.Value<string>());
|
||||
|
||||
if (script_loc != null)
|
||||
CheckLocation(script_loc, line, column, ctx.scripts, wait_res["callFrames"][0]["location"]);
|
||||
|
||||
if (wait_for_event_fn != null)
|
||||
await wait_for_event_fn(wait_res);
|
||||
|
||||
if (locals_fn != null)
|
||||
{
|
||||
var locals = await GetProperties(wait_res["callFrames"][0]["callFrameId"].Value<string>());
|
||||
locals_fn(locals);
|
||||
}
|
||||
|
||||
return wait_res;
|
||||
}
|
||||
|
||||
internal async Task CheckDelegate(JToken locals, string name, string className, string target)
|
||||
{
|
||||
var l = GetAndAssertObjectWithName(locals, name);
|
||||
var val = l["value"];
|
||||
|
||||
await CheckDelegate(l, TDelegate(className, target), name);
|
||||
}
|
||||
|
||||
internal async Task CheckDelegate(JToken actual_val, JToken exp_val, string label)
|
||||
{
|
||||
AssertEqual("object", actual_val["type"]?.Value<string>(), $"{label}-type");
|
||||
AssertEqual(exp_val["className"]?.Value<string>(), actual_val["className"]?.Value<string>(), $"{label}-className");
|
||||
|
||||
var actual_target = actual_val["description"]?.Value<string>();
|
||||
Assert.True(actual_target != null, $"${label}-description");
|
||||
var exp_target = exp_val["target"].Value<string>();
|
||||
|
||||
CheckDelegateTarget(actual_target, exp_target);
|
||||
|
||||
var del_props = await GetProperties(actual_val["objectId"]?.Value<string>());
|
||||
AssertEqual(1, del_props.Count(), $"${label}-delegate-properties-count");
|
||||
|
||||
var obj = del_props.Where(jt => jt["name"]?.Value<string>() == "Target").FirstOrDefault();
|
||||
Assert.True(obj != null, $"[{label}] Property named 'Target' found found in delegate properties");
|
||||
|
||||
AssertEqual("symbol", obj["value"] ? ["type"]?.Value<string>(), $"{label}#Target#type");
|
||||
CheckDelegateTarget(obj["value"] ? ["value"]?.Value<string>(), exp_target);
|
||||
|
||||
return;
|
||||
|
||||
void CheckDelegateTarget(string actual_target, string exp_target)
|
||||
{
|
||||
var parts = exp_target.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length == 1)
|
||||
{
|
||||
// not a generated method
|
||||
AssertEqual(exp_target, actual_target, $"{label}-description");
|
||||
}
|
||||
else
|
||||
{
|
||||
bool prefix = actual_target.StartsWith(parts[0], StringComparison.Ordinal);
|
||||
Assert.True(prefix, $"{label}-description, Expected target to start with '{parts[0]}'. Actual: '{actual_target}'");
|
||||
|
||||
var remaining = actual_target.Substring(parts[0].Length);
|
||||
bool suffix = remaining.EndsWith(parts[1], StringComparison.Ordinal);
|
||||
Assert.True(prefix, $"{label}-description, Expected target to end with '{parts[1]}'. Actual: '{remaining}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task CheckCustomType(JToken actual_val, JToken exp_val, string label)
|
||||
{
|
||||
var ctype = exp_val["__custom_type"].Value<string>();
|
||||
switch (ctype)
|
||||
{
|
||||
case "delegate":
|
||||
await CheckDelegate(actual_val, exp_val, label);
|
||||
break;
|
||||
|
||||
case "pointer":
|
||||
{
|
||||
|
||||
if (exp_val["is_null"]?.Value<bool>() == true)
|
||||
{
|
||||
AssertEqual("symbol", actual_val["type"]?.Value<string>(), $"{label}-type");
|
||||
|
||||
var exp_val_str = $"({exp_val ["type_name"]?.Value<string>()}) 0";
|
||||
AssertEqual(exp_val_str, actual_val["value"]?.Value<string>(), $"{label}-value");
|
||||
AssertEqual(exp_val_str, actual_val["description"]?.Value<string>(), $"{label}-description");
|
||||
}
|
||||
else if (exp_val["is_void"]?.Value<bool>() == true)
|
||||
{
|
||||
AssertEqual("symbol", actual_val["type"]?.Value<string>(), $"{label}-type");
|
||||
|
||||
var exp_val_str = $"({exp_val ["type_name"]?.Value<string>()})";
|
||||
AssertStartsWith(exp_val_str, actual_val["value"]?.Value<string>(), $"{label}-value");
|
||||
AssertStartsWith(exp_val_str, actual_val["description"]?.Value<string>(), $"{label}-description");
|
||||
}
|
||||
else
|
||||
{
|
||||
AssertEqual("object", actual_val["type"]?.Value<string>(), $"{label}-type");
|
||||
|
||||
var exp_prefix = $"({exp_val ["type_name"]?.Value<string>()})";
|
||||
AssertStartsWith(exp_prefix, actual_val["className"]?.Value<string>(), $"{label}-className");
|
||||
AssertStartsWith(exp_prefix, actual_val["description"]?.Value<string>(), $"{label}-description");
|
||||
Assert.False(actual_val["className"]?.Value<string>() == $"{exp_prefix} 0", $"[{label}] Expected a non-null value, but got {actual_val}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "getter":
|
||||
{
|
||||
// For getter, `actual_val` is not `.value`, instead it's the container object
|
||||
// which has a `.get` instead of a `.value`
|
||||
var get = actual_val["get"];
|
||||
Assert.True(get != null, $"[{label}] No `get` found. {(actual_val != null ? "Make sure to pass the container object for testing getters, and not the ['value']": String.Empty)}");
|
||||
|
||||
AssertEqual("Function", get["className"]?.Value<string>(), $"{label}-className");
|
||||
AssertStartsWith($"get {exp_val ["type_name"]?.Value<string> ()} ()", get["description"]?.Value<string>(), $"{label}-description");
|
||||
AssertEqual("function", get["type"]?.Value<string>(), $"{label}-type");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "ignore_me":
|
||||
// nothing to check ;)
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"{ctype} not supported");
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task CheckProps(JToken actual, object exp_o, string label, int num_fields = -1)
|
||||
{
|
||||
if (exp_o.GetType().IsArray || exp_o is JArray)
|
||||
{
|
||||
if (!(actual is JArray actual_arr))
|
||||
{
|
||||
Assert.True(false, $"[{label}] Expected to get an array here but got {actual}");
|
||||
return;
|
||||
}
|
||||
|
||||
var exp_v_arr = JArray.FromObject(exp_o);
|
||||
AssertEqual(exp_v_arr.Count, actual_arr.Count(), $"{label}-count");
|
||||
|
||||
for (int i = 0; i < exp_v_arr.Count; i++)
|
||||
{
|
||||
var exp_i = exp_v_arr[i];
|
||||
var act_i = actual_arr[i];
|
||||
|
||||
AssertEqual(i.ToString(), act_i["name"]?.Value<string>(), $"{label}-[{i}].name");
|
||||
if (exp_i != null)
|
||||
await CheckValue(act_i["value"], exp_i, $"{label}-{i}th value");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Not an array
|
||||
var exp = exp_o as JObject;
|
||||
if (exp == null)
|
||||
exp = JObject.FromObject(exp_o);
|
||||
|
||||
num_fields = num_fields < 0 ? exp.Values<JToken>().Count() : num_fields;
|
||||
Assert.True(num_fields == actual.Count(), $"[{label}] Number of fields don't match, Expected: {num_fields}, Actual: {actual.Count()}");
|
||||
|
||||
foreach (var kvp in exp)
|
||||
{
|
||||
var exp_name = kvp.Key;
|
||||
var exp_val = kvp.Value;
|
||||
|
||||
var actual_obj = actual.FirstOrDefault(jt => jt["name"]?.Value<string>() == exp_name);
|
||||
if (actual_obj == null)
|
||||
{
|
||||
Assert.True(actual_obj != null, $"[{label}] Could not find property named '{exp_name}'");
|
||||
}
|
||||
|
||||
Assert.True(actual_obj != null, $"[{label}] not value found for property named '{exp_name}'");
|
||||
|
||||
var actual_val = actual_obj["value"];
|
||||
if (exp_val.Type == JTokenType.Array)
|
||||
{
|
||||
var actual_props = await GetProperties(actual_val["objectId"]?.Value<string>());
|
||||
await CheckProps(actual_props, exp_val, $"{label}-{exp_name}");
|
||||
}
|
||||
else if (exp_val["__custom_type"] != null && exp_val["__custom_type"]?.Value<string>() == "getter")
|
||||
{
|
||||
// hack: for getters, actual won't have a .value
|
||||
await CheckCustomType(actual_obj, exp_val, $"{label}#{exp_name}");
|
||||
}
|
||||
else
|
||||
{
|
||||
await CheckValue(actual_val, exp_val, $"{label}#{exp_name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task CheckValue(JToken actual_val, JToken exp_val, string label)
|
||||
{
|
||||
if (exp_val["__custom_type"] != null)
|
||||
{
|
||||
await CheckCustomType(actual_val, exp_val, label);
|
||||
return;
|
||||
}
|
||||
|
||||
if (exp_val["type"] == null && actual_val["objectId"] != null)
|
||||
{
|
||||
var new_val = await GetProperties(actual_val["objectId"].Value<string>());
|
||||
await CheckProps(new_val, exp_val, $"{label}-{actual_val["objectId"]?.Value<string>()}");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var jp in exp_val.Values<JProperty>())
|
||||
{
|
||||
if (jp.Value.Type == JTokenType.Object)
|
||||
{
|
||||
var new_val = await GetProperties(actual_val["objectId"].Value<string>());
|
||||
await CheckProps(new_val, jp.Value, $"{label}-{actual_val["objectId"]?.Value<string>()}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var exp_val_str = jp.Value.Value<string>();
|
||||
bool null_or_empty_exp_val = String.IsNullOrEmpty(exp_val_str);
|
||||
|
||||
var actual_field_val = actual_val.Values<JProperty>().FirstOrDefault(a_jp => a_jp.Name == jp.Name);
|
||||
var actual_field_val_str = actual_field_val?.Value?.Value<string>();
|
||||
if (null_or_empty_exp_val && String.IsNullOrEmpty(actual_field_val_str))
|
||||
continue;
|
||||
|
||||
Assert.True(actual_field_val != null, $"[{label}] Could not find value field named {jp.Name}");
|
||||
|
||||
Assert.True(exp_val_str == actual_field_val_str,
|
||||
$"[{label}] Value for json property named {jp.Name} didn't match.\n" +
|
||||
$"Expected: {jp.Value.Value<string> ()}\n" +
|
||||
$"Actual: {actual_field_val.Value.Value<string> ()}");
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<JToken> GetLocalsForFrame(JToken frame, string script_loc, int line, int column, string function_name)
|
||||
{
|
||||
CheckLocation(script_loc, line, column, ctx.scripts, frame["location"]);
|
||||
Assert.Equal(function_name, frame["functionName"].Value<string>());
|
||||
|
||||
return await GetProperties(frame["callFrameId"].Value<string>());
|
||||
}
|
||||
|
||||
internal async Task<JToken> GetObjectOnFrame(JToken frame, string name)
|
||||
{
|
||||
var locals = await GetProperties(frame["callFrameId"].Value<string>());
|
||||
return await GetObjectOnLocals(locals, name);
|
||||
}
|
||||
|
||||
// Find an object with @name, *fetch* the object, and check against @o
|
||||
internal async Task<JToken> CompareObjectPropertiesFor(JToken locals, string name, object o, string label = null, int num_fields = -1)
|
||||
{
|
||||
if (label == null)
|
||||
label = name;
|
||||
var props = await GetObjectOnLocals(locals, name);
|
||||
try
|
||||
{
|
||||
if (o != null)
|
||||
await CheckProps(props, o, label, num_fields);
|
||||
return props;
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<JToken> GetObjectOnLocals(JToken locals, string name)
|
||||
{
|
||||
var obj = GetAndAssertObjectWithName(locals, name);
|
||||
var objectId = obj["value"]["objectId"]?.Value<string>();
|
||||
Assert.True(!String.IsNullOrEmpty(objectId), $"No objectId found for {name}");
|
||||
|
||||
return await GetProperties(objectId);
|
||||
}
|
||||
|
||||
/* @fn_args is for use with `Runtime.callFunctionOn` only */
|
||||
internal async Task<JToken> GetProperties(string id, JToken fn_args = null)
|
||||
{
|
||||
if (ctx.UseCallFunctionOnBeforeGetProperties && !id.StartsWith("dotnet:scope:"))
|
||||
{
|
||||
var fn_decl = "function () { return this; }";
|
||||
var cfo_args = JObject.FromObject(new
|
||||
{
|
||||
functionDeclaration = fn_decl,
|
||||
objectId = id
|
||||
});
|
||||
if (fn_args != null)
|
||||
cfo_args["arguments"] = fn_args;
|
||||
|
||||
var result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token);
|
||||
AssertEqual(true, result.IsOk, $"Runtime.getProperties failed for {cfo_args.ToString ()}, with Result: {result}");
|
||||
id = result.Value["result"] ? ["objectId"]?.Value<string>();
|
||||
}
|
||||
|
||||
var get_prop_req = JObject.FromObject(new
|
||||
{
|
||||
objectId = id
|
||||
});
|
||||
|
||||
var frame_props = await ctx.cli.SendCommand("Runtime.getProperties", get_prop_req, ctx.token);
|
||||
if (!frame_props.IsOk)
|
||||
Assert.True(false, $"Runtime.getProperties failed for {get_prop_req.ToString ()}, with Result: {frame_props}");
|
||||
|
||||
var locals = frame_props.Value["result"];
|
||||
// FIXME: Should be done when generating the list in library_mono.js, but not sure yet
|
||||
// whether to remove it, and how to do it correctly.
|
||||
if (locals is JArray)
|
||||
{
|
||||
foreach (var p in locals)
|
||||
{
|
||||
if (p["name"]?.Value<string>() == "length" && p["enumerable"]?.Value<bool>() != true)
|
||||
{
|
||||
p.Remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return locals;
|
||||
}
|
||||
|
||||
internal async Task<JToken> EvaluateOnCallFrame(string id, string expression)
|
||||
{
|
||||
var evaluate_req = JObject.FromObject(new
|
||||
{
|
||||
callFrameId = id,
|
||||
expression = expression
|
||||
});
|
||||
|
||||
var frame_evaluate = await ctx.cli.SendCommand("Debugger.evaluateOnCallFrame", evaluate_req, ctx.token);
|
||||
if (!frame_evaluate.IsOk)
|
||||
Assert.True(false, $"Debugger.evaluateOnCallFrame failed for {evaluate_req.ToString ()}, with Result: {frame_evaluate}");
|
||||
|
||||
var evaluate_result = frame_evaluate.Value["result"];
|
||||
return evaluate_result;
|
||||
}
|
||||
|
||||
internal async Task<Result> SetBreakpoint(string url_key, int line, int column, bool expect_ok = true, bool use_regex = false)
|
||||
{
|
||||
var bp1_req = !use_regex ?
|
||||
JObject.FromObject(new { lineNumber = line, columnNumber = column, url = dicFileToUrl[url_key], }) :
|
||||
JObject.FromObject(new { lineNumber = line, columnNumber = column, urlRegex = url_key, });
|
||||
|
||||
var bp1_res = await ctx.cli.SendCommand("Debugger.setBreakpointByUrl", bp1_req, ctx.token);
|
||||
Assert.True(expect_ok ? bp1_res.IsOk : bp1_res.IsErr);
|
||||
|
||||
return bp1_res;
|
||||
}
|
||||
|
||||
internal async Task<Result> SetBreakpointInMethod(string assembly, string type, string method, int lineOffset = 0, int col = 0)
|
||||
{
|
||||
var req = JObject.FromObject(new { assemblyName = assembly, typeName = type, methodName = method, lineOffset = lineOffset });
|
||||
|
||||
// Protocol extension
|
||||
var res = await ctx.cli.SendCommand("DotnetDebugger.getMethodLocation", req, ctx.token);
|
||||
Assert.True(res.IsOk);
|
||||
|
||||
var m_url = res.Value["result"]["url"].Value<string>();
|
||||
var m_line = res.Value["result"]["line"].Value<int>();
|
||||
|
||||
var bp1_req = JObject.FromObject(new
|
||||
{
|
||||
lineNumber = m_line + lineOffset,
|
||||
columnNumber = col,
|
||||
url = m_url
|
||||
});
|
||||
|
||||
res = await ctx.cli.SendCommand("Debugger.setBreakpointByUrl", bp1_req, ctx.token);
|
||||
Assert.True(res.IsOk);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
internal void AssertEqual(object expected, object actual, string label) => Assert.True(expected?.Equals(actual),
|
||||
$"[{label}]\n" +
|
||||
$"Expected: {expected?.ToString()}\n" +
|
||||
$"Actual: {actual?.ToString()}\n");
|
||||
|
||||
internal void AssertStartsWith(string expected, string actual, string label) => Assert.True(actual?.StartsWith(expected), $"[{label}] Does not start with the expected string\nExpected: {expected}\nActual: {actual}");
|
||||
|
||||
internal static Func<int, int, string, string, object> TSimpleClass = (X, Y, Id, Color) => new
|
||||
{
|
||||
X = TNumber(X),
|
||||
Y = TNumber(Y),
|
||||
Id = TString(Id),
|
||||
Color = TEnum("DebuggerTests.RGB", Color),
|
||||
PointWithCustomGetter = TGetter("PointWithCustomGetter")
|
||||
};
|
||||
|
||||
internal static Func<int, int, string, string, object> TPoint = (X, Y, Id, Color) => new
|
||||
{
|
||||
X = TNumber(X),
|
||||
Y = TNumber(Y),
|
||||
Id = TString(Id),
|
||||
Color = TEnum("DebuggerTests.RGB", Color),
|
||||
};
|
||||
|
||||
//FIXME: um maybe we don't need to convert jobject right here!
|
||||
internal static JObject TString(string value) =>
|
||||
value == null ?
|
||||
TObject("string", is_null : true) :
|
||||
JObject.FromObject(new { type = "string", value = @value, description = @value });
|
||||
|
||||
internal static JObject TNumber(int value) =>
|
||||
JObject.FromObject(new { type = "number", value = @value.ToString(), description = value.ToString() });
|
||||
|
||||
internal static JObject TValueType(string className, string description = null, object members = null) =>
|
||||
JObject.FromObject(new { type = "object", isValueType = true, className = className, description = description ?? className });
|
||||
|
||||
internal static JObject TEnum(string className, string descr, object members = null) =>
|
||||
JObject.FromObject(new { type = "object", isEnum = true, className = className, description = descr });
|
||||
|
||||
internal static JObject TObject(string className, string description = null, bool is_null = false) =>
|
||||
is_null ?
|
||||
JObject.FromObject(new { type = "object", className = className, description = description ?? className, subtype = is_null ? "null" : null }) :
|
||||
JObject.FromObject(new { type = "object", className = className, description = description ?? className });
|
||||
|
||||
internal static JObject TArray(string className, int length = 0) => JObject.FromObject(new { type = "object", className = className, description = $"{className}({length})", subtype = "array" });
|
||||
|
||||
internal static JObject TBool(bool value) => JObject.FromObject(new { type = "boolean", value = @value, description = @value ? "true" : "false" });
|
||||
|
||||
internal static JObject TSymbol(string value) => JObject.FromObject(new { type = "symbol", value = @value, description = @value });
|
||||
|
||||
/*
|
||||
For target names with generated method names like
|
||||
`void <ActionTSignatureTest>b__11_0 (Math.GenericStruct<int[]>)`
|
||||
|
||||
.. pass target "as `target: "void <ActionTSignatureTest>|(Math.GenericStruct<int[]>)"`
|
||||
*/
|
||||
internal static JObject TDelegate(string className, string target) => JObject.FromObject(new
|
||||
{
|
||||
__custom_type = "delegate",
|
||||
className = className,
|
||||
target = target
|
||||
});
|
||||
|
||||
internal static JObject TPointer(string type_name, bool is_null = false) => JObject.FromObject(new { __custom_type = "pointer", type_name = type_name, is_null = is_null, is_void = type_name.StartsWith("void*") });
|
||||
|
||||
internal static JObject TIgnore() => JObject.FromObject(new { __custom_type = "ignore_me" });
|
||||
|
||||
internal static JObject TGetter(string type) => JObject.FromObject(new { __custom_type = "getter", type_name = type });
|
||||
}
|
||||
|
||||
class DebugTestContext
|
||||
{
|
||||
public InspectorClient cli;
|
||||
public Inspector insp;
|
||||
public CancellationToken token;
|
||||
public Dictionary<string, string> scripts;
|
||||
|
||||
public bool UseCallFunctionOnBeforeGetProperties;
|
||||
|
||||
public DebugTestContext(InspectorClient cli, Inspector insp, CancellationToken token, Dictionary<string, string> scripts)
|
||||
{
|
||||
this.cli = cli;
|
||||
this.insp = insp;
|
||||
this.token = token;
|
||||
this.scripts = scripts;
|
||||
}
|
||||
}
|
||||
|
||||
enum StepKind
|
||||
{
|
||||
Into,
|
||||
Over,
|
||||
Out
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Error",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,308 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
namespace DebuggerTests
|
||||
{
|
||||
public class ArrayTestsClass
|
||||
{
|
||||
public static void PrimitiveTypeLocals(bool call_other = false)
|
||||
{
|
||||
var int_arr = new int[] { 4, 70, 1 };
|
||||
var int_arr_empty = new int[0];
|
||||
int[] int_arr_null = null;
|
||||
|
||||
if (call_other)
|
||||
OtherMethod();
|
||||
|
||||
Console.WriteLine($"int_arr: {int_arr.Length}, {int_arr_empty.Length}, {int_arr_null?.Length}");
|
||||
}
|
||||
|
||||
public static void ValueTypeLocals(bool call_other = false)
|
||||
{
|
||||
var point_arr = new Point[]
|
||||
{
|
||||
new Point { X = 5, Y = -2, Id = "point_arr#Id#0", Color = RGB.Green },
|
||||
new Point { X = 123, Y = 0, Id = "point_arr#Id#1", Color = RGB.Blue },
|
||||
};
|
||||
|
||||
var point_arr_empty = new Point[0];
|
||||
Point[] point_arr_null = null;
|
||||
|
||||
if (call_other)
|
||||
OtherMethod();
|
||||
|
||||
Console.WriteLine($"point_arr: {point_arr.Length}, {point_arr_empty.Length}, {point_arr_null?.Length}");
|
||||
}
|
||||
|
||||
public static void ObjectTypeLocals(bool call_other = false)
|
||||
{
|
||||
var class_arr = new SimpleClass[]
|
||||
{
|
||||
new SimpleClass { X = 5, Y = -2, Id = "class_arr#Id#0", Color = RGB.Green },
|
||||
null,
|
||||
new SimpleClass { X = 123, Y = 0, Id = "class_arr#Id#2", Color = RGB.Blue },
|
||||
};
|
||||
|
||||
var class_arr_empty = new SimpleClass[0];
|
||||
SimpleClass[] class_arr_null = null;
|
||||
|
||||
if (call_other)
|
||||
OtherMethod();
|
||||
|
||||
Console.WriteLine($"class_arr: {class_arr.Length}, {class_arr_empty.Length}, {class_arr_null?.Length}");
|
||||
}
|
||||
|
||||
public static void GenericTypeLocals(bool call_other = false)
|
||||
{
|
||||
var gclass_arr = new GenericClass<int>[]
|
||||
{
|
||||
null,
|
||||
new GenericClass<int> { Id = "gclass_arr#1#Id", Color = RGB.Red, Value = 5 },
|
||||
new GenericClass<int> { Id = "gclass_arr#2#Id", Color = RGB.Blue, Value = -12 },
|
||||
};
|
||||
|
||||
var gclass_arr_empty = new GenericClass<int>[0];
|
||||
GenericClass<int>[] gclass_arr_null = null;
|
||||
|
||||
if (call_other)
|
||||
OtherMethod();
|
||||
|
||||
Console.WriteLine($"gclass_arr: {gclass_arr.Length}, {gclass_arr_empty.Length}, {gclass_arr_null?.Length}");
|
||||
}
|
||||
|
||||
public static void GenericValueTypeLocals(bool call_other = false)
|
||||
{
|
||||
var gvclass_arr = new SimpleGenericStruct<Point>[]
|
||||
{
|
||||
new SimpleGenericStruct<Point> { Id = "gvclass_arr#1#Id", Color = RGB.Red, Value = new Point { X = 100, Y = 200, Id = "gvclass_arr#1#Value#Id", Color = RGB.Red } },
|
||||
new SimpleGenericStruct<Point> { Id = "gvclass_arr#2#Id", Color = RGB.Blue, Value = new Point { X = 10, Y = 20, Id = "gvclass_arr#2#Value#Id", Color = RGB.Green } }
|
||||
};
|
||||
|
||||
var gvclass_arr_empty = new SimpleGenericStruct<Point>[0];
|
||||
SimpleGenericStruct<Point>[] gvclass_arr_null = null;
|
||||
|
||||
if (call_other)
|
||||
OtherMethod();
|
||||
|
||||
Console.WriteLine($"gvclass_arr: {gvclass_arr.Length}, {gvclass_arr_empty.Length}, {gvclass_arr_null?.Length}");
|
||||
}
|
||||
|
||||
static void OtherMethod()
|
||||
{
|
||||
YetAnotherMethod();
|
||||
Console.WriteLine($"Just a placeholder for breakpoints");
|
||||
}
|
||||
|
||||
static void YetAnotherMethod()
|
||||
{
|
||||
Console.WriteLine($"Just a placeholder for breakpoints");
|
||||
}
|
||||
|
||||
public static void ObjectArrayMembers()
|
||||
{
|
||||
var c = new Container
|
||||
{
|
||||
id = "c#id",
|
||||
ClassArrayProperty = new SimpleClass[]
|
||||
{
|
||||
new SimpleClass { X = 5, Y = -2, Id = "ClassArrayProperty#Id#0", Color = RGB.Green },
|
||||
new SimpleClass { X = 30, Y = 1293, Id = "ClassArrayProperty#Id#1", Color = RGB.Green },
|
||||
null
|
||||
},
|
||||
ClassArrayField = new SimpleClass[]
|
||||
{
|
||||
null,
|
||||
new SimpleClass { X = 5, Y = -2, Id = "ClassArrayField#Id#1", Color = RGB.Blue },
|
||||
new SimpleClass { X = 30, Y = 1293, Id = "ClassArrayField#Id#2", Color = RGB.Green },
|
||||
},
|
||||
PointsProperty = new Point[]
|
||||
{
|
||||
new Point { X = 5, Y = -2, Id = "PointsProperty#Id#0", Color = RGB.Green },
|
||||
new Point { X = 123, Y = 0, Id = "PointsProperty#Id#1", Color = RGB.Blue },
|
||||
},
|
||||
PointsField = new Point[]
|
||||
{
|
||||
new Point { X = 5, Y = -2, Id = "PointsField#Id#0", Color = RGB.Green },
|
||||
new Point { X = 123, Y = 0, Id = "PointsField#Id#1", Color = RGB.Blue },
|
||||
}
|
||||
};
|
||||
|
||||
Console.WriteLine($"Back from PlaceholderMethod, {c.ClassArrayProperty?.Length}");
|
||||
c.PlaceholderMethod();
|
||||
Console.WriteLine($"Back from PlaceholderMethod, {c.id}");
|
||||
}
|
||||
|
||||
public static async Task<bool> ValueTypeLocalsAsync(bool call_other = false)
|
||||
{
|
||||
var gvclass_arr = new SimpleGenericStruct<Point>[]
|
||||
{
|
||||
new SimpleGenericStruct<Point> { Id = "gvclass_arr#1#Id", Color = RGB.Red, Value = new Point { X = 100, Y = 200, Id = "gvclass_arr#1#Value#Id", Color = RGB.Red } },
|
||||
new SimpleGenericStruct<Point> { Id = "gvclass_arr#2#Id", Color = RGB.Blue, Value = new Point { X = 10, Y = 20, Id = "gvclass_arr#2#Value#Id", Color = RGB.Green } }
|
||||
};
|
||||
|
||||
var gvclass_arr_empty = new SimpleGenericStruct<Point>[0];
|
||||
SimpleGenericStruct<Point>[] gvclass_arr_null = null;
|
||||
Console.WriteLine($"ValueTypeLocalsAsync: call_other: {call_other}");
|
||||
SimpleGenericStruct<Point> gvclass;
|
||||
Point[] points = null;
|
||||
|
||||
if (call_other)
|
||||
{
|
||||
(gvclass, points) = await new ArrayTestsClass().InstanceMethodValueTypeLocalsAsync<SimpleGenericStruct<Point>>(gvclass_arr[0]);
|
||||
Console.WriteLine($"* gvclass: {gvclass}, points: {points.Length}");
|
||||
}
|
||||
|
||||
Console.WriteLine($"gvclass_arr: {gvclass_arr.Length}, {gvclass_arr_empty.Length}, {gvclass_arr_null?.Length}");
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task < (T, Point[]) > InstanceMethodValueTypeLocalsAsync<T>(T t1)
|
||||
{
|
||||
var point_arr = new Point[]
|
||||
{
|
||||
new Point { X = 5, Y = -2, Id = "point_arr#Id#0", Color = RGB.Red },
|
||||
new Point { X = 123, Y = 0, Id = "point_arr#Id#1", Color = RGB.Blue }
|
||||
};
|
||||
var point = new Point { X = 45, Y = 51, Id = "point#Id", Color = RGB.Green };
|
||||
|
||||
Console.WriteLine($"point_arr: {point_arr.Length}, T: {t1}, point: {point}");
|
||||
return (t1, new Point[] { point_arr[0], point_arr[1], point });
|
||||
}
|
||||
|
||||
// A workaround for method invocations on structs not working right now
|
||||
public static async Task EntryPointForStructMethod(bool call_other = false)
|
||||
{
|
||||
await Point.AsyncMethod(call_other);
|
||||
}
|
||||
|
||||
public static void GenericValueTypeLocals2(bool call_other = false)
|
||||
{
|
||||
var gvclass_arr = new SimpleGenericStruct<Point[]>[]
|
||||
{
|
||||
new SimpleGenericStruct<Point[]>
|
||||
{
|
||||
Id = "gvclass_arr#0#Id",
|
||||
Color = RGB.Red,
|
||||
Value = new Point[]
|
||||
{
|
||||
new Point { X = 100, Y = 200, Id = "gvclass_arr#0#0#Value#Id", Color = RGB.Red },
|
||||
new Point { X = 100, Y = 200, Id = "gvclass_arr#0#1#Value#Id", Color = RGB.Green }
|
||||
}
|
||||
},
|
||||
|
||||
new SimpleGenericStruct<Point[]>
|
||||
{
|
||||
Id = "gvclass_arr#1#Id",
|
||||
Color = RGB.Blue,
|
||||
Value = new Point[]
|
||||
{
|
||||
new Point { X = 100, Y = 200, Id = "gvclass_arr#1#0#Value#Id", Color = RGB.Green },
|
||||
new Point { X = 100, Y = 200, Id = "gvclass_arr#1#1#Value#Id", Color = RGB.Blue }
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var gvclass_arr_empty = new SimpleGenericStruct<Point[]>[0];
|
||||
SimpleGenericStruct<Point[]>[] gvclass_arr_null = null;
|
||||
|
||||
if (call_other)
|
||||
OtherMethod();
|
||||
|
||||
Console.WriteLine($"gvclass_arr: {gvclass_arr.Length}, {gvclass_arr_empty.Length}, {gvclass_arr_null?.Length}");
|
||||
}
|
||||
}
|
||||
|
||||
public class Container
|
||||
{
|
||||
public string id;
|
||||
public SimpleClass[] ClassArrayProperty { get; set; }
|
||||
public SimpleClass[] ClassArrayField;
|
||||
|
||||
public Point[] PointsProperty { get; set; }
|
||||
public Point[] PointsField;
|
||||
|
||||
public void PlaceholderMethod()
|
||||
{
|
||||
Console.WriteLine($"Container.PlaceholderMethod");
|
||||
}
|
||||
}
|
||||
|
||||
public struct Point
|
||||
{
|
||||
public int X, Y;
|
||||
public string Id { get; set; }
|
||||
public RGB Color { get; set; }
|
||||
|
||||
/* instance too */
|
||||
public static async Task AsyncMethod(bool call_other)
|
||||
{
|
||||
int local_i = 5;
|
||||
var sc = new SimpleClass { X = 10, Y = 45, Id = "sc#Id", Color = RGB.Blue };
|
||||
if (call_other)
|
||||
await new Point { X = 90, Y = -4, Id = "point#Id", Color = RGB.Green }.AsyncInstanceMethod(sc);
|
||||
Console.WriteLine($"AsyncMethod local_i: {local_i}, sc: {sc.Id}");
|
||||
}
|
||||
|
||||
public async Task AsyncInstanceMethod(SimpleClass sc_arg)
|
||||
{
|
||||
var local_gs = new SimpleGenericStruct<int> { Id = "local_gs#Id", Color = RGB.Green, Value = 4 };
|
||||
sc_arg.Id = "sc_arg#Id";
|
||||
Console.WriteLine($"AsyncInstanceMethod sc_arg: {sc_arg.Id}, local_gs: {local_gs.Id}");
|
||||
}
|
||||
|
||||
public void GenericInstanceMethod<T>(T sc_arg) where T : SimpleClass
|
||||
{
|
||||
var local_gs = new SimpleGenericStruct<int> { Id = "local_gs#Id", Color = RGB.Green, Value = 4 };
|
||||
sc_arg.Id = "sc_arg#Id";
|
||||
Console.WriteLine($"AsyncInstanceMethod sc_arg: {sc_arg.Id}, local_gs: {local_gs.Id}");
|
||||
}
|
||||
}
|
||||
|
||||
public class SimpleClass
|
||||
{
|
||||
public int X, Y;
|
||||
public string Id { get; set; }
|
||||
public RGB Color { get; set; }
|
||||
|
||||
public Point PointWithCustomGetter { get { return new Point { X = 100, Y = 400, Id = "SimpleClass#Point#gen#Id", Color = RGB.Green }; } }
|
||||
}
|
||||
|
||||
public class GenericClass<T>
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public RGB Color { get; set; }
|
||||
public T Value { get; set; }
|
||||
}
|
||||
|
||||
public struct SimpleGenericStruct<T>
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public RGB Color { get; set; }
|
||||
public T Value { get; set; }
|
||||
}
|
||||
|
||||
public class EntryClass
|
||||
{
|
||||
public static void run()
|
||||
{
|
||||
ArrayTestsClass.PrimitiveTypeLocals(true);
|
||||
ArrayTestsClass.ValueTypeLocals(true);
|
||||
ArrayTestsClass.ObjectTypeLocals(true);
|
||||
|
||||
ArrayTestsClass.GenericTypeLocals(true);
|
||||
ArrayTestsClass.GenericValueTypeLocals(true);
|
||||
ArrayTestsClass.GenericValueTypeLocals2(true);
|
||||
|
||||
ArrayTestsClass.ObjectArrayMembers();
|
||||
|
||||
ArrayTestsClass.ValueTypeLocalsAsync(true).Wait();
|
||||
|
||||
ArrayTestsClass.EntryPointForStructMethod(true).Wait();
|
||||
|
||||
var sc = new SimpleClass { X = 10, Y = 45, Id = "sc#Id", Color = RGB.Blue };
|
||||
new Point { X = 90, Y = -4, Id = "point#Id", Color = RGB.Green }.GenericInstanceMethod(sc);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System;
|
||||
|
||||
namespace DebuggerTests
|
||||
{
|
||||
public class CallFunctionOnTest
|
||||
{
|
||||
public static void LocalsTest(int len)
|
||||
{
|
||||
var big = new int[len];
|
||||
for (int i = 0; i < len; i++)
|
||||
big[i] = i + 1000;
|
||||
|
||||
var simple_struct = new Math.SimpleStruct() { dt = new DateTime(2020, 1, 2, 3, 4, 5), gs = new Math.GenericStruct<DateTime> { StringField = $"simple_struct # gs # StringField" } };
|
||||
|
||||
var ss_arr = new Math.SimpleStruct[len];
|
||||
for (int i = 0; i < len; i++)
|
||||
ss_arr[i] = new Math.SimpleStruct() { dt = new DateTime(2020 + i, 1, 2, 3, 4, 5), gs = new Math.GenericStruct<DateTime> { StringField = $"ss_arr # {i} # gs # StringField" } };
|
||||
|
||||
var nim = new Math.NestedInMath { SimpleStructProperty = new Math.SimpleStruct() { dt = new DateTime(2010, 6, 7, 8, 9, 10) } };
|
||||
Action<Math.GenericStruct<int[]>> action = Math.DelegateTargetWithVoidReturn;
|
||||
Console.WriteLine("foo");
|
||||
}
|
||||
|
||||
public static void PropertyGettersTest()
|
||||
{
|
||||
var ptd = new ClassWithProperties { DTAutoProperty = new DateTime(4, 5, 6, 7, 8, 9) };
|
||||
var swp = new StructWithProperties();
|
||||
System.Console.WriteLine("break here");
|
||||
}
|
||||
|
||||
public static async System.Threading.Tasks.Task PropertyGettersTestAsync()
|
||||
{
|
||||
var ptd = new ClassWithProperties { DTAutoProperty = new DateTime(4, 5, 6, 7, 8, 9) };
|
||||
var swp = new StructWithProperties();
|
||||
System.Console.WriteLine("break here");
|
||||
await System.Threading.Tasks.Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
class ClassWithProperties
|
||||
{
|
||||
public int Int { get { return 5; } }
|
||||
public string String { get { return "foobar"; } }
|
||||
public DateTime DT { get { return new DateTime(3, 4, 5, 6, 7, 8); } }
|
||||
|
||||
public int[] IntArray { get { return new int[] { 10, 20 }; } }
|
||||
public DateTime[] DTArray { get { return new DateTime[] { new DateTime(6, 7, 8, 9, 10, 11), new DateTime(1, 2, 3, 4, 5, 6) }; } }
|
||||
public DateTime DTAutoProperty { get; set; }
|
||||
public string StringField;
|
||||
}
|
||||
|
||||
struct StructWithProperties
|
||||
{
|
||||
public int Int { get { return 5; } }
|
||||
public string String { get { return "foobar"; } }
|
||||
public DateTime DT { get { return new DateTime(3, 4, 5, 6, 7, 8); } }
|
||||
|
||||
public int[] IntArray { get { return new int[] { 10, 20 }; } }
|
||||
public DateTime[] DTArray { get { return new DateTime[] { new DateTime(6, 7, 8, 9, 10, 11), new DateTime(1, 2, 3, 4, 5, 6) }; } }
|
||||
}
|
||||
}
|
|
@ -0,0 +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);
|
||||
|
||||
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);
|
||||
|
||||
return dt_str;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<script type='text/javascript'>
|
||||
var App = {
|
||||
init: function () {
|
||||
this.int_add = Module.mono_bind_static_method ("[debugger-test] Math:IntAdd");
|
||||
this.use_complex = Module.mono_bind_static_method ("[debugger-test] Math:UseComplex");
|
||||
this.delegates_test = Module.mono_bind_static_method ("[debugger-test] Math:DelegatesTest");
|
||||
this.generic_types_test = Module.mono_bind_static_method ("[debugger-test] Math:GenericTypesTest");
|
||||
this.outer_method = Module.mono_bind_static_method ("[debugger-test] Math:OuterMethod");
|
||||
this.async_method = Module.mono_bind_static_method ("[debugger-test] Math/NestedInMath:AsyncTest");
|
||||
this.method_with_structs = Module.mono_bind_static_method ("[debugger-test] DebuggerTests.ValueTypesTest:MethodWithLocalStructs");
|
||||
this.run_all = Module.mono_bind_static_method ("[debugger-test] DebuggerTest:run_all");
|
||||
this.static_method_table = {};
|
||||
console.log ("ready");
|
||||
},
|
||||
};
|
||||
function invoke_static_method (method_name, ...args) {
|
||||
var method = App.static_method_table [method_name];
|
||||
if (method == undefined)
|
||||
method = App.static_method_table [method_name] = Module.mono_bind_static_method (method_name);
|
||||
|
||||
return method (...args);
|
||||
}
|
||||
|
||||
async function invoke_static_method_async (method_name, ...args) {
|
||||
var method = App.static_method_table [method_name];
|
||||
if (method == undefined) {
|
||||
method = App.static_method_table [method_name] = Module.mono_bind_static_method (method_name);
|
||||
}
|
||||
|
||||
return await method (...args);
|
||||
}
|
||||
|
||||
function invoke_big_array_js_test (len) {
|
||||
big_array_js_test(len);
|
||||
}
|
||||
|
||||
function invoke_getters_js_test () {
|
||||
getters_js_test ();
|
||||
}
|
||||
|
||||
function invoke_add () {
|
||||
return App.int_add (10, 20);
|
||||
}
|
||||
function invoke_use_complex () {
|
||||
return App.use_complex (10, 20);
|
||||
}
|
||||
function invoke_delegates_test () {
|
||||
return App.delegates_test ();
|
||||
}
|
||||
function invoke_generic_types_test () {
|
||||
return App.generic_types_test ();
|
||||
}
|
||||
function invoke_bad_js_test () {
|
||||
console.log ("js: In invoke_bad_js_test");
|
||||
App.non_existant ();
|
||||
console.log ("js: After.. shouldn't reach here");
|
||||
}
|
||||
function invoke_outer_method () {
|
||||
console.log('invoke_outer_method called');
|
||||
return App.outer_method ();
|
||||
}
|
||||
async function invoke_async_method_with_await () {
|
||||
return await App.async_method ("string from js", 42);
|
||||
}
|
||||
function invoke_method_with_structs () {
|
||||
return App.method_with_structs ();
|
||||
}
|
||||
function invoke_run_all () {
|
||||
return App.run_all ();
|
||||
}
|
||||
</script>
|
||||
<script type="text/javascript" src="mono-config.js"></script>
|
||||
<script type="text/javascript" src="runtime-debugger.js"></script>
|
||||
<script type="text/javascript" src="other.js"></script>
|
||||
<script async type="text/javascript" src="dotnet.js"></script>
|
||||
Stuff goes here
|
||||
</body>
|
||||
</html>
|
||||
|
||||
17 sdks/wasm/debugger-test.cs
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
public 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;
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
public static int UseComplex () {
|
||||
var complex = new Simple.Complex (10, "xx");
|
||||
var res = complex.DoStuff ();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
19 sdks/wasm/debugger-test2.cs
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
|
||||
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; }
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
// 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 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)
|
||||
{
|
||||
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);
|
||||
a = 1;
|
||||
b = 2;
|
||||
c = 3;
|
||||
a = a + 1;
|
||||
b = b + 1;
|
||||
c = c + 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static void EvaluateLocals()
|
||||
{
|
||||
TestEvaluate f = new TestEvaluate();
|
||||
f.run(100, 200, "test");
|
||||
|
||||
var f_s = new EvaluateTestsStruct();
|
||||
f_s.EvaluateTestsStructInstanceMethod(100, 200, "test");
|
||||
f_s.GenericInstanceMethodOnStruct<int>(100, 200, "test");
|
||||
|
||||
var f_g_s = new EvaluateTestsGenericStruct<int>();
|
||||
f_g_s.EvaluateTestsGenericStructInstanceMethod(100, 200, "test");
|
||||
Console.WriteLine($"a: {f.a}, b: {f.b}, c: {f.c}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct EvaluateTestsStruct
|
||||
{
|
||||
public int a;
|
||||
public int b;
|
||||
public int c;
|
||||
DateTime dateTime;
|
||||
public void EvaluateTestsStructInstanceMethod(int g, int h, string valString)
|
||||
{
|
||||
int d = g + 1;
|
||||
int e = g + 2;
|
||||
int f = g + 3;
|
||||
int i = d + e + f;
|
||||
a = 1;
|
||||
b = 2;
|
||||
c = 3;
|
||||
dateTime = new DateTime(2020, 1, 2, 3, 4, 5);
|
||||
a = a + 1;
|
||||
b = b + 1;
|
||||
c = c + 1;
|
||||
}
|
||||
|
||||
public void GenericInstanceMethodOnStruct<T>(int g, int h, string valString)
|
||||
{
|
||||
int d = g + 1;
|
||||
int e = g + 2;
|
||||
int f = g + 3;
|
||||
int i = d + e + f;
|
||||
a = 1;
|
||||
b = 2;
|
||||
c = 3;
|
||||
dateTime = new DateTime(2020, 1, 2, 3, 4, 5);
|
||||
T t = default(T);
|
||||
a = a + 1;
|
||||
b = b + 1;
|
||||
c = c + 1;
|
||||
}
|
||||
}
|
||||
|
||||
public struct EvaluateTestsGenericStruct<T>
|
||||
{
|
||||
public int a;
|
||||
public int b;
|
||||
public int c;
|
||||
DateTime dateTime;
|
||||
public void EvaluateTestsGenericStructInstanceMethod(int g, int h, string valString)
|
||||
{
|
||||
int d = g + 1;
|
||||
int e = g + 2;
|
||||
int f = g + 3;
|
||||
int i = d + e + f;
|
||||
a = 1;
|
||||
b = 2;
|
||||
c = 3;
|
||||
dateTime = new DateTime(2020, 1, 2, 3, 4, 5);
|
||||
T t = default(T);
|
||||
a = a + 1;
|
||||
b = b + 2;
|
||||
c = c + 3;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DebuggerTests
|
||||
{
|
||||
public class PointerTests
|
||||
{
|
||||
|
||||
public static unsafe void LocalPointers()
|
||||
{
|
||||
int ivalue0 = 5;
|
||||
int ivalue1 = 10;
|
||||
|
||||
int* ip = &ivalue0;
|
||||
int* ip_null = null;
|
||||
int** ipp = &ip;
|
||||
int** ipp_null = &ip_null;
|
||||
int*[] ipa = new int*[] { &ivalue0, &ivalue1, null };
|
||||
int**[] ippa = new int**[] { &ip, &ip_null, ipp, ipp_null, null };
|
||||
char cvalue0 = 'q';
|
||||
char* cp = &cvalue0;
|
||||
|
||||
DateTime dt = new DateTime(5, 6, 7, 8, 9, 10);
|
||||
void* vp = &dt;
|
||||
void* vp_null = null;
|
||||
void** vpp = &vp;
|
||||
void** vpp_null = &vp_null;
|
||||
|
||||
DateTime* dtp = &dt;
|
||||
DateTime* dtp_null = null;
|
||||
DateTime*[] dtpa = new DateTime*[] { dtp, dtp_null };
|
||||
DateTime**[] dtppa = new DateTime**[] { &dtp, &dtp_null, null };
|
||||
Console.WriteLine($"-- break here: ip_null==null: {ip_null == null}, ipp_null: {ipp_null == null}, *ipp_null==ip_null: {*ipp_null == ip_null}, *ipp_null==null: {*ipp_null == null}");
|
||||
|
||||
var gs = new GenericStructWithUnmanagedT<DateTime> { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp };
|
||||
var gs_null = new GenericStructWithUnmanagedT<DateTime> { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp_null };
|
||||
var gsp = &gs;
|
||||
var gsp_null = &gs_null;
|
||||
var gspa = new GenericStructWithUnmanagedT<DateTime>*[] { null, gsp, gsp_null };
|
||||
|
||||
var cwp = new GenericClassWithPointers<DateTime> { Ptr = dtp };
|
||||
var cwp_null = new GenericClassWithPointers<DateTime>();
|
||||
Console.WriteLine($"{(int)*ip}, {(int)**ipp}, {ipp_null == null}, {ip_null == null}, {ippa == null}, {ipa}, {(char)*cp}, {(vp == null ? "null" : "not null")}, {dtp->Second}, {gsp->IntField}, {cwp}, {cwp_null}, {gs_null}");
|
||||
|
||||
PointersAsArgsTest(ip, ipp, ipa, ippa, &dt, &dtp, dtpa, dtppa);
|
||||
}
|
||||
|
||||
static unsafe void PointersAsArgsTest(int* ip, int** ipp, int*[] ipa, int**[] ippa,
|
||||
DateTime* dtp, DateTime** dtpp, DateTime*[] dtpa, DateTime**[] dtppa)
|
||||
{
|
||||
Console.WriteLine($"break here!");
|
||||
if (ip == null)
|
||||
Console.WriteLine($"ip is null");
|
||||
Console.WriteLine($"done!");
|
||||
}
|
||||
|
||||
public static unsafe async Task LocalPointersAsync()
|
||||
{
|
||||
int ivalue0 = 5;
|
||||
int ivalue1 = 10;
|
||||
|
||||
int* ip = &ivalue0;
|
||||
int* ip_null = null;
|
||||
int** ipp = &ip;
|
||||
int** ipp_null = &ip_null;
|
||||
int*[] ipa = new int*[] { &ivalue0, &ivalue1, null };
|
||||
int**[] ippa = new int**[] { &ip, &ip_null, ipp, ipp_null, null };
|
||||
char cvalue0 = 'q';
|
||||
char* cp = &cvalue0;
|
||||
|
||||
DateTime dt = new DateTime(5, 6, 7, 8, 9, 10);
|
||||
void* vp = &dt;
|
||||
void* vp_null = null;
|
||||
void** vpp = &vp;
|
||||
void** vpp_null = &vp_null;
|
||||
|
||||
DateTime* dtp = &dt;
|
||||
DateTime* dtp_null = null;
|
||||
DateTime*[] dtpa = new DateTime*[] { dtp, dtp_null };
|
||||
DateTime**[] dtppa = new DateTime**[] { &dtp, &dtp_null, null };
|
||||
Console.WriteLine($"-- break here: ip_null==null: {ip_null == null}, ipp_null: {ipp_null == null}, *ipp_null==ip_null: {*ipp_null == ip_null}, *ipp_null==null: {*ipp_null == null}");
|
||||
|
||||
var gs = new GenericStructWithUnmanagedT<DateTime> { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp };
|
||||
var gs_null = new GenericStructWithUnmanagedT<DateTime> { Value = new DateTime(1, 2, 3, 4, 5, 6), IntField = 4, DTPP = &dtp_null };
|
||||
var gsp = &gs;
|
||||
var gsp_null = &gs_null;
|
||||
var gspa = new GenericStructWithUnmanagedT<DateTime>*[] { null, gsp, gsp_null };
|
||||
|
||||
var cwp = new GenericClassWithPointers<DateTime> { Ptr = dtp };
|
||||
var cwp_null = new GenericClassWithPointers<DateTime>();
|
||||
Console.WriteLine($"{(int)*ip}, {(int)**ipp}, {ipp_null == null}, {ip_null == null}, {ippa == null}, {ipa}, {(char)*cp}, {(vp == null ? "null" : "not null")}, {dtp->Second}, {gsp->IntField}, {cwp}, {cwp_null}, {gs_null}");
|
||||
}
|
||||
|
||||
// async methods cannot have unsafe params, so no test for that
|
||||
}
|
||||
|
||||
public unsafe struct GenericStructWithUnmanagedT<T> where T : unmanaged
|
||||
{
|
||||
public T Value;
|
||||
public int IntField;
|
||||
|
||||
public DateTime** DTPP;
|
||||
}
|
||||
|
||||
public unsafe class GenericClassWithPointers<T> where T : unmanaged
|
||||
{
|
||||
public unsafe T* Ptr;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,325 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System;
|
||||
|
||||
public partial class Math
|
||||
{ //Only append content to this class as the test suite depends on line info
|
||||
public static int IntAdd(int a, int b)
|
||||
{
|
||||
int c = a + b;
|
||||
int d = c + b;
|
||||
int e = d + a;
|
||||
int f = 0;
|
||||
return e;
|
||||
}
|
||||
|
||||
public static int UseComplex(int a, int b)
|
||||
{
|
||||
var complex = new Simple.Complex(10, "xx");
|
||||
int c = a + b;
|
||||
int d = c + b;
|
||||
int e = d + a;
|
||||
int f = 0;
|
||||
e += complex.DoStuff();
|
||||
return e;
|
||||
}
|
||||
|
||||
delegate bool IsMathNull(Math m);
|
||||
|
||||
public static int DelegatesTest()
|
||||
{
|
||||
Func<Math, bool> fn_func = (Math m) => m == null;
|
||||
Func<Math, bool> fn_func_null = null;
|
||||
Func<Math, bool>[] fn_func_arr = new Func<Math, bool>[] {
|
||||
(Math m) => m == null };
|
||||
|
||||
Math.IsMathNull fn_del = Math.IsMathNullDelegateTarget;
|
||||
var fn_del_arr = new Math.IsMathNull[] { Math.IsMathNullDelegateTarget };
|
||||
var m_obj = new Math();
|
||||
Math.IsMathNull fn_del_null = null;
|
||||
bool res = fn_func(m_obj) && fn_del(m_obj) && fn_del_arr[0](m_obj) && fn_del_null == null && fn_func_null == null && fn_func_arr[0] != null;
|
||||
|
||||
// Unused locals
|
||||
|
||||
Func<Math, bool> fn_func_unused = (Math m) => m == null;
|
||||
Func<Math, bool> fn_func_null_unused = null;
|
||||
Func<Math, bool>[] fn_func_arr_unused = new Func<Math, bool>[] { (Math m) => m == null };
|
||||
|
||||
Math.IsMathNull fn_del_unused = Math.IsMathNullDelegateTarget;
|
||||
Math.IsMathNull fn_del_null_unused = null;
|
||||
var fn_del_arr_unused = new Math.IsMathNull[] { Math.IsMathNullDelegateTarget };
|
||||
OuterMethod();
|
||||
Console.WriteLine("Just a test message, ignore");
|
||||
return res ? 0 : 1;
|
||||
}
|
||||
|
||||
public static int GenericTypesTest()
|
||||
{
|
||||
var list = new System.Collections.Generic.Dictionary<Math[], IsMathNull>();
|
||||
System.Collections.Generic.Dictionary<Math[], IsMathNull> list_null = null;
|
||||
|
||||
var list_arr = new System.Collections.Generic.Dictionary<Math[], IsMathNull>[] { new System.Collections.Generic.Dictionary<Math[], IsMathNull>() };
|
||||
System.Collections.Generic.Dictionary<Math[], IsMathNull>[] list_arr_null = null;
|
||||
|
||||
Console.WriteLine($"list_arr.Length: {list_arr.Length}, list.Count: {list.Count}");
|
||||
|
||||
// Unused locals
|
||||
|
||||
var list_unused = new System.Collections.Generic.Dictionary<Math[], IsMathNull>();
|
||||
System.Collections.Generic.Dictionary<Math[], IsMathNull> list_null_unused = null;
|
||||
|
||||
var list_arr_unused = new System.Collections.Generic.Dictionary<Math[], IsMathNull>[] { new System.Collections.Generic.Dictionary<Math[], IsMathNull>() };
|
||||
System.Collections.Generic.Dictionary<Math[], IsMathNull>[] list_arr_null_unused = null;
|
||||
|
||||
OuterMethod();
|
||||
Console.WriteLine("Just a test message, ignore");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool IsMathNullDelegateTarget(Math m) => m == null;
|
||||
|
||||
public static void OuterMethod()
|
||||
{
|
||||
Console.WriteLine($"OuterMethod called");
|
||||
var nim = new Math.NestedInMath();
|
||||
var i = 5;
|
||||
var text = "Hello";
|
||||
var new_i = nim.InnerMethod(i);
|
||||
Console.WriteLine($"i: {i}");
|
||||
Console.WriteLine($"-- InnerMethod returned: {new_i}, nim: {nim}, text: {text}");
|
||||
int k = 19;
|
||||
new_i = InnerMethod2("test string", new_i, out k);
|
||||
Console.WriteLine($"-- InnerMethod2 returned: {new_i}, and k: {k}");
|
||||
}
|
||||
|
||||
static int InnerMethod2(string s, int i, out int k)
|
||||
{
|
||||
k = i + 10;
|
||||
Console.WriteLine($"s: {s}, i: {i}, k: {k}");
|
||||
return i - 2;
|
||||
}
|
||||
|
||||
public class NestedInMath
|
||||
{
|
||||
public int InnerMethod(int i)
|
||||
{
|
||||
SimpleStructProperty = new SimpleStruct() { dt = new DateTime(2020, 1, 2, 3, 4, 5) };
|
||||
int j = i + 10;
|
||||
string foo_str = "foo";
|
||||
Console.WriteLine($"i: {i} and j: {j}, foo_str: {foo_str} ");
|
||||
j += 9;
|
||||
Console.WriteLine($"i: {i} and j: {j}");
|
||||
return j;
|
||||
}
|
||||
|
||||
Math m = new Math();
|
||||
public async System.Threading.Tasks.Task<bool> AsyncMethod0(string s, int i)
|
||||
{
|
||||
string local0 = "value0";
|
||||
await System.Threading.Tasks.Task.Delay(1);
|
||||
Console.WriteLine($"* time for the second await, local0: {local0}");
|
||||
await AsyncMethodNoReturn();
|
||||
return true;
|
||||
}
|
||||
|
||||
public async System.Threading.Tasks.Task AsyncMethodNoReturn()
|
||||
{
|
||||
var ss = new SimpleStruct() { dt = new DateTime(2020, 1, 2, 3, 4, 5) };
|
||||
var ss_arr = new SimpleStruct[] { };
|
||||
//ss.gs.StringField = "field in GenericStruct";
|
||||
|
||||
//Console.WriteLine ($"Using the struct: {ss.dt}, {ss.gs.StringField}, ss_arr: {ss_arr.Length}");
|
||||
string str = "AsyncMethodNoReturn's local";
|
||||
//Console.WriteLine ($"* field m: {m}");
|
||||
await System.Threading.Tasks.Task.Delay(1);
|
||||
Console.WriteLine($"str: {str}");
|
||||
}
|
||||
|
||||
public static async System.Threading.Tasks.Task<bool> AsyncTest(string s, int i)
|
||||
{
|
||||
var li = 10 + i;
|
||||
var ls = s + "test";
|
||||
return await new NestedInMath().AsyncMethod0(s, i);
|
||||
}
|
||||
|
||||
public SimpleStruct SimpleStructProperty { get; set; }
|
||||
}
|
||||
|
||||
public static void PrimitiveTypesTest()
|
||||
{
|
||||
char c0 = '€';
|
||||
char c1 = 'A';
|
||||
// TODO: other types!
|
||||
// just trying to ensure vars don't get optimized out
|
||||
if (c0 < 32 || c1 > 32)
|
||||
Console.WriteLine($"{c0}, {c1}");
|
||||
}
|
||||
|
||||
public static int DelegatesSignatureTest()
|
||||
{
|
||||
Func<Math, GenericStruct<GenericStruct<int[]>>, GenericStruct<bool[]>> fn_func = (m, gs) => new GenericStruct<bool[]>();
|
||||
Func<Math, GenericStruct<GenericStruct<int[]>>, GenericStruct<bool[]>> fn_func_del = GenericStruct<int>.DelegateTargetForSignatureTest;
|
||||
Func<Math, GenericStruct<GenericStruct<int[]>>, GenericStruct<bool[]>> fn_func_null = null;
|
||||
Func<bool> fn_func_only_ret = () => { Console.WriteLine ($"hello"); return true; };
|
||||
var fn_func_arr = new Func<Math, GenericStruct<GenericStruct<int[]>>, GenericStruct<bool[]>>[] {
|
||||
(m, gs) => new GenericStruct<bool[]> () };
|
||||
|
||||
Math.DelegateForSignatureTest fn_del = GenericStruct<int>.DelegateTargetForSignatureTest;
|
||||
Math.DelegateForSignatureTest fn_del_l = (m, gs) => new GenericStruct<bool[]> { StringField = "fn_del_l#lambda" };
|
||||
var fn_del_arr = new Math.DelegateForSignatureTest[] { GenericStruct<int>.DelegateTargetForSignatureTest, (m, gs) => new GenericStruct<bool[]> { StringField = "fn_del_arr#1#lambda" } };
|
||||
var m_obj = new Math();
|
||||
Math.DelegateForSignatureTest fn_del_null = null;
|
||||
var gs_gs = new GenericStruct<GenericStruct<int[]>>
|
||||
{
|
||||
List = new System.Collections.Generic.List<GenericStruct<int[]>>
|
||||
{
|
||||
new GenericStruct<int[]> { StringField = "gs#List#0#StringField" },
|
||||
new GenericStruct<int[]> { StringField = "gs#List#1#StringField" }
|
||||
}
|
||||
};
|
||||
|
||||
Math.DelegateWithVoidReturn fn_void_del = Math.DelegateTargetWithVoidReturn;
|
||||
var fn_void_del_arr = new Math.DelegateWithVoidReturn[] { Math.DelegateTargetWithVoidReturn };
|
||||
Math.DelegateWithVoidReturn fn_void_del_null = null;
|
||||
|
||||
var rets = new GenericStruct<bool[]>[]
|
||||
{
|
||||
fn_func(m_obj, gs_gs),
|
||||
fn_func_del(m_obj, gs_gs),
|
||||
fn_del(m_obj, gs_gs),
|
||||
fn_del_l(m_obj, gs_gs),
|
||||
fn_del_arr[0](m_obj, gs_gs),
|
||||
fn_func_arr[0](m_obj, gs_gs)
|
||||
};
|
||||
|
||||
var gs = new GenericStruct<int[]>();
|
||||
fn_void_del(gs);
|
||||
fn_void_del_arr[0](gs);
|
||||
fn_func_only_ret();
|
||||
foreach (var ret in rets) Console.WriteLine($"ret: {ret}");
|
||||
OuterMethod();
|
||||
Console.WriteLine($"- {gs_gs.List[0].StringField}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int ActionTSignatureTest()
|
||||
{
|
||||
Action<GenericStruct<int[]>> fn_action = (_) => { };
|
||||
Action<GenericStruct<int[]>> fn_action_del = Math.DelegateTargetWithVoidReturn;
|
||||
Action fn_action_bare = () => { };
|
||||
Action<GenericStruct<int[]>> fn_action_null = null;
|
||||
var fn_action_arr = new Action<GenericStruct<int[]>>[]
|
||||
{
|
||||
(gs) => new GenericStruct<int[]>(),
|
||||
Math.DelegateTargetWithVoidReturn,
|
||||
null
|
||||
};
|
||||
|
||||
var gs = new GenericStruct<int[]>();
|
||||
fn_action(gs);
|
||||
fn_action_del(gs);
|
||||
fn_action_arr[0](gs);
|
||||
fn_action_bare();
|
||||
OuterMethod();
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int NestedDelegatesTest()
|
||||
{
|
||||
Func<Func<int, bool>, bool> fn_func = (_) => { return true; };
|
||||
Func<Func<int, bool>, bool> fn_func_null = null;
|
||||
var fn_func_arr = new Func<Func<int, bool>, bool>[] {
|
||||
(gs) => { return true; } };
|
||||
|
||||
var fn_del_arr = new Func<Func<int, bool>, bool>[] { DelegateTargetForNestedFunc<Func<int, bool>> };
|
||||
var m_obj = new Math();
|
||||
Func<Func<int, bool>, bool> fn_del_null = null;
|
||||
Func<int, bool> fs = (i) => i == 0;
|
||||
fn_func(fs);
|
||||
fn_del_arr[0](fs);
|
||||
fn_func_arr[0](fs);
|
||||
OuterMethod();
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static void DelegatesAsMethodArgsTest()
|
||||
{
|
||||
var _dst_arr = new DelegateForSignatureTest[]
|
||||
{
|
||||
GenericStruct<int>.DelegateTargetForSignatureTest,
|
||||
(m, gs) => new GenericStruct<bool[]>()
|
||||
};
|
||||
Func<char[], bool> _fn_func = (cs) => cs.Length == 0;
|
||||
Action<GenericStruct<int>[]> _fn_action = (gss) => { };
|
||||
|
||||
new Math().MethodWithDelegateArgs(_dst_arr, _fn_func, _fn_action);
|
||||
}
|
||||
|
||||
void MethodWithDelegateArgs(Math.DelegateForSignatureTest[] dst_arr, Func<char[], bool> fn_func,
|
||||
Action<GenericStruct<int>[]> fn_action)
|
||||
{
|
||||
Console.WriteLine($"Placeholder for breakpoint");
|
||||
OuterMethod();
|
||||
}
|
||||
|
||||
public static async System.Threading.Tasks.Task MethodWithDelegatesAsyncTest()
|
||||
{
|
||||
await new Math().MethodWithDelegatesAsync();
|
||||
}
|
||||
|
||||
async System.Threading.Tasks.Task MethodWithDelegatesAsync()
|
||||
{
|
||||
var _dst_arr = new DelegateForSignatureTest[]
|
||||
{
|
||||
GenericStruct<int>.DelegateTargetForSignatureTest,
|
||||
(m, gs) => new GenericStruct<bool[]>()
|
||||
};
|
||||
Func<char[], bool> _fn_func = (cs) => cs.Length == 0;
|
||||
Action<GenericStruct<int>[]> _fn_action = (gss) => { };
|
||||
|
||||
Console.WriteLine($"Placeholder for breakpoint");
|
||||
await System.Threading.Tasks.Task.CompletedTask;
|
||||
}
|
||||
|
||||
public delegate void DelegateWithVoidReturn(GenericStruct<int[]> gs);
|
||||
public static void DelegateTargetWithVoidReturn(GenericStruct<int[]> gs) { }
|
||||
|
||||
delegate GenericStruct<bool[]> DelegateForSignatureTest(Math m, GenericStruct<GenericStruct<int[]>> gs);
|
||||
static bool DelegateTargetForNestedFunc<T>(T arg) => true;
|
||||
|
||||
public struct SimpleStruct
|
||||
{
|
||||
public DateTime dt;
|
||||
public GenericStruct<DateTime> gs;
|
||||
}
|
||||
|
||||
public struct GenericStruct<T>
|
||||
{
|
||||
public System.Collections.Generic.List<T> List;
|
||||
public string StringField;
|
||||
|
||||
public static GenericStruct<bool[]> DelegateTargetForSignatureTest(Math m, GenericStruct<GenericStruct<T[]>> gs) => new GenericStruct<bool[]>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public 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() { }
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk" DefaultTargets="BuildApp">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
|
||||
<TargetArchitecture>wasm</TargetArchitecture>
|
||||
<TargetOS>Browser</TargetOS>
|
||||
<MicrosoftNetCoreAppRuntimePackDir>$(ArtifactsBinDir)microsoft.netcore.app.runtime.browser-wasm\Release\runtimes\browser-wasm\</MicrosoftNetCoreAppRuntimePackDir>
|
||||
<BuildDir>$(MSBuildThisFileDirectory)obj\$(Configuration)\wasm</BuildDir>
|
||||
<AppDir>$(MSBuildThisFileDirectory)bin\$(Configuration)\publish</AppDir>
|
||||
<RuntimeBuildConfig Condition="'$(RuntimeBuildConfig)' == ''">$(Configuration)</RuntimeBuildConfig>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<OutputType>Library</OutputType>
|
||||
<NoWarn>219</NoWarn>
|
||||
<DebugType>portable</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="RebuildWasmAppBuilder">
|
||||
<ItemGroup>
|
||||
<WasmAppBuildProject Include="$(RepoTasksDir)mobile.tasks\WasmAppBuilder\WasmAppBuilder.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<MSBuild Projects="@(WasmAppBuildProject)"
|
||||
Properties="Configuration=$(Configuration);MSBuildRestoreSessionId=$([System.Guid]::NewGuid())"
|
||||
Targets="Restore"/>
|
||||
|
||||
<MSBuild Projects="@(WasmAppBuildProject)"
|
||||
Properties="Configuration=$(Configuration)"
|
||||
Targets="Build;Publish"/>
|
||||
</Target>
|
||||
|
||||
<UsingTask TaskName="WasmAppBuilder"
|
||||
AssemblyFile="$(ArtifactsBinDir)WasmAppBuilder\$(Configuration)\$(NetCoreAppCurrent)\publish\WasmAppBuilder.dll"/>
|
||||
|
||||
<Target Name="BuildApp" DependsOnTargets="RebuildWasmAppBuilder;Build">
|
||||
<ItemGroup>
|
||||
<AssemblySearchPaths Include="$(MicrosoftNetCoreAppRuntimePackDir)native"/>
|
||||
<AssemblySearchPaths Include="$(MicrosoftNetCoreAppRuntimePackDir)lib\$(NetCoreAppCurrent)"/>
|
||||
</ItemGroup>
|
||||
<WasmAppBuilder
|
||||
AppDir="$(AppDir)"
|
||||
MicrosoftNetCoreAppRuntimePackDir="$(MicrosoftNetCoreAppRuntimePackDir)"
|
||||
MainAssembly="$(ArtifactsBinDir)debugger-test/wasm/Debug/debugger-test.dll"
|
||||
MainJS="$(MonoProjectRoot)wasm\runtime-test.js"
|
||||
DebugLevel="1"
|
||||
AssemblySearchPaths="@(AssemblySearchPaths)"
|
||||
ExtraAssemblies="$(ArtifactsBinDir)\System.Runtime.InteropServices.JavaScript\$(NetCoreAppCurrent)-Browser-$(RuntimeConfiguration)\System.Runtime.InteropServices.JavaScript.dll"/>
|
||||
<Exec Command="chmod a+x $(AppDir)/run-v8.sh" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,51 @@
|
|||
// 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 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;
|
||||
}
|
||||
}
|
|
@ -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.Threading.Tasks;
|
||||
namespace DebuggerTests
|
||||
{
|
||||
public class ValueTypesTest
|
||||
{ //Only append content to this class as the test suite depends on line info
|
||||
|
||||
public static void MethodWithLocalStructs()
|
||||
{
|
||||
var ss_local = new SimpleStruct("set in MethodWithLocalStructs", 1, DateTimeKind.Utc);
|
||||
var gs_local = new GenericStruct<ValueTypesTest> { StringField = "gs_local#GenericStruct<ValueTypesTest>#StringField" };
|
||||
|
||||
ValueTypesTest vt_local = new ValueTypesTest
|
||||
{
|
||||
StringField = "string#0",
|
||||
SimpleStructField = new SimpleStruct("SimpleStructField#string#0", 5, DateTimeKind.Local),
|
||||
SimpleStructProperty = new SimpleStruct("SimpleStructProperty#string#0", 2, DateTimeKind.Utc), DT = new DateTime(2020, 1, 2, 3, 4, 5), RGB = RGB.Blue
|
||||
};
|
||||
Console.WriteLine($"Using the struct: {ss_local.gs.StringField}, gs: {gs_local.StringField}, {vt_local.StringField}");
|
||||
}
|
||||
|
||||
public static void TestStructsAsMethodArgs()
|
||||
{
|
||||
var ss_local = new SimpleStruct("ss_local#SimpleStruct#string#0", 5, DateTimeKind.Local);
|
||||
var ss_ret = MethodWithStructArgs("TestStructsAsMethodArgs#label", ss_local, 3);
|
||||
Console.WriteLine($"got back ss_local: {ss_local.gs.StringField}, ss_ret: {ss_ret.gs.StringField}");
|
||||
}
|
||||
|
||||
static SimpleStruct MethodWithStructArgs(string label, SimpleStruct ss_arg, int x)
|
||||
{
|
||||
Console.WriteLine($"- ss_arg: {ss_arg.str_member}");
|
||||
ss_arg.Kind = DateTimeKind.Utc;
|
||||
ss_arg.str_member = $"ValueTypesTest#MethodWithStructArgs#updated#ss_arg#str_member";
|
||||
ss_arg.gs.StringField = $"ValueTypesTest#MethodWithStructArgs#updated#gs#StringField#{x}";
|
||||
return ss_arg;
|
||||
}
|
||||
|
||||
public static async Task<bool> MethodWithLocalStructsStaticAsync()
|
||||
{
|
||||
var ss_local = new SimpleStruct("set in MethodWithLocalStructsStaticAsync", 1, DateTimeKind.Utc);
|
||||
var gs_local = new GenericStruct<int>
|
||||
{
|
||||
StringField = "gs_local#GenericStruct<ValueTypesTest>#StringField",
|
||||
List = new System.Collections.Generic.List<int> { 5, 3 },
|
||||
Options = Options.Option2
|
||||
|
||||
};
|
||||
|
||||
var result = await ss_local.AsyncMethodWithStructArgs(gs_local);
|
||||
Console.WriteLine($"Using the struct: {ss_local.gs.StringField}, result: {result}");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public string StringField;
|
||||
public SimpleStruct SimpleStructProperty { get; set; }
|
||||
public SimpleStruct SimpleStructField;
|
||||
|
||||
public struct SimpleStruct
|
||||
{
|
||||
public string str_member;
|
||||
public DateTime dt;
|
||||
public GenericStruct<DateTime> gs;
|
||||
public DateTimeKind Kind;
|
||||
|
||||
public SimpleStruct(string str, int f, DateTimeKind kind)
|
||||
{
|
||||
str_member = $"{str}#SimpleStruct#str_member";
|
||||
dt = new DateTime(2020 + f, 1 + f, 2 + f, 3 + f, 5 + f, 6 + f);
|
||||
gs = new GenericStruct<DateTime>
|
||||
{
|
||||
StringField = $"{str}#SimpleStruct#gs#StringField",
|
||||
List = new System.Collections.Generic.List<DateTime> { new DateTime(2010 + f, 2 + f, 3 + f, 10 + f, 2 + f, 3 + f) },
|
||||
Options = Options.Option1
|
||||
};
|
||||
Kind = kind;
|
||||
}
|
||||
|
||||
public Task<bool> AsyncMethodWithStructArgs(GenericStruct<int> gs)
|
||||
{
|
||||
Console.WriteLine($"placeholder line for a breakpoint");
|
||||
if (gs.List.Count > 0)
|
||||
return Task.FromResult(true);
|
||||
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
}
|
||||
|
||||
public struct GenericStruct<T>
|
||||
{
|
||||
public System.Collections.Generic.List<T> List;
|
||||
public string StringField;
|
||||
|
||||
public Options Options { get; set; }
|
||||
}
|
||||
|
||||
public DateTime DT { get; set; }
|
||||
public RGB RGB;
|
||||
|
||||
public static void MethodWithLocalsForToStringTest(bool call_other)
|
||||
{
|
||||
var dt0 = new DateTime(2020, 1, 2, 3, 4, 5);
|
||||
var dt1 = new DateTime(2010, 5, 4, 3, 2, 1);
|
||||
var ts = dt0 - dt1;
|
||||
var dto = new DateTimeOffset(dt0, new TimeSpan(4, 5, 0));
|
||||
decimal dec = 123987123;
|
||||
var guid = new Guid("3d36e07e-ac90-48c6-b7ec-a481e289d014");
|
||||
|
||||
var dts = new DateTime[]
|
||||
{
|
||||
new DateTime(1983, 6, 7, 5, 6, 10),
|
||||
new DateTime(1999, 10, 15, 1, 2, 3)
|
||||
};
|
||||
|
||||
var obj = new ClassForToStringTests
|
||||
{
|
||||
DT = new DateTime(2004, 10, 15, 1, 2, 3),
|
||||
DTO = new DateTimeOffset(dt0, new TimeSpan(2, 14, 0)),
|
||||
TS = ts,
|
||||
Dec = 1239871,
|
||||
Guid = guid
|
||||
};
|
||||
|
||||
var sst = new StructForToStringTests
|
||||
{
|
||||
DT = new DateTime(2004, 10, 15, 1, 2, 3),
|
||||
DTO = new DateTimeOffset(dt0, new TimeSpan(3, 15, 0)),
|
||||
TS = ts,
|
||||
Dec = 1239871,
|
||||
Guid = guid
|
||||
};
|
||||
Console.WriteLine($"MethodWithLocalsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}");
|
||||
if (call_other)
|
||||
MethodWithArgumentsForToStringTest(call_other, dt0, dt1, ts, dto, dec, guid, dts, obj, sst);
|
||||
}
|
||||
|
||||
static void MethodWithArgumentsForToStringTest(
|
||||
bool call_other, // not really used, just to help with using common code in the tests
|
||||
DateTime dt0, DateTime dt1, TimeSpan ts, DateTimeOffset dto, decimal dec,
|
||||
Guid guid, DateTime[] dts, ClassForToStringTests obj, StructForToStringTests sst)
|
||||
{
|
||||
Console.WriteLine($"MethodWithArgumentsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}");
|
||||
}
|
||||
|
||||
public static async Task MethodWithLocalsForToStringTestAsync(bool call_other)
|
||||
{
|
||||
var dt0 = new DateTime(2020, 1, 2, 3, 4, 5);
|
||||
var dt1 = new DateTime(2010, 5, 4, 3, 2, 1);
|
||||
var ts = dt0 - dt1;
|
||||
var dto = new DateTimeOffset(dt0, new TimeSpan(4, 5, 0));
|
||||
decimal dec = 123987123;
|
||||
var guid = new Guid("3d36e07e-ac90-48c6-b7ec-a481e289d014");
|
||||
|
||||
var dts = new DateTime[]
|
||||
{
|
||||
new DateTime(1983, 6, 7, 5, 6, 10),
|
||||
new DateTime(1999, 10, 15, 1, 2, 3)
|
||||
};
|
||||
|
||||
var obj = new ClassForToStringTests
|
||||
{
|
||||
DT = new DateTime(2004, 10, 15, 1, 2, 3),
|
||||
DTO = new DateTimeOffset(dt0, new TimeSpan(2, 14, 0)),
|
||||
TS = ts,
|
||||
Dec = 1239871,
|
||||
Guid = guid
|
||||
};
|
||||
|
||||
var sst = new StructForToStringTests
|
||||
{
|
||||
DT = new DateTime(2004, 10, 15, 1, 2, 3),
|
||||
DTO = new DateTimeOffset(dt0, new TimeSpan(3, 15, 0)),
|
||||
TS = ts,
|
||||
Dec = 1239871,
|
||||
Guid = guid
|
||||
};
|
||||
Console.WriteLine($"MethodWithLocalsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}");
|
||||
if (call_other)
|
||||
await MethodWithArgumentsForToStringTestAsync(call_other, dt0, dt1, ts, dto, dec, guid, dts, obj, sst);
|
||||
}
|
||||
|
||||
static async Task MethodWithArgumentsForToStringTestAsync(
|
||||
bool call_other, // not really used, just to help with using common code in the tests
|
||||
DateTime dt0, DateTime dt1, TimeSpan ts, DateTimeOffset dto, decimal dec,
|
||||
Guid guid, DateTime[] dts, ClassForToStringTests obj, StructForToStringTests sst)
|
||||
{
|
||||
Console.WriteLine($"MethodWithArgumentsForToStringTest: {dt0}, {dt1}, {ts}, {dec}, {guid}, {dts[0]}, {obj.DT}, {sst.DT}");
|
||||
}
|
||||
|
||||
public static void MethodUpdatingValueTypeMembers()
|
||||
{
|
||||
var obj = new ClassForToStringTests
|
||||
{
|
||||
DT = new DateTime(1, 2, 3, 4, 5, 6)
|
||||
};
|
||||
var vt = new StructForToStringTests
|
||||
{
|
||||
DT = new DateTime(4, 5, 6, 7, 8, 9)
|
||||
};
|
||||
Console.WriteLine($"#1");
|
||||
obj.DT = new DateTime(9, 8, 7, 6, 5, 4);
|
||||
vt.DT = new DateTime(5, 1, 3, 7, 9, 10);
|
||||
Console.WriteLine($"#2");
|
||||
}
|
||||
|
||||
public static async Task MethodUpdatingValueTypeLocalsAsync()
|
||||
{
|
||||
var dt = new DateTime(1, 2, 3, 4, 5, 6);
|
||||
Console.WriteLine($"#1");
|
||||
dt = new DateTime(9, 8, 7, 6, 5, 4);
|
||||
Console.WriteLine($"#2");
|
||||
}
|
||||
|
||||
public static void MethodUpdatingVTArrayMembers()
|
||||
{
|
||||
var ssta = new []
|
||||
{
|
||||
new StructForToStringTests { DT = new DateTime(1, 2, 3, 4, 5, 6) }
|
||||
};
|
||||
Console.WriteLine($"#1");
|
||||
ssta[0].DT = new DateTime(9, 8, 7, 6, 5, 4);
|
||||
Console.WriteLine($"#2");
|
||||
}
|
||||
}
|
||||
|
||||
class ClassForToStringTests
|
||||
{
|
||||
public DateTime DT;
|
||||
public DateTimeOffset DTO;
|
||||
public TimeSpan TS;
|
||||
public decimal Dec;
|
||||
public Guid Guid;
|
||||
}
|
||||
|
||||
struct StructForToStringTests
|
||||
{
|
||||
public DateTime DT;
|
||||
public DateTimeOffset DTO;
|
||||
public TimeSpan TS;
|
||||
public decimal Dec;
|
||||
public Guid Guid;
|
||||
}
|
||||
|
||||
public enum RGB
|
||||
{
|
||||
Red,
|
||||
Green,
|
||||
Blue
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum Options
|
||||
{
|
||||
None = 0,
|
||||
Option1 = 1,
|
||||
Option2 = 2,
|
||||
Option3 = 4,
|
||||
|
||||
All = Option1 | Option3
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// 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 ++) {
|
||||
big[i]=i + 1000;
|
||||
}
|
||||
console.log('break here');
|
||||
};
|
||||
|
||||
function object_js_test () {
|
||||
var obj = {
|
||||
a_obj: { aa: 5, ab: 'foo' },
|
||||
b_arr: [ 10, 12 ]
|
||||
};
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
function getters_js_test () {
|
||||
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: "string field value"
|
||||
};
|
||||
console.log (`break here`);
|
||||
return ptd;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
var Module = {
|
||||
onRuntimeInitialized: function () {
|
||||
config.loaded_cb = function () {
|
||||
App.init ();
|
||||
};
|
||||
MONO.mono_load_runtime_and_bcl_args (config)
|
||||
},
|
||||
};
|
|
@ -73,6 +73,12 @@ var MonoSupportLib = {
|
|||
},
|
||||
},
|
||||
|
||||
mono_wasm_get_exception_object: function() {
|
||||
var exception_obj = MONO.active_exception;
|
||||
MONO.active_exception = null;
|
||||
return exception_obj ;
|
||||
},
|
||||
|
||||
mono_wasm_get_call_stack: function() {
|
||||
if (!this.mono_wasm_current_bp_id)
|
||||
this.mono_wasm_current_bp_id = Module.cwrap ("mono_wasm_current_bp_id", 'number');
|
||||
|
@ -114,8 +120,10 @@ var MonoSupportLib = {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (i + 1 < var_list.length)
|
||||
o.value = _fixup_value(var_list[i + 1].value);
|
||||
if (i + 1 < var_list.length) {
|
||||
_fixup_value(var_list[i + 1].value);
|
||||
o = Object.assign (o, var_list [i + 1]);
|
||||
}
|
||||
|
||||
out_list.push (o);
|
||||
i += 2;
|
||||
|
@ -145,6 +153,25 @@ var MonoSupportLib = {
|
|||
return final_var_list;
|
||||
},
|
||||
|
||||
// Given `dotnet:object:foo:bar`,
|
||||
// returns [ 'dotnet', 'object', 'foo:bar']
|
||||
_split_object_id: function (id, delimiter = ':', count = 3) {
|
||||
if (id === undefined || id == "")
|
||||
return [];
|
||||
|
||||
if (delimiter === undefined) delimiter = ':';
|
||||
if (count === undefined) count = 3;
|
||||
|
||||
var var_arr = id.split (delimiter);
|
||||
var result = var_arr.splice (0, count - 1);
|
||||
|
||||
if (var_arr.length > 0)
|
||||
result.push (var_arr.join (delimiter));
|
||||
return result;
|
||||
},
|
||||
|
||||
//
|
||||
// @var_list: [ { index: <var_id>, name: <var_name> }, .. ]
|
||||
mono_wasm_get_variables: function(scope, var_list) {
|
||||
if (!this.mono_wasm_get_var_info)
|
||||
this.mono_wasm_get_var_info = Module.cwrap ("mono_wasm_get_var_info", null, [ 'number', 'number', 'number']);
|
||||
|
@ -153,26 +180,34 @@ var MonoSupportLib = {
|
|||
var numBytes = var_list.length * Int32Array.BYTES_PER_ELEMENT;
|
||||
var ptr = Module._malloc(numBytes);
|
||||
var heapBytes = new Int32Array(Module.HEAP32.buffer, ptr, numBytes);
|
||||
for (let i=0; i<var_list.length; i++)
|
||||
heapBytes[i] = var_list[i];
|
||||
for (let i=0; i<var_list.length; i++) {
|
||||
heapBytes[i] = var_list[i].index;
|
||||
}
|
||||
|
||||
this._async_method_objectId = 0;
|
||||
this.mono_wasm_get_var_info (scope, heapBytes.byteOffset, var_list.length);
|
||||
Module._free(heapBytes.byteOffset);
|
||||
var res = MONO._fixup_name_value_objects (this.var_info);
|
||||
|
||||
//Async methods are special in the way that local variables can be lifted to generated class fields
|
||||
//value of "this" comes here either
|
||||
for (let i in res) {
|
||||
var name = res [i].name;
|
||||
if (name != undefined && name.indexOf ('>') > 0)
|
||||
res [i].name = name.substring (1, name.indexOf ('>'));
|
||||
}
|
||||
var res_name = res [i].name;
|
||||
|
||||
if (this._async_method_objectId != 0) {
|
||||
for (let i in res) {
|
||||
if (res [i].value.isValueType != undefined && res [i].value.isValueType)
|
||||
res [i].value.objectId = `dotnet:valuetype:${this._async_method_objectId}:${res [i].fieldOffset}`;
|
||||
var value = res[i].value;
|
||||
if (this._async_method_objectId != 0) {
|
||||
//Async methods are special in the way that local variables can be lifted to generated class fields
|
||||
//value of "this" comes here either
|
||||
if (res_name !== undefined && res_name.indexOf ('>') > 0) {
|
||||
// For async methods, we get the names too, so use that
|
||||
// ALTHOUGH, the name wouldn't have `<>` for method args
|
||||
res [i].name = res_name.substring (1, res_name.indexOf ('>'));
|
||||
}
|
||||
|
||||
if (value.isValueType)
|
||||
value.objectId = `dotnet:valuetype:${this._async_method_objectId}:${res [i].fieldOffset}`;
|
||||
} else if (res_name === undefined && var_list [i] !== undefined) {
|
||||
// For non-async methods, we just have the var id, but we have the name
|
||||
// from the caller
|
||||
res [i].name = var_list [i].name;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,6 +217,7 @@ var MonoSupportLib = {
|
|||
return res;
|
||||
},
|
||||
|
||||
|
||||
mono_wasm_get_object_properties: function(objId, expandValueTypes) {
|
||||
if (!this.mono_wasm_get_object_properties_info)
|
||||
this.mono_wasm_get_object_properties_info = Module.cwrap ("mono_wasm_get_object_properties", null, [ 'number', 'bool' ]);
|
||||
|
@ -191,8 +227,10 @@ var MonoSupportLib = {
|
|||
|
||||
var res = MONO._filter_automatic_properties (MONO._fixup_name_value_objects (this.var_info));
|
||||
for (var i = 0; i < res.length; i++) {
|
||||
if (res [i].value.isValueType != undefined && res [i].value.isValueType)
|
||||
res [i].value.objectId = `dotnet:valuetype:${objId}:${res [i].fieldOffset}`;
|
||||
var res_val = res [i].value;
|
||||
// we might not have a `.value`, like in case of getters which have a `.get` instead
|
||||
if (res_val !== undefined && res_val.isValueType != undefined && res_val.isValueType)
|
||||
res_val.objectId = `dotnet:valuetype:${objId}:${res [i].fieldOffset}`;
|
||||
}
|
||||
|
||||
this.var_info = [];
|
||||
|
@ -209,8 +247,14 @@ var MonoSupportLib = {
|
|||
|
||||
var res = MONO._fixup_name_value_objects (this.var_info);
|
||||
for (var i = 0; i < res.length; i++) {
|
||||
if (res [i].value.isValueType != undefined && res [i].value.isValueType)
|
||||
var prop_value = res [i].value;
|
||||
if (prop_value.isValueType) {
|
||||
res [i].value.objectId = `dotnet:array:${objId}:${i}`;
|
||||
} else if (prop_value.objectId !== undefined && prop_value.objectId.startsWith("dotnet:pointer")) {
|
||||
prop_value.objectId = this._get_updated_ptr_id (prop_value.objectId, {
|
||||
varName: `[${i}]`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.var_info = [];
|
||||
|
@ -255,12 +299,26 @@ var MonoSupportLib = {
|
|||
|
||||
for (let i in var_list) {
|
||||
var value = var_list [i].value;
|
||||
if (value == undefined || value.type != "object")
|
||||
if (value === undefined)
|
||||
continue;
|
||||
|
||||
if (value.isValueType != true || value.expanded != true) // undefined would also give us false
|
||||
if (value.objectId !== undefined && value.objectId.startsWith ("dotnet:pointer:")) {
|
||||
var ptr_args = this._get_ptr_args (value.objectId);
|
||||
if (ptr_args.varName === undefined) {
|
||||
// It might have been already set in some cases, like arrays
|
||||
// where the name would be `0`, but we want `[0]` for pointers,
|
||||
// so the deref would look like `*[0]`
|
||||
value.objectId = this._get_updated_ptr_id (value.objectId, {
|
||||
varName: var_list [i].name
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (value.type != "object" || value.isValueType != true || value.expanded != true) // undefined would also give us false
|
||||
continue;
|
||||
|
||||
// Generate objectId for expanded valuetypes
|
||||
|
||||
var objectId = value.objectId;
|
||||
if (objectId == undefined)
|
||||
objectId = `dotnet:valuetype:${this._next_value_type_id ()}`;
|
||||
|
@ -309,6 +367,104 @@ var MonoSupportLib = {
|
|||
}
|
||||
},
|
||||
|
||||
_get_cfo_res_details: function (objectId, args) {
|
||||
if (!(objectId in this._call_function_res_cache))
|
||||
throw new Error(`Could not find any object with id ${objectId}`);
|
||||
|
||||
var real_obj = this._call_function_res_cache [objectId];
|
||||
|
||||
var descriptors = Object.getOwnPropertyDescriptors (real_obj);
|
||||
if (args.accessorPropertiesOnly) {
|
||||
Object.keys (descriptors).forEach (k => {
|
||||
if (descriptors [k].get === undefined)
|
||||
Reflect.deleteProperty (descriptors, k);
|
||||
});
|
||||
}
|
||||
|
||||
var res_details = [];
|
||||
Object.keys (descriptors).forEach (k => {
|
||||
var new_obj;
|
||||
var prop_desc = descriptors [k];
|
||||
if (typeof prop_desc.value == "object") {
|
||||
// convert `{value: { type='object', ... }}`
|
||||
// to `{ name: 'foo', value: { type='object', ... }}
|
||||
new_obj = Object.assign ({ name: k }, prop_desc);
|
||||
} else if (prop_desc.value !== undefined) {
|
||||
// This is needed for values that were not added by us,
|
||||
// thus are like { value: 5 }
|
||||
// instead of { value: { type = 'number', value: 5 }}
|
||||
//
|
||||
// This can happen, for eg., when `length` gets added for arrays
|
||||
// or `__proto__`.
|
||||
new_obj = {
|
||||
name: k,
|
||||
// merge/add `type` and `description` to `d.value`
|
||||
value: Object.assign ({ type: (typeof prop_desc.value), description: '' + prop_desc.value },
|
||||
prop_desc)
|
||||
};
|
||||
} else if (prop_desc.get !== undefined) {
|
||||
// The real_obj has the actual getter. We are just returning a placeholder
|
||||
// If the caller tries to run function on the cfo_res object,
|
||||
// that accesses this property, then it would be run on `real_obj`,
|
||||
// which *has* the original getter
|
||||
new_obj = {
|
||||
name: k,
|
||||
get: {
|
||||
className: "Function",
|
||||
description: `get ${k} () {}`,
|
||||
type: "function"
|
||||
}
|
||||
};
|
||||
} else {
|
||||
new_obj = { name: k, value: { type: "symbol", value: "<Unknown>", description: "<Unknown>"} };
|
||||
}
|
||||
|
||||
res_details.push (new_obj);
|
||||
});
|
||||
|
||||
return { __value_as_json_string__: JSON.stringify (res_details) };
|
||||
},
|
||||
|
||||
_get_ptr_args: function (objectId) {
|
||||
var parts = this._split_object_id (objectId);
|
||||
if (parts.length != 3)
|
||||
throw new Error (`Bug: Unexpected objectId format for a pointer, expected 3 parts: ${objectId}`);
|
||||
return JSON.parse (parts [2]);
|
||||
},
|
||||
|
||||
_get_updated_ptr_id: function (objectId, new_args) {
|
||||
var old_args = {};
|
||||
if (typeof (objectId) === 'string' && objectId.length)
|
||||
old_args = this._get_ptr_args (objectId);
|
||||
|
||||
return `dotnet:pointer:${JSON.stringify ( Object.assign (old_args, new_args) )}`;
|
||||
},
|
||||
|
||||
_get_deref_ptr_value: function (objectId) {
|
||||
if (!this.mono_wasm_get_deref_ptr_value_info)
|
||||
this.mono_wasm_get_deref_ptr_value_info = Module.cwrap("mono_wasm_get_deref_ptr_value", null, ['number', 'number']);
|
||||
|
||||
var ptr_args = this._get_ptr_args (objectId);
|
||||
if (ptr_args.ptr_addr == 0 || ptr_args.klass_addr == 0)
|
||||
throw new Error (`Both ptr_addr and klass_addr need to be non-zero, to dereference a pointer. objectId: ${objectId}`);
|
||||
|
||||
this.var_info = [];
|
||||
var value_addr = new DataView (Module.HEAPU8.buffer).getUint32 (ptr_args.ptr_addr, /* littleEndian */ true);
|
||||
this.mono_wasm_get_deref_ptr_value_info (value_addr, ptr_args.klass_addr);
|
||||
|
||||
var res = MONO._fixup_name_value_objects(this.var_info);
|
||||
if (res.length > 0) {
|
||||
if (ptr_args.varName === undefined)
|
||||
throw new Error (`Bug: no varName found for the pointer. objectId: ${objectId}`);
|
||||
|
||||
res [0].name = `*${ptr_args.varName}`;
|
||||
}
|
||||
|
||||
res = this._post_process_details (res);
|
||||
this.var_info = [];
|
||||
return res;
|
||||
},
|
||||
|
||||
mono_wasm_get_details: function (objectId, args) {
|
||||
var parts = objectId.split(":");
|
||||
if (parts[0] != "dotnet")
|
||||
|
@ -334,51 +490,11 @@ var MonoSupportLib = {
|
|||
var containerObjectId = parts[2];
|
||||
return this._get_details_for_value_type(objectId, () => this.mono_wasm_get_object_properties(containerObjectId, true));
|
||||
|
||||
case "cfo_res": {
|
||||
if (!(objectId in this._call_function_res_cache))
|
||||
throw new Error(`Could not find any object with id ${objectId}`);
|
||||
case "cfo_res":
|
||||
return this._get_cfo_res_details (objectId, args);
|
||||
|
||||
var real_obj = this._call_function_res_cache [objectId];
|
||||
if (args.accessorPropertiesOnly) {
|
||||
// var val_accessors = JSON.stringify ([
|
||||
// {
|
||||
// name: "__proto__",
|
||||
// get: { type: "function", className: "Function", description: "function get __proto__ () {}", objectId: "dotnet:cfo_res:9999" },
|
||||
// set: { type: "function", className: "Function", description: "function set __proto__ () {}", objectId: "dotnet:cfo_res:8888" },
|
||||
// isOwn: false
|
||||
// }], undefined, 4);
|
||||
return { __value_as_json_string__: "[]" };
|
||||
}
|
||||
|
||||
// behaving as if (args.ownProperties == true)
|
||||
var descriptors = Object.getOwnPropertyDescriptors (real_obj);
|
||||
var own_properties = [];
|
||||
Object.keys (descriptors).forEach (k => {
|
||||
var new_obj;
|
||||
var prop_desc = descriptors [k];
|
||||
if (typeof prop_desc.value == "object") {
|
||||
// convert `{value: { type='object', ... }}`
|
||||
// to `{ name: 'foo', value: { type='object', ... }}
|
||||
new_obj = Object.assign ({ name: k}, prop_desc);
|
||||
} else {
|
||||
// This is needed for values that were not added by us,
|
||||
// thus are like { value: 5 }
|
||||
// instead of { value: { type = 'number', value: 5 }}
|
||||
//
|
||||
// This can happen, for eg., when `length` gets added for arrays
|
||||
// or `__proto__`.
|
||||
new_obj = {
|
||||
name: k,
|
||||
// merge/add `type` and `description` to `d.value`
|
||||
value: Object.assign ({ type: (typeof prop_desc.value), description: '' + prop_desc.value },
|
||||
prop_desc)
|
||||
};
|
||||
}
|
||||
|
||||
own_properties.push (new_obj);
|
||||
});
|
||||
|
||||
return { __value_as_json_string__: JSON.stringify (own_properties) };
|
||||
case "pointer": {
|
||||
return this._get_deref_ptr_value (objectId);
|
||||
}
|
||||
|
||||
default:
|
||||
|
@ -397,39 +513,83 @@ var MonoSupportLib = {
|
|||
delete this._cache_call_function_res[objectId];
|
||||
},
|
||||
|
||||
_invoke_getter_on_object: function (objectId, name) {
|
||||
if (!this.mono_wasm_invoke_getter_on_object)
|
||||
this.mono_wasm_invoke_getter_on_object = Module.cwrap ("mono_wasm_invoke_getter_on_object", 'void', [ 'number', 'string' ]);
|
||||
|
||||
if (objectId < 0) {
|
||||
// invalid id
|
||||
return [];
|
||||
}
|
||||
|
||||
this.mono_wasm_invoke_getter_on_object (objectId, name);
|
||||
var getter_res = MONO._post_process_details (MONO.var_info);
|
||||
|
||||
MONO.var_info = [];
|
||||
return getter_res [0];
|
||||
},
|
||||
|
||||
_create_proxy_from_object_id: function (objectId) {
|
||||
var details = this.mono_wasm_get_details(objectId);
|
||||
|
||||
if (this._is_object_id_array (objectId))
|
||||
return details.map (p => p.value);
|
||||
|
||||
var objIdParts = objectId.split (':');
|
||||
var objIdNum = -1;
|
||||
if (objectId.startsWith ("dotnet:object:"))
|
||||
objIdNum = objIdParts [2];
|
||||
|
||||
var proxy = {};
|
||||
Object.keys (details).forEach (p => {
|
||||
var prop = details [p];
|
||||
if (prop.get !== undefined) {
|
||||
// TODO: `set`
|
||||
|
||||
// We don't add a `get` for non-object types right now,
|
||||
// so, we shouldn't get here with objIdNum==-1
|
||||
Object.defineProperty (proxy,
|
||||
prop.name,
|
||||
{ get () { return MONO._invoke_getter_on_object (objIdNum, prop.name); } }
|
||||
);
|
||||
} else {
|
||||
proxy [prop.name] = prop.value;
|
||||
}
|
||||
});
|
||||
|
||||
return proxy;
|
||||
},
|
||||
|
||||
mono_wasm_call_function_on: function (request) {
|
||||
if (request.arguments != undefined && !Array.isArray (request.arguments))
|
||||
throw new Error (`"arguments" should be an array, but was ${request.arguments}`);
|
||||
|
||||
var objId = request.objectId;
|
||||
var proxy;
|
||||
|
||||
if (objId in this._call_function_res_cache) {
|
||||
proxy = this._call_function_res_cache [objId];
|
||||
} else if (!objId.startsWith ('dotnet:cfo_res:')) {
|
||||
var details = this.mono_wasm_get_details(objId);
|
||||
var target_is_array = this._is_object_id_array (objId);
|
||||
proxy = target_is_array ? [] : {};
|
||||
|
||||
Object.keys(details).forEach(p => {
|
||||
var prop = details[p];
|
||||
if (target_is_array) {
|
||||
proxy.push(prop.value);
|
||||
} else {
|
||||
if (prop.name != undefined)
|
||||
proxy [prop.name] = prop.value;
|
||||
else // when can this happen??
|
||||
proxy[''+p] = prop.value;
|
||||
}
|
||||
});
|
||||
proxy = this._create_proxy_from_object_id (objId);
|
||||
}
|
||||
|
||||
var fn_args = request.arguments != undefined ? request.arguments.map(a => a.value) : [];
|
||||
var fn_args = request.arguments != undefined ? request.arguments.map(a => JSON.stringify(a.value)) : [];
|
||||
var fn_eval_str = `var fn = ${request.functionDeclaration}; fn.call (proxy, ...[${fn_args}]);`;
|
||||
|
||||
var fn_res = eval (fn_eval_str);
|
||||
if (request.returnByValue)
|
||||
if (fn_res == undefined) // should we just return undefined?
|
||||
throw Error ('Function returned undefined result');
|
||||
|
||||
// primitive type
|
||||
if (Object (fn_res) !== fn_res)
|
||||
return fn_res;
|
||||
|
||||
if (fn_res == undefined)
|
||||
throw Error ('Function returned undefined result');
|
||||
// return .value, if it is a primitive type
|
||||
if (fn_res.value !== undefined && Object (fn_res.value.value) !== fn_res.value.value)
|
||||
return fn_res.value;
|
||||
|
||||
if (request.returnByValue)
|
||||
return {type: "object", value: fn_res};
|
||||
|
||||
var fn_res_id = this._cache_call_function_res (fn_res);
|
||||
if (Object.getPrototypeOf (fn_res) == Array.prototype) {
|
||||
|
@ -445,24 +605,46 @@ var MonoSupportLib = {
|
|||
}
|
||||
},
|
||||
|
||||
_clear_per_step_state: function () {
|
||||
this._next_value_type_id_var = 0;
|
||||
this._value_types_cache = {};
|
||||
},
|
||||
|
||||
mono_wasm_debugger_resume: function () {
|
||||
this._clear_per_step_state ();
|
||||
},
|
||||
|
||||
mono_wasm_start_single_stepping: function (kind) {
|
||||
console.log (">> mono_wasm_start_single_stepping " + kind);
|
||||
if (!this.mono_wasm_setup_single_step)
|
||||
this.mono_wasm_setup_single_step = Module.cwrap ("mono_wasm_setup_single_step", 'number', [ 'number']);
|
||||
|
||||
this._next_value_type_id_var = 0;
|
||||
this._value_types_cache = {};
|
||||
this._clear_per_step_state ();
|
||||
|
||||
return this.mono_wasm_setup_single_step (kind);
|
||||
},
|
||||
|
||||
mono_wasm_set_pause_on_exceptions: function (state) {
|
||||
if (!this.mono_wasm_pause_on_exceptions)
|
||||
this.mono_wasm_pause_on_exceptions = Module.cwrap ("mono_wasm_pause_on_exceptions", 'number', [ 'number']);
|
||||
var state_enum = 0;
|
||||
switch (state) {
|
||||
case 'uncaught':
|
||||
state_enum = 1; //EXCEPTION_MODE_UNCAUGHT
|
||||
break;
|
||||
case 'all':
|
||||
state_enum = 2; //EXCEPTION_MODE_ALL
|
||||
break;
|
||||
}
|
||||
return this.mono_wasm_pause_on_exceptions (state_enum);
|
||||
},
|
||||
|
||||
mono_wasm_runtime_ready: function () {
|
||||
this.mono_wasm_runtime_is_ready = true;
|
||||
// DO NOT REMOVE - magic debugger init function
|
||||
console.debug ("mono_wasm_runtime_ready", "fe00e07a-5519-4dfe-b35a-f867dbaf2e28");
|
||||
|
||||
this._next_value_type_id_var = 0;
|
||||
this._value_types_cache = {};
|
||||
this._clear_per_step_state ();
|
||||
|
||||
// FIXME: where should this go?
|
||||
this._next_call_function_res_id = 0;
|
||||
|
@ -484,7 +666,7 @@ var MonoSupportLib = {
|
|||
},
|
||||
|
||||
// Set environment variable NAME to VALUE
|
||||
// Should be called before mono_load_runtime_and_bcl () in most cases
|
||||
// Should be called before mono_load_runtime_and_bcl () in most cases
|
||||
mono_wasm_setenv: function (name, value) {
|
||||
if (!this.wasm_setenv)
|
||||
this.wasm_setenv = Module.cwrap ('mono_wasm_setenv', null, ['string', 'string']);
|
||||
|
@ -663,12 +845,12 @@ var MonoSupportLib = {
|
|||
|
||||
// deprecated
|
||||
mono_load_runtime_and_bcl: function (
|
||||
unused_vfs_prefix, deploy_prefix, enable_debugging, file_list, loaded_cb, fetch_file_cb
|
||||
unused_vfs_prefix, deploy_prefix, debug_level, file_list, loaded_cb, fetch_file_cb
|
||||
) {
|
||||
var args = {
|
||||
fetch_file_cb: fetch_file_cb,
|
||||
loaded_cb: loaded_cb,
|
||||
enable_debugging: enable_debugging,
|
||||
debug_level: debug_level,
|
||||
assembly_root: deploy_prefix,
|
||||
assets: []
|
||||
};
|
||||
|
@ -693,7 +875,7 @@ var MonoSupportLib = {
|
|||
// Initializes the runtime and loads assemblies, debug information, and other files.
|
||||
// @args is a dictionary-style Object with the following properties:
|
||||
// assembly_root: (required) the subfolder containing managed assemblies and pdbs
|
||||
// enable_debugging: (required)
|
||||
// debug_level or enable_debugging: (required)
|
||||
// assets: (required) a list of assets to load along with the runtime. each asset
|
||||
// is a dictionary-style Object with the following properties:
|
||||
// name: (required) the name of the asset, including extension.
|
||||
|
@ -777,7 +959,7 @@ var MonoSupportLib = {
|
|||
|
||||
if (ENVIRONMENT_IS_SHELL || ENVIRONMENT_IS_NODE) {
|
||||
try {
|
||||
load_runtime ("unused", args.enable_debugging);
|
||||
load_runtime ("unused", args.debug_level);
|
||||
} catch (ex) {
|
||||
print ("MONO_WASM: load_runtime () failed: " + ex);
|
||||
print ("MONO_WASM: Stacktrace: \n");
|
||||
|
@ -787,7 +969,7 @@ var MonoSupportLib = {
|
|||
wasm_exit (1);
|
||||
}
|
||||
} else {
|
||||
load_runtime ("unused", args.enable_debugging);
|
||||
load_runtime ("unused", args.debug_level);
|
||||
}
|
||||
|
||||
MONO.mono_wasm_runtime_ready ();
|
||||
|
@ -795,6 +977,8 @@ var MonoSupportLib = {
|
|||
},
|
||||
|
||||
_load_assets_and_runtime: function (args) {
|
||||
if (args.enable_debugging)
|
||||
args.debug_level = args.enable_debugging;
|
||||
if (args.assembly_list)
|
||||
throw new Error ("Invalid args (assembly_list was replaced by assets)");
|
||||
if (args.runtime_assets)
|
||||
|
@ -999,16 +1183,31 @@ var MonoSupportLib = {
|
|||
});
|
||||
},
|
||||
|
||||
_mono_wasm_add_getter_var: function(className) {
|
||||
_mono_wasm_add_getter_var: function(className, invokable) {
|
||||
fixed_class_name = MONO._mono_csharp_fixup_class_name (className);
|
||||
var value = `${fixed_class_name} { get; }`;
|
||||
MONO.var_info.push({
|
||||
value: {
|
||||
type: "symbol",
|
||||
value: value,
|
||||
description: value
|
||||
}
|
||||
});
|
||||
if (invokable != 0) {
|
||||
var name;
|
||||
if (MONO.var_info.length > 0)
|
||||
name = MONO.var_info [MONO.var_info.length - 1].name;
|
||||
name = (name === undefined) ? "" : name;
|
||||
|
||||
MONO.var_info.push({
|
||||
get: {
|
||||
className: "Function",
|
||||
description: `get ${name} () {}`,
|
||||
type: "function",
|
||||
}
|
||||
});
|
||||
} else {
|
||||
var value = `${fixed_class_name} { get; }`;
|
||||
MONO.var_info.push({
|
||||
value: {
|
||||
type: "symbol",
|
||||
description: value,
|
||||
value: value,
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_mono_wasm_add_array_var: function(className, objectId, length) {
|
||||
|
@ -1069,7 +1268,7 @@ var MonoSupportLib = {
|
|||
break;
|
||||
|
||||
case "getter":
|
||||
MONO._mono_wasm_add_getter_var (str_value);
|
||||
MONO._mono_wasm_add_getter_var (str_value, value);
|
||||
break;
|
||||
|
||||
case "array":
|
||||
|
@ -1077,13 +1276,26 @@ var MonoSupportLib = {
|
|||
break;
|
||||
|
||||
case "pointer": {
|
||||
MONO.var_info.push ({
|
||||
value: {
|
||||
type: "symbol",
|
||||
value: str_value,
|
||||
description: str_value
|
||||
}
|
||||
});
|
||||
var fixed_value_str = MONO._mono_csharp_fixup_class_name (str_value);
|
||||
if (value.klass_addr == 0 || value.ptr_addr == 0 || fixed_value_str.startsWith ('(void*')) {
|
||||
// null or void*, which we can't deref
|
||||
MONO.var_info.push({
|
||||
value: {
|
||||
type: "symbol",
|
||||
value: fixed_value_str,
|
||||
description: fixed_value_str
|
||||
}
|
||||
});
|
||||
} else {
|
||||
MONO.var_info.push({
|
||||
value: {
|
||||
type: "object",
|
||||
className: fixed_value_str,
|
||||
description: fixed_value_str,
|
||||
objectId: this._get_updated_ptr_id ('', value)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ public class WasmAppBuilder : Task
|
|||
public string? MainJS { get; set; }
|
||||
[Required]
|
||||
public ITaskItem[]? AssemblySearchPaths { get; set; }
|
||||
public int DebugLevel { get; set; }
|
||||
public ITaskItem[]? ExtraAssemblies { get; set; }
|
||||
public ITaskItem[]? FilesToIncludeInFileSystem { get; set; }
|
||||
public ITaskItem[]? RemoteSources { get; set; }
|
||||
|
@ -41,8 +42,8 @@ public class WasmAppBuilder : Task
|
|||
{
|
||||
[JsonPropertyName("assembly_root")]
|
||||
public string AssemblyRoot { get; set; } = "managed";
|
||||
[JsonPropertyName("enable_debugging")]
|
||||
public int EnableDebugging { get; set; } = 0;
|
||||
[JsonPropertyName("debug_level")]
|
||||
public int DebugLevel { get; set; } = 0;
|
||||
[JsonPropertyName("assets")]
|
||||
public List<object> Assets { get; } = new List<object>();
|
||||
[JsonPropertyName("remote_sources")]
|
||||
|
@ -116,8 +117,15 @@ public class WasmAppBuilder : Task
|
|||
// Create app
|
||||
Directory.CreateDirectory(AppDir!);
|
||||
Directory.CreateDirectory(Path.Join(AppDir, config.AssemblyRoot));
|
||||
foreach (var assembly in _assemblies!.Values)
|
||||
foreach (var assembly in _assemblies!.Values) {
|
||||
File.Copy(assembly.Location, Path.Join(AppDir, config.AssemblyRoot, Path.GetFileName(assembly.Location)), true);
|
||||
if (DebugLevel > 0) {
|
||||
var pdb = assembly.Location;
|
||||
pdb = Path.ChangeExtension(pdb, ".pdb");
|
||||
if (File.Exists(pdb))
|
||||
File.Copy(pdb, Path.Join(AppDir, config.AssemblyRoot, Path.GetFileName(pdb)), true);
|
||||
}
|
||||
}
|
||||
|
||||
List<string> nativeAssets = new List<string>() { "dotnet.wasm", "dotnet.js", "dotnet.timezones.blat" };
|
||||
|
||||
|
@ -130,8 +138,17 @@ public class WasmAppBuilder : Task
|
|||
File.Copy(Path.Join (MicrosoftNetCoreAppRuntimePackDir, "native", f), Path.Join(AppDir, f), true);
|
||||
File.Copy(MainJS!, Path.Join(AppDir, "runtime.js"), true);
|
||||
|
||||
foreach (var assembly in _assemblies.Values)
|
||||
foreach (var assembly in _assemblies.Values) {
|
||||
config.Assets.Add(new AssemblyEntry (Path.GetFileName(assembly.Location)));
|
||||
if (DebugLevel > 0) {
|
||||
var pdb = assembly.Location;
|
||||
pdb = Path.ChangeExtension(pdb, ".pdb");
|
||||
if (File.Exists(pdb))
|
||||
config.Assets.Add(new AssemblyEntry (Path.GetFileName(pdb)));
|
||||
}
|
||||
}
|
||||
|
||||
config.DebugLevel = DebugLevel;
|
||||
|
||||
if (FilesToIncludeInFileSystem != null)
|
||||
{
|
||||
|
|
Загрузка…
Ссылка в новой задаче