diff options
Diffstat (limited to 'src/mono/wasm')
38 files changed, 576 insertions, 182 deletions
diff --git a/src/mono/wasm/Makefile b/src/mono/wasm/Makefile index 494c7fe365e..9188f97550f 100644 --- a/src/mono/wasm/Makefile +++ b/src/mono/wasm/Makefile @@ -140,6 +140,8 @@ build-dbg-proxy: $(DOTNET) build $(TOP)/src/mono/wasm/debugger/BrowserDebugHost $(MSBUILD_ARGS) build-dbg-testsuite: $(DOTNET) build $(TOP)/src/mono/wasm/debugger/DebuggerTestSuite $(MSBUILD_ARGS) +build-app-host: + $(DOTNET) build $(TOP)/src/mono/wasm/host $(_MSBUILD_WASM_BUILD_ARGS) $(MSBUILD_ARGS) patch-deterministic: cd emsdk/upstream/emscripten/ && patch -p1 < ../../../runtime/deterministic.diff diff --git a/src/mono/wasm/README.md b/src/mono/wasm/README.md index 221fce0e52b..6be63856cd6 100644 --- a/src/mono/wasm/README.md +++ b/src/mono/wasm/README.md @@ -177,7 +177,7 @@ Also check [bench](../sample/wasm/browser-bench/README.md) sample to measure mon ## Templates -The wasm templates, located in the `templates` directory, are templates for `dotnet new`, VS and VS for Mac. They are packaged and distributed as part of the `wasm-tools` workload. We have 2 templates, `wasmbrowser` and `wasmconsole`, for browser and console WebAssembly applications. +The wasm templates, located in the `templates` directory, are templates for `dotnet new`, VS and VS for Mac. They are packaged and distributed as part of the `wasm-experimental` workload. We have 2 templates, `wasmbrowser` and `wasmconsole`, for browser and console WebAssembly applications. For details about using `dotnet new` see the dotnet tool [documentation](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-new). diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserRunner.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserRunner.cs index 13a4ff96ab7..718e7f90a84 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BrowserRunner.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserRunner.cs @@ -10,6 +10,7 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.Playwright; using Wasm.Tests.Internal; +using Xunit.Abstractions; namespace Wasm.Build.Tests; @@ -29,6 +30,9 @@ internal class BrowserRunner : IAsyncDisposable public Task<CommandResult>? RunTask { get; private set; } public IList<string> OutputLines { get; private set; } = new List<string>(); private TaskCompletionSource<int> _exited = new(); + private readonly ITestOutputHelper _testOutput; + + public BrowserRunner(ITestOutputHelper testOutput) => _testOutput = testOutput; // FIXME: options public async Task<IPage> RunAsync(ToolCommand cmd, string args, bool headless = true) @@ -78,7 +82,7 @@ internal class BrowserRunner : IAsyncDisposable var url = new Uri(urlAvailable.Task.Result); Playwright = await Microsoft.Playwright.Playwright.CreateAsync(); string[] chromeArgs = new[] { $"--explicitly-allowed-ports={url.Port}" }; - Console.WriteLine($"Launching chrome ('{s_chromePath.Value}') via playwright with args = {string.Join(',', chromeArgs)}"); + _testOutput.WriteLine($"Launching chrome ('{s_chromePath.Value}') via playwright with args = {string.Join(',', chromeArgs)}"); Browser = await Playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions{ ExecutablePath = s_chromePath.Value, Headless = headless, @@ -99,7 +103,7 @@ internal class BrowserRunner : IAsyncDisposable await Task.WhenAny(RunTask!, _exited.Task, Task.Delay(timeout)); if (_exited.Task.IsCompleted) { - Console.WriteLine ($"Exited with {await _exited.Task}"); + _testOutput.WriteLine ($"Exited with {await _exited.Task}"); return; } @@ -114,7 +118,7 @@ internal class BrowserRunner : IAsyncDisposable await Task.WhenAny(RunTask!, _exited.Task, Task.Delay(timeout)); if (RunTask.IsCanceled) { - Console.WriteLine ($"Exited with {(await RunTask).ExitCode}"); + _testOutput.WriteLine ($"Exited with {(await RunTask).ExitCode}"); return; } diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildEnvironment.cs b/src/mono/wasm/Wasm.Build.Tests/BuildEnvironment.cs index d8ec1e5005b..f921b870375 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildEnvironment.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildEnvironment.cs @@ -27,6 +27,7 @@ namespace Wasm.Build.Tests public static readonly string RelativeTestAssetsPath = @"..\testassets\"; public static readonly string TestAssetsPath = Path.Combine(AppContext.BaseDirectory, "testassets"); public static readonly string TestDataPath = Path.Combine(AppContext.BaseDirectory, "data"); + public static readonly string TmpPath = Path.Combine(Path.GetTempPath(), "wasmbuildtests"); private static readonly Dictionary<string, string> s_runtimePackVersions = new(); @@ -141,6 +142,10 @@ namespace Wasm.Build.Tests { LogRootPath = Environment.CurrentDirectory; } + + if (Directory.Exists(TmpPath)) + Directory.Delete(TmpPath, recursive: true); + Directory.CreateDirectory(TmpPath); } // FIXME: error checks diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs index e34917b2226..f4a73bdbb0f 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs @@ -290,13 +290,25 @@ namespace Wasm.Build.Tests Directory.CreateDirectory(_logPath); } - protected static void InitProjectDir(string dir) + protected static void InitProjectDir(string dir, bool addNuGetSourceForLocalPackages = false) { Directory.CreateDirectory(dir); File.WriteAllText(Path.Combine(dir, "Directory.Build.props"), s_buildEnv.DirectoryBuildPropsContents); File.WriteAllText(Path.Combine(dir, "Directory.Build.targets"), s_buildEnv.DirectoryBuildTargetsContents); - File.Copy(Path.Combine(BuildEnvironment.TestDataPath, NuGetConfigFileNameForDefaultFramework), Path.Combine(dir, "nuget.config")); + string targetNuGetConfigPath = Path.Combine(dir, "nuget.config"); + if (addNuGetSourceForLocalPackages) + { + File.WriteAllText(targetNuGetConfigPath, + GetNuGetConfigWithLocalPackagesPath( + Path.Combine(BuildEnvironment.TestDataPath, NuGetConfigFileNameForDefaultFramework), + s_buildEnv.BuiltNuGetsPath)); + } + else + { + File.Copy(Path.Combine(BuildEnvironment.TestDataPath, NuGetConfigFileNameForDefaultFramework), + targetNuGetConfigPath); + } Directory.CreateDirectory(Path.Combine(dir, ".nuget")); } @@ -444,10 +456,10 @@ namespace Wasm.Build.Tests return contents.Replace(s_nugetInsertionTag, $@"<add key=""nuget-local"" value=""{localNuGetsPath}"" />"); } - public string CreateWasmTemplateProject(string id, string template = "wasmbrowser") + public string CreateWasmTemplateProject(string id, string template = "wasmbrowser", string extraArgs = "") { InitPaths(id); - InitProjectDir(id); + InitProjectDir(id, addNuGetSourceForLocalPackages: true); File.WriteAllText(Path.Combine(_projectDir, "Directory.Build.props"), "<Project />"); File.WriteAllText(Path.Combine(_projectDir, "Directory.Build.targets"), @@ -464,7 +476,7 @@ namespace Wasm.Build.Tests new DotNetCommand(s_buildEnv, _testOutput, useDefaultArgs: false) .WithWorkingDirectory(_projectDir!) - .ExecuteWithCapturedOutput($"new {template}") + .ExecuteWithCapturedOutput($"new {template} {extraArgs}") .EnsureSuccessful(); return Path.Combine(_projectDir!, $"{id}.csproj"); diff --git a/src/mono/wasm/Wasm.Build.Tests/DotNetCommand.cs b/src/mono/wasm/Wasm.Build.Tests/DotNetCommand.cs index 84ccf2fa23a..44f58f5cc45 100644 --- a/src/mono/wasm/Wasm.Build.Tests/DotNetCommand.cs +++ b/src/mono/wasm/Wasm.Build.Tests/DotNetCommand.cs @@ -16,6 +16,8 @@ namespace Wasm.Build.Tests _useDefaultArgs = useDefaultArgs; if (useDefaultArgs) WithEnvironmentVariables(buildEnv.EnvVars); + // workaround msbuild issue - https://github.com/dotnet/runtime/issues/74328 + WithEnvironmentVariable("DOTNET_CLI_DO_NOT_USE_MSBUILD_SERVER", "1"); } protected override string GetFullArgs(params string[] args) diff --git a/src/mono/wasm/Wasm.Build.Tests/RunCommand.cs b/src/mono/wasm/Wasm.Build.Tests/RunCommand.cs index 01f1e1efacd..33759316d1e 100644 --- a/src/mono/wasm/Wasm.Build.Tests/RunCommand.cs +++ b/src/mono/wasm/Wasm.Build.Tests/RunCommand.cs @@ -14,5 +14,7 @@ public class RunCommand : DotNetCommand WithEnvironmentVariable("DOTNET_INSTALL_DIR", Path.GetDirectoryName(buildEnv.DotNet)!); WithEnvironmentVariable("DOTNET_MULTILEVEL_LOOKUP", "0"); WithEnvironmentVariable("DOTNET_SKIP_FIRST_TIME_EXPERIENCE", "1"); + // workaround msbuild issue - https://github.com/dotnet/runtime/issues/74328 + WithEnvironmentVariable("DOTNET_CLI_DO_NOT_USE_MSBUILD_SERVER", "1"); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj b/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj index 3941d5dfca1..7a9c16c6a34 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj +++ b/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj @@ -77,7 +77,8 @@ <ItemGroup> <_RuntimePackVersions Include="$(PackageVersion)" EnvVarName="RUNTIME_PACK_VER8" /> - <_RuntimePackVersions Include="$(PackageVersionNet7)" EnvVarName="RUNTIME_PACK_VER7" /> + <_RuntimePackVersions Include="$(PackageVersionNet7)" EnvVarName="RUNTIME_PACK_VER7" Condition="'$(PackageVersionNet7)' != ''"/> + <_RuntimePackVersions Include="$(PackageVersion)" EnvVarName="RUNTIME_PACK_VER7" Condition="'$(PackageVersionNet7)' == ''"/> <_RuntimePackVersions Include="$(PackageVersionNet6)" EnvVarName="RUNTIME_PACK_VER6" /> <RunScriptCommands Condition="'$(OS)' != 'Windows_NT'" Include="export %(_RuntimePackVersions.EnvVarName)="%(_RuntimePackVersions.Identity)"" /> diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmTemplateTests.cs b/src/mono/wasm/Wasm.Build.Tests/WasmTemplateTests.cs index e96ed8b797e..01ea2a6d9d1 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WasmTemplateTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WasmTemplateTests.cs @@ -55,8 +55,7 @@ namespace Wasm.Build.Tests string mainJsContent = File.ReadAllText(mainJsPath); mainJsContent = mainJsContent - .Replace(".create()", ".withConsoleForwarding().create()") - .Replace("[\"dotnet\", \"is\", \"great!\"]", "(await import(/* webpackIgnore: true */\"process\")).argv.slice(2)"); + .Replace(".create()", ".withConsoleForwarding().create()"); File.WriteAllText(mainJsPath, mainJsContent); } @@ -185,10 +184,19 @@ namespace Wasm.Build.Tests [InlineData("Debug", true)] [InlineData("Release", false)] [InlineData("Release", true)] - public void ConsoleBuildAndRun(string config, bool relinking) + public void ConsoleBuildAndRunDefault(string config, bool relinking) + => ConsoleBuildAndRun(config, relinking, string.Empty); + + [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + [InlineData("Debug", "-f net7.0")] + [InlineData("Debug", "-f net8.0")] + public void ConsoleBuildAndRunForSpecificTFM(string config, string extraNewArgs) + => ConsoleBuildAndRun(config, false, extraNewArgs); + + private void ConsoleBuildAndRun(string config, bool relinking, string extraNewArgs) { string id = $"{config}_{Path.GetRandomFileName()}"; - string projectFile = CreateWasmTemplateProject(id, "wasmconsole"); + string projectFile = CreateWasmTemplateProject(id, "wasmconsole", extraNewArgs); string projectName = Path.GetFileNameWithoutExtension(projectFile); UpdateProgramCS(); @@ -214,9 +222,24 @@ namespace Wasm.Build.Tests (int exitCode, string output) = RunProcess(s_buildEnv.DotNet, _testOutput, args: $"run --no-build -c {config} x y z", workingDir: _projectDir); Assert.Equal(42, exitCode); - Assert.Contains("args[0] = x", output); - Assert.Contains("args[1] = y", output); - Assert.Contains("args[2] = z", output); + + try + { + Assert.Contains("args[0] = x", output); + Assert.Contains("args[1] = y", output); + Assert.Contains("args[2] = z", output); + } + catch + { + if (!extraNewArgs.Contains("-f net7.0")) + throw; + + // Workaround for https://github.com/dotnet/runtime/issues/76429 + // till a 7.0 sdk with the fix becomes available + Assert.Contains("args[0] = dotnet", output); + Assert.Contains("args[1] = is", output); + Assert.Contains("args[2] = great!", output); + } } public static TheoryData<bool, bool, string> TestDataForAppBundleDir() @@ -234,9 +257,9 @@ namespace Wasm.Build.Tests //data.Add(runOutsideProjectDirectory, forConsole, string.Empty); data.Add(runOutsideProjectDirectory, forConsole, - $"<OutputPath>{Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())}</OutputPath>"); + $"<OutputPath>{Path.Combine(BuildEnvironment.TmpPath, Path.GetRandomFileName())}</OutputPath>"); data.Add(runOutsideProjectDirectory, forConsole, - $"<WasmAppDir>{Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())}</WasmAppDir>"); + $"<WasmAppDir>{Path.Combine(BuildEnvironment.TmpPath, Path.GetRandomFileName())}</WasmAppDir>"); } return data; @@ -259,13 +282,13 @@ namespace Wasm.Build.Tests if (!string.IsNullOrEmpty(extraProperties)) AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties); - string workingDir = runOutsideProjectDirectory ? Path.GetTempPath() : _projectDir!; + string workingDir = runOutsideProjectDirectory ? BuildEnvironment.TmpPath : _projectDir!; { using var runCommand = new RunCommand(s_buildEnv, _testOutput) .WithWorkingDirectory(workingDir); - await using var runner = new BrowserRunner(); + await using var runner = new BrowserRunner(_testOutput); var page = await runner.RunAsync(runCommand, $"run -c {config} --project {projectFile} --forward-console"); await runner.WaitForExitMessageAsync(TimeSpan.FromMinutes(2)); Assert.Contains("Hello, Browser!", string.Join(Environment.NewLine, runner.OutputLines)); @@ -275,7 +298,7 @@ namespace Wasm.Build.Tests using var runCommand = new RunCommand(s_buildEnv, _testOutput) .WithWorkingDirectory(workingDir); - await using var runner = new BrowserRunner(); + await using var runner = new BrowserRunner(_testOutput); var page = await runner.RunAsync(runCommand, $"run -c {config} --no-build --project {projectFile} --forward-console"); await runner.WaitForExitMessageAsync(TimeSpan.FromMinutes(2)); Assert.Contains("Hello, Browser!", string.Join(Environment.NewLine, runner.OutputLines)); @@ -293,7 +316,7 @@ namespace Wasm.Build.Tests if (!string.IsNullOrEmpty(extraProperties)) AddItemsPropertiesToProject(projectFile, extraProperties: extraProperties); - string workingDir = runOutsideProjectDirectory ? Path.GetTempPath() : _projectDir!; + string workingDir = runOutsideProjectDirectory ? BuildEnvironment.TmpPath : _projectDir!; { string runArgs = $"run -c {config} --project {projectFile}"; @@ -420,7 +443,7 @@ namespace Wasm.Build.Tests using var runCommand = new RunCommand(s_buildEnv, _testOutput) .WithWorkingDirectory(_projectDir!); - await using var runner = new BrowserRunner(); + await using var runner = new BrowserRunner(_testOutput); var page = await runner.RunAsync(runCommand, $"run -c {config} --no-build"); await page.Locator("text=Counter").ClickAsync(); @@ -432,12 +455,15 @@ namespace Wasm.Build.Tests Assert.Equal("Current count: 1", txt); } - [ConditionalFact(typeof(BuildTestBase), nameof(IsUsingWorkloads))] - public async Task BrowserTest() + [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + [InlineData("")] + [InlineData("-f net7.0")] + [InlineData("-f net8.0")] + public async Task BrowserBuildAndRun(string extraNewArgs) { string config = "Debug"; string id = $"browser_{config}_{Path.GetRandomFileName()}"; - CreateWasmTemplateProject(id, "wasmbrowser"); + CreateWasmTemplateProject(id, "wasmbrowser", extraNewArgs); UpdateBrowserMainJs(DefaultTargetFramework); @@ -449,7 +475,7 @@ namespace Wasm.Build.Tests using var runCommand = new RunCommand(s_buildEnv, _testOutput) .WithWorkingDirectory(_projectDir!); - await using var runner = new BrowserRunner(); + await using var runner = new BrowserRunner(_testOutput); var page = await runner.RunAsync(runCommand, $"run -c {config} --no-build -r browser-wasm --forward-console"); await runner.WaitForExitMessageAsync(TimeSpan.FromMinutes(2)); Assert.Contains("Hello, Browser!", string.Join(Environment.NewLine, runner.OutputLines)); diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index 811e858ca83..fbd16363dd9 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -115,6 +115,11 @@ <WasmDebugLevel Condition="('$(WasmDebugLevel)' == '' or '$(WasmDebugLevel)' == '0') and ('$(DebuggerSupport)' == 'true' or '$(Configuration)' == 'Debug')">-1</WasmDebugLevel> </PropertyGroup> + <ItemGroup> + <!-- Allow running/debugging from VS --> + <ProjectCapability Include="DotNetCoreWeb"/> + </ItemGroup> + <PropertyGroup Label="Identify app bundle directory to run from"> <!-- Allow running from custom WasmAppDir --> <_AppBundleDirForRunCommand Condition="'$(WasmAppDir)' != ''">$(WasmAppDir)</_AppBundleDirForRunCommand> diff --git a/src/mono/wasm/debugger/BrowserDebugHost/DebugProxyHost.cs b/src/mono/wasm/debugger/BrowserDebugHost/DebugProxyHost.cs index be3095eeffc..b97ffe7ef0f 100644 --- a/src/mono/wasm/debugger/BrowserDebugHost/DebugProxyHost.cs +++ b/src/mono/wasm/debugger/BrowserDebugHost/DebugProxyHost.cs @@ -1,7 +1,6 @@ // 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.Runtime.ExceptionServices; diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs index 24f4d8522cb..0a854864306 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs @@ -64,13 +64,8 @@ namespace BrowserDebugProxy typePropertiesBrowsableInfo.TryGetValue(field.Name, out state); } fieldValue["__state"] = state?.ToString(); - - fieldValue["__section"] = field.Attributes switch - { - FieldAttributes.Private => "private", - FieldAttributes.Public => "result", - _ => "internal" - }; + fieldValue["__section"] = field.Attributes.HasFlag(FieldAttributes.Private) + ? "private" : "result"; if (field.IsBackingField) { @@ -238,6 +233,9 @@ namespace BrowserDebugProxy JObject fieldValue = await ReadFieldValue(sdbHelper, retDebuggerCmdReader, field, id.Value, typeInfo, valtype, isOwn, parentTypeId, getCommandOptions, token); numFieldsRead++; + if (typeInfo.Info.IsNonUserCode && getCommandOptions.HasFlag(GetObjectCommandOptions.JustMyCode) && field.Attributes.HasFlag(FieldAttributes.Private)) + continue; + if (!Enum.TryParse(fieldValue["__state"].Value<string>(), out DebuggerBrowsableState fieldState) || fieldState == DebuggerBrowsableState.Collapsed) { @@ -311,7 +309,7 @@ namespace BrowserDebugProxy int typeId, string typeName, ArraySegment<byte> getterParamsBuffer, - bool isAutoExpandable, + GetObjectCommandOptions getCommandOptions, DotnetObjectId objectId, bool isValueType, bool isOwn, @@ -347,6 +345,10 @@ namespace BrowserDebugProxy MethodAttributes getterAttrs = getterInfo.Info.Attributes; MethodAttributes getterMemberAccessAttrs = getterAttrs & MethodAttributes.MemberAccessMask; MethodAttributes vtableLayout = getterAttrs & MethodAttributes.VtableLayoutMask; + + if (typeInfo.Info.IsNonUserCode && getCommandOptions.HasFlag(GetObjectCommandOptions.JustMyCode) && getterMemberAccessAttrs == MethodAttributes.Private) + continue; + bool isNewSlot = (vtableLayout & MethodAttributes.NewSlot) == MethodAttributes.NewSlot; typePropertiesBrowsableInfo.TryGetValue(propName, out DebuggerBrowsableState? state); @@ -425,8 +427,7 @@ namespace BrowserDebugProxy backingField["__section"] = getterMemberAccessAttrs switch { MethodAttributes.Private => "private", - MethodAttributes.Public => "result", - _ => "internal" + _ => "result" }; backingField["__state"] = state?.ToString(); @@ -454,7 +455,7 @@ namespace BrowserDebugProxy { string returnTypeName = await sdbHelper.GetReturnType(getMethodId, token); JObject propRet = null; - if (isAutoExpandable || (state is DebuggerBrowsableState.RootHidden && IsACollectionType(returnTypeName))) + if (getCommandOptions.HasFlag(GetObjectCommandOptions.AutoExpandable) || getCommandOptions.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute) || (state is DebuggerBrowsableState.RootHidden && IsACollectionType(returnTypeName))) { try { @@ -474,8 +475,7 @@ namespace BrowserDebugProxy propRet["__section"] = getterAttrs switch { MethodAttributes.Private => "private", - MethodAttributes.Public => "result", - _ => "internal" + _ => "result" }; propRet["__state"] = state?.ToString(); if (parentTypeId != -1) @@ -568,10 +568,6 @@ namespace BrowserDebugProxy for (int i = 0; i < typeIdsCnt; i++) { int typeId = typeIdsIncludingParents[i]; - var typeInfo = await sdbHelper.GetTypeInfo(typeId, token); - - if (typeInfo.Info.IsNonUserCode && getCommandType.HasFlag(GetObjectCommandOptions.JustMyCode)) - continue; int parentTypeId = i + 1 < typeIdsCnt ? typeIdsIncludingParents[i + 1] : -1; string typeName = await sdbHelper.GetTypeName(typeId, token); @@ -604,7 +600,7 @@ namespace BrowserDebugProxy typeId, typeName, getPropertiesParamBuffer, - getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute), + getCommandType, id, isValueType: false, isOwn, @@ -656,25 +652,21 @@ namespace BrowserDebugProxy internal sealed class GetMembersResult { - // public: + // public / protected / internal: public JArray Result { get; set; } // private: public JArray PrivateMembers { get; set; } - // protected / internal: - public JArray OtherMembers { get; set; } public JObject JObject => JObject.FromObject(new { result = Result, - privateProperties = PrivateMembers, - internalProperties = OtherMembers + privateProperties = PrivateMembers }); public GetMembersResult() { Result = new JArray(); PrivateMembers = new JArray(); - OtherMembers = new JArray(); } public GetMembersResult(JArray value, bool sortByAccessLevel) @@ -682,7 +674,6 @@ namespace BrowserDebugProxy var t = FromValues(value, sortByAccessLevel); Result = t.Result; PrivateMembers = t.PrivateMembers; - OtherMembers = t.OtherMembers; } public static GetMembersResult FromValues(IEnumerable<JToken> values, bool splitMembersByAccessLevel = false) => @@ -717,9 +708,6 @@ namespace BrowserDebugProxy case "private": PrivateMembers.Add(member); return; - case "internal": - OtherMembers.Add(member); - return; default: Result.Add(member); return; @@ -730,7 +718,6 @@ namespace BrowserDebugProxy { Result = (JArray)Result.DeepClone(), PrivateMembers = (JArray)PrivateMembers.DeepClone(), - OtherMembers = (JArray)OtherMembers.DeepClone() }; public IEnumerable<JToken> Where(Func<JToken, bool> predicate) @@ -749,26 +736,17 @@ namespace BrowserDebugProxy yield return item; } } - foreach (var item in OtherMembers) - { - if (predicate(item)) - { - yield return item; - } - } } internal JToken FirstOrDefault(Func<JToken, bool> p) => Result.FirstOrDefault(p) - ?? PrivateMembers.FirstOrDefault(p) - ?? OtherMembers.FirstOrDefault(p); + ?? PrivateMembers.FirstOrDefault(p); internal JArray Flatten() { var result = new JArray(); result.AddRange(Result); result.AddRange(PrivateMembers); - result.AddRange(OtherMembers); return result; } public override string ToString() => $"{JObject}\n"; diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index cbe0692125b..902d339da85 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -60,7 +60,8 @@ namespace Microsoft.WebAssembly.Diagnostics ForDebuggerProxyAttribute = 8, ForDebuggerDisplayAttribute = 16, WithProperties = 32, - JustMyCode = 64 + JustMyCode = 64, + AutoExpandable = 128 } internal enum CommandSet { @@ -1608,6 +1609,14 @@ namespace Microsoft.WebAssembly.Diagnostics { dispAttrStr = dispAttrStr.Replace(",nq", ""); } + if (dispAttrStr.Contains(", raw")) + { + dispAttrStr = dispAttrStr.Replace(", raw", ""); + } + if (dispAttrStr.Contains(",raw")) + { + dispAttrStr = dispAttrStr.Replace(",raw", ""); + } expr = "$\"" + dispAttrStr + "\""; JObject retValue = await resolver.Resolve(expr, token); retValue ??= await ExpressionEvaluator.CompileAndRunTheExpression(expr, resolver, logger, token); diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs index ebbcb6c5d3d..865785bf51e 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs @@ -98,9 +98,7 @@ namespace BrowserDebugProxy if (isStatic) fieldValue["name"] = field.Name; FieldAttributes attr = field.Attributes & FieldAttributes.FieldAccessMask; - fieldValue["__section"] = attr == FieldAttributes.Public - ? "public" : - attr == FieldAttributes.Private ? "private" : "internal"; + fieldValue["__section"] = attr == FieldAttributes.Private ? "private" : "result"; if (field.IsBackingField) { @@ -218,7 +216,6 @@ namespace BrowserDebugProxy result = _combinedResult.Clone(); RemovePropertiesFrom(result.Result); RemovePropertiesFrom(result.PrivateMembers); - RemovePropertiesFrom(result.OtherMembers); } // 4 - fields + properties @@ -290,7 +287,7 @@ namespace BrowserDebugProxy typeId, className, Buffer, - autoExpand, + autoExpand ? GetObjectCommandOptions.AutoExpandable : GetObjectCommandOptions.None, Id, isValueType: true, isOwn: i == 0, diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs index 555c0cf744d..b533d39910a 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs @@ -988,7 +988,7 @@ namespace DebuggerTests return locals; } - internal async Task<(JToken, JToken, JToken)> GetPropertiesSortedByProtectionLevels(string id, JToken fn_args = null, bool? own_properties = null, bool? accessors_only = null, bool expect_ok = true) + internal async Task<(JToken, JToken)> GetPropertiesSortedByProtectionLevels(string id, JToken fn_args = null, bool? own_properties = null, bool? accessors_only = null, bool expect_ok = true) { if (UseCallFunctionOnBeforeGetProperties && !id.StartsWith("dotnet:scope:")) { @@ -1004,7 +1004,7 @@ namespace DebuggerTests var result = await cli.SendCommand("Runtime.callFunctionOn", cfo_args, token); AssertEqual(expect_ok, result.IsOk, $"Runtime.getProperties returned {result.IsOk} instead of {expect_ok}, for {cfo_args.ToString()}, with Result: {result}"); if (!result.IsOk) - return (null, null, null); + return (null, null); id = result.Value["result"]?["objectId"]?.Value<string>(); } @@ -1024,10 +1024,9 @@ namespace DebuggerTests var frame_props = await cli.SendCommand("Runtime.getProperties", get_prop_req, token); AssertEqual(expect_ok, frame_props.IsOk, $"Runtime.getProperties returned {frame_props.IsOk} instead of {expect_ok}, for {get_prop_req}, with Result: {frame_props}"); if (!frame_props.IsOk) - return (null, null, null);; + return (null, null);; var locals = frame_props.Value["result"]; - var locals_internal = frame_props.Value["internalProperties"]; var locals_private = frame_props.Value["privateProperties"]; // FIXME: Should be done when generating the list in dotnet.es6.lib.js, but not sure yet @@ -1044,7 +1043,7 @@ namespace DebuggerTests } } - return (locals, locals_internal, locals_private); + return (locals, locals_private); } internal virtual async Task<(JToken, Result)> EvaluateOnCallFrame(string id, string expression, bool expect_ok = true) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs index d9fc2098dc0..3bf376c8954 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs @@ -26,6 +26,7 @@ namespace DebuggerTests var type_name = "DerivedClass2"; var all_props = new Dictionary<string, (JObject, bool)>() { + // ------------------instance members-------------------------- // own: // public: {"BaseBase_PropertyForHidingWithField", (TNumber(210), true)}, @@ -80,6 +81,45 @@ namespace DebuggerTests {"BaseBase_AutoPropertyForHidingWithAutoProperty (BaseBaseClass2)", (TString("BaseBase#BaseBase_AutoPropertyForHidingWithAutoProperty"), false)}, {"BaseBase_PropertyForVHO (BaseBaseClass2)", (TGetter("BaseBase_PropertyForVHO (BaseBaseClass2)", TString("BaseBase#BaseBase_PropertyForVHO")), false)}, {"BaseBase_AutoPropertyForVHO (BaseBaseClass2)", (TString("BaseBase#BaseBase_AutoPropertyForVHO"), false)}, + + // ------------------static members-------------------------- + // own: + // public: + {"S_BaseBase_PropertyForHidingWithField", (TNumber(210), true)}, + + // protected / internal: + {"S_BaseBase_AutoPropertyForHidingWithProperty", (TGetter("S_BaseBase_AutoPropertyForHidingWithProperty", TString("Derived#BaseBase_AutoPropertyForHidingWithProperty")), true)}, + + // private: + {"S_BaseBase_FieldForHidingWithAutoProperty", (TString(null), true)}, + + // inherited from Base: + // public: + {"S_BaseBase_AutoPropertyForHidingWithField", (TNumber(115), false)}, + {"S_BaseBase_PropertyForHidingWithProperty", (TGetter("S_BaseBase_PropertyForHidingWithProperty", TString("Base#BaseBase_PropertyForHidingWithProperty")), false)}, + {"S_BaseBase_FieldForHidingWithAutoProperty (BaseClass2)", (TString(null), false)}, + + // protected / internal: + {"S_BaseBase_PropertyForHidingWithField (BaseClass2)", (TNumber(110), false)}, + {"S_BaseBase_FieldForHidingWithProperty", (TGetter("S_BaseBase_FieldForHidingWithProperty", TString("Base#BaseBase_FieldForHidingWithProperty")), false)}, + {"S_BaseBase_AutoPropertyForHidingWithAutoProperty", (TString(null), false)}, + + // private: + {"S_BaseBase_FieldForHidingWithField", (TNumber(105), false)}, + {"S_BaseBase_AutoPropertyForHidingWithProperty (BaseClass2)", (TGetter("S_BaseBase_AutoPropertyForHidingWithProperty (BaseClass2)", TString("Base#BaseBase_AutoPropertyForHidingWithProperty")), false)}, + {"S_BaseBase_PropertyForHidingWithAutoProperty", (TString(null), false)}, + + // inherited from BaseBase: + // public: + {"S_BaseBase_FieldForHidingWithField (BaseBaseClass2)", (TNumber(5), false)}, + {"S_BaseBase_PropertyForHidingWithField (BaseBaseClass2)", (TGetter("S_BaseBase_PropertyForHidingWithField (BaseBaseClass2)", TNumber(10)), false)}, + {"S_BaseBase_AutoPropertyForHidingWithField (BaseBaseClass2)", (TNumber(0), false)}, + {"S_BaseBase_FieldForHidingWithProperty (BaseBaseClass2)", (TString("BaseBase#BaseBase_FieldForHidingWithProperty"), false)}, + {"S_BaseBase_PropertyForHidingWithProperty (BaseBaseClass2)", (TGetter("S_BaseBase_PropertyForHidingWithProperty (BaseBaseClass2)", TString("BaseBase#BaseBase_PropertyForHidingWithProperty")), false)}, + {"S_BaseBase_AutoPropertyForHidingWithProperty (BaseBaseClass2)", (TString(null), false)}, + {"S_BaseBase_FieldForHidingWithAutoProperty (BaseBaseClass2)", (TString("BaseBase#BaseBase_FieldForHidingWithAutoProperty"), false)}, + {"S_BaseBase_PropertyForHidingWithAutoProperty (BaseBaseClass2)", (TGetter("S_BaseBase_PropertyForHidingWithAutoProperty (BaseBaseClass2)", TString("BaseBase#BaseBase_PropertyForHidingWithAutoProperty")), false)}, + {"S_BaseBase_AutoPropertyForHidingWithAutoProperty (BaseBaseClass2)", (TString(null), false)}, }; // default, all properties @@ -109,6 +149,7 @@ namespace DebuggerTests "BaseBase_PropertyForVHO", "BaseBase_PropertyForVOH", // "BaseBase_PropertyForVOO", // FixMe: Issue #69788 + "S_BaseBase_AutoPropertyForHidingWithProperty", "BaseBase_PropertyForHidingWithProperty", "FirstName", @@ -116,12 +157,18 @@ namespace DebuggerTests "BaseBase_FieldForHidingWithProperty", "BaseBase_AutoPropertyForHidingWithProperty (BaseClass2)", "BaseBase_PropertyForVOH (BaseClass2)", + "S_BaseBase_PropertyForHidingWithProperty", + "S_BaseBase_FieldForHidingWithProperty", + "S_BaseBase_AutoPropertyForHidingWithProperty (BaseClass2)", "BaseBase_PropertyForHidingWithField (BaseBaseClass2)", "BaseBase_PropertyForHidingWithProperty (BaseBaseClass2)", "BaseBase_PropertyForHidingWithAutoProperty (BaseBaseClass2)", "Base_VirtualPropertyNotOverriddenOrHidden", - "BaseBase_PropertyForVHO (BaseBaseClass2)" + "BaseBase_PropertyForVHO (BaseBaseClass2)", + "S_BaseBase_PropertyForHidingWithField (BaseBaseClass2)", + "S_BaseBase_PropertyForHidingWithProperty (BaseBaseClass2)", + "S_BaseBase_PropertyForHidingWithAutoProperty (BaseBaseClass2)" }; var only_own_accessors = new[] @@ -132,6 +179,7 @@ namespace DebuggerTests "BaseBase_PropertyForVHO", "BaseBase_PropertyForVOH", // "BaseBase_PropertyForVOO", // FixMe: Issue #69788 + "S_BaseBase_AutoPropertyForHidingWithProperty", }; // all own, only accessors @@ -449,12 +497,13 @@ namespace DebuggerTests throw new XunitException($"missing or unexpected members found"); } - public static TheoryData<Dictionary<string, JObject>, Dictionary<string, JObject>, Dictionary<string, JObject>, string> GetDataForProtectionLevels() + public static TheoryData<Dictionary<string, JObject>, Dictionary<string, JObject>, string> GetDataForProtectionLevels() { - var data = new TheoryData<Dictionary<string, JObject>, Dictionary<string, JObject>, Dictionary<string, JObject>, string>(); + var data = new TheoryData<Dictionary<string, JObject>, Dictionary<string, JObject>, string>(); var public_props = new Dictionary<string, JObject>() { + // --------- public ------------: // own: {"BaseBase_PropertyForHidingWithField", TNumber(210)}, {"Base_PropertyForOverridingWithProperty", TGetter("Base_PropertyForOverridingWithProperty", TDateTime(new DateTime(2020, 7, 6, 5, 4, 3)))}, @@ -464,6 +513,7 @@ namespace DebuggerTests {"BaseBase_AutoPropertyForVHO", TString("Derived#BaseBase_AutoPropertyForVHO")}, {"BaseBase_AutoPropertyForVOH", TString("Derived#BaseBase_AutoPropertyForVOH")}, // {"BaseBase_AutoPropertyForVOO", TString("Derived#BaseBase_AutoPropertyForVOO")}, //FixMe: Issue #69788 + {"S_BaseBase_PropertyForHidingWithField", TNumber(210)}, // inherited from Base: {"BaseBase_AutoPropertyForHidingWithField", TNumber(115)}, @@ -474,6 +524,9 @@ namespace DebuggerTests {"Base_VirtualPropertyNotOverriddenOrHidden", TGetter("Base_VirtualPropertyNotOverriddenOrHidden", TDateTime(new DateTime(2124, 5, 7, 1, 9, 2)))}, {"BaseBase_PropertyForVOH (BaseClass2)", TGetter("BaseBase_PropertyForVOH (BaseClass2)", TString("Base#BaseBase_PropertyForVOH"))}, {"BaseBase_AutoPropertyForVOH (BaseClass2)", TString("Base#BaseBase_AutoPropertyForVOH")}, + {"S_BaseBase_AutoPropertyForHidingWithField", TNumber(115)}, + {"S_BaseBase_PropertyForHidingWithProperty", TGetter("S_BaseBase_PropertyForHidingWithProperty", TString("Base#BaseBase_PropertyForHidingWithProperty"))}, + {"S_BaseBase_FieldForHidingWithAutoProperty (BaseClass2)", TString(null)}, // inherited from BaseBase: {"BaseBase_FieldForHidingWithField (BaseBaseClass2)", TNumber(5)}, @@ -487,43 +540,57 @@ namespace DebuggerTests {"BaseBase_AutoPropertyForHidingWithAutoProperty (BaseBaseClass2)", TString("BaseBase#BaseBase_AutoPropertyForHidingWithAutoProperty")}, {"BaseBase_PropertyForVHO (BaseBaseClass2)", TGetter("BaseBase_PropertyForVHO (BaseBaseClass2)", TString("BaseBase#BaseBase_PropertyForVHO"))}, {"BaseBase_AutoPropertyForVHO (BaseBaseClass2)", TString("BaseBase#BaseBase_AutoPropertyForVHO")}, - }; - - var internal_protected_props = new Dictionary<string, JObject>(){ - + {"S_BaseBase_FieldForHidingWithField (BaseBaseClass2)", TNumber(5)}, + {"S_BaseBase_PropertyForHidingWithField (BaseBaseClass2)", TGetter("S_BaseBase_PropertyForHidingWithField (BaseBaseClass2)", TNumber(10))}, + {"S_BaseBase_AutoPropertyForHidingWithField (BaseBaseClass2)", TNumber(0)}, + {"S_BaseBase_FieldForHidingWithProperty (BaseBaseClass2)", TString("BaseBase#BaseBase_FieldForHidingWithProperty")}, + {"S_BaseBase_PropertyForHidingWithProperty (BaseBaseClass2)", TGetter("S_BaseBase_PropertyForHidingWithProperty (BaseBaseClass2)", TString("BaseBase#BaseBase_PropertyForHidingWithProperty"))}, + {"S_BaseBase_AutoPropertyForHidingWithProperty (BaseBaseClass2)", TString(null)}, + {"S_BaseBase_FieldForHidingWithAutoProperty (BaseBaseClass2)", TString("BaseBase#BaseBase_FieldForHidingWithAutoProperty")}, + {"S_BaseBase_PropertyForHidingWithAutoProperty (BaseBaseClass2)", TGetter("S_BaseBase_PropertyForHidingWithAutoProperty (BaseBaseClass2)", TString("BaseBase#BaseBase_PropertyForHidingWithAutoProperty"))}, + {"S_BaseBase_AutoPropertyForHidingWithAutoProperty (BaseBaseClass2)",TString(null)}, + + // ---- internal / protected ----: // own: {"BaseBase_AutoPropertyForHidingWithProperty", TGetter("BaseBase_AutoPropertyForHidingWithProperty", TString("Derived#BaseBase_AutoPropertyForHidingWithProperty"))}, {"Base_PropertyForOverridingWithAutoProperty", TDateTime(new DateTime(2022, 7, 6, 5, 4, 3))}, {"Base_AutoPropertyForOverridingWithAutoProperty", TDateTime(new DateTime(2023, 7, 6, 5, 4, 3))}, {"Base_AutoPropertyForOverridingWithProperty", TGetter("Base_AutoPropertyForOverridingWithProperty", TDateTime(new DateTime(2021, 7, 6, 5, 4, 3)))}, + {"S_BaseBase_AutoPropertyForHidingWithProperty", TGetter("S_BaseBase_AutoPropertyForHidingWithProperty", TString("Derived#BaseBase_AutoPropertyForHidingWithProperty"))}, // inherited from Base: {"BaseBase_PropertyForHidingWithField (BaseClass2)", TNumber(110)}, {"BaseBase_FieldForHidingWithProperty", TGetter("BaseBase_FieldForHidingWithProperty", TString("Base#BaseBase_FieldForHidingWithProperty"))}, - {"BaseBase_AutoPropertyForHidingWithAutoProperty", TString("Base#BaseBase_AutoPropertyForHidingWithAutoProperty")} + {"BaseBase_AutoPropertyForHidingWithAutoProperty", TString("Base#BaseBase_AutoPropertyForHidingWithAutoProperty")}, + {"S_BaseBase_PropertyForHidingWithField (BaseClass2)", TNumber(110)}, + {"S_BaseBase_FieldForHidingWithProperty", TGetter("S_BaseBase_FieldForHidingWithProperty", TString("Base#BaseBase_FieldForHidingWithProperty"))}, + {"S_BaseBase_AutoPropertyForHidingWithAutoProperty", TString(null)}, }; var private_props = new Dictionary<string, JObject>(){ // own - {"BaseBase_FieldForHidingWithAutoProperty", TString("Derived#BaseBase_FieldForHidingWithAutoProperty")}, + {"BaseBase_FieldForHidingWithAutoProperty", TString("Derived#BaseBase_FieldForHidingWithAutoProperty")}, + {"S_BaseBase_FieldForHidingWithAutoProperty", TString(null)}, // from Base: - {"BaseBase_FieldForHidingWithField", TNumber(105)}, - {"BaseBase_AutoPropertyForHidingWithProperty (BaseClass2)", TGetter("BaseBase_AutoPropertyForHidingWithProperty (BaseClass2)", TString("Base#BaseBase_AutoPropertyForHidingWithProperty"))}, - {"BaseBase_PropertyForHidingWithAutoProperty", TString("Base#BaseBase_PropertyForHidingWithAutoProperty")}, + {"BaseBase_FieldForHidingWithField", TNumber(105)}, + {"BaseBase_AutoPropertyForHidingWithProperty (BaseClass2)", TGetter("BaseBase_AutoPropertyForHidingWithProperty (BaseClass2)", TString("Base#BaseBase_AutoPropertyForHidingWithProperty"))}, + {"BaseBase_PropertyForHidingWithAutoProperty", TString("Base#BaseBase_PropertyForHidingWithAutoProperty")}, + {"S_BaseBase_FieldForHidingWithField", TNumber(105)}, + {"S_BaseBase_AutoPropertyForHidingWithProperty (BaseClass2)",TGetter("S_BaseBase_AutoPropertyForHidingWithProperty (BaseClass2)", TString("Base#BaseBase_AutoPropertyForHidingWithProperty"))}, + {"S_BaseBase_PropertyForHidingWithAutoProperty", TString(null)}, }; - data.Add(public_props, internal_protected_props, private_props, "DerivedClass2"); + data.Add(public_props, private_props, "DerivedClass2"); // structure CloneableStruct: public_props = new Dictionary<string, JObject>() { // own + // public {"a", TNumber(4)}, {"DateTime", TGetter("DateTime")}, {"AutoStringProperty", TString("CloneableStruct#AutoStringProperty")}, {"FirstName", TGetter("FirstName")}, - {"LastName", TGetter("LastName")} - }; - internal_protected_props = new Dictionary<string, JObject>() - { + {"LastName", TGetter("LastName")}, + // internal {"b", TBool(true)} }; @@ -533,14 +600,14 @@ namespace DebuggerTests {"_dateTime", TDateTime(new DateTime(2020, 7, 6, 5, 4, 3 + 3))}, {"_DTProp", TGetter("_DTProp")} }; - data.Add(public_props, internal_protected_props, private_props, "CloneableStruct"); + data.Add(public_props, private_props, "CloneableStruct"); return data; } [ConditionalTheory(nameof(RunningOnChrome))] [MemberData(nameof(GetDataForProtectionLevels))] public async Task PropertiesSortedByProtectionLevel( - Dictionary<string, JObject> expectedPublic, Dictionary<string, JObject> expectedProtInter, Dictionary<string, JObject> expectedPriv, string entryMethod) => + Dictionary<string, JObject> expectedPublicInternalAndProtected, Dictionary<string, JObject> expectedPriv, string entryMethod) => await CheckInspectLocalsAtBreakpointSite( $"DebuggerTests.GetPropertiesTests.{entryMethod}", "InstanceMethod", 1, $"DebuggerTests.GetPropertiesTests.{entryMethod}.InstanceMethod", $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] DebuggerTests.GetPropertiesTests.{entryMethod}:run'); }})", @@ -548,14 +615,12 @@ namespace DebuggerTests { var id = pause_location["callFrames"][0]["callFrameId"].Value<string>(); var (obj, _) = await EvaluateOnCallFrame(id, "this"); - var (pub, internalAndProtected, priv) = await GetPropertiesSortedByProtectionLevels(obj["objectId"]?.Value<string>()); + var (pubInternalAndProtected, priv) = await GetPropertiesSortedByProtectionLevels(obj["objectId"]?.Value<string>()); - AssertHasOnlyExpectedProperties(expectedPublic.Keys.ToArray(), pub.Values<JObject>()); - AssertHasOnlyExpectedProperties(expectedProtInter.Keys.ToArray(), internalAndProtected.Values<JObject>()); + AssertHasOnlyExpectedProperties(expectedPublicInternalAndProtected.Keys.ToArray(), pubInternalAndProtected.Values<JObject>()); AssertHasOnlyExpectedProperties(expectedPriv.Keys.ToArray(), priv.Values<JObject>()); - await CheckProps(pub, expectedPublic, "public"); - await CheckProps(internalAndProtected, expectedProtInter, "internalAndProtected"); + await CheckProps(pubInternalAndProtected, expectedPublicInternalAndProtected, "result"); await CheckProps(priv, expectedPriv, "private"); }); } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs index 5e1cfda9a92..d0f8be8e660 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs @@ -1067,7 +1067,11 @@ namespace DebuggerTests { myField = TNumber(0), myField2 = TNumber(0), - }, "this_props", num_fields: 2); + propB = TGetter("propB"), + propC = TGetter("propC"), + e = TNumber(50), + f = TNumber(60), + }, "this_props", num_fields: 6); } else { @@ -1100,5 +1104,30 @@ namespace DebuggerTests bp.Value["locations"][0]["columnNumber"].Value<int>(), $"DebuggerTests.CheckChineseCharacterInPath.Evaluate"); } + + [Fact] + public async Task InspectReadOnlySpan() + { + var expression = $"{{ invoke_static_method('[debugger-test] ReadOnlySpanTest:Run'); }}"; + + await EvaluateAndCheck( + "window.setTimeout(function() {" + expression + "; }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 1371, 8, + "ReadOnlySpanTest.CheckArguments", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value<string>(); + await EvaluateOnCallFrameAndCheck(id, + ("parameters.ToString()", TString("System.ReadOnlySpan<Object>[1]")) + ); + } + ); + await StepAndCheck(StepKind.Resume, "dotnet://debugger-test.dll/debugger-test.cs", 1363, 8, "ReadOnlySpanTest.Run", + locals_fn: async (locals) => + { + await CheckValueType(locals, "var1", "System.ReadOnlySpan<object>", description: "System.ReadOnlySpan<Object>[0]"); + } + ); + } } } diff --git a/src/mono/wasm/debugger/tests/debugger-test-with-non-user-code-class/test.cs b/src/mono/wasm/debugger/tests/debugger-test-with-non-user-code-class/test.cs index af11a6329d0..1626d47d5d4 100644 --- a/src/mono/wasm/debugger/tests/debugger-test-with-non-user-code-class/test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test-with-non-user-code-class/test.cs @@ -16,11 +16,11 @@ namespace DebuggerTests private int d; public int e; protected int f; - public int G + private int G { get {return f + 1;} } - public int H => f; + private int H => f; public ClassNonUserCodeToInheritThatInheritsFromNormalClass() { diff --git a/src/mono/wasm/debugger/tests/debugger-test-without-debug-symbols-to-load/test.cs b/src/mono/wasm/debugger/tests/debugger-test-without-debug-symbols-to-load/test.cs index 5dcdc29f93f..4899b8869ef 100644 --- a/src/mono/wasm/debugger/tests/debugger-test-without-debug-symbols-to-load/test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test-without-debug-symbols-to-load/test.cs @@ -10,11 +10,11 @@ namespace DebuggerTests private int d; public int e; protected int f; - public int G + private int G { get {return f + 1;} } - public int H => f; + private int H => f; public ClassWithoutDebugSymbolsToInherit() { diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-get-properties-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-get-properties-test.cs index e7359ee8c33..dc02bbdfb79 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-get-properties-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-get-properties-test.cs @@ -140,9 +140,25 @@ namespace DebuggerTests.GetPropertiesTests public virtual string BaseBase_AutoPropertyForVHO { get; set; } // public virtual string BaseBase_AutoPropertyForVOO { get; set; } // FixMe: Issue #69788 + // -----------------static members-------------- + // for new-hidding with a field: + public static int S_BaseBase_FieldForHidingWithField = 5; + public static int S_BaseBase_PropertyForHidingWithField => 10; + public static int S_BaseBase_AutoPropertyForHidingWithField { get; set; } + + // for new-hidding with a property: + public static string S_BaseBase_FieldForHidingWithProperty = "BaseBase#BaseBase_FieldForHidingWithProperty"; + public static string S_BaseBase_PropertyForHidingWithProperty => "BaseBase#BaseBase_PropertyForHidingWithProperty"; + public static string S_BaseBase_AutoPropertyForHidingWithProperty { get; set; } + + // for new-hidding with an auto-property: + public static string S_BaseBase_FieldForHidingWithAutoProperty = "BaseBase#BaseBase_FieldForHidingWithAutoProperty"; + public static string S_BaseBase_PropertyForHidingWithAutoProperty => "BaseBase#BaseBase_PropertyForHidingWithAutoProperty"; + public static string S_BaseBase_AutoPropertyForHidingWithAutoProperty { get; set; } + public BaseBaseClass2() { - BaseBase_AutoPropertyForHidingWithField = 15; + BaseBase_AutoPropertyForHidingWithField = 10 + S_BaseBase_FieldForHidingWithField; // = 15; suppressing non-used variable warnings BaseBase_AutoPropertyForHidingWithProperty = "BaseBase#BaseBase_AutoPropertyForHidingWithProperty"; BaseBase_AutoPropertyForHidingWithAutoProperty = "BaseBase#BaseBase_AutoPropertyForHidingWithAutoProperty"; @@ -192,8 +208,25 @@ namespace DebuggerTests.GetPropertiesTests public new virtual string BaseBase_AutoPropertyForVHO { get; set; } // public override string BaseBase_AutoPropertyForVOO { get; set; }// FixMe: Issue #69788 + // -----------------static members-------------- + // hiding with a field: + private static new int S_BaseBase_FieldForHidingWithField = 105; + protected static new int S_BaseBase_PropertyForHidingWithField = 110; + public static new int S_BaseBase_AutoPropertyForHidingWithField = 115; + + // hiding with a property: + protected static new string S_BaseBase_FieldForHidingWithProperty => "Base#BaseBase_FieldForHidingWithProperty"; + public static new string S_BaseBase_PropertyForHidingWithProperty => "Base#BaseBase_PropertyForHidingWithProperty"; + private static new string S_BaseBase_AutoPropertyForHidingWithProperty => "Base#BaseBase_AutoPropertyForHidingWithProperty"; + + // hiding with an auto-property: + public static new string S_BaseBase_FieldForHidingWithAutoProperty { get; set; } + private static new string S_BaseBase_PropertyForHidingWithAutoProperty { get; set; } + protected static new string S_BaseBase_AutoPropertyForHidingWithAutoProperty { get; set; } + public BaseClass2() { + S_BaseBase_PropertyForHidingWithField = S_BaseBase_FieldForHidingWithField + 5; // suppressing non-used variable warning BaseBase_PropertyForHidingWithField = BaseBase_FieldForHidingWithField + 5; // suppressing non-used variable warning BaseBase_FieldForHidingWithAutoProperty = "Base#BaseBase_FieldForHidingWithAutoProperty"; BaseBase_PropertyForHidingWithAutoProperty = "Base#BaseBase_PropertyForHidingWithAutoProperty"; @@ -230,6 +263,12 @@ namespace DebuggerTests.GetPropertiesTests public override string BaseBase_AutoPropertyForVHO { get; set; } // public override string BaseBase_AutoPropertyForVOO { get; set; } // FixMe: Issue #69788 + // -----------------static members-------------- + // hiding sample members from BaseBase: + public static new int S_BaseBase_PropertyForHidingWithField = 210; + protected static new string S_BaseBase_AutoPropertyForHidingWithProperty => "Derived#BaseBase_AutoPropertyForHidingWithProperty"; + private static new string S_BaseBase_FieldForHidingWithAutoProperty { get; set; } + public DerivedClass2() { Base_PropertyForOverridingWithAutoProperty = new (2022, 7, 6, 5, 4, 3); diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs index b8f52c788f0..5e30c999b33 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs @@ -1300,11 +1300,11 @@ public class ClassNonUserCodeToInherit private int d; public int e; protected int f; - public int G + private int G { get {return f + 1;} } - public int H => f; + private int H => f; public ClassNonUserCodeToInherit() { @@ -1354,4 +1354,21 @@ public class ClassInheritsFromNonUserCodeClassThatInheritsFromNormalClass : Debu } public int myField; +} +public class ReadOnlySpanTest +{ + public static void Run() + { + Invoke(new string[] {"TEST"}); + ReadOnlySpan<object> var1 = new ReadOnlySpan<object>(); + System.Diagnostics.Debugger.Break(); + } + public static void Invoke(object[] parameters) + { + CheckArguments(parameters); + } + public static void CheckArguments(ReadOnlySpan<object> parameters) + { + System.Diagnostics.Debugger.Break(); + } }
\ No newline at end of file diff --git a/src/mono/wasm/host/BrowserHost.cs b/src/mono/wasm/host/BrowserHost.cs index bccca9ff4f1..a592ca7386f 100644 --- a/src/mono/wasm/host/BrowserHost.cs +++ b/src/mono/wasm/host/BrowserHost.cs @@ -71,9 +71,13 @@ internal sealed class BrowserHost debugging: _args.CommonConfig.Debugging); runArgsJson.Save(Path.Combine(_args.CommonConfig.AppPath, "runArgs.json")); + string[] urls = envVars.TryGetValue("ASPNETCORE_URLS", out string? aspnetUrls) + ? aspnetUrls.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries) + : new string[] { $"http://127.0.0.1:{_args.CommonConfig.HostProperties.WebServerPort}", "https://127.0.0.1:0" }; + (ServerURLs serverURLs, IWebHost host) = await StartWebServerAsync(_args.CommonConfig.AppPath, _args.ForwardConsoleOutput ?? false, - _args.CommonConfig.HostProperties.WebServerPort, + urls, token); string[] fullUrls = BuildUrls(serverURLs, _args.AppArgs); @@ -84,7 +88,7 @@ internal sealed class BrowserHost await host.WaitForShutdownAsync(token); } - private async Task<(ServerURLs, IWebHost)> StartWebServerAsync(string appPath, bool forwardConsole, int port, CancellationToken token) + private async Task<(ServerURLs, IWebHost)> StartWebServerAsync(string appPath, bool forwardConsole, string[] urls, CancellationToken token) { WasmTestMessagesProcessor? logProcessor = null; if (forwardConsole) @@ -100,7 +104,7 @@ internal sealed class BrowserHost ContentRootPath: Path.GetFullPath(appPath), WebServerUseCors: true, WebServerUseCrossOriginPolicy: true, - Port: port + Urls: urls ); (ServerURLs serverURLs, IWebHost host) = await WebServer.StartAsync(options, _logger, token); diff --git a/src/mono/wasm/host/WebServer.cs b/src/mono/wasm/host/WebServer.cs index 51bda607167..44d02432002 100644 --- a/src/mono/wasm/host/WebServer.cs +++ b/src/mono/wasm/host/WebServer.cs @@ -1,13 +1,9 @@ // 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.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -20,7 +16,7 @@ public class WebServer { internal static async Task<(ServerURLs, IWebHost)> StartAsync(WebServerOptions options, ILogger logger, CancellationToken token) { - string[]? urls = new string[] { $"http://127.0.0.1:{options.Port}", "https://127.0.0.1:0" }; + TaskCompletionSource<ServerURLs> realUrlsAvailableTcs = new(); IWebHostBuilder builder = new WebHostBuilder() .UseKestrel() @@ -43,9 +39,10 @@ public class WebServer } services.AddSingleton(logger); services.AddSingleton(Options.Create(options)); + services.AddSingleton(realUrlsAvailableTcs); services.AddRouting(); }) - .UseUrls(urls); + .UseUrls(options.Urls); if (options.ContentRootPath != null) builder.UseContentRoot(options.ContentRootPath); @@ -53,27 +50,11 @@ public class WebServer IWebHost? host = builder.Build(); await host.StartAsync(token); - ICollection<string>? addresses = host.ServerFeatures - .Get<IServerAddressesFeature>()? - .Addresses; + if (token.CanBeCanceled) + token.Register(async () => await host.StopAsync()); - string? ipAddress = - addresses? - .Where(a => a.StartsWith("http:", StringComparison.InvariantCultureIgnoreCase)) - .Select(a => new Uri(a)) - .Select(uri => uri.ToString()) - .FirstOrDefault(); - - string? ipAddressSecure = - addresses? - .Where(a => a.StartsWith("https:", StringComparison.OrdinalIgnoreCase)) - .Select(a => new Uri(a)) - .Select(uri => uri.ToString()) - .FirstOrDefault(); - - return ipAddress == null || ipAddressSecure == null - ? throw new InvalidOperationException("Failed to determine web server's IP address or port") - : (new ServerURLs(ipAddress, ipAddressSecure), host); + ServerURLs serverUrls = await realUrlsAvailableTcs.Task; + return (serverUrls, host); } } diff --git a/src/mono/wasm/host/WebServerOptions.cs b/src/mono/wasm/host/WebServerOptions.cs index bc8b5f2acee..43e05c10b7c 100644 --- a/src/mono/wasm/host/WebServerOptions.cs +++ b/src/mono/wasm/host/WebServerOptions.cs @@ -15,6 +15,6 @@ internal sealed record WebServerOptions string? ContentRootPath, bool WebServerUseCors, bool WebServerUseCrossOriginPolicy, - int Port, + string [] Urls, string DefaultFileName = "index.html" ); diff --git a/src/mono/wasm/host/WebServerStartup.cs b/src/mono/wasm/host/WebServerStartup.cs index ae5e906c518..64bf9e7cccb 100644 --- a/src/mono/wasm/host/WebServerStartup.cs +++ b/src/mono/wasm/host/WebServerStartup.cs @@ -1,12 +1,23 @@ // 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.Diagnostics; +using System.IO; +using System.Linq; using System.Net.WebSockets; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using System.Web; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; #nullable enable @@ -16,11 +27,40 @@ namespace Microsoft.WebAssembly.AppHost; internal sealed class WebServerStartup { private readonly IWebHostEnvironment _hostingEnvironment; - + private static readonly object LaunchLock = new object(); + private static string LaunchedDebugProxyUrl = ""; + private ILogger? _logger; public WebServerStartup(IWebHostEnvironment hostingEnvironment) => _hostingEnvironment = hostingEnvironment; - public void Configure(IApplicationBuilder app, IOptions<WebServerOptions> optionsContainer) + public static int StartDebugProxy(string devToolsHost) + { + //we need to start another process, otherwise it will be running the BrowserDebugProxy in the same process that will be debugged, so pausing in a breakpoint + //on managed code will freeze because it will not be able to continue executing the BrowserDebugProxy to get the locals value + var executablePath = Path.Combine(System.AppContext.BaseDirectory, "BrowserDebugHost.dll"); + var ownerPid = Environment.ProcessId; + var generateRandomPort = new Random().Next(5000, 5300); + var processStartInfo = new ProcessStartInfo + { + FileName = "dotnet" + (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : ""), + Arguments = $"exec \"{executablePath}\" --OwnerPid {ownerPid} --DevToolsUrl {devToolsHost} --DevToolsProxyPort {generateRandomPort}", + UseShellExecute = false, + RedirectStandardOutput = true, + }; + var debugProxyProcess = Process.Start(processStartInfo); + if (debugProxyProcess is null) + { + throw new InvalidOperationException("Unable to start debug proxy process."); + } + return generateRandomPort; + } + + public void Configure(IApplicationBuilder app, + IOptions<WebServerOptions> optionsContainer, + TaskCompletionSource<ServerURLs> realUrlsAvailableTcs, + ILogger logger, + IHostApplicationLifetime applicationLifetime) { + _logger = logger; var provider = new FileExtensionContentTypeProvider(); provider.Mappings[".wasm"] = "application/wasm"; provider.Mappings[".cjs"] = "text/javascript"; @@ -73,9 +113,81 @@ internal sealed class WebServerStartup }); } - // app.UseEndpoints(endpoints => - // { - // endpoints.MapFallbackToFile(options.DefaultFileName); - // }); + app.Map("/debug", app => + { + app.Run(async (context) => + { + //debug from VS + var queryParams = HttpUtility.ParseQueryString(context.Request.QueryString.Value!); + var browserParam = queryParams.Get("browser"); + Uri? browserUrl = null; + var devToolsHost = "http://localhost:9222"; + if (browserParam != null) + { + browserUrl = new Uri(browserParam); + devToolsHost = $"http://{browserUrl.Host}:{browserUrl.Port}"; + } + lock (LaunchLock) + { + if (LaunchedDebugProxyUrl == "") + { + LaunchedDebugProxyUrl = $"http://localhost:{StartDebugProxy(devToolsHost)}"; + } + } + var requestPath = context.Request.Path.ToString(); + if (requestPath == string.Empty) + { + requestPath = "/"; + } + context.Response.Redirect($"{LaunchedDebugProxyUrl}{browserUrl!.PathAndQuery}"); + await Task.FromResult(0); + }); + }); + app.UseEndpoints(endpoints => + { + endpoints.MapGet("/", context => + { + context.Response.Redirect("index.html", permanent: false); + return Task.CompletedTask; + }); + }); + + + applicationLifetime.ApplicationStarted.Register(() => + { + TaskCompletionSource<ServerURLs> tcs = realUrlsAvailableTcs; + try + { + ICollection<string>? addresses = app.ServerFeatures + .Get<IServerAddressesFeature>() + ?.Addresses; + + string? ipAddress = null; + string? ipAddressSecure = null; + if (addresses is not null) + { + ipAddress = GetHttpServerAddress(addresses, secure: false); + ipAddressSecure = GetHttpServerAddress(addresses, secure: true); + } + + if (ipAddress == null) + tcs.SetException(new InvalidOperationException("Failed to determine web server's IP address or port")); + else + tcs.SetResult(new ServerURLs(ipAddress, ipAddressSecure)); + } + catch (Exception ex) + { + _logger?.LogError($"Failed to get urls for the webserver: {ex}"); + tcs.TrySetException(ex); + throw; + } + + static string? GetHttpServerAddress(ICollection<string> addresses, bool secure) + => addresses? + .Where(a => a.StartsWith(secure ? "https:" : "http:", StringComparison.InvariantCultureIgnoreCase)) + .Select(a => new Uri(a)) + .Select(uri => uri.ToString()) + .FirstOrDefault(); + }); } } diff --git a/src/mono/wasm/runtime/corebindings.c b/src/mono/wasm/runtime/corebindings.c index c9ebdc01abd..d1b5a9e6842 100644 --- a/src/mono/wasm/runtime/corebindings.c +++ b/src/mono/wasm/runtime/corebindings.c @@ -35,7 +35,7 @@ extern void mono_wasm_bind_cs_function(MonoString **fully_qualified_name, int si extern void mono_wasm_marshal_promise(void *data); -void core_initialize_internals () +void core_initialize_internals (void) { mono_add_internal_call ("Interop/Runtime::InvokeJSWithArgsRef", mono_wasm_invoke_js_with_args_ref); mono_add_internal_call ("Interop/Runtime::GetObjectPropertyRef", mono_wasm_get_object_property_ref); diff --git a/src/mono/wasm/runtime/driver.c b/src/mono/wasm/runtime/driver.c index e2db123f7ab..b14e341f6c8 100644 --- a/src/mono/wasm/runtime/driver.c +++ b/src/mono/wasm/runtime/driver.c @@ -452,7 +452,7 @@ get_native_to_interp (MonoMethod *method, void *extra_arg) typedef void (*background_job_cb)(void); void mono_threads_schedule_background_job (background_job_cb cb); -void mono_initialize_internals () +void mono_initialize_internals (void) { // Blazor specific custom routines - see dotnet_support.js for backing code mono_add_internal_call ("WebAssembly.JSInterop.InternalCalls::InvokeJS", mono_wasm_invoke_js_blazor); @@ -465,7 +465,7 @@ void mono_initialize_internals () } EMSCRIPTEN_KEEPALIVE void -mono_wasm_register_bundled_satellite_assemblies () +mono_wasm_register_bundled_satellite_assemblies (void) { /* In legacy satellite_assembly_count is always false */ if (satellite_assembly_count) { @@ -641,7 +641,7 @@ mono_wasm_assembly_load (const char *name) } EMSCRIPTEN_KEEPALIVE MonoAssembly* -mono_wasm_get_corlib () +mono_wasm_get_corlib (void) { MonoAssembly* result; MONO_ENTER_GC_UNSAFE; @@ -912,7 +912,7 @@ _get_uri_class(MonoException** exc) } static void -_ensure_classes_resolved () +_ensure_classes_resolved (void) { MONO_ENTER_GC_UNSAFE; if (!datetime_class && !resolved_datetime_class) { diff --git a/src/mono/wasm/runtime/logging.ts b/src/mono/wasm/runtime/logging.ts index 2d40d7c4d20..cdb2d00dba7 100644 --- a/src/mono/wasm/runtime/logging.ts +++ b/src/mono/wasm/runtime/logging.ts @@ -62,8 +62,9 @@ export function mono_wasm_symbolicate_string(message: string): string { export function mono_wasm_stringify_as_error_with_stack(err: Error | string): string { let errObj: any = err; - if (!(err instanceof Error)) - errObj = new Error(err); + if (!(errObj instanceof Error)) { + errObj = new Error(errObj); + } // Error return mono_wasm_symbolicate_string(errObj.stack); diff --git a/src/mono/wasm/runtime/managed-exports.ts b/src/mono/wasm/runtime/managed-exports.ts index 09f6d9a33d0..65186e4c0b2 100644 --- a/src/mono/wasm/runtime/managed-exports.ts +++ b/src/mono/wasm/runtime/managed-exports.ts @@ -7,7 +7,7 @@ import { Module, runtimeHelpers, ENVIRONMENT_IS_PTHREAD } from "./imports"; import { alloc_stack_frame, get_arg, get_arg_gc_handle, MarshalerType, set_arg_type, set_gc_handle } from "./marshal"; import { invoke_method_and_handle_exception } from "./invoke-cs"; import { marshal_array_to_cs_impl, marshal_exception_to_cs, marshal_intptr_to_cs } from "./marshal-to-cs"; -import { marshal_int32_to_js, marshal_task_to_js } from "./marshal-to-js"; +import { marshal_int32_to_js, marshal_string_to_js, marshal_task_to_js } from "./marshal-to-js"; export function init_managed_exports(): void { const anyModule = Module as any; @@ -34,6 +34,9 @@ export function init_managed_exports(): void { mono_assert(complete_task_method, "Can't find CompleteTask method"); const call_delegate_method = get_method("CallDelegate"); mono_assert(call_delegate_method, "Can't find CallDelegate method"); + const get_managed_stack_trace_method = get_method("GetManagedStackTrace"); + mono_assert(get_managed_stack_trace_method, "Can't find GetManagedStackTrace method"); + runtimeHelpers.javaScriptExports.call_entry_point = (entry_point: MonoMethod, program_args?: string[]) => { const sp = anyModule.stackSave(); try { @@ -134,6 +137,22 @@ export function init_managed_exports(): void { anyModule.stackRestore(sp); } }; + runtimeHelpers.javaScriptExports.get_managed_stack_trace = (exception_gc_handle: GCHandle) => { + const sp = anyModule.stackSave(); + try { + const args = alloc_stack_frame(3); + + const arg1 = get_arg(args, 2); + set_arg_type(arg1, MarshalerType.Exception); + set_gc_handle(arg1, exception_gc_handle); + + invoke_method_and_handle_exception(get_managed_stack_trace_method, args); + const res = get_arg(args, 1); + return marshal_string_to_js(res); + } finally { + anyModule.stackRestore(sp); + } + }; if (install_sync_context) { runtimeHelpers.javaScriptExports.install_synchronization_context = () => { diff --git a/src/mono/wasm/runtime/marshal-to-js.ts b/src/mono/wasm/runtime/marshal-to-js.ts index b3f469fc05f..a26de6dc147 100644 --- a/src/mono/wasm/runtime/marshal-to-js.ts +++ b/src/mono/wasm/runtime/marshal-to-js.ts @@ -33,7 +33,7 @@ export function initialize_marshalers_to_js(): void { cs_to_js_marshalers.set(MarshalerType.Single, _marshal_float_to_js); cs_to_js_marshalers.set(MarshalerType.IntPtr, _marshal_intptr_to_js); cs_to_js_marshalers.set(MarshalerType.Double, _marshal_double_to_js); - cs_to_js_marshalers.set(MarshalerType.String, _marshal_string_to_js); + cs_to_js_marshalers.set(MarshalerType.String, marshal_string_to_js); cs_to_js_marshalers.set(MarshalerType.Exception, marshal_exception_to_js); cs_to_js_marshalers.set(MarshalerType.JSException, marshal_exception_to_js); cs_to_js_marshalers.set(MarshalerType.JSObject, _marshal_js_object_to_js); @@ -296,7 +296,7 @@ export function mono_wasm_marshal_promise(args: JSMarshalerArguments): void { set_arg_type(exc, MarshalerType.None); } -function _marshal_string_to_js(arg: JSMarshalerArgument): string | null { +export function marshal_string_to_js(arg: JSMarshalerArgument): string | null { const type = get_arg_type(arg); if (type == MarshalerType.None) { return null; @@ -326,7 +326,7 @@ export function marshal_exception_to_js(arg: JSMarshalerArgument): Error | null let result = _lookup_js_owned_object(gc_handle); if (result === null || result === undefined) { // this will create new ManagedError - const message = _marshal_string_to_js(arg); + const message = marshal_string_to_js(arg); result = new ManagedError(message!); setup_managed_proxy(result, gc_handle); @@ -405,7 +405,7 @@ function _marshal_array_to_js_impl(arg: JSMarshalerArgument, element_type: Marsh result = new Array(length); for (let index = 0; index < length; index++) { const element_arg = get_arg(<any>buffer_ptr, index); - result[index] = _marshal_string_to_js(element_arg); + result[index] = marshal_string_to_js(element_arg); } cwraps.mono_wasm_deregister_root(<any>buffer_ptr); } diff --git a/src/mono/wasm/runtime/marshal.ts b/src/mono/wasm/runtime/marshal.ts index 591f3976292..c1e7e35535d 100644 --- a/src/mono/wasm/runtime/marshal.ts +++ b/src/mono/wasm/runtime/marshal.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import { js_owned_gc_handle_symbol, teardown_managed_proxy } from "./gc-handles"; -import { Module } from "./imports"; +import { Module, runtimeHelpers } from "./imports"; import { getF32, getF64, getI16, getI32, getI64Big, getU16, getU32, getU8, setF32, setF64, setI16, setI32, setI64Big, setU16, setU32, setU8 } from "./memory"; import { mono_wasm_new_external_root } from "./roots"; import { mono_assert, GCHandle, JSHandle, MonoObject, MonoString, GCHandleNull, JSMarshalerArguments, JSFunctionSignature, JSMarshalerType, JSMarshalerArgument, MarshalerToJs, MarshalerToCs, WasmRoot } from "./types"; @@ -317,13 +317,31 @@ export class ManagedObject implements IDisposable { } export class ManagedError extends Error implements IDisposable { + private superStack: any; constructor(message: string) { super(message); + this.superStack = Object.getOwnPropertyDescriptor(this, "stack"); // this works on Chrome + Object.defineProperty(this, "stack", { + get: this.getManageStack, + }); } - get stack(): string | undefined { - //todo implement lazy managed stack strace from this[js_owned_gc_handle_symbol]! - return super.stack; + getSuperStack() { + if (this.superStack) { + return this.superStack.value; + } + return super.stack; // this works on FF + } + + getManageStack() { + const gc_handle = (<any>this)[js_owned_gc_handle_symbol]; + if (gc_handle) { + const managed_stack = runtimeHelpers.javaScriptExports.get_managed_stack_trace(gc_handle); + if (managed_stack) { + return managed_stack + "\n" + this.getSuperStack(); + } + } + return this.getSuperStack(); } dispose(): void { @@ -333,10 +351,6 @@ export class ManagedError extends Error implements IDisposable { get isDisposed(): boolean { return (<any>this)[js_owned_gc_handle_symbol] === GCHandleNull; } - - toString(): string { - return `ManagedError(gc_handle: ${(<any>this)[js_owned_gc_handle_symbol]})`; - } } export function get_signature_marshaler(signature: JSFunctionSignature, index: number): JSHandle { diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index ba56005ef2d..ca914585d1d 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -414,6 +414,9 @@ export interface JavaScriptExports { // the marshaled signature is: void InstallSynchronizationContext() install_synchronization_context(): void; + + // the marshaled signature is: string GetManagedStackTrace(GCHandle exception) + get_managed_stack_trace(exception_gc_handle: GCHandle): string | null } export type MarshalerToJs = (arg: JSMarshalerArgument, sig?: JSMarshalerType, res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs, arg3_converter?: MarshalerToCs) => any; diff --git a/src/mono/wasm/templates/templates/browser/.template.config/template.json b/src/mono/wasm/templates/templates/browser/.template.config/template.json index 8051e4c6aab..7fc4f155519 100644 --- a/src/mono/wasm/templates/templates/browser/.template.config/template.json +++ b/src/mono/wasm/templates/templates/browser/.template.config/template.json @@ -2,13 +2,52 @@ "$schema": "http://json.schemastore.org/template", "author": "Microsoft", "classifications": [ "Web", "WebAssembly", "Browser" ], - "identity": "WebAssembly.Browser", + "generatorVersions": "[1.0.0.0-*)", + "groupIdentity": "WebAssembly.Browser", + "precedence": 8000, + "identity": "WebAssembly.Browser.8.0", + "description": "WebAssembly Browser App", "name": "WebAssembly Browser App", + "description": "A project template for creating a .NET app that runs on WebAssembly in a browser", "shortName": "wasmbrowser", "sourceName": "browser.0", "preferNameDirectory": true, "tags": { "language": "C#", "type": "project" + }, + "symbols": { + "kestrelHttpPortGenerated": { + "type": "generated", + "generator": "port", + "parameters": { + "low": 5000, + "high": 5300 + }, + "replaces": "5000" + }, + "kestrelHttpsPortGenerated": { + "type": "generated", + "generator": "port", + "parameters": { + "low": 7000, + "high": 7300 + }, + "replaces": "5001" + }, + "framework": { + "type": "parameter", + "description": "The target framework for the project.", + "datatype": "choice", + "choices": [ + { + "choice": "net8.0", + "description": "Target net8.0", + "displayName": ".NET 8.0" + } + ], + "defaultValue": "net8.0", + "displayName": "framework" + } } } diff --git a/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json b/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json new file mode 100644 index 00000000000..4807594b8f5 --- /dev/null +++ b/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{
+ "profiles": {
+ "browser.0": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "https://localhost:5001;http://localhost:5000",
+ "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/debug?browser={browserInspectUri}"
+ }
+ }
+}
diff --git a/src/mono/wasm/templates/templates/browser/main.js b/src/mono/wasm/templates/templates/browser/main.js index 32c1599749d..6d9bd43f7eb 100644 --- a/src/mono/wasm/templates/templates/browser/main.js +++ b/src/mono/wasm/templates/templates/browser/main.js @@ -3,15 +3,12 @@ import { dotnet } from './dotnet.js' -const is_browser = typeof window != "undefined"; -if (!is_browser) throw new Error(`Expected to be running in a browser`); - -const { setModuleImports, getAssemblyExports, getConfig, runMainAndExit } = await dotnet +const { setModuleImports, getAssemblyExports, getConfig } = await dotnet .withDiagnosticTracing(false) .withApplicationArgumentsFromQuery() .create(); -setModuleImports("main.js", { +setModuleImports('main.js', { window: { location: { href: () => globalThis.window.location.href @@ -24,5 +21,5 @@ const exports = await getAssemblyExports(config.mainAssemblyName); const text = exports.MyClass.Greeting(); console.log(text); -document.getElementById("out").innerHTML = `${text}`; -await runMainAndExit(config.mainAssemblyName, ["dotnet", "is", "great!"]);
\ No newline at end of file +document.getElementById('out').innerHTML = text; +await dotnet.run();
\ No newline at end of file diff --git a/src/mono/wasm/templates/templates/console/.template.config/template.json b/src/mono/wasm/templates/templates/console/.template.config/template.json index 8ead39edc0f..0ea523824ae 100644 --- a/src/mono/wasm/templates/templates/console/.template.config/template.json +++ b/src/mono/wasm/templates/templates/console/.template.config/template.json @@ -2,13 +2,33 @@ "$schema": "http://json.schemastore.org/template", "author": "Microsoft", "classifications": [ "Web", "WebAssembly", "Console" ], - "identity": "WebAssembly.Console", + "groupIdentity": "WebAssembly.Console", + "precedence": 8000, + "identity": "WebAssembly.Console.8.0", + "description": "WebAssembly Console App", "name": "WebAssembly Console App", + "description": "A project template for creating a .NET app that runs on WebAssembly on Node JS or V8", "shortName": "wasmconsole", "sourceName": "console.0", "preferNameDirectory": true, "tags": { "language": "C#", "type": "project" + }, + "symbols": { + "framework": { + "type": "parameter", + "description": "The target framework for the project.", + "datatype": "choice", + "choices": [ + { + "choice": "net8.0", + "description": "Target net8.0", + "displayName": ".NET 8.0" + } + ], + "defaultValue": "net8.0", + "displayName": "framework" + } } } diff --git a/src/mono/wasm/templates/templates/console/main.mjs b/src/mono/wasm/templates/templates/console/main.mjs index 1072711c59c..6dd163a3741 100644 --- a/src/mono/wasm/templates/templates/console/main.mjs +++ b/src/mono/wasm/templates/templates/console/main.mjs @@ -3,14 +3,11 @@ import { dotnet } from './dotnet.js' -const is_node = typeof process === 'object' && typeof process.versions === 'object' && typeof process.versions.node === 'string'; -if (!is_node) throw new Error(`This file only supports nodejs`); - -const { setModuleImports, getAssemblyExports, getConfig, runMainAndExit } = await dotnet +const { setModuleImports, getAssemblyExports, getConfig } = await dotnet .withDiagnosticTracing(false) .create(); -setModuleImports("main.mjs", { +setModuleImports('main.mjs', { node: { process: { version: () => globalThis.process.version @@ -23,4 +20,4 @@ const exports = await getAssemblyExports(config.mainAssemblyName); const text = exports.MyClass.Greeting(); console.log(text); -await runMainAndExit(config.mainAssemblyName, ["dotnet", "is", "great!"]); +await dotnet.run(); diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index cac849c5818..1a8fd0bf0fa 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -332,6 +332,9 @@ <!-- npm install is faster on dev machine as it doesn't wipe node_modules folder --> <RunWithEmSdkEnv Condition="'$(ContinuousIntegrationBuild)' != 'true'" Command="npm install" EmSdkPath="$(EMSDK_PATH)" IgnoreStandardErrorWarningFormat="true" WorkingDirectory="$(MonoProjectRoot)wasm/runtime/"/> + <!-- Delete malformed package.json used for tests, it confuses Component Governance tooling --> + <Delete Files="$(MonoProjectRoot)wasm/runtime/node_modules/resolve/test/resolver/malformed_package_json/package.json" ContinueOnError="true" /> + <Touch Files="$(MonoProjectRoot)wasm/runtime/node_modules/.npm-stamp" AlwaysCreate="true" /> </Target> |