[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:
Thays Grazia 2020-08-05 19:34:41 -03:00 коммит произвёл GitHub
Родитель 8140826a72
Коммит 03d2e48212
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
41 изменённых файлов: 10627 добавлений и 113 удалений

Просмотреть файл

@ -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)
{